{"version":3,"sources":["vendor/ember-cli/app-prefix.js","bocce/adapters/-json-api.js","bocce/adapters/application.js","bocce/app.js","bocce/component-managers/glimmer.js","bocce/components/adblock-warning.js","bocce/components/advice-card/audio-recorder.js","bocce/components/assignment-user.js","bocce/components/assignment/assignment-comment.js","bocce/components/assignment/assignment-list.js","bocce/components/attachment-preview.js","bocce/components/attachments/attached-file.js","bocce/components/av-player.js","bocce/components/banner-drawer.js","bocce/components/bookmarks/bookmark-note.js","bocce/components/bookmarks/bookmarks-header.js","bocce/components/buttons/add-button.js","bocce/components/buttons/async-button.js","bocce/components/buttons/button-generic.js","bocce/components/buttons/delete-button.js","bocce/components/buttons/submit-button.js","bocce/components/character-limited-textarea.js","bocce/components/classroom/current-lesson-item.js","bocce/components/classroom/header/section-info.js","bocce/components/classroom/lessons/admin/quizzes.js","bocce/components/dashboard/course-list/course-list-item.js","bocce/components/datetime-picker.js","bocce/components/discussion-response.js","bocce/components/discussion/multisection-picker.js","bocce/components/discussion/reply-button.js","bocce/components/drag-and-drop.js","bocce/components/expandable-list.js","bocce/components/grade-student-list/list-item.js","bocce/components/grade-student-list/list.js","bocce/components/inbox.js","bocce/components/inbox/message-tile.js","bocce/components/inbox/message-view.js","bocce/components/inputs/toggle-switch.js","bocce/components/interactions/guitar/main.js","bocce/components/kaltura-player.js","bocce/components/legacy-boot.js","bocce/components/lessons/advice-card-submitter.js","bocce/components/lessons/advice-card-viewer.js","bocce/components/lessons/event-new-content.js","bocce/components/lessons/event-new-template-actions.js","bocce/components/lessons/event-new-template-manager.js","bocce/components/lessons/lesson-wrapper.js","bocce/components/lessons/topic-body.js","bocce/components/login-streak.js","bocce/components/modals/attachments-modal.js","bocce/components/modals/attachments/attachments-record-audio.js","bocce/components/modals/attachments/attachments-record-video.js","bocce/components/modals/attachments/attachments-upload-file.js","bocce/components/modals/cover-backdrop.js","bocce/components/modals/dark-backdrop.js","bocce/components/new-activity-modal.js","bocce/components/notification-item.js","bocce/components/polls/poll-drawer.js","bocce/components/quiz-admin/delete-latest-attempt.js","bocce/components/quiz-admin/operation.js","bocce/components/quiz-admin/unlock-for-next-attempt.js","bocce/components/quiz-questions/essay.js","bocce/components/quiz-questions/file-upload.js","bocce/components/quiz-questions/fill-in-multiple-blanks.js","bocce/components/quiz-questions/multianswer.js","bocce/components/quiz-questions/multichoice.js","bocce/components/quiz-questions/multiple-answers.js","bocce/components/quiz-questions/multiple-choice.js","bocce/components/quiz-questions/multiple-dropdowns.js","bocce/components/quiz-questions/question.js","bocce/components/quiz-questions/short-answer.js","bocce/components/quiz-questions/true-false.js","bocce/components/quiz-scenarios/quiz-all-graded.js","bocce/components/quiz-scenarios/quiz-all.js","bocce/components/quiz-scenarios/quiz-instructor.js","bocce/components/quiz-scenarios/quiz-intercept.js","bocce/components/quiz-scenarios/quiz-single-graded-results.js","bocce/components/quiz-scenarios/quiz-single-graded-reviewing.js","bocce/components/quiz-scenarios/quiz-single.js","bocce/components/quiz-stats/question-statistics-manual.js","bocce/components/quiz-stats/question-statistics-multiple.js","bocce/components/quiz-stats/question-statistics-single.js","bocce/components/quiz-stats/quiz-stats-actions.js","bocce/components/quiz-stats/quiz-stats.js","bocce/components/quiz-stats/types/essay-question-statistics.js","bocce/components/quiz-stats/types/short-answer-question-statistics.js","bocce/components/quiz.js","bocce/components/quiz/previous-questions.js","bocce/components/quiz/quiz-overview.js","bocce/components/recipient-filter.js","bocce/components/rte-input.js","bocce/components/rubric/assessment.js","bocce/components/rubric/grading.js","bocce/components/rubric/instructor-graded.js","bocce/components/rubric/rubric.js","bocce/components/rubric/ungraded.js","bocce/components/side-panel/activity.js","bocce/components/side-panel/late-grading-policy.js","bocce/components/side-panel/panel-list-item.js","bocce/components/side-panel/panel-list-item/assignment-icon.js","bocce/components/side-panel/panel-list-item/discussion-icon.js","bocce/components/side-panel/panel-list-item/icon.js","bocce/components/side-panel/panel-list-item/portrait.js","bocce/components/side-panel/panel-list-item/quiz-icon.js","bocce/components/side-panel/work.js","bocce/components/submission/footer-interact.js","bocce/components/upload-preview.js","bocce/components/user-dot.js","bocce/components/user-icons.js","bocce/components/user-portrait.js","bocce/controllers/announcement.js","bocce/controllers/classroom.js","bocce/controllers/classroom/lessons.js","bocce/controllers/classroom/lessons/admin.js","bocce/controllers/classroom/lessons/admin/assignments.js","bocce/controllers/classroom/lessons/admin/banners.js","bocce/controllers/classroom/lessons/admin/cache.js","bocce/controllers/classroom/lessons/admin/live.js","bocce/controllers/classroom/lessons/admin/quizzes.js","bocce/controllers/classroom/lessons/announcement-new.js","bocce/controllers/classroom/lessons/conversation-new-with.js","bocce/controllers/classroom/lessons/conversation-new.js","bocce/controllers/classroom/lessons/conversation.js","bocce/controllers/classroom/lessons/discussion-new.js","bocce/controllers/classroom/lessons/discussion.js","bocce/controllers/classroom/lessons/event-new.js","bocce/controllers/classroom/lessons/event-quick-new.js","bocce/controllers/classroom/lessons/event.js","bocce/controllers/classroom/lessons/no-submissions.js","bocce/controllers/classroom/lessons/student-event-new.js","bocce/controllers/classroom/lessons/submission-new.js","bocce/controllers/classroom/lessons/submission.js","bocce/controllers/classroom/lessons/survey.js","bocce/controllers/conversations.js","bocce/controllers/dashboard.js","bocce/controllers/discussions.js","bocce/controllers/events.js","bocce/controllers/gradebook.js","bocce/controllers/index.js","bocce/controllers/lobby.js","bocce/controllers/notifications.js","bocce/controllers/userprofile.js","bocce/data-adapter.js","bocce/helpers/activity-count.js","bocce/helpers/and.js","bocce/helpers/app-version.js","bocce/helpers/array-item.js","bocce/helpers/calendar.js","bocce/helpers/cancel-all.js","bocce/helpers/contains.js","bocce/helpers/correct.js","bocce/helpers/dashify.js","bocce/helpers/decode.js","bocce/helpers/diff.js","bocce/helpers/difference.js","bocce/helpers/dropdown-scale.js","bocce/helpers/epoch-to-dayhr.js","bocce/helpers/eq.js","bocce/helpers/feature-flag.js","bocce/helpers/file-extension.js","bocce/helpers/file-name.js","bocce/helpers/format-grade.js","bocce/helpers/heartbeat.js","bocce/helpers/image.js","bocce/helpers/inarray.js","bocce/helpers/isnull.js","bocce/helpers/lessthan.js","bocce/helpers/letter-grade.js","bocce/helpers/letter.js","bocce/helpers/longdate.js","bocce/helpers/moment.js","bocce/helpers/morethan.js","bocce/helpers/morethanzero.js","bocce/helpers/not.js","bocce/helpers/notnull.js","bocce/helpers/null.js","bocce/helpers/or.js","bocce/helpers/past-due.js","bocce/helpers/percentage.js","bocce/helpers/perform.js","bocce/helpers/pluralize.js","bocce/helpers/reply-count.js","bocce/helpers/safehtml.js","bocce/helpers/same.js","bocce/helpers/show-discussions.js","bocce/helpers/show-ungraded.js","bocce/helpers/singularize.js","bocce/helpers/spacelist.js","bocce/helpers/startswith.js","bocce/helpers/substring.js","bocce/helpers/sum.js","bocce/helpers/task.js","bocce/helpers/teaser.js","bocce/helpers/total.js","bocce/helpers/trim.js","bocce/helpers/two-decimals.js","bocce/helpers/upload.js","bocce/initializers/app-version.js","bocce/initializers/container-debug-adapter.js","bocce/initializers/ember-cli-mirage.js","bocce/initializers/ember-data-data-adapter.js","bocce/initializers/ember-data.js","bocce/initializers/export-application-global.js","bocce/initializers/global-session.js","bocce/instance-initializers/ember-cli-mirage-autostart.js","bocce/instance-initializers/ember-data.js","bocce/js/quizzes/quizzes-factory.js","bocce/js/quizzes/quizzes.js","bocce/js/store-util.js","bocce/js/utils.js","bocce/mirage/config.js","bocce/mirage/factories/assignment.js","bocce/mirage/factories/course.js","bocce/mirage/factories/lesson.js","bocce/mirage/factories/user.js","bocce/mirage/scenarios/default.js","bocce/mirage/scenarios/test-assignment.js","bocce/mirage/serializers/application.js","bocce/mirage/serializers/course.js","bocce/mixins/assignments.js","bocce/mixins/attachments-mixin.js","bocce/mixins/audio-rec.js","bocce/mixins/av-players.js","bocce/mixins/boot.js","bocce/mixins/calendar-events.js","bocce/mixins/conversable.js","bocce/mixins/discussable.js","bocce/mixins/editable.js","bocce/mixins/embed-parser.js","bocce/mixins/enmodal.js","bocce/mixins/event-model.js","bocce/mixins/helpguide.js","bocce/mixins/interactions/abstract_quiz.js","bocce/mixins/interactions/audio_comments.js","bocce/mixins/interactions/audio_markers_quiz.js","bocce/mixins/interactions/bpm_tap_pad.js","bocce/mixins/interactions/codec_comparison.js","bocce/mixins/interactions/content_container.js","bocce/mixins/interactions/content_framing.js","bocce/mixins/interactions/cycle_5.js","bocce/mixins/interactions/drag_and_drop_quiz.js","bocce/mixins/interactions/embedder.js","bocce/mixins/interactions/fill_in_the_blanks_quiz.js","bocce/mixins/interactions/flash_cards.js","bocce/mixins/interactions/form_builder.js","bocce/mixins/interactions/guitar.js","bocce/mixins/interactions/hot_spot_quiz.js","bocce/mixins/interactions/image_explorer.js","bocce/mixins/interactions/javascript.js","bocce/mixins/interactions/list_rollover.js","bocce/mixins/interactions/mix_visualizer.js","bocce/mixins/interactions/piano.js","bocce/mixins/interactions/piano_quiz.js","bocce/mixins/interactions/requirements.js","bocce/mixins/interactions/reveal_content.js","bocce/mixins/interactions/reveal_text_quiz.js","bocce/mixins/interactions/rive.js","bocce/mixins/interactions/rotatable.js","bocce/mixins/interactions/simple_slideshow.js","bocce/mixins/interactions/slideshow.js","bocce/mixins/interactions/solfege_ladder.js","bocce/mixins/interactions/sound_mixer.js","bocce/mixins/interactions/text_line_and_text_select_hybrid.js","bocce/mixins/interactions/timed_assignment.js","bocce/mixins/interactions/timed_writing.js","bocce/mixins/interactions/timeline.js","bocce/mixins/interactions/timeline_knightlab.js","bocce/mixins/interactions/video_commentary.js","bocce/mixins/interactions/video_mixer.js","bocce/mixins/interactions/view-or-take-a-survey.js","bocce/mixins/kaltura-upload.js","bocce/mixins/legacy-attachment-manager.js","bocce/mixins/lesson-redirect.js","bocce/mixins/menus.js","bocce/mixins/nested-resources.js","bocce/mixins/notify.js","bocce/mixins/polls.js","bocce/mixins/prefs.js","bocce/mixins/quiz-question.js","bocce/mixins/quiz.js","bocce/mixins/routable.js","bocce/mixins/rtc-rec.js","bocce/mixins/showcase.js","bocce/mixins/support/layout.js","bocce/mixins/support/local-storage.js","bocce/mixins/support/render-template.js","bocce/mixins/support/sound_mixer_core.js","bocce/mixins/support/util.js","bocce/mixins/uploadable.js","bocce/mixins/video-rec.js","bocce/mixins/webex.js","bocce/models/assessment.js","bocce/models/assignment.js","bocce/models/attachment.js","bocce/models/attempt.js","bocce/models/banner.js","bocce/models/bookmark.js","bocce/models/comment.js","bocce/models/conversation.js","bocce/models/conversation_message.js","bocce/models/course.js","bocce/models/dashboard.js","bocce/models/discussion.js","bocce/models/enrollment.js","bocce/models/essay.js","bocce/models/event.js","bocce/models/file_upload.js","bocce/models/fill_in_multiple_blank.js","bocce/models/gradebook.js","bocce/models/gradebook_weight.js","bocce/models/item.js","bocce/models/lesson.js","bocce/models/lessons.js","bocce/models/lobby.js","bocce/models/multiple_answer.js","bocce/models/multiple_choice.js","bocce/models/multiple_dropdown.js","bocce/models/notification.js","bocce/models/page.js","bocce/models/poll.js","bocce/models/poll_submission.js","bocce/models/question_statistic.js","bocce/models/quiz.js","bocce/models/quiz_question.js","bocce/models/quiz_question_archived.js","bocce/models/quiz_statistic.js","bocce/models/response.js","bocce/models/rubric_criterion.js","bocce/models/section.js","bocce/models/session.js","bocce/models/short_answer.js","bocce/models/submission.js","bocce/models/term.js","bocce/models/true_false.js","bocce/models/user.js","bocce/modifiers/did-insert.js","bocce/modifiers/did-update.js","bocce/modifiers/will-destroy.js","bocce/router.js","bocce/routes/application.js","bocce/routes/classroom.js","bocce/routes/classroom/lessons.js","bocce/routes/classroom/lessons/admin.js","bocce/routes/classroom/lessons/admin/assignments.js","bocce/routes/classroom/lessons/admin/banners.js","bocce/routes/classroom/lessons/admin/cache.js","bocce/routes/classroom/lessons/admin/index.js","bocce/routes/classroom/lessons/admin/live.js","bocce/routes/classroom/lessons/admin/quizzes.js","bocce/routes/classroom/lessons/announcement-new.js","bocce/routes/classroom/lessons/assignments.js","bocce/routes/classroom/lessons/conversation-new-with.js","bocce/routes/classroom/lessons/conversation-new.js","bocce/routes/classroom/lessons/conversation.js","bocce/routes/classroom/lessons/discussion-new.js","bocce/routes/classroom/lessons/discussion.js","bocce/routes/classroom/lessons/event-new.js","bocce/routes/classroom/lessons/event-quick-new.js","bocce/routes/classroom/lessons/event.js","bocce/routes/classroom/lessons/loading.js","bocce/routes/classroom/lessons/modal-base.js","bocce/routes/classroom/lessons/no-submissions.js","bocce/routes/classroom/lessons/quiz.js","bocce/routes/classroom/lessons/student-event-new.js","bocce/routes/classroom/lessons/submission-new.js","bocce/routes/classroom/lessons/submission.js","bocce/routes/classroom/lessons/survey.js","bocce/routes/course.js","bocce/routes/dashboard.js","bocce/routes/index.js","bocce/routes/lobby.js","bocce/serializers/-default.js","bocce/serializers/-json-api.js","bocce/serializers/-rest.js","bocce/serializers/application.js","bocce/services/adblock-detector.js","bocce/services/attachment-manager.js","bocce/services/bookmarks.js","bocce/services/dashboard-sorter.js","bocce/services/embed-parser.js","bocce/services/events.js","bocce/services/features.js","bocce/services/gainsight.js","bocce/services/grade-calculator.js","bocce/services/heartbeat.js","bocce/services/kaltura-upload.js","bocce/services/legacy-attachment-manager.js","bocce/services/login-refresh.js","bocce/services/login-streak.js","bocce/services/rtc-rec.js","bocce/services/rubric.js","bocce/services/session.js","bocce/services/store.js","bocce/services/templates.js","bocce/services/userprofile.js","bocce/templates/application.js","bocce/templates/assignment-contents.js","bocce/templates/assignment-submission-box.js","bocce/templates/attachments.js","bocce/templates/audio-rec.js","bocce/templates/bookmarks-menu.js","bocce/templates/classlist.js","bocce/templates/classroom.js","bocce/templates/classroom/lessons.js","bocce/templates/classroom/lessons/admin.js","bocce/templates/classroom/lessons/admin/assignments.js","bocce/templates/classroom/lessons/admin/banners.js","bocce/templates/classroom/lessons/admin/cache.js","bocce/templates/classroom/lessons/admin/index.js","bocce/templates/classroom/lessons/admin/live.js","bocce/templates/classroom/lessons/admin/loading.js","bocce/templates/classroom/lessons/admin/quizzes.js","bocce/templates/classroom/lessons/announcement-new.js","bocce/templates/classroom/lessons/assignments.js","bocce/templates/classroom/lessons/conversation-new-with.js","bocce/templates/classroom/lessons/conversation-new.js","bocce/templates/classroom/lessons/conversation.js","bocce/templates/classroom/lessons/discussion-new.js","bocce/templates/classroom/lessons/discussion.js","bocce/templates/classroom/lessons/event-new.js","bocce/templates/classroom/lessons/event-quick-new.js","bocce/templates/classroom/lessons/event.js","bocce/templates/classroom/lessons/loading.js","bocce/templates/classroom/lessons/no-submissions.js","bocce/templates/classroom/lessons/quiz.js","bocce/templates/classroom/lessons/student-event-new.js","bocce/templates/classroom/lessons/submission-new.js","bocce/templates/classroom/lessons/submission.js","bocce/templates/classroom/lessons/survey.js","bocce/templates/classroom/loading.js","bocce/templates/components/adblock-warning.js","bocce/templates/components/assignment-user.js","bocce/templates/components/assignment/assignment-comment.js","bocce/templates/components/assignment/assignment-list.js","bocce/templates/components/attachment-preview.js","bocce/templates/components/attachments-container.js","bocce/templates/components/attachments/attached-file.js","bocce/templates/components/av-player.js","bocce/templates/components/banner-drawer.js","bocce/templates/components/bookmarks/bookmark-note.js","bocce/templates/components/bookmarks/bookmark-preview.js","bocce/templates/components/bookmarks/bookmarks-content.js","bocce/templates/components/bookmarks/bookmarks-header.js","bocce/templates/components/bookmarks/bookmarks.js","bocce/templates/components/buttons/add-button.js","bocce/templates/components/buttons/async-button.js","bocce/templates/components/buttons/delete-button.js","bocce/templates/components/buttons/submit-button.js","bocce/templates/components/classroom/current-lesson-item.js","bocce/templates/components/classroom/header/section-info.js","bocce/templates/components/dashboard/course-list.js","bocce/templates/components/dashboard/course-list/course-list-item-past.js","bocce/templates/components/dashboard/course-list/course-list-item-training.js","bocce/templates/components/dashboard/course-list/course-list-item.js","bocce/templates/components/dashboard/week-number-display.js","bocce/templates/components/datetime-picker.js","bocce/templates/components/discussion-response.js","bocce/templates/components/discussion/multisection-picker.js","bocce/templates/components/discussion/reply-button.js","bocce/templates/components/dummy-component.js","bocce/templates/components/embeded-content.js","bocce/templates/components/expandable-list.js","bocce/templates/components/grade-student-list/list-item.js","bocce/templates/components/grade-student-list/list.js","bocce/templates/components/inbox.js","bocce/templates/components/inbox/message-tile.js","bocce/templates/components/inbox/message-view.js","bocce/templates/components/inputs/toggle-switch.js","bocce/templates/components/kaltura-player.js","bocce/templates/components/legacy-boot.js","bocce/templates/components/lessons/advice-card-submitter.js","bocce/templates/components/lessons/advice-card-viewer.js","bocce/templates/components/lessons/event-new-collapsible.js","bocce/templates/components/lessons/event-new-content.js","bocce/templates/components/lessons/event-new-template-actions.js","bocce/templates/components/lessons/event-new-template-manager.js","bocce/templates/components/lessons/lesson-wrapper.js","bocce/templates/components/lessons/topic-body.js","bocce/templates/components/login-streak.js","bocce/templates/components/modals/attachments-modal.js","bocce/templates/components/modals/attachments/attachments-record-audio.js","bocce/templates/components/modals/attachments/attachments-record-video.js","bocce/templates/components/modals/attachments/attachments-upload-file.js","bocce/templates/components/modals/cover-backdrop.js","bocce/templates/components/modals/dark-backdrop.js","bocce/templates/components/next-live-class.js","bocce/templates/components/notification-item.js","bocce/templates/components/polls/poll-drawer.js","bocce/templates/components/quiz-admin/delete-latest-attempt.js","bocce/templates/components/quiz-admin/operation.js","bocce/templates/components/quiz-admin/unlock-for-next-attempt.js","bocce/templates/components/quiz-questions/essay.js","bocce/templates/components/quiz-questions/file-upload.js","bocce/templates/components/quiz-questions/fill-in-multiple-blanks.js","bocce/templates/components/quiz-questions/multianswer.js","bocce/templates/components/quiz-questions/multichoice.js","bocce/templates/components/quiz-questions/multiple-answers.js","bocce/templates/components/quiz-questions/multiple-choice.js","bocce/templates/components/quiz-questions/multiple-dropdowns.js","bocce/templates/components/quiz-questions/question.js","bocce/templates/components/quiz-questions/short-answer.js","bocce/templates/components/quiz-questions/true-false.js","bocce/templates/components/quiz-scenarios/quiz-all-graded.js","bocce/templates/components/quiz-scenarios/quiz-all.js","bocce/templates/components/quiz-scenarios/quiz-instructor.js","bocce/templates/components/quiz-scenarios/quiz-intercept.js","bocce/templates/components/quiz-scenarios/quiz-single-graded-results.js","bocce/templates/components/quiz-scenarios/quiz-single-graded-reviewing.js","bocce/templates/components/quiz-scenarios/quiz-single.js","bocce/templates/components/quiz-stats/question-statistics-manual.js","bocce/templates/components/quiz-stats/question-statistics-multiple.js","bocce/templates/components/quiz-stats/question-statistics-single.js","bocce/templates/components/quiz-stats/quiz-stats-actions.js","bocce/templates/components/quiz-stats/quiz-stats.js","bocce/templates/components/quiz-stats/types/essay-question-statistics.js","bocce/templates/components/quiz-stats/types/file-upload-question-statistics.js","bocce/templates/components/quiz-stats/types/fill-in-multiple-blanks-question-statistics.js","bocce/templates/components/quiz-stats/types/multiple-answers-question-statistics.js","bocce/templates/components/quiz-stats/types/multiple-choice-question-statistics.js","bocce/templates/components/quiz-stats/types/multiple-dropdowns-question-statistics.js","bocce/templates/components/quiz-stats/types/short-answer-question-statistics.js","bocce/templates/components/quiz-stats/types/true-false-question-statistics.js","bocce/templates/components/quiz/answer-feedback.js","bocce/templates/components/quiz/check-answer.js","bocce/templates/components/quiz/finish-quiz.js","bocce/templates/components/quiz/previous-questions.js","bocce/templates/components/quiz/quiz-overview.js","bocce/templates/components/quiz/results.js","bocce/templates/components/quiz/statistics.js","bocce/templates/components/quiz/submitted-in.js","bocce/templates/components/recipient-filter.js","bocce/templates/components/rte-input.js","bocce/templates/components/rubric/assessment.js","bocce/templates/components/rubric/grading.js","bocce/templates/components/rubric/instructor-graded.js","bocce/templates/components/rubric/rubric.js","bocce/templates/components/rubric/ungraded.js","bocce/templates/components/side-panel/activity.js","bocce/templates/components/side-panel/late-grading-policy.js","bocce/templates/components/side-panel/panel-list-item.js","bocce/templates/components/side-panel/panel-list-item/assignment-icon.js","bocce/templates/components/side-panel/panel-list-item/discussion-icon.js","bocce/templates/components/side-panel/panel-list-item/portrait.js","bocce/templates/components/side-panel/panel-list-item/quiz-icon.js","bocce/templates/components/side-panel/work.js","bocce/templates/components/submission/footer-interact.js","bocce/templates/components/upload-preview.js","bocce/templates/components/user-dot.js","bocce/templates/components/user-icons.js","bocce/templates/components/user-portrait.js","bocce/templates/conversation-reply-box.js","bocce/templates/conversations.js","bocce/templates/course-summary.js","bocce/templates/dashboard.js","bocce/templates/discussion-body.js","bocce/templates/discussion-contents.js","bocce/templates/discussion-header.js","bocce/templates/discussion-reply-box.js","bocce/templates/drafts.js","bocce/templates/events.js","bocce/templates/gradebook.js","bocce/templates/help-guide.js","bocce/templates/index.js","bocce/templates/interactions/audiocomments/comment.js","bocce/templates/interactions/audiocomments/main.js","bocce/templates/interactions/audiomarkersquiz/main.js","bocce/templates/interactions/contentframing/main.js","bocce/templates/interactions/cycle5/main.js","bocce/templates/interactions/draganddropquiz/main.js","bocce/templates/interactions/fillintheblanksquiz/main.js","bocce/templates/interactions/flashcards/main.js","bocce/templates/interactions/formbuilder/main.js","bocce/templates/interactions/guitar/main.js","bocce/templates/interactions/hotspotquiz/main.js","bocce/templates/interactions/hotspotquiz/quizcomplete.js","bocce/templates/interactions/imageexplorer/infobottom.js","bocce/templates/interactions/imageexplorer/loading.js","bocce/templates/interactions/imageexplorer/nav.js","bocce/templates/interactions/imageexplorer/stage.js","bocce/templates/interactions/listrollover/main.js","bocce/templates/interactions/mixvisualizer/main.js","bocce/templates/interactions/rive/main.js","bocce/templates/interactions/slideshow/main.js","bocce/templates/interactions/solfegeladder/main.js","bocce/templates/interactions/solfegeladder/major-key-exp-pallet.js","bocce/templates/interactions/textlineandtextselecthybrid/main.js","bocce/templates/interactions/textlineandtextselecthybrid/quizcomplete.js","bocce/templates/interactions/timedwriting/main.js","bocce/templates/interactions/timeline/main.js","bocce/templates/interactions/videocommentary/main.js","bocce/templates/loading-message.js","bocce/templates/loading.js","bocce/templates/lobby.js","bocce/templates/main-panel.js","bocce/templates/notifications.js","bocce/templates/polls.js","bocce/templates/prefs.js","bocce/templates/quiz.js","bocce/templates/reply.js","bocce/templates/showcase-post.js","bocce/templates/side-nav.js","bocce/templates/side-panel.js","bocce/templates/submission-no-rubric.js","bocce/templates/syllabus.js","bocce/templates/userprofile.js","bocce/templates/video-player.js","bocce/templates/vidrec.js","bocce/transforms/boolean.js","bocce/transforms/date.js","bocce/transforms/number.js","bocce/transforms/string.js","bocce/utilities/dialog.js","bocce/utilities/logout.js","bocce/utilities/promise-queue.js","bocce/utilities/timer.js","vendor/ember-cli/app-suffix.js","vendor/ember-cli/app-config.js","vendor/ember-cli/app-boot.js"],"sourcesContent":["'use strict';\n\n\n","define(\"bocce/adapters/-json-api\", [\"exports\", \"@ember-data/adapter/json-api\"], function (_exports, _jsonApi) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _jsonApi.default;\n }\n });\n});","define(\"bocce/adapters/application\", [\"exports\", \"@ember-data/adapter/rest\", \"ember-inflector\"], function (_exports, _rest, _emberInflector) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/adapters/application.js\n\n let adapter = _rest.default.extend({\n namespace: 'interface',\n init(...args) {\n this._super(...args);\n this.nestedResources = this.nestedResources || {};\n if (window.location.host == 'intro.online.berklee.edu') {\n this.host = window.location.protocol === 'https:' ? 'https://' + window.location.host : 'http://berklee-bocce-static.s3-website-us-east-1.amazonaws.com';\n }\n },\n updateId: function (record, data) {\n var oldId = Ember.get(record, 'id');\n let id = data.id;\n if (id == null || id === '') {\n id = null;\n } else {\n id = id + '';\n }\n if (oldId && oldId !== id) {\n this.typeMapFor(record.constructor).idToRecord[oldId] = record;\n } else {\n this.typeMapFor(record.constructor).idToRecord[id] = record;\n }\n Ember.set(record, 'id', id);\n },\n nestResources: function (type, resources) {\n let r = this.nestedResources || {};\n if (!Array.isArray(resources)) {\n resources = [resources];\n }\n if (r[type] && r[type].ids && r[type].ids.length === resources.length) {\n let match = true;\n for (let i = 0, l = resources.length; i < l; i++) {\n let old = r[type].ids[i],\n key = Object.keys(old)[0];\n if (old[key] !== resources[i][key]) {\n match = false;\n break;\n }\n }\n if (match) {\n return this;\n }\n }\n let reloaded = [];\n\n // Don't need to reload if it's the first go-round .. right?\n if (!r[type]) {\n reloaded.push('all');\n }\n r[type] = {\n ids: resources,\n reloaded: []\n };\n this.set('nestedResources', r);\n return this;\n },\n /*\n hasReloadedType(type, id) {\n return true;\n let nestings = this.get('nestedResources'),\n nesting = nestings[type];\n if ( ! nesting ) {\n return true;\n }\n return nesting.reloaded.indexOf(id) >= 0;\n },\n markReloaded(type, id) {\n let nestings = this.get('nestedResources'),\n t = nestings[type];\n t.reloaded.push(id);\n this.set('nestedResources', nestings);\n },\n // PRM: New logic. Anything that's nested within a course or a section needs to\n // be reloaded when the course changes. This keeps track of what it's already\n // reloaded, so we (hopefully) only hit the API once per object per change.\n shouldReloadRecord: function(store, snapshot) {\n if ( this.hasReloadedType(snapshot.modelName, 'all') ||\n this.hasReloadedType(snapshot.modelName, snapshot.id) ) {\n return false;\n }\n debug(\"reloading \" + snapshot.modelName + \".id = \" + snapshot.id);\n this.markReloaded(snapshot.modelName, snapshot.id);\n return true;\n },\n shouldReloadAll: function(store, snapshot) {\n let type = snapshot.type.modelName;\n if ( this.hasReloadedType(type, 'all') ) {\n return false;\n }\n debug(\"reloading all \" + type);\n this.markReloaded(type, 'all');\n return true;\n },\n */\n\n buildURL: function (type, id, snapshot) {\n var nestings = this.nestedResources[type] || {},\n resources = nestings.ids || [],\n parts = [this.host],\n i,\n resource,\n key,\n joined;\n if (this.namespace) {\n parts.push(this.namespace);\n }\n for (i = 0; i < resources.length; i++) {\n resource = resources[i];\n for (key in resource) {\n if (!Object.prototype.hasOwnProperty.call(resource, key)) {\n continue;\n }\n parts.push(_emberInflector.default.inflector.pluralize(key));\n parts.push(resource[key]);\n }\n }\n parts.push(_emberInflector.default.inflector.pluralize(type), id);\n joined = parts.join('/');\n if (window.location.host == 'intro.online.berklee.edu') {\n // On intro, we don't want to use normal interface URLs. Add a\n // slash at the end to ensure that we get the correct item, then\n // an index.html to avoid an extra redirect.\n if (joined.substr(-1) !== '/') {\n joined += '/';\n }\n joined += 'index.html';\n }\n if (snapshot && snapshot.adapterOptions) {\n let params = [];\n for (let key in snapshot.adapterOptions) {\n params.push(key + '=' + encodeURIComponent(snapshot.adapterOptions[key]));\n }\n if (params.length > 0) {\n joined += '?' + params.join('&');\n }\n }\n return joined;\n },\n // NK: TEMPORARY FIX TO STOP THE MADNESS AAAAA\n // Background reloading: never do it (for now. we want to turn this back on later).\n shouldBackgroundReloadRecord() {\n return false;\n },\n shouldBackgroundReloadAll() {\n return false;\n },\n // store.findAll should always do a fetch.\n shouldReloadAll() {\n return true;\n }\n });\n if (window.location.host == 'intro.online.berklee.edu') {\n adapter = adapter.extend({\n createRecord: function (store, type, snapshot) {\n snapshot.id = new Date().getTime();\n return this.updateRecord(store, type, snapshot);\n },\n updateRecord: function (store, type, snapshot) {\n let data = this.serialize(snapshot, {\n includeId: true\n });\n return new Promise(resolve => {\n let retval = {};\n retval[snapshot.modelName] = data;\n Ember.run(null, resolve, retval);\n });\n }\n });\n }\n var _default = _exports.default = adapter;\n});","define(\"bocce/app\", [\"exports\", \"@ember-data/store\", \"ember-resolver\", \"ember-load-initializers\", \"bocce/config/environment\"], function (_exports, _store, _emberResolver, _emberLoadInitializers, _environment) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n const App = Ember.Application.extend({\n modulePrefix: _environment.default.modulePrefix,\n podModulePrefix: _environment.default.podModulePrefix,\n Resolver: _emberResolver.default,\n init(...args) {\n this._super(...args);\n // this allows us to bind actions to paste and blur events:\n this.customEvents = this.customEvents || {\n paste: 'paste',\n blur: 'blur'\n };\n }\n });\n (0, _emberLoadInitializers.default)(App, _environment.default.modulePrefix);\n\n // TODO (NK): unjank this. We really shouldn't need to extend Store. One\n // possible alternative is to create Adapters that are aware of the necessary\n // parent models and builds URLs automatically, instead of needing manual\n // nesting.\n _store.default.prototype.nestResources = function (type, resources) {\n return this.adapterFor(type).nestResources(type, resources);\n };\n moment.tz.add(['EST|EST|50|0|', 'America/New_York|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 11B0 1qL0 1a10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x40 iv0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e6']);\n moment.locale('en', {\n 'calendar': {\n 'lastDay': 'MMM D',\n 'sameDay': 'h:mmA z',\n 'nextDay': 'MMM D',\n 'lastWeek': 'MMM D',\n 'nextWeek': 'MMM D',\n 'sameElse': 'MMM D'\n }\n });\n if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.isMobile = true;\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.isMobile = false;\n }\n if (navigator.userAgent.toLowerCase().indexOf('safari') !== -1) {\n if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.isSafari = false;\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.isSafari = true;\n }\n }\n if (window.navigator.userAgent.indexOf('Edge') > -1) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.isEdge = true;\n }\n\n // JRW: Make the mouse position available in the window object in case the user\n // is using a browser that doesn't support window.event (Firefox)\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$(document).mousemove(e => {\n window.mouseX = e.pageX;\n window.mouseY = e.pageY;\n }).mouseover();\n var _default = _exports.default = App;\n});","define(\"bocce/component-managers/glimmer\", [\"exports\", \"@glimmer/component/-private/ember-component-manager\"], function (_exports, _emberComponentManager) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _emberComponentManager.default;\n }\n });\n});","define(\"bocce/components/adblock-warning\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n adblockWhitelistInstructions: false,\n actions: {\n dismissAdblockWarning() {\n this.session.set('hasAdBlock', false);\n localStorage.setItem('dismissedAdblockWarning', true);\n },\n toggleABWhitelistInstructions() {\n let whiteListInstructions = this.get('adblockWhitelistInstructions');\n this.set('adblockWhitelistInstructions', !whiteListInstructions);\n },\n imageInNewTab() {\n let imgSrc = event.target.src;\n if (imgSrc) {\n window.open(imgSrc, '_blank');\n }\n }\n }\n });\n});","define(\"bocce/components/advice-card/audio-recorder\", [\"exports\", \"@glimmer/component\", \"bocce/helpers/upload\"], function (_exports, _component, _upload) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7, _descriptor8, _descriptor9, _descriptor10, _descriptor11, _descriptor12;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n const __COLOCATED_TEMPLATE__ = Ember.HTMLBars.template(\n /*\n \n \n
\n
\n \n \n \n \n \n \n
\n \n
\n
\n \n
\n
\n
\n {{{this.recordingStatus}}}\n
\n
\n */\n {\n \"id\": \"Z9CalZxZ\",\n \"block\": \"{\\\"symbols\\\":[],\\\"statements\\\":[[3,\\\" This is a basic audio recorder component \\\"],[2,\\\"\\\\n\\\"],[3,\\\" Record button, switch to stop button when recording, with time code. Once stopped, option to play back, re-record, or upload \\\"],[2,\\\"\\\\n\\\"],[10,\\\"div\\\"],[14,0,\\\"audio-recorder-basic\\\"],[12],[2,\\\"\\\\n \\\"],[10,\\\"div\\\"],[15,0,[31,[\\\"audio-recorder-controls \\\",[32,0,[\\\"recordingClassSelector\\\"]]]]],[12],[2,\\\"\\\\n \\\"],[11,\\\"button\\\"],[24,0,\\\"audio-recorder-record\\\"],[4,[38,0],[[32,0],\\\"startRecording\\\"],null],[12],[10,\\\"i\\\"],[14,0,\\\"fal fa-circle\\\"],[12],[13],[2,\\\" Record\\\"],[13],[2,\\\"\\\\n \\\"],[11,\\\"button\\\"],[24,0,\\\"audio-recorder-stop\\\"],[4,[38,0],[[32,0],\\\"stopRecording\\\"],null],[12],[10,\\\"i\\\"],[14,0,\\\"fal fa-stop\\\"],[12],[13],[2,\\\" Stop\\\"],[13],[2,\\\"\\\\n \\\"],[11,\\\"button\\\"],[24,0,\\\"audio-recorder-play\\\"],[4,[38,0],[[32,0],\\\"playRecording\\\"],null],[12],[10,\\\"i\\\"],[14,0,\\\"fal fa-play\\\"],[12],[13],[2,\\\" Play\\\"],[13],[2,\\\"\\\\n \\\"],[11,\\\"button\\\"],[24,0,\\\"audio-recorder-re-record\\\"],[4,[38,0],[[32,0],\\\"trashRecording\\\"],null],[12],[10,\\\"i\\\"],[14,0,\\\"fal fa-trash\\\"],[12],[13],[2,\\\" Re-record\\\"],[13],[2,\\\"\\\\n \\\"],[11,\\\"button\\\"],[16,0,[31,[\\\"audio-recorder-upload \\\",[30,[36,2],[[35,1],\\\"file-ready\\\"],null]]]],[4,[38,0],[[32,0],\\\"uploadRecording\\\"],null],[12],[10,\\\"i\\\"],[14,0,\\\"fal fa-upload\\\"],[12],[13],[2,\\\" Upload\\\"],[13],[2,\\\"\\\\n\\\\n \\\"],[10,\\\"div\\\"],[14,0,\\\"toggle-container\\\"],[12],[2,\\\"\\\\n \\\"],[11,\\\"button\\\"],[24,1,\\\"echoCancellationToggleAudio\\\"],[16,0,[31,[\\\"slide-toggle \\\",[30,[36,2],[[35,3],\\\"on\\\",\\\"off\\\"],null]]]],[4,[38,0],[[32,0],\\\"echoCancellationToggleAudio\\\"],null],[12],[2,\\\"\\\\n \\\"],[10,\\\"label\\\"],[14,0,\\\"toggle-label tooltip light\\\"],[14,\\\"tooltip\\\",\\\"Echo Cancellation helps remove the echo effect when recording audio. This can improve the audio quality in noisy environments, but it is not recommended for general use because it also tends to remove instrumental background audio.\\\"],[12],[2,\\\"\\\\n Echo Cancellation\\\\n \\\"],[13],[2,\\\"\\\\n \\\"],[10,\\\"span\\\"],[14,0,\\\"slider round\\\"],[12],[13],[2,\\\"\\\\n \\\"],[13],[2,\\\"\\\\n \\\"],[13],[2,\\\"\\\\n \\\"],[10,\\\"div\\\"],[15,0,[31,[\\\"advice-card-audio-player \\\",[30,[36,2],[[35,4],\\\"active\\\"],null]]]],[12],[2,\\\"\\\\n \\\"],[10,\\\"audio\\\"],[14,1,\\\"advice-card-audio\\\"],[14,\\\"preload\\\",\\\"none\\\"],[14,\\\"controlsList\\\",\\\"nodownload\\\"],[14,\\\"controls\\\",\\\"controls\\\"],[12],[10,\\\"source\\\"],[15,\\\"src\\\",[31,[[34,4]]]],[14,4,\\\"audio/mpeg\\\"],[12],[13],[13],[2,\\\"\\\\n \\\"],[13],[2,\\\"\\\\n \\\"],[13],[2,\\\"\\\\n \\\"],[10,\\\"div\\\"],[14,0,\\\"audio-recorder-status\\\"],[12],[2,\\\"\\\\n \\\"],[2,[32,0,[\\\"recordingStatus\\\"]]],[2,\\\"\\\\n \\\"],[13],[2,\\\"\\\\n\\\"],[13]],\\\"hasEval\\\":false,\\\"upvars\\\":[\\\"action\\\",\\\"file\\\",\\\"if\\\",\\\"echoCancellationEnabled\\\",\\\"audioBlob\\\"]}\",\n \"meta\": {\n \"moduleName\": \"bocce/components/advice-card/audio-recorder.hbs\"\n }\n });\n let AudioRecorder = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._tracked, _dec7 = Ember._tracked, _dec8 = Ember._tracked, _dec9 = Ember._tracked, _dec10 = Ember._tracked, _dec11 = Ember._tracked, _dec12 = Ember.inject.service, _dec13 = Ember._action, _dec14 = Ember._action, _dec15 = Ember._action, _dec16 = Ember._action, _dec17 = Ember._action, _dec18 = Ember._action, _dec19 = Ember._action, (_class = class AudioRecorder extends _component.default {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"recording\", _descriptor, this);\n _initializerDefineProperty(this, \"uploaded\", _descriptor2, this);\n _initializerDefineProperty(this, \"audioBlob\", _descriptor3, this);\n _initializerDefineProperty(this, \"audioUrl\", _descriptor4, this);\n _initializerDefineProperty(this, \"timeCode\", _descriptor5, this);\n _initializerDefineProperty(this, \"file\", _descriptor6, this);\n _initializerDefineProperty(this, \"recordingStatus\", _descriptor7, this);\n _initializerDefineProperty(this, \"previousStatus\", _descriptor8, this);\n _initializerDefineProperty(this, \"recordingClassSelector\", _descriptor9, this);\n _initializerDefineProperty(this, \"previousClassSelector\", _descriptor10, this);\n _initializerDefineProperty(this, \"echoCancellationEnabled\", _descriptor11, this);\n _initializerDefineProperty(this, \"store\", _descriptor12, this);\n }\n encodeAudio() {\n let panel = this.panel;\n this.recorder.exportMP3((blob, mp3Name, context) => {\n this.file = new File([blob], mp3Name, {\n type: 'audio/mpeg3'\n });\n this.audioBlob = URL.createObjectURL(blob);\n // reload audio player so it recognizes the new blob\n document.getElementById('advice-card-audio').load();\n this.recordingStatus = 'Recorded. Please upload your recording to enable submission.';\n this.recordingClassSelector = 'done';\n this.recorder.destroy();\n }, this);\n }\n uploadRecording() {\n if (!this.file || this.args.recording) {\n return;\n }\n this.recordingClassSelector = 'uploading';\n this.recordingStatus = ` Uploading...`;\n (0, _upload.default)(this.file, window.session.get('user.id'), this.store, null, null, progress => {\n this.recordingStatus = 'Uploading...' + progress + '%';\n this.recordingClassSelector = 'uploading';\n }).then(att_id => {\n let retval = this.store.findRecord('attachment', att_id).then(attachment => {\n console.log('attachment', attachment);\n this.args.updateRecordingStatus(false);\n this.args.updateAttachment(att_id);\n this.audioBlob = attachment.get('url');\n this.recordingStatus = 'Recording successfully uploaded.';\n this.recordingClassSelector = 'uploaded';\n this.uploaded = true;\n });\n return retval;\n }, err => {\n Ember.debug(err, 'Could not upload file');\n this.recordingStatus = 'Upload failed. Please try again.';\n this.recordingClassSelector = 'done';\n this.uploaded = false;\n });\n }\n startUserMedia(stream) {\n try {\n // WebKit nonsense\n window.AudioContext = window.AudioContext || window.webkitAudioContext;\n window.URL = window.URL || window.webkitURL;\n Ember.debug('Audio context set up.');\n Ember.debug('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));\n this.recordingClassSelector = 'ready';\n } catch (e) {\n Ember.debug('No web audio support in this browser!');\n this.recordingStatus = 'No web audio support in this browser!';\n this.recordingClassSelector = 'no-audio';\n }\n this.recorder = new Recorder(stream);\n }\n async startRecording() {\n let constraints = {\n audio: true\n };\n if (!(localStorage.getItem('bocceEchoCancellation') === 'true')) {\n constraints.audio = {\n echoCancellation: false,\n mozAutoGainControl: false,\n mozNoiseSuppression: false,\n googEchoCancellation: false,\n googAutoGainControl: false,\n googNoiseSuppression: false,\n googHighpassFilter: false\n };\n }\n navigator.mediaDevices.getUserMedia(constraints).then(stream => {\n this.startUserMedia(stream);\n this.recordingStatus = 'Starting...';\n this.recordingClassSelector = 'recording';\n let rec = this.recorder.record(time => {\n this.recordingStatus = `RECORDING: ${time}`;\n });\n if (rec) {\n this.args.updateRecordingStatus(true);\n }\n }).catch(error => {\n Ember.debug('No live audio input: ' + error);\n this.recordingStatus = 'Please enable microphone access in your browser settings and refresh the page.';\n this.recordingClassSelector = 'no-audio';\n });\n }\n async stopRecording() {\n if (this.audioBlob) {\n this.player.pause();\n this.player.currentTime = 0;\n this.recordingStatus = 'Stopped. Please upload your recording to enable submission.';\n this.recordingClassSelector = 'done';\n } else {\n this.recordingStatus = 'Encoding...';\n this.recordingClassSelector = 'done';\n let rec = this.recorder.stop();\n this.encodeAudio(rec);\n }\n }\n playRecording() {\n this.previousStatus = this.recordingStatus;\n this.previousClassSelector = this.recordingClassSelector;\n this.player = document.getElementById('advice-card-audio');\n this.recordingStatus = 'Playing...';\n this.recordingClassSelector = 'playing';\n this.player.play().then(() => {\n if (!this.playerEventListener) {\n // Create a player listener to change the status when the recording is done playing\n\n this.playerEventListener = true;\n this.player.addEventListener('ended', () => {\n // this.recordingStatus = 'Stopped.';\n this.player.pause();\n this.player.currentTime = 0;\n this.recordingStatus = this.previousStatus;\n this.recordingClassSelector = this.previousClassSelector;\n });\n }\n }).catch(error => {\n this.recordingStatus = 'Error playing recording.';\n this.recordingClassSelector = 'error';\n });\n }\n trashRecording() {\n this.player = document.getElementById('advice-card-audio');\n this.player.pause();\n this.player.currentTime = 0;\n this.recordingStatus = 'Ready to record.';\n this.recordingClassSelector = 'ready';\n this.audioBlob = false;\n this.file = null;\n this.uploaded = false;\n this.args.updateAttachment(null);\n this.args.updateRecordingStatus(false);\n }\n echoCancellationToggleAudio() {\n let echoCancellation = localStorage.getItem('bocceEchoCancellation') === 'true';\n if (echoCancellation) {\n localStorage.setItem('bocceEchoCancellation', false);\n this.echoCancellationEnabled = false;\n } else {\n localStorage.setItem('bocceEchoCancellation', true);\n this.echoCancellationEnabled = true;\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"recording\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"uploaded\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"audioBlob\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"audioUrl\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"timeCode\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"file\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, \"recordingStatus\", [_dec7], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return 'Ready to record.';\n }\n }), _descriptor8 = _applyDecoratedDescriptor(_class.prototype, \"previousStatus\", [_dec8], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return '';\n }\n }), _descriptor9 = _applyDecoratedDescriptor(_class.prototype, \"recordingClassSelector\", [_dec9], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return 'ready';\n }\n }), _descriptor10 = _applyDecoratedDescriptor(_class.prototype, \"previousClassSelector\", [_dec10], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return '';\n }\n }), _descriptor11 = _applyDecoratedDescriptor(_class.prototype, \"echoCancellationEnabled\", [_dec11], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return localStorage.getItem('bocceEchoCancellation') === 'true';\n }\n }), _descriptor12 = _applyDecoratedDescriptor(_class.prototype, \"store\", [_dec12], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"uploadRecording\", [_dec13], Object.getOwnPropertyDescriptor(_class.prototype, \"uploadRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"startUserMedia\", [_dec14], Object.getOwnPropertyDescriptor(_class.prototype, \"startUserMedia\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"startRecording\", [_dec15], Object.getOwnPropertyDescriptor(_class.prototype, \"startRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"stopRecording\", [_dec16], Object.getOwnPropertyDescriptor(_class.prototype, \"stopRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"playRecording\", [_dec17], Object.getOwnPropertyDescriptor(_class.prototype, \"playRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"trashRecording\", [_dec18], Object.getOwnPropertyDescriptor(_class.prototype, \"trashRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"echoCancellationToggleAudio\", [_dec19], Object.getOwnPropertyDescriptor(_class.prototype, \"echoCancellationToggleAudio\"), _class.prototype)), _class));\n Ember._setComponentTemplate(__COLOCATED_TEMPLATE__, AudioRecorder);\n});","define(\"bocce/components/assignment-user\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n userprofileService: Ember.inject.service('userprofile')\n });\n});","define(\"bocce/components/assignment/assignment-comment\", [\"exports\", \"bocce/utilities/dialog\"], function (_exports, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n userprofileService: Ember.inject.service('userprofile'),\n tagName: 'li',\n classNames: ['reply'],\n classNameBindings: ['deleted'],\n deleted: Ember.computed.reads('response.is_deleted'),\n actions: {\n deleteConfirm() {\n const message = 'Are you sure you wish to delete this comment? This action CANNOT be undone!';\n (0, _dialog.default)(message, ['Yes. If permanent deletion of attachments is desired, first delete manually in S3. Then manually delete in Canvas.', 'No']).then(choice => {\n if (choice.indexOf('Yes') === 0) {\n /* eslint-disable-next-line ember/no-get */\n const id = this.get('response.id');\n this.send('sendParentAction', 'deleteAssignmentComment', id);\n }\n });\n },\n sendParentAction(action, ...args) {\n (true && !(typeof this.get(action) !== 'undefined') && Ember.assert('action should be defined.', typeof this.get(action) !== 'undefined'));\n this.get(action)(...args);\n }\n }\n });\n});","define(\"bocce/components/assignment/assignment-list\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n init(...args) {\n this._super(...args);\n this.sortProperties = this.sortProperties || ['due_at'];\n },\n tagName: 'ul',\n classNames: 'assignment-list',\n sortedAssignments: Ember.computed.sort('assignments', 'sortProperties'),\n actions: {\n sendGoToAssignment(id) {\n this.goToAssignment(id);\n }\n }\n });\n});","define(\"bocce/components/attachment-preview\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n const supportedVideoTypes = {\n chrome: [\"video/mp4\", \"video/webm\"],\n firefox: [\"video/mp4\", \"video/quicktime\", \"video/ogg\"],\n edge: [\"video/mp4\", \"video/webm\"],\n safari: [\"video/mp4\"],\n opera: [\"video/mp4\"],\n other: [\"video/mp4\"]\n };\n const isEdge = navigator.userAgent.indexOf('Edg') !== -1;\n const isOpera = !!window.opera || navigator.userAgent.indexOf('OPR/') !== -1;\n const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 && 'netscape' in window && / rv:/.test(navigator.userAgent);\n const isChrome = !isOpera && !isEdge && !!navigator.webkitGetUserMedia || navigator.userAgent.toLowerCase().indexOf('chrome/') !== -1;\n const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n function browserCanPlayVideoType(type) {\n let canPlay = false;\n if (isChrome) {\n canPlay = supportedVideoTypes.chrome.includes(type);\n } else if (isFirefox) {\n canPlay = supportedVideoTypes.firefox.includes(type);\n } else if (isEdge) {\n canPlay = supportedVideoTypes.edge.includes(type);\n } else if (isSafari) {\n canPlay = supportedVideoTypes.safari.includes(type);\n } else if (isOpera) {\n canPlay = supportedVideoTypes.opera.includes(type);\n } else {\n canPlay = supportedVideoTypes.other.includes(type);\n }\n return type.includes('video') && (canPlay || document.createElement('video').canPlayType(type) === \"probably\");\n }\n var _default = _exports.default = Ember.Component.extend({\n att_URL: Ember.computed('attachment.url', function () {\n // HOTFIX: Swap docker IP for bocce URL\n /* eslint-disable-next-line ember/no-get */\n let attachment = this.get('attachment.url') || '';\n let docker_ip_regex = /https:\\/\\/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/;\n let host = window.location.origin;\n return attachment.replace(docker_ip_regex, host);\n }),\n created_at_formatted: Ember.computed('attachment.created_at', function () {\n return moment(this.get('attachment.created_at')).tz('America/New_York').format('MMM Do[,] h:mm:ss a') + \" ET\";\n }),\n attachment_name: Ember.computed.or('attachment.display_name', 'attachment.name'),\n att_URL_noDL: Ember.computed('att_URL', function () {\n let url = this.att_URL;\n\n // download file contents from URL and put in a blob\n let xhr = new XMLHttpRequest();\n xhr.open('GET', url, true);\n xhr.responseType = 'blob';\n xhr.onload = e => {\n if (e.target.status == 200 && e.loaded === e.total) {\n let blob = e.target.response;\n let url = window.URL.createObjectURL(blob);\n this.set('att_URL_noDL', url);\n }\n };\n xhr.send();\n return false;\n }),\n isMobile: Ember.computed(function () {\n /* eslint-disable-next-line ember/no-jquery */\n return $.isMobile;\n }),\n is_image: Ember.computed('attachment.type', function () {\n // eslint-disable-next-line ember/no-get\n let t = this.get('attachment.type') || this.attachment[\"content-type\"];\n return t === 'image/jpeg' || t === 'image/png';\n }),\n is_video: Ember.computed('attachment.type', function () {\n // eslint-disable-next-line ember/no-get\n let t = this.get('attachment.type') || this.attachment[\"content-type\"];\n return browserCanPlayVideoType(t);\n }),\n is_quicktime: Ember.computed('attachment.type', function () {\n // eslint-disable-next-line ember/no-get\n let t = this.get('attachment.type') || this.attachment[\"content-type\"];\n return t === 'video/quicktime';\n }),\n is_audio: Ember.computed('attachment.type', function () {\n // eslint-disable-next-line ember/no-get\n let t = this.get('attachment.type') || this.attachment[\"content-type\"];\n return t === 'audio/mpeg3' || t === 'audio/mp3' || t === 'audio/mpeg' || t === 'audio/x-m4a';\n }),\n is_word: Ember.computed('attachment.type', function () {\n // eslint-disable-next-line ember/no-get\n let t = this.get('attachment.type') || this.attachment[\"content-type\"];\n return t === 'application/msword';\n }),\n is_pdf: Ember.computed('attachment.type', function () {\n // eslint-disable-next-line ember/no-get\n let t = this.get('attachment.type') || this.attachment[\"content-type\"];\n return t === 'application/pdf';\n }),\n is_zip: Ember.computed('attachment.type', function () {\n // eslint-disable-next-line ember/no-get\n let t = this.get('attachment.type') || this.attachment[\"content-type\"];\n return t === 'application/zip';\n })\n });\n});","define(\"bocce/components/attachments/attached-file\", [\"exports\", \"@glimmer/component\", \"bocce/helpers/file-extension\", \"bocce/utilities/dialog\"], function (_exports, _component, _fileExtension, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _class, _descriptor, _descriptor2, _descriptor3;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let AttachmentsAttachedFileComponent = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember.inject.service, _dec4 = Ember._action, _dec5 = Ember._action, _dec6 = Ember._action, _dec7 = Ember._action, _dec8 = Ember._action, _dec9 = Ember._action, (_class = class AttachmentsAttachedFileComponent extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _initializerDefineProperty(this, \"renaming\", _descriptor, this);\n _initializerDefineProperty(this, \"renameInput\", _descriptor2, this);\n _initializerDefineProperty(this, \"store\", _descriptor3, this);\n this.renaming = false;\n if (this.fileName && this.fileName.includes(\".\")) {\n this.renameInput = this.fileName.split(\".\")[0];\n } else if (this.fileName && this.fileName.length > 0) {\n this.renameInput = this.fileName;\n } else {\n this.renameInput = \"\";\n }\n }\n get progress() {\n if (this.args.attachment.progress) {\n return this.args.attachment.progress;\n }\n return 0;\n }\n get fileName() {\n return this.args.name || this.args.attachment.name || this.args.attachment.file.name;\n }\n get fileExtension() {\n return (0, _fileExtension.fileExtension)([this.fileName]);\n }\n get uploading() {\n if (this.args.uploading) {\n return this.args.uploading;\n }\n return false;\n }\n get uploaded() {\n if (this.args.attachment?.uploaded) {\n return this.args.attachment.uploaded;\n }\n return false;\n }\n get fileUrl() {\n return this.args.attachment.blobURL || URL.createObjectURL(this.args.attachment.file);\n }\n get attachmentType() {\n if (this.args.attachment.file?.type?.includes('audio')) {\n return \"microphone\";\n } else if (this.args.attachment.file?.type?.includes('video')) {\n return \"video\";\n } else {\n return \"paperclip\";\n }\n }\n deleteAttachment() {\n let dialogMessage = 'Are you sure you wish to delete this attachment?';\n (0, _dialog.default)(dialogMessage, ['Yes', 'No']).then(selection => {\n if (selection === 'Yes') {\n this.args.deleteAttachment(this.args.attachment.file);\n }\n });\n }\n showRename() {\n this.renaming = true;\n }\n confirmRename() {\n if (!this.renameInput || this.renameInput.includes('.')) {\n alert('Invalid file name, must not contain \".\"');\n return;\n }\n let newName = this.fileExtension ? this.renameInput + this.fileExtension : this.renameInput;\n this.args.renameAttachment(this.args.attachment, newName, this.args.index);\n this.renaming = false;\n }\n cancelRename() {\n this.renaming = false;\n }\n retryAttachment() {\n this.args.uploadSingleFile(this.args.attachment.file);\n }\n playPreviewAudio(url) {\n let audioPlayer = document.getElementById('main-audio-player');\n let audioPlayerHTML = audioPlayer.querySelector('audio');\n audioPlayer.classList.add('active');\n audioPlayerHTML.src = url;\n audioPlayerHTML.play();\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"renaming\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"renameInput\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"store\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"deleteAttachment\", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, \"deleteAttachment\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"showRename\", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, \"showRename\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"confirmRename\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"confirmRename\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"cancelRename\", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, \"cancelRename\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"retryAttachment\", [_dec8], Object.getOwnPropertyDescriptor(_class.prototype, \"retryAttachment\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"playPreviewAudio\", [_dec9], Object.getOwnPropertyDescriptor(_class.prototype, \"playPreviewAudio\"), _class.prototype)), _class));\n});","define(\"bocce/components/av-player\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let AvPlayerComponent = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._tracked, _dec7 = Ember._action, _dec8 = Ember._action, _dec9 = Ember._action, _dec10 = Ember._action, _dec11 = Ember._action, _dec12 = Ember._action, _dec13 = Ember._action, _dec14 = Ember._action, (_class = class AvPlayerComponent extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _initializerDefineProperty(this, \"currentTime\", _descriptor, this);\n _initializerDefineProperty(this, \"duration\", _descriptor2, this);\n _initializerDefineProperty(this, \"playing\", _descriptor3, this);\n _initializerDefineProperty(this, \"thumbPosition\", _descriptor4, this);\n _initializerDefineProperty(this, \"muted\", _descriptor5, this);\n _initializerDefineProperty(this, \"playerElement\", _descriptor6, this);\n this.currentTime = 0;\n this.duration = 0;\n this.playing = false;\n this.thumbPosition = 0;\n this.muted = false;\n }\n get currentTimeFormatted() {\n return this.formatTime(this.currentTime);\n }\n get durationFormatted() {\n return this.formatTime(this.duration);\n }\n get linearGradient() {\n let thumbPercent = this.thumbPosition / 10;\n // a gap appears between the slider thumb and background that is biggest at 0% and 100% and not visible at 50%,\n // so adjust the background to fill this gap between 0% and 2% depending on the thumb's proximity to the middle\n let percentDifference = -1 / 25 * thumbPercent + 2;\n let finalPercent = thumbPercent + percentDifference;\n return `linear-gradient(to right, var(--color-base-05) 0%, var(--color-base-05) ${finalPercent}%, var(--color-base-50) ${finalPercent}%, var(--color-base-50) 100%`;\n }\n\n /**\n * Formats the given time in seconds to a string in a mm:ss format\n * @param timeInSeconds the time to be formatted in seconds\n * @returns {string}\n */\n formatTime(timeInSeconds) {\n return `${Math.floor(timeInSeconds / 60)}:${Math.floor(timeInSeconds % 60).toLocaleString('en-US', {\n minimumIntegerDigits: 2,\n useGrouping: false\n })}`;\n }\n setupPlayer(element) {\n this.playerElement = element;\n }\n stopPlaying() {\n this.playing = false;\n this.playerElement.pause();\n }\n togglePlaying() {\n this.playing = !this.playing;\n if (!this.playing) {\n this.playerElement.pause();\n } else {\n this.playerElement.play();\n }\n }\n toggleMuted() {\n this.muted = !this.muted;\n }\n seek(event, time) {\n if (time) {\n this.currentTime = this.currentTime + time;\n } else {\n this.currentTime = event.target.value * this.duration / 1000;\n }\n this.playerElement.currentTime = this.currentTime;\n }\n updateTime(event) {\n this.currentTime = event.target.currentTime;\n if (this.duration == 0) {\n return;\n }\n this.thumbPosition = this.currentTime / this.duration * 1000;\n }\n updateDuration(event) {\n this.duration = event.target.duration;\n }\n changeThumbPosition(event) {\n this.thumbPosition = event.target.value;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"currentTime\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"duration\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"playing\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"thumbPosition\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"muted\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"playerElement\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"setupPlayer\", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, \"setupPlayer\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"stopPlaying\", [_dec8], Object.getOwnPropertyDescriptor(_class.prototype, \"stopPlaying\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"togglePlaying\", [_dec9], Object.getOwnPropertyDescriptor(_class.prototype, \"togglePlaying\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleMuted\", [_dec10], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleMuted\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"seek\", [_dec11], Object.getOwnPropertyDescriptor(_class.prototype, \"seek\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateTime\", [_dec12], Object.getOwnPropertyDescriptor(_class.prototype, \"updateTime\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateDuration\", [_dec13], Object.getOwnPropertyDescriptor(_class.prototype, \"updateDuration\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"changeThumbPosition\", [_dec14], Object.getOwnPropertyDescriptor(_class.prototype, \"changeThumbPosition\"), _class.prototype)), _class));\n});","define(\"bocce/components/banner-drawer\", [\"exports\", \"bocce/mixins/enmodal\", \"bocce/mixins/notify\", \"bocce/utilities/dialog\"], function (_exports, _enmodal, _notify, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Component.extend(_enmodal.default, _notify.default, {\n init(...args) {\n this._super(...args);\n const hashSplit = window.location.hash.split('/');\n let course = null;\n if (hashSplit.length > 1) {\n course = hashSplit[1];\n if (+course) {\n Ember.$.get(`/interface/banners/user/active/-1/${course}`).then(ban => {\n if (ban.id && ban.message) {\n let modal_type = ban.dismiss_condition == \"survey\" ? \"survey\" : null;\n let modal_id = ban.dismiss_condition == \"survey\" ? \"course\" : null;\n this.actions.notify(ban.message, ban.is_dismissable, modal_type, modal_id, 100, this.session, false, ban.link, ban.id, \"banner\", ban.dismiss_condition_id);\n this.session.last_banner_serial = ban.id;\n }\n });\n }\n }\n },\n banners: Ember.computed.reads('session.banners'),\n session: Ember.inject.service(),\n store: Ember.inject.service(),\n actions: {\n toggleDrawer: function () {\n if (!this.drawerActive) {\n this.set('drawerActive', true);\n this.set('session.notification', false);\n } else {\n this.set('drawerActive', false);\n }\n },\n dismissDashboardNotif: function (id) {\n this.send('dismissNotif', id);\n if (Ember.$.isMobile) {\n (0, _dialog.default)(\"You can find it later in the user menu.\");\n } else {\n (0, _dialog.default)(\"You can find it later in the user menu or by clicking the logo in the top left corner.\");\n }\n },\n //Dismiss banner by index\n dismissNotif: function (id) {\n // ID is not the banner id, but the index of the banner in the array\n // the banner id is the 'serial' property\n let singleBanner = this.session.banners[id],\n dismissable = singleBanner.dismissable,\n banner_id = singleBanner.id,\n course_id = this.get('session.course.id'),\n term_id = this.get('session.termID');\n if (dismissable) {\n /* eslint-disable-next-line ember/no-get */\n let banners = this.get('session.banners');\n\n // Mark announcement as read\n if (banners[id].modal_type === 'discussion') {\n this.store.findRecord('discussion', banners[id].modal_id).then(model => {\n model.set('read', true);\n if (model.get('hasUnreadResponses')) {\n model.set('markReadThrough', model.get('lastResponseId'));\n }\n model.save();\n });\n }\n\n // Dismiss the banner for the particular user on the back end\n // show error if failure.\n let dismissRequest = Ember.$.get(`/interface/banners/${banner_id}/dismiss/${term_id}/${course_id}`);\n\n // Remove from banner drawer\n banners.splice(id, 1);\n this.set('session.banners', banners);\n this.notifyPropertyChange('banners');\n }\n },\n viewNotif: function (index) {\n let banner = this.session.banners[index];\n if (banner.reload) {\n location.reload(true);\n return;\n }\n\n // Remove from banner drawer\n let banners = this.session.banners;\n banners.splice(index, 1);\n this.set('session.banners', banners);\n this.notifyPropertyChange('banners');\n\n // Close drawer\n this.set('drawerActive', false);\n if (banner.link) {\n window.open(banner.link, '_blank');\n } else if (banner.modal_id) {\n this.send('viewModal', banner.modal_type, banner.modal_id);\n } else {\n (0, _dialog.default)(banner.message);\n }\n }\n }\n });\n});","define(\"bocce/components/bookmarks/bookmark-note\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _class;\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n let BookmarksHeaderComponent = _exports.default = (_dec = Ember._action, (_class = class BookmarksHeaderComponent extends Ember.Component {\n //This executes when a bookmark note is first rendered. It also executes when \n //a bookmark note is modified via the UI, which ensures that the note's\n //'Show All' button is appropriately shown/hidden depending on \n //the length of the modified note.\n didRender() {\n //This logic prevents an infinite loop caused by the below setting of\n //'noteClipped'.\n if (!this.bookmark.noteClipped) {\n const elem = this.element.querySelector('.bookmark-item-note');\n if (elem && elem.clientHeight < elem.scrollHeight) {\n Ember.set(this.bookmark, 'noteClipped', true);\n }\n }\n }\n setNotesMoreShowing(bookmark, moreShowing) {\n Ember.set(bookmark, 'notesMoreShowing', moreShowing);\n }\n }, (_applyDecoratedDescriptor(_class.prototype, \"setNotesMoreShowing\", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, \"setNotesMoreShowing\"), _class.prototype)), _class));\n});","define(\"bocce/components/bookmarks/bookmarks-header\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n class BookmarksHeaderComponent extends Ember.Component {\n get showActions() {\n return this.bookmarksService.numBookmarksLessons || this.bookmarksService.courses.length > 1 || this.bookmarksService.hasMoreBookmarks;\n }\n }\n _exports.default = BookmarksHeaderComponent;\n});","define(\"bocce/components/buttons/add-button\", [\"exports\", \"bocce/components/buttons/async-button\"], function (_exports, _asyncButton) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _asyncButton.default.extend({\n classNames: ['add']\n });\n});","define(\"bocce/components/buttons/async-button\", [\"exports\", \"bocce/components/buttons/button-generic\"], function (_exports, _buttonGeneric) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /*\n \n buttons/async-button component\n \n Creates a button that plays nicely with async actions. Expects the following parameters:\n * promiseAction - an action that returns a Promise\n * thenAction - an action to call on the Promise resolution. promiseAction will pass its resolved value to thenAction\n * active (optional) - a property to activate/deactivate the button. Button is active when truthy, inactive when\n falsey. When active is not provided, the button defaults to active.\n */\n var _default = _exports.default = _buttonGeneric.default.extend({\n init(...args) {\n this._super(...args);\n // Default to active when no active condition is supplied.\n this.active = typeof this.active !== 'undefined' ? this.active : true;\n },\n attributeBindings: ['ariaLabel:aria-label', 'ariaDisabled:aria-disabled'],\n classNames: ['button', 'async'],\n classNameBindings: ['_ready:active', '_working:working'],\n ariaLabel: Ember.computed('activeLabel', 'inactiveLabel', '_working', '_ready', function () {\n if (this._ready) {\n return this.activeLabel;\n } else {\n if (this._working) {\n return 'loading';\n } else {\n return this.inactiveLabel;\n }\n }\n }),\n ariaDisabled: Ember.computed.not('_ready'),\n _working: false,\n _ready: Ember.computed('_working', 'active', function () {\n return !this._working && this.active;\n }),\n click() {\n if (this._ready) {\n this.set('_working', true);\n this.promiseAction().then(resolved_val => {\n this.set('_working', false);\n if (typeof this.thenAction === 'function') {\n this.thenAction(resolved_val);\n }\n });\n }\n }\n });\n});","define(\"bocce/components/buttons/button-generic\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n tagName: 'button',\n classNames: ['button'],\n click() {\n this.action();\n }\n });\n});","define(\"bocce/components/buttons/delete-button\", [\"exports\", \"bocce/components/buttons/async-button\"], function (_exports, _asyncButton) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _asyncButton.default.extend({\n classNames: ['delete']\n });\n});","define(\"bocce/components/buttons/submit-button\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n tagName: 'button',\n classNames: ['editor-save-btn'],\n classNameBindings: ['active'],\n attributeBindings: ['ariaLabel:aria-label', 'ariaDisabled:aria-disabled'],\n ariaLabel: Ember.computed('active', 'activeLabel', 'inactiveLabel', function () {\n if (this.active) {\n return this.activeLabel;\n } else {\n return this.inactiveLabel;\n }\n }),\n ariaDisabled: Ember.computed.not('active'),\n click() {\n if (this.active) {\n this.action();\n }\n }\n });\n});","define(\"bocce/components/character-limited-textarea\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n const __COLOCATED_TEMPLATE__ = Ember.HTMLBars.template(\n /*\n \n
Character Count: {{this.charCount}}/{{this.charLimit}}
\n */\n {\n \"id\": \"Mi6WPn2t\",\n \"block\": \"{\\\"symbols\\\":[],\\\"statements\\\":[[11,\\\"textarea\\\"],[24,0,\\\"char-limied-textarea rte-editor-input\\\"],[16,2,[32,0,[\\\"text\\\"]]],[16,\\\"maxlength\\\",[32,0,[\\\"charLimit\\\"]]],[4,[38,0],[\\\"input\\\",[32,0,[\\\"updateText\\\"]]],null],[12],[13],[2,\\\"\\\\n\\\"],[10,\\\"div\\\"],[14,0,\\\"textarea-minimal-charcount\\\"],[12],[2,\\\"Character Count: \\\"],[1,[32,0,[\\\"charCount\\\"]]],[2,\\\"/\\\"],[1,[32,0,[\\\"charLimit\\\"]]],[13]],\\\"hasEval\\\":false,\\\"upvars\\\":[\\\"on\\\"]}\",\n \"meta\": {\n \"moduleName\": \"bocce/components/character-limited-textarea.hbs\"\n }\n });\n let CharacterLimitedTextarea = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._action, _dec3 = Ember._action, (_class = class CharacterLimitedTextarea extends _component.default {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"text\", _descriptor, this);\n }\n get charLimit() {\n return this.args.charLimit || 500;\n }\n updateOverCharLimit() {\n if (this.charCount > this.charLimit) {\n this.args.updateOverCharLimit(true);\n } else {\n this.args.updateOverCharLimit(false);\n }\n }\n updateText(event) {\n let inputWithoutNewlines = event.target.value.replace(/[\\r\\n]+/gm, '');\n this.text = inputWithoutNewlines.substring(0, this.charLimit);\n if (this.args.updateBodyText) {\n this.args.updateBodyText(this.text);\n }\n }\n get charCount() {\n return this.text.length;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"text\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return '';\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"updateOverCharLimit\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"updateOverCharLimit\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateText\", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, \"updateText\"), _class.prototype)), _class));\n Ember._setComponentTemplate(__COLOCATED_TEMPLATE__, CharacterLimitedTextarea);\n});","define(\"bocce/components/classroom/current-lesson-item\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n classNames: ['current-course-topic'],\n text: Ember.computed('lesson', 'title', function () {\n let lesson = this.lesson;\n if (Number(lesson) === 0) {\n lesson = 'Getting Started';\n }\n let title = this.title;\n if (!title) {\n return '';\n }\n return `${lesson} - ${title}`;\n })\n });\n});","define(\"bocce/components/classroom/header/section-info\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n tagName: 'span',\n classNames: ['header-course-section'],\n features: Ember.inject.service(),\n showWeek: Ember.computed('features.staticContent', 'hideWeek', function () {\n /* eslint-disable-next-line ember/no-get */\n return !this.get('features.staticContent') && !this.get('hideWeek');\n }),\n _sectionLabel: Ember.computed.or('sectionLabel', 'section.number'),\n _termLabel: Ember.computed.or('termLabel', 'term.name')\n });\n});","define(\"bocce/components/classroom/lessons/admin/quizzes\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _class, _descriptor, _descriptor2, _descriptor3;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let DeleteLastAttempt = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember.inject.service, _dec4 = Ember._action, _dec5 = Ember._action, (_class = class DeleteLastAttempt extends Component {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"selectedUser\", _descriptor, this);\n _initializerDefineProperty(this, \"selectedQuiz\", _descriptor2, this);\n _initializerDefineProperty(this, \"session\", _descriptor3, this);\n }\n init(...args) {\n super.init(...args);\n\n //I don't think this is used any more. But, right now I'm too nervous to touch anything.\n this.set('operations', ['quiz-admin/unlock-for-next-attempt', 'quiz-admin/delete-latest-attempt']);\n }\n async loadQuizUsers(quizId) {\n let path = `/interface/sections/${this.get('session.section.id')}/quizzes/${quizId}/submission_users`,\n data = await $.get(path);\n this.set('quizUsers', data.quiz_submission_users.users);\n }\n selectQuiz(quizId) {\n this.set('selectedQuizId', quizId);\n this.loadQuizUsers(quizId);\n let thisQuiz = this.store.findRecord('quiz', quizId);\n this.selectedQuiz = thisQuiz;\n this.selectedUser = null;\n }\n async expandOperations(userId) {\n let user = await this.store.findRecord('user', userId);\n user.set('quizAttemptMade', this.get('quizUsers').find(u => u.id == userId).attempt_made);\n this.selectedUser = user;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"selectedUser\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"selectedQuiz\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"session\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"selectQuiz\", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, \"selectQuiz\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"expandOperations\", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, \"expandOperations\"), _class.prototype)), _class));\n ;\n});","define(\"bocce/components/dashboard/course-list/course-list-item\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let CourseListItem = _exports.default = (_dec = Ember.inject.service, _dec2 = Ember.inject.service, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember.computed('course.my_stats.user_assignments_on_time_percentage'), _dec7 = Ember.computed('course.my_stats.user_discussions_on_time_percentage'), _dec8 = Ember.computed('course.my_stats.average_submission_on_time_percentage'), _dec9 = Ember.computed('course.my_stats.average_discussion_on_time_percentage'), _dec10 = Ember.computed('course'), _dec11 = Ember.computed('unread_total_count'), _dec12 = Ember.computed('course.unread_discussions_count', 'course.unread_announcements_count', 'course.unread_assignment_comments_count'), _dec13 = Ember.computed('course.upcoming_live_events_count'), _dec14 = Ember.computed('course.quizzes_overdue', 'course.quizzes_due', 'course.assignments_overdue', 'course.assignments_due'), _dec15 = Ember.computed('course.assignments_due', 'course.quizzes_due', 'course.discussions_due'), _dec16 = Ember.computed('course.assignments_overdue', 'course.quizzes_overdue', 'course.discussions_overdue'), _dec17 = Ember._action, _dec18 = Ember._action, _dec19 = Ember._action, _dec20 = Ember._action, _dec21 = Ember._action, _dec22 = Ember._action, _dec23 = Ember._action, _dec24 = Ember._action, (_class = class CourseListItem extends _component.default {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"gradeCalculator\", _descriptor, this);\n _initializerDefineProperty(this, \"dashboardSorter\", _descriptor2, this);\n _initializerDefineProperty(this, \"isMaximized\", _descriptor3, this);\n _defineProperty(this, \"firstLoad\", true);\n _initializerDefineProperty(this, \"displayStatsPopup\", _descriptor4, this);\n _initializerDefineProperty(this, \"hideClassmateStats\", _descriptor5, this);\n }\n // First time loading the component? Let it through\n\n sortAssignmentsProcess(stype, splitter, ascending) {\n let assignments = this.args.courseRaw[stype];\n if (!assignments) {\n return;\n }\n\n // Remove any assignment objects from array, where is_splitter is true\n assignments = assignments.filter(assignment => {\n return !assignment.is_splitter;\n });\n if (assignments.length === 1) {\n this[stype + 'SingleItem'] = true;\n }\n let sorted = assignments.sort((a, b) => {\n // Convert due dates to epoch time\n let aDue = a.due_at ? a.due_at : 0;\n let bDue = b.due_at ? b.due_at : 0;\n\n // Convert due dates to epoch time\n aDue = new Date(aDue).getTime();\n bDue = new Date(bDue).getTime();\n if (splitter) {\n if (a.assignment_type === b.assignment_type) {\n if (ascending) {\n return aDue - bDue;\n } else {\n return bDue - aDue;\n }\n } else {\n return a.assignment_type.localeCompare(b.assignment_type);\n }\n } else {\n if (ascending) {\n return aDue - bDue;\n } else {\n return bDue - aDue;\n }\n }\n });\n if (splitter) {\n let currentGroup = '';\n let currentWeight = '';\n let sortedWithDividers = [];\n let splitterCount = 0;\n sorted.forEach(function (assignment) {\n let groupName = assignment['assignment_type'];\n let groupWeight = assignment['assignment_weight'];\n let isDiscussion = assignment['is_discussion'];\n if (groupName !== currentGroup) {\n // We want to ensure that there aren't any non-discussion assignments in the group\n sorted.forEach(function (assignment2) {\n if (assignment2['assignment_type'] === groupName && !assignment2['is_discussion']) {\n isDiscussion = false;\n }\n });\n currentGroup = groupName;\n currentWeight = groupWeight;\n sortedWithDividers.push({\n is_splitter: true,\n type_name: groupName,\n type_weight: groupWeight,\n is_discussion: isDiscussion\n });\n splitterCount++;\n }\n sortedWithDividers.push(assignment);\n });\n if (splitterCount < 2) {\n this[stype + 'NoSplitter'] = true;\n } else {\n sorted = sortedWithDividers;\n }\n }\n return sorted;\n }\n get course() {\n let fullIndex = this.args.termIndex + '_' + this.args.index;\n if (this.args.maximizedCard !== fullIndex) {\n return this.args.courseRaw;\n }\n let course = this.args.courseRaw;\n\n // If the weighted divisions are toggled on/off, or if the course is not sorted, re-sort the assignments\n let resortWeightedDivisions = this.dashboardSorter.weightedDivisions !== this.currentWeightedDivisions;\n let resortAscending = this.dashboardSorter.sortAscending !== this.currentSortAscending;\n let ungradedAsZeroString = this.dashboardSorter.ungradedAsZero ? 'true' : 'false';\n let resortUngradedAsZero = ungradedAsZeroString !== this.currentUngradedAsZero;\n if (this.firstLoad || resortWeightedDivisions || resortAscending) {\n course.due_this_week = this.sortAssignmentsProcess('due_this_week', this.dashboardSorter.weightedDivisions, this.dashboardSorter.sortAscending);\n course.the_rest = this.sortAssignmentsProcess('the_rest', this.dashboardSorter.weightedDivisions, this.dashboardSorter.sortAscending);\n this.currentWeightedDivisions = this.dashboardSorter.weightedDivisions;\n this.currentSortAscending = this.dashboardSorter.sortAscending;\n }\n\n // If Ungraded as Zero is changed, or if the final grade is not set, recalculate the final grade\n if (this.firstLoad || resortUngradedAsZero || !course.final_grade) {\n let assignments = course.gradebook_entries;\n let finalGrade = this.gradeCalculator.calculateFinalGrade(assignments, this.unmuteAllGrades, this.dashboardSorter.ungradedAsZero);\n let finalAverageClassmateGrade = this.gradeCalculator.calculateFinalGrade(assignments, this.unmuteAllGrades, this.dashboardSorter.ungradedAsZero, {\n score: \"submission_score_avg\",\n grade: \"submission_grade_avg\"\n });\n course.final_grade = finalGrade.finalGrade;\n course.final_score = finalGrade.weightedScore;\n course.finalAvrageClassmateGrade = finalAverageClassmateGrade.finalGrade;\n course.finalAvrageClassmateScore = finalAverageClassmateGrade.weightedScore;\n this.currentUngradedAsZero = ungradedAsZeroString;\n }\n this.firstLoad = false;\n return JSON.parse(JSON.stringify(course));\n }\n get userAssignmentsOnTimePercentage() {\n return Ember.String.htmlSafe(this.course.my_stats.user_assignments_on_time_percentage);\n }\n get userDiscussionsOnTimePercentage() {\n return Ember.String.htmlSafe(this.course.my_stats.user_discussions_on_time_percentage);\n }\n get averageSubmissionOnTimePercentage() {\n return Ember.String.htmlSafe(this.course.my_stats.average_submission_on_time_percentage);\n }\n get averageDiscussionOnTimePercentage() {\n return Ember.String.htmlSafe(this.course.my_stats.average_discussion_on_time_percentage);\n }\n get course_order() {\n let termIndex = this.args.termIndex;\n let courseIndex = this.args.index;\n return `${termIndex}_${courseIndex}`;\n }\n get has_unread() {\n return this.args.unread_total_count > 0;\n }\n get unread_total_count() {\n return this.course.unread_discussions_count + this.course.unread_announcements_count + this.course.unread_assignment_comments_count;\n }\n get has_upcoming() {\n return this.course.upcoming_live_events_count > 0;\n }\n get total_due_and_overdue() {\n // Some items may be null or undefined, so we need to check for that\n let dueCount = 0;\n if (this.course.quizzes_due) {\n dueCount += this.course.quizzes_due;\n }\n if (this.course.assignments_due) {\n dueCount += this.course.assignments_due;\n }\n if (this.course.discussions_due) {\n dueCount += this.course.discussions_due;\n }\n return dueCount;\n }\n get has_due() {\n return this.course.assignments_due > 0 || this.course.quizzes_due > 0 || this.course.discussions_due > 0;\n }\n get has_overdue() {\n return this.course.assignments_overdue > 0 || this.course.quizzes_overdue > 0 || this.course.discussions_overdue > 0;\n }\n displayStatsInfo() {\n this.displayStatsPopup = true;\n }\n toggleReadmoreClass(index, close) {\n event.stopPropagation();\n let value = close ? true : this.isMaximized;\n let scrollTo = value ? `read_more_${index}` : `course_order_${index}`;\n this.isMaximized = !value;\n if (value) {\n this.args.scrollElementIntoView(document.getElementById(scrollTo));\n }\n }\n expandClassCardAccordion(index, open) {\n event.stopPropagation();\n let targetClassList = event.target.classList;\n if (targetClassList.contains('item-count') || targetClassList.contains('class-counter-minimal') || targetClassList.contains('class-counter') || targetClassList.contains('class-counter-compact') || targetClassList.contains('large__grade')) {\n return;\n }\n let current = this.args.maximizedCard;\n let newIndex = index;\n let element = document.getElementById(`course_order_${index}`);\n if (open) {\n this.args.updateMaximizedCard(newIndex);\n } else {\n if (current === newIndex) {\n newIndex = false;\n }\n this.args.updateMaximizedCard(newIndex);\n this.toggleReadmoreClass(index, true);\n }\n\n // Scroll page to ID #course_order_{{index}}\n if (current !== newIndex && element) {\n // Scroll the element into view, minus 100px for the header\n this.args.scrollElementIntoView(element);\n document.getElementsByClassName('dashboard')[0].scrollBy(0, -100);\n }\n }\n assignmentZeroToggle() {\n this.dashboardSorter.toggleProperty('ungradedAsZero');\n }\n sortAssignments() {\n this.dashboardSorter.toggleProperty('sortAscending');\n }\n toggleWeightedDivisions() {\n this.dashboardSorter.toggleProperty('weightedDivisions');\n }\n toggleDiscussions() {\n this.dashboardSorter.toggleProperty('showDiscussions');\n }\n toggleUngraded() {\n this.dashboardSorter.toggleProperty('showUngraded');\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"gradeCalculator\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"dashboardSorter\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"isMaximized\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"displayStatsPopup\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.args.displayStatsPopup;\n }\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"hideClassmateStats\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.args.hideClassmateStats;\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"userAssignmentsOnTimePercentage\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"userAssignmentsOnTimePercentage\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"userDiscussionsOnTimePercentage\", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, \"userDiscussionsOnTimePercentage\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"averageSubmissionOnTimePercentage\", [_dec8], Object.getOwnPropertyDescriptor(_class.prototype, \"averageSubmissionOnTimePercentage\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"averageDiscussionOnTimePercentage\", [_dec9], Object.getOwnPropertyDescriptor(_class.prototype, \"averageDiscussionOnTimePercentage\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"course_order\", [_dec10], Object.getOwnPropertyDescriptor(_class.prototype, \"course_order\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"has_unread\", [_dec11], Object.getOwnPropertyDescriptor(_class.prototype, \"has_unread\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"unread_total_count\", [_dec12], Object.getOwnPropertyDescriptor(_class.prototype, \"unread_total_count\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"has_upcoming\", [_dec13], Object.getOwnPropertyDescriptor(_class.prototype, \"has_upcoming\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"total_due_and_overdue\", [_dec14], Object.getOwnPropertyDescriptor(_class.prototype, \"total_due_and_overdue\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"has_due\", [_dec15], Object.getOwnPropertyDescriptor(_class.prototype, \"has_due\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"has_overdue\", [_dec16], Object.getOwnPropertyDescriptor(_class.prototype, \"has_overdue\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"displayStatsInfo\", [_dec17], Object.getOwnPropertyDescriptor(_class.prototype, \"displayStatsInfo\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleReadmoreClass\", [_dec18], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleReadmoreClass\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"expandClassCardAccordion\", [_dec19], Object.getOwnPropertyDescriptor(_class.prototype, \"expandClassCardAccordion\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"assignmentZeroToggle\", [_dec20], Object.getOwnPropertyDescriptor(_class.prototype, \"assignmentZeroToggle\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"sortAssignments\", [_dec21], Object.getOwnPropertyDescriptor(_class.prototype, \"sortAssignments\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleWeightedDivisions\", [_dec22], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleWeightedDivisions\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleDiscussions\", [_dec23], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleDiscussions\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleUngraded\", [_dec24], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleUngraded\"), _class.prototype)), _class));\n});","define(\"bocce/components/datetime-picker\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n tagName: 'span',\n // datetime must be a moment.js object\n date: Ember.computed('datetime', {\n get() {\n return this.datetime.format('YYYY-MM-DD');\n },\n set(key, value) {\n let date = value,\n time = this.time,\n dateString = `${date}T${time}:00`,\n datetime = moment(dateString);\n this.set('datetime', datetime);\n return date;\n }\n }),\n time: Ember.computed('datetime', {\n get() {\n return this.datetime.format('HH:mm');\n },\n set(key, value) {\n let date = this.date,\n time = value,\n dateString = `${date}T${time}:00`,\n datetime = moment(dateString);\n this.set('datetime', datetime);\n return time;\n }\n })\n });\n});","define(\"bocce/components/discussion-response\", [\"exports\", \"bocce/mixins/editable\", \"bocce/mixins/showcase\"], function (_exports, _editable, _showcase) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Component.extend(_editable.default, _showcase.default, {\n userprofileService: Ember.inject.service('userprofile'),\n replyId: Ember.computed('replyBody', function () {\n /* eslint-disable-next-line ember/no-jquery */\n let replyBody = Ember.$('
' + this.replyBody + '
');\n if (replyBody && replyBody.find('.is-reply-id').length > 0) {\n return replyBody.find('.is-reply-id').text();\n } else {\n return null;\n }\n }),\n // returns the text of the reply\n responseBody: Ember.computed('response.message', function () {\n /* eslint-disable-next-line ember/no-jquery, ember/no-get */\n let message = Ember.$(`
${this.get('response.message')}
`);\n if (message) {\n if (message.find('.is-reply')) {\n message.find('.is-reply').remove();\n }\n if (message[0]) {\n return message[0].innerHTML;\n }\n }\n return null;\n }),\n // returns an HTML string with metadata about the message to which this is replying\n replyBody: Ember.computed('response.message', function () {\n /* eslint-disable-next-line ember/no-jquery, ember/no-get */\n let message = Ember.$(`
${this.get('response.message')}
`);\n if (message) {\n let replyInfo = message.find('.is-reply');\n if (replyInfo[0]) {\n return replyInfo[0].innerHTML;\n }\n }\n return null;\n }),\n actions: {\n scrollTo: function (id) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#discussion-response-' + id).removeClass('resp-flash');\n setTimeout(function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#discussion-response-' + id).addClass('resp-flash');\n }, 2);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#discussion-response-' + id)[0].scrollIntoView();\n },\n edit: function (id) {\n this.edit(id);\n },\n delete: function (id) {\n this.delete(id);\n }\n }\n });\n});","define(\"bocce/components/discussion/multisection-picker\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n const SectionOption = Ember.Object.extend({\n init(...args) {\n this._super(...args);\n this.set('selected', false);\n },\n selected: false\n });\n var _default = _exports.default = Ember.Component.extend({\n init(...args) {\n this._super(...args);\n this.sections = this.sections || Ember.ArrayProxy.create({\n content: Ember.A([])\n });\n this.selected = this.selected || Ember.ArrayProxy.create({\n content: Ember.A([])\n });\n this.options = Ember.ArrayProxy.create({\n content: Ember.A([])\n });\n this.initSelections();\n },\n initSelections() {\n this.options.clear();\n this.sections.forEach(section => this.options.pushObject(SectionOption.create({\n section\n })));\n },\n /* eslint-disable-next-line ember/no-get */\n multipleSectionsSelected: Ember.computed.gt('selected.length', 0),\n classNames: ['modal-popover-shade'],\n attributeBindings: ['role', 'ariaDescribedBy:aria-describedby'],\n role: 'dialog',\n ariaDescribedBy: 'popover-title',\n actions: {\n toggleSection(option) {\n option.toggleProperty('selected');\n if (option.selected) {\n this.selected.pushObject(option.section.sectionId);\n } else {\n this.selected.removeObject(option.section.sectionId);\n }\n },\n selectAll() {\n let selected = this.selected;\n selected.clear();\n this.options.forEach(option => {\n option.set('selected', true);\n this.selected.pushObject(option.section.sectionId);\n });\n },\n selectNone() {\n this.selected.clear();\n this.options.forEach(option => option.set('selected', false));\n },\n cancel() {\n this.send('selectNone');\n this.send('close');\n },\n close() {\n let closeAction = this.closeAction;\n if (closeAction) {\n closeAction();\n }\n }\n }\n });\n});","define(\"bocce/components/discussion/reply-button\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n actions: {\n reply: function (id, txt, user) {\n this.reply({\n id: id,\n user: 'Reply to:',\n text: user\n });\n }\n }\n });\n});","define(\"bocce/components/drag-and-drop\", [\"exports\", \"bocce/mixins/editable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/uploadable\", \"bocce/mixins/discussable\", \"bocce/mixins/enmodal\", \"bocce/mixins/kaltura-upload\", \"bocce/mixins/legacy-attachment-manager\", \"bocce/utilities/dialog\"], function (_exports, _editable, _audioRec, _videoRec, _rtcRec, _uploadable, _discussable, _enmodal, _kalturaUpload, _legacyAttachmentManager, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Component.extend(_legacyAttachmentManager.default, _editable.default, _audioRec.default, _videoRec.default, _rtcRec.default, _uploadable.default, _discussable.default, _enmodal.default, _kalturaUpload.default, {\n userprofileService: Ember.inject.service('userprofile'),\n didInsertElement: function () {\n this.boundAppropriateAttachment = this.appropriateAttachment.bind(this);\n },\n willDestroyElement: function () {\n Ember.$('.discussionAttachment').off('change', this.boundAppropriateAttachment);\n },\n //The attachments are included as a partial. This partial is removed from the dom when editing a post. So, we\n //have to re-add the event handler upon each render to ensure that the event gets added back to the element\n //when the attachments partial is shown again (i.e: didRender gets triggered.)\n didRender() {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.discussionAttachment').off('change', this.boundAppropriateAttachment);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.discussionAttachment').on('change', this.boundAppropriateAttachment);\n },\n appropriateAttachment: function (e) {\n var files = e.target.files,\n len = files.length,\n controller = this.controller,\n mimeTypes = controller.mimeTypes,\n i;\n if (mimeTypes) {\n controller.set('processInProgress', true);\n for (i = 0; i < len; i++) {\n if (files[i].type.indexOf('video') > -1) {\n let encoding_video = {\n name: files[i].name,\n percent_uploaded: 0\n };\n this.encoding_videos.pushObject(encoding_video);\n let fileIndex = this.encoding_videos.length - 1;\n this.kalturaUploadVideo(files[i], async () => {\n await (0, _dialog.default)('Video embedding unsuccessful; this file will be available for download only.', ['OK']);\n this.encoding_videos.removeObject(encoding_video);\n files[i].ignoreDownloadOnlyPrompt = true;\n controller.send('addValidFile', files[i]);\n }, fileIndex);\n } else if (mimeTypes.indexOf(files[i].type) === -1) {\n controller.send('addValidFile', files[i]);\n } else {\n controller.send('addInvalidFile', files[i]);\n }\n }\n }\n\n // Purge file input\n e.target.value = '';\n }\n });\n});","define(\"bocce/components/expandable-list\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n expandable: Ember.computed('elements', 'threshold', function () {\n /* eslint-disable-next-line ember/no-get */\n return this.get('elements.length') > this.threshold;\n }),\n result: Ember.computed('elements', 'threshold', 'expanded', function () {\n if (this.expanded) {\n return this.elements;\n } else {\n let threshold = this.threshold;\n return this.elements.slice(0, threshold);\n }\n })\n });\n});","define(\"bocce/components/grade-student-list/list-item\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n tagName: 'li',\n classNames: ['clearfix'],\n classNameBindings: ['graded', 'my_submission', 'current', 'unread'],\n attributeBindings: ['getRole:role', 'getAriaLabel:aria-label'],\n // returns a default value if it's not provided by the template\n getRole: Ember.computed('role', function () {\n if (this.role) {\n return this.role;\n } else {\n return 'button';\n }\n }),\n getAriaLabel: Ember.computed('submission.user.short_name', 'ariaLabel', function () {\n let ariaLabel = this.ariaLabel;\n if (ariaLabel) {\n return ariaLabel;\n } else {\n /* eslint-disable-next-line ember/no-get */\n let name = this.get('submission.user.short_name');\n if (name) {\n return `Assignment submission belonging to ${name}`;\n } else {\n return 'Assignment submission';\n }\n }\n }),\n graded: Ember.computed('submission.grade', function () {\n /* eslint-disable-next-line ember/no-get */\n return Boolean(this.get('submission.grade'));\n }),\n my_submission: Ember.computed.reads('submission.my_submission'),\n current: Ember.computed('submission.id', 'currentSubmissionId', function () {\n /* eslint-disable-next-line ember/no-get */\n return this.get('submission.id') === this.currentSubmissionId;\n }),\n unread: Ember.computed.not('submission.read'),\n // triggers clickAction on click\n click() {\n this.clickAction();\n }\n });\n});","define(\"bocce/components/grade-student-list/list\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n userprofileService: Ember.inject.service('userprofile'),\n actions: {\n sendDownloadAll() {\n /* eslint-disable-next-line ember/no-get */\n let id = this.get('model.assignment.id');\n this.downloadAll(id);\n },\n sendToggleProfile(id) {\n this.toggleProfile(id);\n },\n sendViewSubmission(id) {\n this.viewSubmission(id);\n }\n }\n });\n});","define(\"bocce/components/inbox\", [\"exports\", \"@glimmer/component\", \"ember-concurrency\"], function (_exports, _component, _emberConcurrency) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7, _descriptor8, _descriptor9;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let Inbox = _exports.default = (_dec = Ember.inject.service, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._action, _dec6 = Ember._tracked, _dec7 = Ember._tracked, _dec8 = Ember._tracked, _dec9 = Ember._tracked, _dec10 = Ember._tracked, _dec11 = Ember._action, _dec12 = Ember._action, _dec13 = Ember._action, _dec14 = Ember._action, _dec15 = Ember._action, _dec16 = Ember._action, _dec17 = Ember._action, _dec18 = Ember._action, _dec19 = Ember._action, _dec20 = Ember._action, _dec21 = Ember._action, _dec22 = Ember._action, _dec23 = Ember._action, (_class = class Inbox extends _component.default {\n constructor() {\n super(...arguments);\n _initializerDefineProperty(this, \"store\", _descriptor, this);\n _initializerDefineProperty(this, \"inboxData\", _descriptor2, this);\n _initializerDefineProperty(this, \"working\", _descriptor3, this);\n _initializerDefineProperty(this, \"isLoading\", _descriptor4, this);\n _defineProperty(this, \"pollInterval\", this.args.pollInterval || 30000);\n _defineProperty(this, \"lastPollTime\", null);\n _initializerDefineProperty(this, \"externalDM\", _descriptor5, this);\n _initializerDefineProperty(this, \"selectedConversation\", _descriptor6, this);\n _initializerDefineProperty(this, \"composingConversation\", _descriptor7, this);\n _initializerDefineProperty(this, \"conversationLoading\", _descriptor8, this);\n _initializerDefineProperty(this, \"selectedConversations\", _descriptor9, this);\n this.setupPolling();\n window.addEventListener('visibilitychange', () => this.handleVisibilityChange());\n }\n willDestroy() {\n super.willDestroy();\n window.removeEventListener('visibilitychange', () => this.handleVisibilityChange());\n this.pollInbox.cancelAll(); // Cancel any ongoing polling when the component is destroyed\n }\n *loadInboxData() {\n this.isLoading = true;\n try {\n let conversations = yield this.store.findAll('conversation', {\n reload: true\n });\n conversations = conversations.sortBy('lastMessageDate').reverse().filterBy('active');\n this.inboxData = conversations;\n this.lastPollTime = Date.now();\n } catch (error) {\n console.error(\"Failed to load conversations:\", error);\n if (!this.inboxData || this.inboxData.length === 0) {\n this.inboxData = null;\n }\n } finally {\n this.isLoading = false;\n if (this.selectedConversation && this.selectedConversation.id) {\n this.updateSelectedConversation(this.selectedConversation.id, true);\n }\n }\n }\n *pollInbox() {\n while (true) {\n yield this.loadInboxData.perform();\n yield (0, _emberConcurrency.timeout)(this.pollInterval);\n }\n }\n refreshInbox() {\n this.pollInbox.perform();\n }\n handleVisibilityChange() {\n if (document.visibilityState === 'visible') {\n const now = Date.now();\n if (!this.lastPollTime || now - this.lastPollTime > this.pollInterval) {\n this.pollInbox.perform();\n }\n }\n }\n setupPolling() {\n this.pollInbox.perform();\n }\n get sendRequest() {\n let currentConversation = this.currentConvo;\n let convoUser = this.args.externalSendRequest;\n if (!convoUser || convoUser.length === 0 || convoUser === currentConversation) {\n return;\n }\n this.createNewOrOpenExistingConversation([convoUser.id]);\n }\n async createNewOrOpenExistingConversation(userIds, users) {\n // First check inboxData for existing conversation with the same recipients\n let existingConversation = this.inboxData.find(conversation => {\n let participants = conversation.conversationPartners.toArray();\n return participants.length === userIds.length && participants.every(user => userIds.includes(user.id));\n });\n if (existingConversation) {\n let isNew = existingConversation.isNew;\n\n // New conversations are already loaded in the inboxData array but are not yet in the system with an ID\n if (isNew) {\n this.updateSelectedConversation(false, true, existingConversation);\n return;\n }\n this.updateSelectedConversation(existingConversation.id);\n return;\n }\n if (!users) {\n users = userIds.map(id => {\n return this.store.findRecord('user', id);\n });\n let groupConversation = userIds.length > 1;\n users = await Promise.all(users);\n }\n if (!userIds) {\n userIds = users.map(user => user.id);\n }\n userIds.push(this.args.userId);\n let groupConversation = userIds.length > 2;\n let conversation = await this.store.createRecord('conversation', {\n recipients: userIds,\n private: !groupConversation,\n conversationPartners: users,\n group_conversation: groupConversation\n });\n this.updateSelectedConversation(false, false, conversation);\n this.refreshInbox();\n }\n async toggleReadUnread() {\n this.working = true;\n let isRead = this.selectedConversation.read;\n this.toggleConversationRead(this.selectedConversation.id);\n if (isRead) {\n this.closeConversation();\n this.working = false;\n }\n }\n updateSelectedConversation(messageId, noLoad, conversation) {\n this.working = true;\n if (!noLoad) {\n this.conversationLoading = true;\n }\n if (conversation) {\n this.selectedConversation = conversation;\n this.conversationLoading = false;\n return;\n }\n return this.store.findRecord('conversation', messageId, {\n reload: true\n }).then(message => {\n if (!noLoad) {\n this.conversationLoading = false;\n }\n\n // If there are no new messages, do nothing.\n if (this.selectedConversation && this.selectedConversation.message_count === message.message_count && this.selectedConversation === message) {\n return;\n }\n this.selectedConversation = message;\n this.toggleConversationRead(messageId, null, true);\n });\n }\n bulkStar() {\n this.selectedConversations.forEach(messageId => {\n this.toggleConversationStar(messageId);\n });\n this.selectedConversations = [];\n }\n bulkUnread() {\n this.selectedConversations.forEach(messageId => {\n this.toggleConversationRead(messageId, false, false, true);\n });\n this.selectedConversations = [];\n }\n bulkArchive() {\n this.selectedConversations.forEach(messageId => {\n this.toggleConversationArchive(messageId);\n });\n this.selectedConversations = [];\n }\n closeConversation() {\n this.selectedConversation = false;\n this.composingConversation = false;\n }\n composeConversation() {\n this.composingConversation = true;\n this.selectedConversation = false;\n }\n selectConversation(messageId, event) {\n event.stopPropagation();\n // Add/remove message ID to selectedConversations array\n let selectedConversations = this.selectedConversations;\n let messageIndex = selectedConversations.indexOf(messageId);\n if (messageIndex === -1) {\n selectedConversations.push(messageId);\n } else {\n selectedConversations.splice(messageIndex, 1);\n }\n this.selectedConversations = selectedConversations;\n }\n updateConversationInList(conversation) {\n let conversations = this.inboxData;\n let conversationIndex = conversations.findIndex(c => c.id === conversation.id);\n if (conversationIndex !== -1) {\n // Replace the conversation in a way that triggers Ember's reactivity\n this.inboxData = [...conversations.slice(0, conversationIndex), conversation, ...conversations.slice(conversationIndex + 1)];\n }\n }\n toggleConversationStar(conversationId, event) {\n if (event) {\n event.stopPropagation();\n }\n let conversation = this.store.peekRecord('conversation', conversationId);\n conversation.toggleProperty('starred');\n conversation.save().then(() => {\n this.updateConversationInList(conversation);\n });\n }\n toggleConversationRead(conversationId, event, forceRead, forceUnread) {\n if (event) {\n event.stopPropagation();\n }\n let conversation = this.store.peekRecord('conversation', conversationId);\n let readState = false;\n if (forceRead) {\n readState = true;\n } else if (forceUnread) {\n readState = false;\n } else {\n readState = !conversation.read;\n }\n conversation.set('workflow_state', readState ? 'read' : 'unread');\n conversation.save().then(() => {\n this.updateConversationInList(conversation);\n this.working = false;\n });\n }\n toggleConversationArchive(conversationId, event) {\n if (event) {\n event.stopPropagation();\n }\n let conversation = this.store.peekRecord('conversation', conversationId);\n conversation.toggleProperty('isArchived');\n conversation.save().then(() => {\n this.updateConversationInList(conversation);\n });\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"store\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"inboxData\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"working\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"isLoading\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"loadInboxData\", [_emberConcurrency.task], Object.getOwnPropertyDescriptor(_class.prototype, \"loadInboxData\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"pollInbox\", [_emberConcurrency.task], Object.getOwnPropertyDescriptor(_class.prototype, \"pollInbox\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"refreshInbox\", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, \"refreshInbox\"), _class.prototype), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"externalDM\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.args.conversation.id;\n }\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"selectedConversation\", [_dec7], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, \"composingConversation\", [_dec8], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor8 = _applyDecoratedDescriptor(_class.prototype, \"conversationLoading\", [_dec9], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor9 = _applyDecoratedDescriptor(_class.prototype, \"selectedConversations\", [_dec10], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"createNewOrOpenExistingConversation\", [_dec11], Object.getOwnPropertyDescriptor(_class.prototype, \"createNewOrOpenExistingConversation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleReadUnread\", [_dec12], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleReadUnread\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateSelectedConversation\", [_dec13], Object.getOwnPropertyDescriptor(_class.prototype, \"updateSelectedConversation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"bulkStar\", [_dec14], Object.getOwnPropertyDescriptor(_class.prototype, \"bulkStar\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"bulkUnread\", [_dec15], Object.getOwnPropertyDescriptor(_class.prototype, \"bulkUnread\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"bulkArchive\", [_dec16], Object.getOwnPropertyDescriptor(_class.prototype, \"bulkArchive\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"closeConversation\", [_dec17], Object.getOwnPropertyDescriptor(_class.prototype, \"closeConversation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"composeConversation\", [_dec18], Object.getOwnPropertyDescriptor(_class.prototype, \"composeConversation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"selectConversation\", [_dec19], Object.getOwnPropertyDescriptor(_class.prototype, \"selectConversation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateConversationInList\", [_dec20], Object.getOwnPropertyDescriptor(_class.prototype, \"updateConversationInList\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleConversationStar\", [_dec21], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleConversationStar\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleConversationRead\", [_dec22], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleConversationRead\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleConversationArchive\", [_dec23], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleConversationArchive\"), _class.prototype)), _class));\n});","define(\"bocce/components/inbox/message-tile\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let MessageTile = _exports.default = (_dec = Ember.inject.service, _dec2 = Ember.computed('conversation', 'conversation.messages.[]', 'conversation.messages.length'), _dec3 = Ember.computed('conversation', 'conversation.message_count'), _dec4 = Ember.computed('conversation', 'conversation.conversationPartners.[]'), _dec5 = Ember.computed('args.conversation.conversationPartners.[]'), _dec6 = Ember.computed('this.args.conversation', 'this.args.conversation.isRead'), _dec7 = Ember.computed('this.args.conversation', 'this.args.conversation.conversationPartners'), _dec8 = Ember._action, (_class = class MessageTile extends _component.default {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"store\", _descriptor, this);\n }\n get isValidMessage() {\n return this.args.conversation.conversationPartners.length > 0;\n }\n get replyCount() {\n let replyCount = this.args.conversation.message_count ? this.args.conversation.message_count - 1 : 0;\n if (replyCount > 0) {\n return replyCount;\n }\n return false;\n }\n get threePlusParticipants() {\n let allParticipants = this.args.conversation ? this.args.conversation.conversationPartners.toArray() : [];\n let totalParticipants = allParticipants.length;\n if (totalParticipants > 2) {\n return true;\n }\n }\n get enrichedConversationPartners() {\n return this.args.conversation.conversationPartners.map((partner, index, partners) => {\n return {\n ...partner,\n isLast: index === partners.length - 1\n };\n });\n }\n get conversationRead() {\n return this.args.conversation ? this.args.conversation.isRead : false;\n }\n get conversationPartners() {\n let allParticipants = this.args.conversation ? this.args.conversation.conversationPartners.toArray() : [];\n let totalParticipants = allParticipants.length;\n allParticipants[totalParticipants - 1].isLast = true;\n if (totalParticipants <= 2) {\n return allParticipants;\n }\n\n // Only keep the first 2 participants\n let partners = allParticipants.slice(0, 2);\n let moreCount = allParticipants.length - 2;\n let placeholderObject = {\n isPlaceholder: true,\n moreCount: moreCount\n };\n partners.push(placeholderObject);\n return partners;\n }\n viewConversation(conversation) {\n let isNew = this.args.conversation.isNew;\n if (isNew) {\n this.args.updateSelectedConversation(false, true, conversation);\n } else {\n this.args.updateSelectedConversation(conversation.id);\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"store\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"isValidMessage\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"isValidMessage\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"replyCount\", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, \"replyCount\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"threePlusParticipants\", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, \"threePlusParticipants\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"enrichedConversationPartners\", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, \"enrichedConversationPartners\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"conversationRead\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"conversationRead\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"conversationPartners\", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, \"conversationPartners\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"viewConversation\", [_dec8], Object.getOwnPropertyDescriptor(_class.prototype, \"viewConversation\"), _class.prototype)), _class));\n});","define(\"bocce/components/inbox/message-view\", [\"exports\", \"@glimmer/component\", \"ember-concurrency\", \"bocce/utilities/dialog\", \"bocce/helpers/upload\"], function (_exports, _component, _emberConcurrency, _dialog, _upload) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _dec25, _dec26, _dec27, _dec28, _dec29, _dec30, _dec31, _dec32, _dec33, _dec34, _dec35, _dec36, _dec37, _dec38, _dec39, _dec40, _dec41, _dec42, _dec43, _dec44, _dec45, _dec46, _dec47, _class2, _descriptor7, _descriptor8, _descriptor9, _descriptor10, _descriptor11, _descriptor12, _descriptor13, _descriptor14, _descriptor15, _descriptor16, _descriptor17, _descriptor18, _descriptor19, _descriptor20, _descriptor21, _descriptor22;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let FileModel = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._tracked, (_class = class FileModel {\n constructor(file, deleted, progress, uploading, uploaded, valid) {\n _initializerDefineProperty(this, \"uploading\", _descriptor, this);\n _initializerDefineProperty(this, \"uploaded\", _descriptor2, this);\n _initializerDefineProperty(this, \"valid\", _descriptor3, this);\n _initializerDefineProperty(this, \"deleted\", _descriptor4, this);\n _initializerDefineProperty(this, \"progress\", _descriptor5, this);\n _initializerDefineProperty(this, \"file\", _descriptor6, this);\n this.uploading = uploading;\n this.uploaded = uploaded;\n this.valid = valid;\n this.deleted = deleted;\n this.progress = progress;\n this.file = file;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"uploading\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"uploaded\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"valid\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return true;\n }\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"deleted\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"progress\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return 0;\n }\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"file\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n })), _class));\n let MessageView = _exports.default = (_dec7 = Ember.inject.service, _dec8 = Ember.inject.service, _dec9 = Ember.inject.service, _dec10 = Ember.inject.service, _dec11 = Ember._tracked, _dec12 = Ember._tracked, _dec13 = Ember._tracked, _dec14 = Ember._tracked, _dec15 = Ember._tracked, _dec16 = Ember._tracked, _dec17 = Ember._tracked, _dec18 = Ember._tracked, _dec19 = Ember._tracked, _dec20 = Ember.computed('attachmentsUploading', 'postInProgress'), _dec21 = Ember.computed('bodyInput'), _dec22 = Ember.computed('attachmentsUploading', 'conversation', 'bodyInput', 'bodyInputHasContent', 'files'), _dec23 = Ember.computed('conversation'), _dec24 = Ember.computed('conversation.audience.{length,[]}'), _dec25 = Ember._action, _dec26 = Ember._action, _dec27 = Ember._tracked, _dec28 = Ember._tracked, _dec29 = Ember._tracked, _dec30 = Ember._action, _dec31 = Ember._action, _dec32 = Ember._action, _dec33 = Ember._action, _dec34 = Ember._action, _dec35 = Ember._action, _dec36 = Ember._action, _dec37 = Ember._action, _dec38 = Ember._action, _dec39 = Ember._action, _dec40 = Ember._action, _dec41 = Ember._action, _dec42 = Ember._action, _dec43 = Ember._action, _dec44 = Ember._action, _dec45 = Ember._action, _dec46 = Ember._action, _dec47 = Ember._action, (_class2 = class MessageView extends _component.default {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"embedParser\", _descriptor7, this);\n _initializerDefineProperty(this, \"attachmentManager\", _descriptor8, this);\n _initializerDefineProperty(this, \"kalturaUpload\", _descriptor9, this);\n _initializerDefineProperty(this, \"store\", _descriptor10, this);\n _initializerDefineProperty(this, \"bodyInput\", _descriptor11, this);\n _initializerDefineProperty(this, \"replyAuthor\", _descriptor12, this);\n _initializerDefineProperty(this, \"encoding_videos\", _descriptor13, this);\n _initializerDefineProperty(this, \"inEditor\", _descriptor14, this);\n _initializerDefineProperty(this, \"foundRecipients\", _descriptor15, this);\n _initializerDefineProperty(this, \"recipients\", _descriptor16, this);\n _initializerDefineProperty(this, \"showSharedEnrollments\", _descriptor17, this);\n _initializerDefineProperty(this, \"postInProgress\", _descriptor18, this);\n _initializerDefineProperty(this, \"files\", _descriptor19, this);\n _initializerDefineProperty(this, \"messages\", _descriptor20, this);\n _initializerDefineProperty(this, \"messagesSorted\", _descriptor21, this);\n _initializerDefineProperty(this, \"convoId\", _descriptor22, this);\n }\n get attachmentsUploading() {\n const uploadingFiles = this.files ? !!this.files.findBy('uploaded', false) : false;\n return uploadingFiles;\n }\n get posterWorking() {\n return this.postInProgress;\n }\n get bodyInputHasContent() {\n // NK note to future self: Needing to handle all this at this stage\n // is ugly, but it gets the job done. A friendlier interface will\n // be to abstract away the sanitization/cleanup code within an\n // rte-editor Component, so that the Controller can just interact\n // with the bodyInput value without needing to do too much cleanup.\n\n const bodyInput = this.bodyInput || '';\n // cut out HTML\n let cleaned = this.attachmentManager.sanitize(bodyInput);\n // remove line breaks\n cleaned = cleaned.replace(/ /g, '');\n // Remove the comment block that the browser puts in.\n cleaned = cleaned.replace(//g, '');\n // Remove spaces\n cleaned = cleaned.replace(/\\s/g, '');\n return cleaned.length > 0;\n }\n get postable() {\n let bodyInput = this.bodyInputHasContent;\n return bodyInput && !this.attachmentsUploading;\n }\n get currentConversationId() {\n return this.args.conversation.id;\n }\n get audienceString() {\n let audience = this.args.conversation.audience || [];\n let audienceString = '';\n for (let i = 0; i < audience.length; i++) {\n audienceString += audience[i].name;\n if (i < audience.length - 1) {\n audienceString += ', ';\n }\n }\n return audienceString;\n }\n scrollAndUpdateLocalConversation(conversation) {\n Ember.run.later(() => this.scrollConvoToBottom(conversation), 100);\n conversation.set('last_authored_message_at', new Date());\n conversation.set('last_message_at', new Date());\n conversation.set('message_count', conversation.message_count + 1);\n }\n scrollConvoToBottom() {\n let messageBody = this.convoDiv;\n messageBody.scrollTop = messageBody.scrollHeight;\n }\n serializeMessages(messages) {\n let serializedMessages = [];\n let messageArray = messages.toArray();\n for (let i = 0; i < messageArray.length; i++) {\n let message = messageArray[i];\n let authorObj = {\n id: message.get('author.id'),\n name: message.get('author.name'),\n image_url: message.get('author.image_url')\n };\n let attachmentsObj = message.get('attachments').toArray();\n let messageData = {\n id: message.id,\n body: message.body,\n createdAtFormatted: message.createdAtFormatted,\n belongsToSelf: message.belongsToSelf,\n author: authorObj,\n attachments: attachmentsObj\n };\n serializedMessages.push(messageData);\n }\n return serializedMessages;\n }\n get sortedMessages() {\n let oldMessages = this.oldMessages;\n let messages = this.args.conversation.messages;\n let retVal = [];\n let currentConversationId = this.currentConversationId;\n let newConversationId = this.args.conversation.id;\n let isNewConversation = currentConversationId !== newConversationId;\n if (!messages) {\n return false;\n }\n messages = this.serializeMessages(messages);\n messages = messages.sort((a, b) => {\n return a.id - b.id;\n });\n if (oldMessages && oldMessages.length > 0 && !isNewConversation) {\n let oldMessageIds = oldMessages.map(message => {\n return message.id;\n });\n let newMessages = messages.filter(message => {\n return oldMessageIds.indexOf(message.id) === -1;\n });\n if (newMessages.length > 0) {\n retVal = oldMessages.concat(newMessages);\n } else {\n retVal = oldMessages;\n }\n } else {\n retVal = messages;\n this.oldMessages = messages;\n this.currentConversationId = newConversationId;\n }\n return retVal;\n }\n *scrollTask() {\n // Wait for 2 seconds\n yield new Promise(resolve => setTimeout(resolve, 1000));\n\n // Scroll to the bottom of the div\n this.scrollConvoToBottom();\n }\n get sharedEnrollments() {\n let conversation = this.args.conversation,\n retval = [],\n name = false,\n shared = false,\n sectionCodes = this.args.conversation.get('currentUser.sectionCodes');\n if (conversation) {\n let participantRelationships = conversation.participantRelationships || [];\n if (participantRelationships.length === 0 || conversation.participants.length > 2) {\n conversation.conversationPartners.forEach(partner => {\n retval.push({\n name: partner.name,\n user: partner\n });\n });\n } else {\n participantRelationships.forEach(participantRelationship => {\n sectionCodes.forEach(sectionCode => {\n if (sectionCode.sectionId === participantRelationship.section) {\n shared = true;\n name = conversation.conversationPartners.toArray()[0].name;\n retval.push({\n sectionId: sectionCode.sectionId,\n relationship: participantRelationship.relationship,\n courseTitle: sectionCode.courseTitle,\n sectionLabel: sectionCode.sectionLabel,\n term: sectionCode.termLabel\n });\n }\n });\n });\n }\n }\n return {\n shared: shared,\n name: name,\n enrollments: retval\n };\n }\n updateLocalDocs() {\n this.notifyPropertyChange('localDocs');\n }\n setupScroll(element) {\n this.convoDiv = element;\n this.scrollConvoToBottom();\n }\n loadMoreMessages() {\n // Check if div is scrolled to the top\n let messageBody = this.convoDiv;\n let isOverflowing = messageBody.scrollHeight > messageBody.clientHeight;\n let scrollTop = messageBody.scrollTop;\n if (isOverflowing && scrollTop > 0) {\n return;\n }\n let messages = this.args.conversation.messages;\n if (!messages || this.allMessagesLoaded || this.loadingMoreMessages) {\n this.loadingMoreMessages = false;\n return;\n }\n let num_loaded = messages.length,\n user_id = this.session.user.id,\n args = '';\n if (num_loaded > 0) {\n let min_id;\n for (let i = 0; i < num_loaded; i++) {\n let id = parseInt(messages.objectAt(i).id, 10);\n if (!min_id || id < min_id) {\n min_id = id;\n }\n }\n args = `?page=${min_id}`;\n }\n setTimeout(() => {\n this.loadingMoreMessages = true;\n\n /* eslint-disable-next-line ember/no-jquery, ember/no-get */\n $.get(`/interface/conversations/${this.args.conversation.id}/conversation-messages${args}`).then(data => {\n if (!data || !data.conversation_message || data.conversation_message.length === 0) {\n this.allMessagesLoaded = true;\n } else {\n for (let m of data.conversation_message) {\n let auth_id = Number(m.author_id);\n if (auth_id === Number(user_id)) {\n m.belongsToSelf = true;\n }\n }\n this.store.pushPayload(data);\n }\n this.loadingMoreMessages = false;\n });\n }, 250);\n }\n toggleRecipient(index) {\n let foundRecipients = this.foundRecipients;\n let user = foundRecipients[index];\n let recipients = this.recipients;\n if (recipients.indexOf(user) === -1) {\n recipients.push(user);\n } else {\n recipients.slice(recipients.indexOf(user), 1);\n }\n this.foundRecipients = [];\n this.recipients = recipients;\n this.searchUsersInput.value = '';\n }\n removeRecipient(index) {\n let recipients = this.recipients;\n recipients.splice(index, 1);\n this.recipients = recipients;\n }\n searchUsers(evt) {\n this.searchUsersInput = evt.target;\n let param = evt.target.value;\n param = param.trim();\n if (param !== '' && param.length > 2) {\n let handle = this.autoCompleteTimeoutHandle;\n if (handle) {\n Ember.run.cancel(handle);\n }\n this.foundRecipients = [{\n name: 'Searching...',\n notFound: true\n }];\n\n // Wait a moment before we spam the server with searches for \"jam\", \"jame\", and \"james\"\n // all at once\n handle = Ember.run.later(() => {\n param = param.toLowerCase();\n param = encodeURIComponent(param);\n\n /* eslint-disable-next-line ember/no-jquery */\n $.ajax({\n type: 'get',\n param: param,\n url: '/interface/user-search/' + param,\n success: results => {\n // Go trough results\n // Filter out results[x].common_courses.{course_id}.[y] === 'ObserverEnrollment'\n\n results = results.filter(result => {\n return !result.common_courses || !Object.keys(result.common_courses).some(courseId => {\n return result.common_courses[courseId].some(role => {\n return role === 'ObserverEnrollment';\n });\n });\n });\n this.foundRecipients = results;\n if (results.length === 0) {\n this.foundRecipients = [{\n name: 'No results found.',\n notFound: true\n }];\n }\n },\n error: function (error) {\n Ember.debug('Error in conversation recipient list.');\n Ember.debug(error);\n }\n });\n }, 333);\n this.autoCompleteTimeoutHandle = handle;\n } else {\n this.foundRecipients = [];\n }\n }\n addFile(file) {\n if (file.type.includes('video')) {\n let fileIndex = this.files.length;\n this.addValidFile(file, fileIndex, true);\n this.files[fileIndex].uploading = true;\n this.kalturaUpload.kalturaUploadVideo(file, async () => {\n Ember.debug('Unable to upload video to Kaltura. Uploading to S3...');\n await (0, _dialog.default)('Kaltura upload failed. This file will be available for download only.', ['OK']);\n file.ignoreDownloadOnlyPrompt = true;\n // Swap the file slot from an embed to a regular file\n this.addValidFile(file, fileIndex);\n }, true, false, record => {\n let files = this.files.slice();\n files[fileIndex].file = record.file;\n files[fileIndex].uploading = false;\n files[fileIndex].uploaded = true;\n files[fileIndex].progress = 100;\n files[fileIndex].blobURL = URL.createObjectURL(file);\n files[fileIndex].uploaded_id = record.uploaded_id;\n this.files = files;\n }, progress => {\n this.files[fileIndex].progress = progress;\n });\n } else {\n this.addValidFile(file);\n }\n this.files = [...this.files];\n }\n addValidFile(file, fileIndex, stub) {\n let isExisting = typeof fileIndex === 'number';\n let record = new FileModel(file, false, 0, false, false, true);\n fileIndex = isExisting ? fileIndex : this.files.length;\n this.files[fileIndex] = record;\n if (!stub) {\n this.uploadSingleFile(file, fileIndex);\n }\n }\n deleteAttachment(file, index) {\n if (index) {\n // Remove file from this.files by index\n this.files = this.files.filter((f, i) => {\n return i !== index;\n });\n } else {\n this.files = this.files.filter(f => {\n return f.file !== file;\n });\n }\n }\n renameAttachment(attachment, newName, index) {\n if (attachment.file?.isUrl) {\n this.kalturaUpload.kalturaRenameVideo(attachment.uploaded_id, newName);\n this.files[index].name = newName;\n } else {\n this.files[index].name = newName;\n this.store.findRecord('attachment', attachment.uploaded_id).then(loadedAttachment => {\n loadedAttachment.set('name', newName);\n loadedAttachment.save();\n });\n }\n }\n startConversation() {\n let recipients = this.recipients;\n let recipientIds = recipients.map(recipient => {\n return recipient.id;\n });\n this.args.composeConversation(recipientIds, false);\n this.recipients = [];\n }\n openAttachmentDrawer() {\n const modal = document.querySelector('.attachments-modal');\n modal.showModal();\n }\n uploadSingleFile(file, index) {\n let userId = this.args.userId;\n if (!file || !userId) {\n return;\n }\n if (index === undefined) {\n index = this.files.findIndex(f => f.file === file);\n }\n this.files[index].uploading = true;\n this.files[index].uploaded = false;\n this.files[index].progress = 0;\n this.files = this.files.slice();\n (0, _upload.default)(file, userId, this.store, 'conversation attachments', null, progress => {\n let files = this.files.slice();\n files[index].progress = progress;\n this.files = files;\n }).then(att_id => {\n return this.store.findRecord('attachment', att_id).then(attachment => {\n let files = this.files.slice();\n files[index].attachment = attachment;\n files[index].uploading = false;\n files[index].uploaded = true;\n files[index].progress = 100;\n files[index].uploaded_id = att_id;\n this.files = files;\n });\n }, err => {\n Ember.debug(err, 'Could not upload file');\n let files = this.files.slice();\n files[index].uploading = false;\n files[index].uploaded = false;\n files[index].progress = 0;\n this.files = files;\n });\n }\n updateConversation() {\n let message = this.embedParser.parseEmbed(this.bodyInput);\n\n // parse out everything but the text...\n let trimmedMessage = message.replace(/<[^>]+>/g, '');\n if (trimmedMessage === '' || this.attachmentsUploading) {\n return;\n }\n if (message.replace(/ /g, ' ').replace(//g, '').trim().length === 0) {\n return;\n }\n message = message.replace('', '');\n message = message.trim();\n\n // Add any video embeds\n let videoEmbedString = this.attachmentManager.generateVideoEmbedString(this.files) || '';\n message = message + videoEmbedString;\n let current_conversation = this.args.conversation,\n conversationPartners = current_conversation.conversationPartners;\n\n // posting...\n this.postInProgress = true;\n\n // The first message is saved along with the original conversation\n if (current_conversation.isNew) {\n current_conversation.set('lastActivity', new Date());\n current_conversation.set('new_message_body', message);\n if (this.files.length > 0) {\n let attachments = [];\n for (let i = 0; i < this.files.length; i++) {\n // Exclude removed attachments -- or video embeds\n if (this.files[i].removed || !this.files[i].attachment) {\n continue;\n }\n attachments.push({\n id: this.files[i].attachment.id\n });\n }\n current_conversation.set('attachments', attachments);\n }\n current_conversation.save().then(savedConversation => {\n this.bodyInput = '';\n this.convoSent = false;\n this.files = [];\n this.postInProgress = false;\n savedConversation.set('last_authored_message', message);\n this.args.updateSelectedConversation(savedConversation.id);\n }, err => {\n Ember.debug(err, 'Create new conversation to ' + conversationPartners.id);\n this.postInProgress = false;\n window.alert('Unable to post message. Please try again in a few moments.');\n }).then(() => {\n this.convoSent = true;\n this.scrollAndUpdateLocalConversation(current_conversation);\n });\n } else {\n this.store.nestResources('conversation-message', [{\n 'conversation': current_conversation.get('id')\n }]);\n let new_conversation_message = current_conversation.store.createRecord('conversation_message');\n new_conversation_message.set('body', message);\n new_conversation_message.set('createdAt', new Date());\n new_conversation_message.set('belongsToSelf', true);\n new_conversation_message.set('conversation', current_conversation.id);\n new_conversation_message.set('conversationPartners', conversationPartners);\n if (this.files.length > 0) {\n let attachments = [];\n for (let i = 0; i < this.files.length; i++) {\n // Exclude removed attachments -- or video embeds\n if (this.files[i].removed || !this.files[i].attachment) {\n continue;\n }\n attachments.push({\n id: this.files[i].attachment.id\n });\n }\n new_conversation_message.set('attachments', attachments);\n }\n new_conversation_message.save().then(() => {\n this.bodyInput = '';\n this.convoSent = false;\n this.files = [];\n this.postInProgress = false;\n current_conversation.set('last_authored_message', message);\n current_conversation.set('last_message', message);\n current_conversation.set('lastActivity', new Date());\n this.args.updateSelectedConversation(current_conversation.id);\n }, err => {\n Ember.debug({\n Location: 'Post conversation reply',\n Status: err.status,\n Message: err.message,\n Error: err.responseText\n });\n this.postInProgress = false;\n window.alert('Unable to post message. Please try again.');\n }).then(() => {\n this.convoSent = true;\n this.scrollAndUpdateLocalConversation(current_conversation);\n });\n }\n this.attachmentDrawerOpen = false;\n }\n deleteUnsentConversation() {\n let current_conversation = this.args.conversation;\n if (current_conversation && current_conversation.isNew) {\n current_conversation.destroyRecord();\n }\n }\n toggleSharedEnrollments() {\n this.showSharedEnrollments = !this.showSharedEnrollments;\n }\n toggleArchived() {\n let convo = this.model;\n let isArchived = this.args.conversation.archived;\n let un = isArchived ? 'un' : '';\n let dialogMessage = 'Are you sure you wish to ' + un + 'archive this conversation? To undo this action later, click the folder icon again.';\n (0, _dialog.default)(dialogMessage, ['Yes', 'No']).then(selection => {\n if (selection === 'Yes') {\n convo.set('workflow_state', isArchived ? 'active' : 'archived');\n convo.save();\n }\n });\n }\n toggleConversationStar() {\n let current_conversation = this.args.conversation;\n let isStarred = current_conversation.starred;\n current_conversation.set('starred', !isStarred);\n current_conversation.save();\n }\n }, (_descriptor7 = _applyDecoratedDescriptor(_class2.prototype, \"embedParser\", [_dec7], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor8 = _applyDecoratedDescriptor(_class2.prototype, \"attachmentManager\", [_dec8], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor9 = _applyDecoratedDescriptor(_class2.prototype, \"kalturaUpload\", [_dec9], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor10 = _applyDecoratedDescriptor(_class2.prototype, \"store\", [_dec10], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor11 = _applyDecoratedDescriptor(_class2.prototype, \"bodyInput\", [_dec11], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return '';\n }\n }), _descriptor12 = _applyDecoratedDescriptor(_class2.prototype, \"replyAuthor\", [_dec12], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return '';\n }\n }), _descriptor13 = _applyDecoratedDescriptor(_class2.prototype, \"encoding_videos\", [_dec13], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _descriptor14 = _applyDecoratedDescriptor(_class2.prototype, \"inEditor\", [_dec14], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor15 = _applyDecoratedDescriptor(_class2.prototype, \"foundRecipients\", [_dec15], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _descriptor16 = _applyDecoratedDescriptor(_class2.prototype, \"recipients\", [_dec16], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _descriptor17 = _applyDecoratedDescriptor(_class2.prototype, \"showSharedEnrollments\", [_dec17], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor18 = _applyDecoratedDescriptor(_class2.prototype, \"postInProgress\", [_dec18], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor19 = _applyDecoratedDescriptor(_class2.prototype, \"files\", [_dec19], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return Ember.A([]);\n }\n }), _applyDecoratedDescriptor(_class2.prototype, \"posterWorking\", [_dec20], Object.getOwnPropertyDescriptor(_class2.prototype, \"posterWorking\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"bodyInputHasContent\", [_dec21], Object.getOwnPropertyDescriptor(_class2.prototype, \"bodyInputHasContent\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"postable\", [_dec22], Object.getOwnPropertyDescriptor(_class2.prototype, \"postable\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"currentConversationId\", [_dec23], Object.getOwnPropertyDescriptor(_class2.prototype, \"currentConversationId\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"audienceString\", [_dec24], Object.getOwnPropertyDescriptor(_class2.prototype, \"audienceString\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"scrollConvoToBottom\", [_dec25], Object.getOwnPropertyDescriptor(_class2.prototype, \"scrollConvoToBottom\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"serializeMessages\", [_dec26], Object.getOwnPropertyDescriptor(_class2.prototype, \"serializeMessages\"), _class2.prototype), _descriptor20 = _applyDecoratedDescriptor(_class2.prototype, \"messages\", [_dec27], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.args.conversation.messages;\n }\n }), _descriptor21 = _applyDecoratedDescriptor(_class2.prototype, \"messagesSorted\", [_dec28], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.sortedMessages;\n }\n }), _applyDecoratedDescriptor(_class2.prototype, \"scrollTask\", [_emberConcurrency.task], Object.getOwnPropertyDescriptor(_class2.prototype, \"scrollTask\"), _class2.prototype), _descriptor22 = _applyDecoratedDescriptor(_class2.prototype, \"convoId\", [_dec29], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.args.conversation.id;\n }\n }), _applyDecoratedDescriptor(_class2.prototype, \"updateLocalDocs\", [_dec30], Object.getOwnPropertyDescriptor(_class2.prototype, \"updateLocalDocs\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"setupScroll\", [_dec31], Object.getOwnPropertyDescriptor(_class2.prototype, \"setupScroll\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"loadMoreMessages\", [_dec32], Object.getOwnPropertyDescriptor(_class2.prototype, \"loadMoreMessages\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"toggleRecipient\", [_dec33], Object.getOwnPropertyDescriptor(_class2.prototype, \"toggleRecipient\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"removeRecipient\", [_dec34], Object.getOwnPropertyDescriptor(_class2.prototype, \"removeRecipient\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"searchUsers\", [_dec35], Object.getOwnPropertyDescriptor(_class2.prototype, \"searchUsers\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"addFile\", [_dec36], Object.getOwnPropertyDescriptor(_class2.prototype, \"addFile\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"addValidFile\", [_dec37], Object.getOwnPropertyDescriptor(_class2.prototype, \"addValidFile\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"deleteAttachment\", [_dec38], Object.getOwnPropertyDescriptor(_class2.prototype, \"deleteAttachment\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"renameAttachment\", [_dec39], Object.getOwnPropertyDescriptor(_class2.prototype, \"renameAttachment\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"startConversation\", [_dec40], Object.getOwnPropertyDescriptor(_class2.prototype, \"startConversation\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"openAttachmentDrawer\", [_dec41], Object.getOwnPropertyDescriptor(_class2.prototype, \"openAttachmentDrawer\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"uploadSingleFile\", [_dec42], Object.getOwnPropertyDescriptor(_class2.prototype, \"uploadSingleFile\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"updateConversation\", [_dec43], Object.getOwnPropertyDescriptor(_class2.prototype, \"updateConversation\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"deleteUnsentConversation\", [_dec44], Object.getOwnPropertyDescriptor(_class2.prototype, \"deleteUnsentConversation\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"toggleSharedEnrollments\", [_dec45], Object.getOwnPropertyDescriptor(_class2.prototype, \"toggleSharedEnrollments\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"toggleArchived\", [_dec46], Object.getOwnPropertyDescriptor(_class2.prototype, \"toggleArchived\"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, \"toggleConversationStar\", [_dec47], Object.getOwnPropertyDescriptor(_class2.prototype, \"toggleConversationStar\"), _class2.prototype)), _class2));\n});","define(\"bocce/components/inputs/toggle-switch\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let InputsSliderComponent = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._action, (_class = class InputsSliderComponent extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _initializerDefineProperty(this, \"activated\", _descriptor, this);\n this.activated = this.args.initialState;\n }\n toggleSwitch() {\n this.activated = !this.activated;\n this.args.action();\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"activated\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"toggleSwitch\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleSwitch\"), _class.prototype)), _class));\n});","define(\"bocce/components/interactions/guitar/main\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n didInsertElement() {\n console.log('guitar main!');\n }\n });\n});","define(\"bocce/components/kaltura-player\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n kalturaEmbed: function () {\n if (window.kWidget) {\n let kalturaRecording = this.kalturaRecording;\n let kEmbedObject = {\n targetId: this.elementId,\n flashvars: {\n 'autoPlay': false\n },\n wid: '_2588802',\n uiconf_id: 44413892,\n entry_id: kalturaRecording.kalturaId\n };\n let embedResult = window.kWidget.embed(kEmbedObject);\n (true && !(embedResult !== false) && Ember.assert('kWidget.embed is called on an existing DOM element', embedResult !== false));\n }\n },\n didRender() {\n this.kalturaEmbed();\n }\n });\n});","define(\"bocce/components/legacy-boot\", [\"exports\", \"bocce/mixins/boot\"], function (_exports, _boot) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Component.extend(_boot.default, {\n didInsertElement() {\n this.bootContent();\n },\n bootContent() {\n /* eslint-disable-next-line ember/no-jquery */\n let node = Ember.$(this.element).find('.bootable-area');\n if (node && node.length) {\n return this.boot_area(node, false, true, false, false, true);\n } else {\n return null;\n }\n }\n });\n});","define(\"bocce/components/lessons/advice-card-submitter\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7, _descriptor8, _descriptor9, _descriptor10;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let AdviceCardSubmitter = _exports.default = (_dec = Ember.inject.service, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._tracked, _dec7 = Ember._tracked, _dec8 = Ember._tracked, _dec9 = Ember._tracked, _dec10 = Ember._tracked, _dec11 = Ember.computed('session.section.adviceCards.adviceCardsCount', 'adviceCardsArray'), _dec12 = Ember.computed('session.user.id'), _dec13 = Ember._action, _dec14 = Ember._action, _dec15 = Ember._action, _dec16 = Ember._action, _dec17 = Ember._action, _dec18 = Ember._action, _dec19 = Ember._action, _dec20 = Ember._action, (_class = class AdviceCardSubmitter extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _initializerDefineProperty(this, \"session\", _descriptor, this);\n _initializerDefineProperty(this, \"adviceCardBodyText\", _descriptor2, this);\n _initializerDefineProperty(this, \"overCharLimit\", _descriptor3, this);\n _initializerDefineProperty(this, \"adviceCardPostable\", _descriptor4, this);\n _initializerDefineProperty(this, \"recordingUploading\", _descriptor5, this);\n _initializerDefineProperty(this, \"attachmentURL\", _descriptor6, this);\n _initializerDefineProperty(this, \"consentChecked\", _descriptor7, this);\n _initializerDefineProperty(this, \"adviceCard\", _descriptor8, this);\n _initializerDefineProperty(this, \"myAdviceCard\", _descriptor9, this);\n _initializerDefineProperty(this, \"adviceCardsArray\", _descriptor10, this);\n this.fetchAdviceCards();\n }\n get adviceCards() {\n if (this.session.section.adviceCards.adviceCardsCount) {\n this.fetchAdviceCards();\n }\n return this.adviceCardsArray;\n }\n fetchAdviceCards() {\n let section = window.session.get('section.id');\n return fetch(`/interface/submit-advice-card/${section}`, {\n method: 'GET'\n }).then(response => {\n // Make sure to check if the response is OK before proceeding\n if (!response.ok) {\n throw new Error('Network response was not ok');\n }\n // Use the .json() method to parse the response body as JSON\n return response.json();\n }).then(data => {\n // 'data' is the JSON object that was parsed from the response body\n if (data.adviceCards.map(card => card.id).toSorted().toString() != this.adviceCards.map(card => card.id).toSorted().toString()) {\n this.adviceCardsArray = Ember.A(data.adviceCards);\n }\n if (!this.isInstructor) {\n this.updateMyAdviceCard();\n }\n return this.adviceCardsArray;\n }).catch(error => {\n // Handle any errors that occurred during the fetch\n console.error('There was a problem with your fetch operation:', error);\n return [];\n });\n }\n get otherAdviceCards() {\n let userId = parseInt(window.session.get('user.id'));\n if (this.adviceCards) {\n return this.adviceCards.filter(c => parseInt(c.author_canvas_id) != userId);\n }\n return [];\n }\n updateMyAdviceCard() {\n let userId = parseInt(window.session.get('user.id'));\n if (this.adviceCards) {\n let adviceCard = this.adviceCards.find(function (card) {\n return parseInt(card.author_canvas_id) === userId;\n });\n this.myAdviceCard = adviceCard;\n } else {\n return false;\n }\n }\n get isInstructor() {\n let userId = parseInt(window.session.get('user.id'));\n return window.session.section.teachers.find(user => {\n return parseInt(user.id) === userId;\n });\n }\n updateRecordingStatus(status) {\n this.recordingUploading = status;\n this.adviceCardPostable = this.adviceCardBodyText.length > 0 && !this.recordingUploading;\n }\n resubmitAdviceCard() {\n this.myAdviceCard = false;\n }\n updateBody(newText) {\n this.adviceCardBodyText = newText;\n this.adviceCardPostable = this.adviceCardBodyText.length > 0 && !this.recordingUploading;\n }\n updateOverCharLimit(newVal) {\n this.overCharLimit = newVal;\n }\n updateAttachment(attachmentURL) {\n this.attachmentURL = attachmentURL;\n this.recordingUploading = false;\n }\n async toggleAcceptAdviceCard(id, advIndex) {\n let accept = !this.adviceCards[advIndex].accepted;\n let response = await fetch(`/interface/submit-advice-card/${window.session.get('section.id')}/accept`, {\n method: 'PUT',\n body: JSON.stringify({\n id: id,\n accepted: accept\n })\n });\n response = await response.json();\n if (response.success) {\n // Check this.adviceCards for the card with the id and update it with the accepted status\n let advIndex = this.adviceCards.findIndex(function (card) {\n return card.id === id;\n });\n if (advIndex !== -1) {\n // Dealing with deep arrays in Ember is a bit tricky...\n // Step 1 & 2: Make a copy of the object and update it\n const updatedCard = {\n ...this.adviceCards[advIndex],\n accepted: accept\n };\n // Step 3: Create a new array with the updated object\n const newAdviceCards = this.adviceCards.map((card, index) => index === advIndex ? updatedCard : card);\n // Step 4: Set the new array to the state\n this.adviceCardsArray = newAdviceCards;\n }\n }\n this.session.set('section.adviceCards.adviceCardsCount', 0);\n }\n async updateCheckbox(event) {\n this.consentChecked = event.target.checked;\n }\n async submitAdviceCard() {\n let assignmentBody = this.adviceCardBodyText;\n let attachmentURL = this.attachmentURL;\n let publicize_consent = this.consentChecked;\n let author_name = window.session.get('user.name');\n let course = window.session.get('course.id');\n let course_code = window.session.get('course.code').split('.')[0];\n let course_title = window.session.get('course.title');\n let term = window.session.get('course.term.name');\n let userId = parseInt(window.session.get('user.id'));\n let existing_card = this.adviceCards.find(function (card) {\n return parseInt(card.author_canvas_id) === userId;\n }) || null;\n let thisCard = {\n body: assignmentBody,\n attachment: attachmentURL,\n author_name: author_name,\n section_id: window.session.get('section.id'),\n course_id: course,\n course_code: course_code,\n course_title: course_title,\n instructor_id: null,\n submission_id: 0,\n term: term,\n publicize_consent: publicize_consent\n };\n if (existing_card) {\n thisCard.id = existing_card.id;\n }\n let response = await fetch(`/interface/submit-advice-card/`, {\n method: 'POST',\n body: JSON.stringify(thisCard)\n });\n if (response.ok) {\n // Need id of new card to prevent dupe\n this.fetchAdviceCards();\n this.args.submitAdviceCard();\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"session\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"adviceCardBodyText\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return '';\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"overCharLimit\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"adviceCardPostable\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"recordingUploading\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"attachmentURL\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, \"consentChecked\", [_dec7], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor8 = _applyDecoratedDescriptor(_class.prototype, \"adviceCard\", [_dec8], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.args.model;\n }\n }), _descriptor9 = _applyDecoratedDescriptor(_class.prototype, \"myAdviceCard\", [_dec9], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor10 = _applyDecoratedDescriptor(_class.prototype, \"adviceCardsArray\", [_dec10], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"adviceCards\", [_dec11], Object.getOwnPropertyDescriptor(_class.prototype, \"adviceCards\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"isInstructor\", [_dec12], Object.getOwnPropertyDescriptor(_class.prototype, \"isInstructor\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateRecordingStatus\", [_dec13], Object.getOwnPropertyDescriptor(_class.prototype, \"updateRecordingStatus\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"resubmitAdviceCard\", [_dec14], Object.getOwnPropertyDescriptor(_class.prototype, \"resubmitAdviceCard\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateBody\", [_dec15], Object.getOwnPropertyDescriptor(_class.prototype, \"updateBody\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateOverCharLimit\", [_dec16], Object.getOwnPropertyDescriptor(_class.prototype, \"updateOverCharLimit\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateAttachment\", [_dec17], Object.getOwnPropertyDescriptor(_class.prototype, \"updateAttachment\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleAcceptAdviceCard\", [_dec18], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleAcceptAdviceCard\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"updateCheckbox\", [_dec19], Object.getOwnPropertyDescriptor(_class.prototype, \"updateCheckbox\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"submitAdviceCard\", [_dec20], Object.getOwnPropertyDescriptor(_class.prototype, \"submitAdviceCard\"), _class.prototype)), _class));\n});","define(\"bocce/components/lessons/advice-card-viewer\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let AdviceCardViewer = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._action, _dec7 = Ember._action, _dec8 = Ember._action, _dec9 = Ember._action, (_class = class AdviceCardViewer extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _initializerDefineProperty(this, \"adviceCards\", _descriptor, this);\n _initializerDefineProperty(this, \"currentIndex\", _descriptor2, this);\n _initializerDefineProperty(this, \"currentLikes\", _descriptor3, this);\n _initializerDefineProperty(this, \"currentCardLiked\", _descriptor4, this);\n _initializerDefineProperty(this, \"currentCardHidden\", _descriptor5, this);\n let header = document.querySelector('[aria-labelledby=\"advice-cards-header\"]') || document.querySelector('[aria-labelledby=\"advice-cards-intro-header\"]') || document.querySelector('[aria-labelledby=\"advice-cards-lesson-1-header\"]');\n try {\n header.style.background = '#F4F6F8';\n document.querySelector('[topic_title=\"Peer-to-Peer Support: Advice Cards\"]').style.background = '#F4F6F8';\n } catch (e) {\n console.log(e);\n }\n let course_id = window.session.get('course.course_id');\n fetch(`/interface/advice_cards/${course_id}`, {\n method: 'GET'\n }).then(response => {\n // Make sure to check if the response is OK before proceeding\n if (!response.ok) {\n throw new Error('Network response was not ok');\n }\n // Use the .json() method to parse the response body as JSON\n return response.json();\n }).then(data => {\n // 'data' is the JSON object that was parsed from the response body\n this.adviceCards = Ember.A(data);\n }).catch(error => {\n // Handle any errors that occurred during the fetch\n console.error('There was a problem with your fetch operation:', error);\n });\n }\n get currentCard() {\n if (this.adviceCards?.length) {\n this.currentLikes = this.adviceCards[this.currentIndex].num_likes;\n this.currentCardLiked = this.adviceCards[this.currentIndex].liked;\n this.currentCardHidden = this.adviceCards[this.currentIndex].is_hidden;\n return this.adviceCards[this.currentIndex];\n }\n return false;\n }\n async toggleHide() {\n let requestType = this.currentCard.is_hidden ? 'unhide' : 'hide';\n let response = await fetch(`/interface/advice_cards/${window.session.course.get('course_id')}/${this.currentCard.id}/${requestType}`, {\n method: \"PATCH\"\n });\n if (!response.ok) {\n throw response.status;\n }\n this.currentCard.is_hidden = !this.currentCard.is_hidden;\n this.currentCardHidden = !this.currentCardHidden;\n return response.json();\n }\n async toggleLike() {\n let response;\n if (this.currentCard.liked) {\n response = await fetch(`/interface/advice_cards/${window.session.course.get('course_id')}/${this.currentCard.id}/unlike`, {\n method: \"DELETE\"\n });\n } else {\n response = await fetch(`/interface/advice_cards/${window.session.course.get('course_id')}/${this.currentCard.id}/like`, {\n method: \"PUT\"\n });\n }\n if (!response.ok) {\n throw response.status;\n }\n if (this.currentCard.liked) {\n this.currentLikes--;\n this.currentCard.num_likes--;\n } else {\n this.currentLikes++;\n this.currentCard.num_likes++;\n }\n this.currentCard.liked = !this.currentCard.liked;\n this.currentCardLiked = !this.currentCardLiked;\n return response.json();\n }\n nextSlide() {\n this.currentIndex++;\n if (this.currentIndex >= this.adviceCards.length) {\n this.currentIndex = 0;\n }\n this.logInteraction();\n }\n prevSlide() {\n this.currentIndex--;\n if (this.currentIndex < 0) {\n this.currentIndex = this.adviceCards.length - 1;\n }\n this.logInteraction();\n }\n async logInteraction() {\n fetch(`/interface/advice_cards/${window.session.course.get('course_id')}/interaction`, {\n method: 'PUT'\n }).catch(error => {\n // Handle any errors that occurred during the fetch\n console.error('There was a problem with your fetch operation:', error);\n });\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"adviceCards\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"currentIndex\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return 0;\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"currentLikes\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"currentCardLiked\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"currentCardHidden\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"toggleHide\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleHide\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleLike\", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleLike\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"nextSlide\", [_dec8], Object.getOwnPropertyDescriptor(_class.prototype, \"nextSlide\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"prevSlide\", [_dec9], Object.getOwnPropertyDescriptor(_class.prototype, \"prevSlide\"), _class.prototype)), _class));\n});","define(\"bocce/components/lessons/event-new-content\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n class EventNewContentComponent extends Ember.Component {\n didInsertElement() {\n const stickyHeader = document.getElementById(\"new-event-sticky-header\");\n const headerOffsetHeight = stickyHeader ? stickyHeader.offsetHeight : 42;\n\n //Scroll the newly created element to a visually pleasing spot on the screen.\n $('#new-event').animate({\n scrollTop: this.element.offsetTop - headerOffsetHeight\n }, 1250);\n }\n }\n _exports.default = EventNewContentComponent;\n ;\n});","define(\"bocce/components/lessons/event-new-template-actions\", [\"exports\", \"bocce/utilities/dialog\", \"bocce/config/environment\"], function (_exports, _dialog, _environment) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let EventNewTemplateActionsComponent = _exports.default = (_dec = Ember.inject.service('templates'), (_class = class EventNewTemplateActionsComponent extends Ember.Component {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"templatesService\", _descriptor, this);\n }\n didInsertElement() {\n const self = this;\n Ember.$('.template-actions-content .template-select')\n\n //Keep track of the last selected option so that it can be reverted to if the user\n //decides not to change templates.\n .on('click', function () {\n Ember.$(this).data('lastSelected', Ember.$(this).find('option:selected'));\n }).on('change', function () {\n const lastSelected = Ember.$(this).data('lastSelected');\n\n //Verify the user wants to change templates if the content is dirty, i.e. content has been changed and\n //is now different from one of the saved templates.\n if (!self.contentNotDirty) {\n (0, _dialog.default)(_environment.default.APP.text.confirmChangeTemplate, ['Yes', 'No']).then(choice => {\n if (choice === 'No') {\n lastSelected.prop('selected', true);\n return false;\n }\n self.templatesService.changeTemplate(Ember.$(this).val());\n });\n } else {\n self.templatesService.changeTemplate(Ember.$(this).val());\n }\n });\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"templatesService\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n })), _class));\n});","define(\"bocce/components/lessons/event-new-template-manager\", [\"exports\", \"bocce/utilities/dialog\", \"bocce/config/environment\"], function (_exports, _dialog, _environment) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n didInsertElement() {\n //Move this template to the 'template-manager-root' div, which is in 'event-new.hbs'. The design \n //was much easier with this pattern.\n $(this.element).appendTo('#template-manager-root');\n },\n changeTemplate(index) {\n this.onTemplateChange(index);\n this.hideTemplateManager();\n },\n actions: {\n useTemplate(index) {\n const self = this;\n if (!this.contentNotDirty) {\n //User has made changes without saving, confirm they want to change templates. \n (0, _dialog.default)(_environment.default.APP.text.confirmChangeTemplate, ['Yes', 'No']).then(choice => {\n if (choice === 'No') {\n return false;\n }\n self.changeTemplate(index);\n });\n } else {\n self.changeTemplate(index);\n }\n }\n }\n });\n});","define(\"bocce/components/lessons/lesson-wrapper\", [\"exports\", \"bocce/mixins/av-players\", \"bocce/mixins/notify\"], function (_exports, _avPlayers, _notify) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Component.extend(_avPlayers.default, _notify.default, {\n didInsertElement: function () {\n var ctl = this.parent;\n this.startAV();\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('img').on('error', function () {\n /* eslint-disable-next-line ember/no-jquery */\n var imgSrc = Ember.$(this).attr('src');\n if (imgSrc && imgSrc.length > 0 && this.getAttribute('att') !== '2') {\n this.setAttribute('att', '2');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$(this).attr('src', imgSrc + '&att=2');\n Ember.debug('Missing image - attempting to fetch again');\n } else {\n Ember.debug('Already tried fetching image once during this runtime. Giving up.');\n }\n });\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel')\n /* eslint-disable-next-line ember/no-jquery */.on('scrollstart', Ember.$.proxy(this.didScroll, this))\n /* eslint-disable-next-line ember/no-jquery */.on('scrollstop', Ember.$.proxy(this.scrollStop, this));\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel')\n /* eslint-disable-next-line ember/no-jquery */.on('scrollstart', Ember.$.proxy(this.sidePanelScrollStart, this))\n /* eslint-disable-next-line ember/no-jquery */.on('scrollstop', Ember.$.proxy(this.sidePanelScrollStop, this));\n this.sidePanelScrollStop();\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel-container').on('resize', Ember.$.proxy(this.didResize, this));\n\n // Temporary stopgap so boot functions run while refactoring\n ctl.getThingsRolling();\n },\n willDestroy() {\n this._super(...arguments);\n // kill all event handlers!\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('img').off('error');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').off('scrollstart').off('strollstop');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').off('scrollstart').off('scrollstop');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel-container').off('resize');\n if (this.avPlayerOnClickListener) {\n document.removeEventListener('click', this.avPlayerOnClickListener);\n }\n },\n willDestroyElement: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel')\n /* eslint-disable-next-line ember/no-jquery */.off('scrollstart', Ember.$.proxy(this.didScroll, this))\n /* eslint-disable-next-line ember/no-jquery */.off('scrollstop', Ember.$.proxy(this.scrollStop, this));\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel')\n /* eslint-disable-next-line ember/no-jquery */.on('scrollstart', Ember.$.proxy(this.sidePanelScrollStart, this))\n /* eslint-disable-next-line ember/no-jquery */.on('scrollstop', Ember.$.proxy(this.sidePanelScrollStop, this));\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel-container').off('resize', Ember.$.proxy(this.didResize, this));\n },\n // Scrolling tracker\n didScroll: function () {\n //Show current course topic display when scrolling\n this.set('amScrolling', true);\n },\n scrollStop: function () {\n //Check to make sure nothing is loading\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$('.main-panel.no-scroll').length > 0) {\n return;\n }\n //Update current topic, if necessary\n this.parent.send('updatePath');\n this.parent.set('amScrolling', false);\n },\n sidePanelScrollStart: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.lesson-divider').removeClass('current-lesson');\n },\n sidePanelScrollStop: function () {\n /* eslint-disable-next-line ember/no-jquery */\n var firstItem = Ember.$('.syllabus > a:in-viewport').first(),\n currHeader = firstItem.prevAll('.syllabus-divider').first();\n if (currHeader.length === 0) {\n /* eslint-disable-next-line ember/no-jquery */\n currHeader = Ember.$('.syllabus .syllabus-divider').first();\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.syllabus-divider').removeClass('current-lesson');\n currHeader.addClass('current-lesson');\n let lessonNum = currHeader.attr('lesson_number');\n let lessonTitle = currHeader.attr('lesson_title');\n let newTitle;\n if (lessonNum === '0') {\n newTitle = 'Getting Started';\n } else {\n newTitle = `${lessonNum} - ${lessonTitle}`;\n }\n this.parent.send('updateShortcutsDrawer', newTitle);\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.shortcut-list-item').removeClass('current');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.shortcut-list-item:nth(' + currHeader.attr('lesson_number') + ')').addClass('current');\n },\n // Half-height infinite scroll resizer\n didResize: function () {\n this.parent.send('resetHeight');\n }\n });\n});","define(\"bocce/components/lessons/topic-body\", [\"exports\", \"bocce/mixins/boot\", \"bocce/mixins/quiz\"], function (_exports, _boot, _quiz) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Component.extend(_quiz.default, _boot.default, {\n bookmarks: Ember.inject.service(),\n userprofileService: Ember.inject.service('userprofile'),\n didInsertElement() {\n // run boot function on this topic's element\n this.bootContent();\n this.addLinkTargets();\n this.modifyBookmarks();\n },\n bootContent() {\n /* eslint-disable-next-line ember/no-jquery */\n let node = Ember.$(this.element).find('.bootable-area');\n if (node && node.length) {\n // Boot each bootable-area node found in this component\n let promises = [];\n /* eslint-disable-next-line ember/no-jquery */\n node.each((i, n) => promises.push(this.boot_area(Ember.$(n))));\n return Promise.all(promises);\n } else {\n return Promise.resolve(null);\n }\n },\n addLinkTargets() {\n // TODO-FIXME:\n // This is ugly. Is there a way to do this in the CMS?\n // Give every link a target=\"_blank\" property\n // This will ensure all links open in new tab/window\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$(this.element).find('a[href]:not(.ember-view)').attr('target', '_blank');\n },\n modifyBookmarks() {\n const lesson = this.lessons.get('model.lesson');\n const lesson_id = lesson.get('id');\n const lesson_name = lesson.get('title');\n Ember.$(`.bookmark-add.${this.index}`).on('click', () => {\n this.bookmarksService.addBookmark(this.item.db_id, this.item.id, this.item.title, this.topicbody.body, lesson_id, lesson_name);\n });\n },\n templateForType: Ember.computed('item.type', function () {\n let templates = {\n 'assignment': 'assignment-contents',\n 'discussion': 'discussion-contents',\n 'quiz': 'quiz'\n };\n /* eslint-disable-next-line ember/no-get */\n let type = this.get('item.type').toLowerCase();\n return templates[type] || null;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n discussioncontents: Ember.computed('item', 'data', function () {\n /* eslint-disable-next-line ember/no-get */\n let type = this.get('item.type').toLowerCase();\n if (type === 'discussion') {\n return this.data;\n }\n return null;\n }),\n discussionLastReply: Ember.computed('discussioncontents', async function () {\n let discussion = this.discussioncontents;\n if (discussion) {\n let responses = await discussion.get('responses');\n let latestResponse = responses.content ? responses.content[0] : null;\n return latestResponse;\n }\n return null;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n assignmentcontents: Ember.computed('item', 'data', function () {\n /* eslint-disable-next-line ember/no-get */\n let type = this.get('item.type').toLowerCase();\n if (type === 'assignment') {\n return this.data;\n }\n return null;\n }),\n topicbody: Ember.computed('templateForType', 'data', function () {\n if (this.templateForType === null) {\n return this.data;\n }\n return null;\n }),\n actions: {\n sendParentAction(action, ...args) {\n this[action](...args);\n },\n viewModal() {\n this.viewModal(...arguments);\n }\n }\n });\n});","define(\"bocce/components/login-streak\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let LoginStreak = _exports.default = (_dec = Ember.inject.service('login-streak'), (_class = class LoginStreak extends _component.default {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"loginStreakService\", _descriptor, this);\n }\n get consecutiveWeeks() {\n return this.loginStreakService.consecutiveWeeks;\n }\n get weekNum() {\n return this.loginStreakService.weekNum;\n }\n get daysThisWeek() {\n return this.loginStreakService.daysThisWeek;\n }\n get days() {\n return [{\n day: 'M',\n num: 1\n }, {\n day: 'Tu',\n num: 2\n }, {\n day: 'W',\n num: 3\n }, {\n day: 'Th',\n num: 4\n }, {\n day: 'F',\n num: 5\n }, {\n day: 'Sa',\n num: 6\n }, {\n day: 'Su',\n num: 7\n }];\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"loginStreakService\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n })), _class));\n});","define(\"bocce/components/modals/attachments-modal\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _class, _descriptor, _descriptor2;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let AttachmentsModal = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._action, _dec4 = Ember._action, _dec5 = Ember._action, _dec6 = Ember._action, (_class = class AttachmentsModal extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _defineProperty(this, \"tabs\", [{\n name: 'Upload File'\n }, {\n name: 'Record Video'\n }, {\n name: 'Record Audio'\n }]);\n _initializerDefineProperty(this, \"activeTab\", _descriptor, this);\n _initializerDefineProperty(this, \"minimized\", _descriptor2, this);\n this.minimized = false;\n }\n changeTab(clickedTab) {\n this.activeTab = clickedTab;\n }\n closeModal() {\n const modal = document.querySelector('.attachments-modal');\n modal.close();\n this.minimized = false;\n }\n minimize() {\n console.log(\"minimizing\");\n const modal = document.querySelector('.attachments-modal');\n if (modal?.open) {\n modal.close();\n }\n modal.show();\n this.minimized = true;\n }\n maximize() {\n console.log(\"maximizing\");\n const modal = document.querySelector('.attachments-modal');\n if (modal?.open) {\n modal.close();\n }\n modal.showModal();\n this.minimized = false;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"activeTab\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return this.tabs[0];\n }\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"minimized\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"changeTab\", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, \"changeTab\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"closeModal\", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, \"closeModal\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"minimize\", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, \"minimize\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"maximize\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"maximize\"), _class.prototype)), _class));\n});","define(\"bocce/components/modals/attachments/attachments-record-audio\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let ModalsAttachmentsAttachmentsRecordAudioComponent = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._action, _dec7 = Ember._action, _dec8 = Ember._action, _dec9 = Ember._action, _dec10 = Ember._action, _dec11 = Ember._action, _dec12 = Ember._action, (_class = class ModalsAttachmentsAttachmentsRecordAudioComponent extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _initializerDefineProperty(this, \"recordedAudio\", _descriptor, this);\n _initializerDefineProperty(this, \"currentlyRecordingAudio\", _descriptor2, this);\n _initializerDefineProperty(this, \"timeRecorded\", _descriptor3, this);\n _initializerDefineProperty(this, \"echoCancellation\", _descriptor4, this);\n _initializerDefineProperty(this, \"showEraseConfirmation\", _descriptor5, this);\n _defineProperty(this, \"recorder\", void 0);\n this.echoCancellation = localStorage.getItem('bocceEchoCancellation') === 'true' || false;\n this.reInitialize();\n }\n get generatedUrl() {\n if (!this.recordedAudio) {\n return \"placeholder\";\n } else if (!this.recordedAudio.url) {\n return URL.createObjectURL(this.recordedAudio);\n }\n\n // Grab Kaltura thumbnail\n let url = this.recordedAudio.url;\n let kaltura = url.split('https://cdnapisec.kaltura.com/p/2588802/sp/258880200/playManifest/entryId/');\n if (kaltura.length > 1) {\n kaltura = kaltura[1].split('/format/url/protocol/https/flavorParamId/4128/name/course_video.mp4');\n let thumbnailUrl = 'https://cdnsecakmi.kaltura.com/p/2588802/thumbnail/entry_id/' + kaltura[0] + '/width/250';\n return thumbnailUrl;\n }\n return url;\n }\n startUserMedia(stream) {\n this.recorder = new Recorder(stream);\n console.debug('Recorder initialised.');\n }\n async startRecording() {\n let constraints = {\n audio: true\n };\n if (this.echoCancellation) {\n constraints.audio = {\n echoCancellation: false,\n mozAutoGainControl: false,\n mozNoiseSuppression: false,\n googEchoCancellation: false,\n googAutoGainControl: false,\n googNoiseSuppression: false,\n googHighpassFilter: false\n };\n }\n navigator.mediaDevices.getUserMedia(constraints).then(stream => {\n this.currentlyRecordingAudio = true;\n this.startUserMedia(stream);\n this.recorder.record(time => {\n this.timeRecorded = time;\n });\n }).catch(error => {\n console.debug('No live audio input: ' + error);\n });\n }\n stopRecording() {\n let rec = this.recorder.stop();\n if (rec) {\n // create MP3 download link using audio data blob\n this.recorder.exportMP3((blob, mp3Name) => {\n // this.args.controller.send('addValidFile', file); todo maybe dont need this?\n this.recordedAudio = new File([blob], mp3Name, {\n type: 'audio/mpeg3'\n });\n }, this);\n this.currentlyRecordingAudio = false;\n }\n }\n attachToPost() {\n if (this.recordedAudio) {\n this.args.addFile(this.recordedAudio);\n this.args.closeModal();\n }\n }\n reRecord() {\n this.showEraseConfirmation = true;\n }\n toggleEchoCancellation() {\n this.echoCancellation = !this.echoCancellation;\n localStorage.setItem('echoCancellation', this.echoCancellation);\n }\n reInitialize() {\n this.recordedAudio = undefined;\n this.currentlyRecordingAudio = false;\n this.showEraseConfirmation = false;\n this.timeRecorded = \"0:00\";\n this.recorder = undefined;\n }\n cancelErase() {\n this.showEraseConfirmation = false;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"recordedAudio\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"currentlyRecordingAudio\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"timeRecorded\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"echoCancellation\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"showEraseConfirmation\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"startRecording\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"startRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"stopRecording\", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, \"stopRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"attachToPost\", [_dec8], Object.getOwnPropertyDescriptor(_class.prototype, \"attachToPost\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"reRecord\", [_dec9], Object.getOwnPropertyDescriptor(_class.prototype, \"reRecord\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleEchoCancellation\", [_dec10], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleEchoCancellation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"reInitialize\", [_dec11], Object.getOwnPropertyDescriptor(_class.prototype, \"reInitialize\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"cancelErase\", [_dec12], Object.getOwnPropertyDescriptor(_class.prototype, \"cancelErase\"), _class.prototype)), _class));\n});","define(\"bocce/components/modals/attachments/attachments-record-video\", [\"exports\", \"@glimmer/component\", \"moment\"], function (_exports, _component, _moment) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _dec25, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7, _descriptor8, _descriptor9, _descriptor10, _descriptor11, _descriptor12, _descriptor13;\n /* global MediaStreamRecorder getSeekableBlob*/\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let ModalsAttachmentsAttachmentsRecordVideoComponent = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._tracked, _dec7 = Ember._tracked, _dec8 = Ember._tracked, _dec9 = Ember._tracked, _dec10 = Ember._tracked, _dec11 = Ember._tracked, _dec12 = Ember._tracked, _dec13 = Ember._tracked, _dec14 = Ember._action, _dec15 = Ember._action, _dec16 = Ember._action, _dec17 = Ember._action, _dec18 = Ember._action, _dec19 = Ember._action, _dec20 = Ember._action, _dec21 = Ember._action, _dec22 = Ember._action, _dec23 = Ember._action, _dec24 = Ember._action, _dec25 = Ember._action, (_class = class ModalsAttachmentsAttachmentsRecordVideoComponent extends _component.default {\n get useCameraTag() {\n return $.isSafari;\n }\n constructor(owner, args) {\n super(owner, args);\n // UI switches\n _initializerDefineProperty(this, \"expandedSettings\", _descriptor, this);\n _initializerDefineProperty(this, \"recordingVideo\", _descriptor2, this);\n _initializerDefineProperty(this, \"showEraseConfirmation\", _descriptor3, this);\n _initializerDefineProperty(this, \"showLoadingWheel\", _descriptor4, this);\n // video settings\n _initializerDefineProperty(this, \"videoSources\", _descriptor5, this);\n _initializerDefineProperty(this, \"audioSources\", _descriptor6, this);\n _initializerDefineProperty(this, \"quality\", _descriptor7, this);\n _initializerDefineProperty(this, \"audioInputId\", _descriptor8, this);\n _initializerDefineProperty(this, \"videoInputId\", _descriptor9, this);\n _initializerDefineProperty(this, \"echoCancellation\", _descriptor10, this);\n // recording\n _initializerDefineProperty(this, \"recordingStartTime\", _descriptor11, this);\n _initializerDefineProperty(this, \"recordingCurrentTime\", _descriptor12, this);\n _initializerDefineProperty(this, \"blobUrl\", _descriptor13, this);\n _defineProperty(this, \"stream\", void 0);\n _defineProperty(this, \"recorder\", void 0);\n _defineProperty(this, \"timeInterval\", void 0);\n _defineProperty(this, \"recordedVideo\", void 0);\n this.expandedSettings = false;\n this.quality = 'sd';\n this.echoCancellation = localStorage.getItem('bocceEchoCancellation') === 'true' || false;\n this.recordingVideo = false;\n this.recordingCurrentTime = '00:00';\n if (this.useCameraTag) {\n this.setupCameraTag();\n } else {\n this.startPreview();\n }\n }\n startPreview() {\n this.showLoadingWheel = true;\n let vidSettings = {};\n let audioSettings = {};\n if (this.videoInputId) {\n vidSettings = {\n deviceId: this.videoInputId\n };\n }\n if (this.audioInputId) {\n audioSettings = {\n deviceId: this.audioInputId\n };\n }\n if (this.quality === 'hd') {\n vidSettings = {\n ...vidSettings,\n width: {\n min: 1280\n },\n height: {\n min: 720\n }\n };\n }\n // If false, set the audio settings to echoCancellation: false\n // Echo cancellation is set to true by default\n if (!this.echoCancellation) {\n audioSettings = {\n ...audioSettings,\n echoCancellation: false,\n mozAutoGainControl: false,\n mozNoiseSuppression: false,\n googEchoCancellation: false,\n googAutoGainControl: false,\n googNoiseSuppression: false,\n googHighpassFilter: false\n };\n }\n\n // if any streams are currently running, stop them\n if (this.stream) {\n this.stream.getTracks().forEach(track => track.stop());\n }\n navigator.mediaDevices.getUserMedia({\n audio: audioSettings,\n video: vidSettings\n }).then(stream => {\n navigator.mediaDevices.enumerateDevices().then(deviceInfos => {\n this.videoSources = deviceInfos.filter(deviceInfo => deviceInfo.kind === 'videoinput');\n this.audioSources = deviceInfos.filter(deviceInfo => deviceInfo.kind === 'audioinput');\n });\n let video = document.querySelector(\"#attachment-video-playback\");\n video.volume = 0;\n video.srcObject = stream;\n let fileType = $.isSafari ? 'video/mp4' : 'video/webm';\n this.recorder = new MediaStreamRecorder(stream, {\n mimeType: fileType\n });\n this.stream = stream;\n }).catch(function (error) {\n console.log(error);\n });\n }\n setupCameraTag() {\n setTimeout(() => window.CameraTag.setup(), 500);\n window.CameraTag.observe('recorderAtt', 'published', () => {\n let myCamera = window.CameraTag.cameras['recorderAtt'],\n myVideo = myCamera.getVideo(),\n mp4_url = myVideo.medias.vga_mp4,\n /* eslint-disable-next-line ember/no-jquery */\n model_title = $('.floating-modal.active#assignment').length > 0 ? 'new-assignment' : this.get('model.title') || 'new-discussion',\n name = model_title.toLowerCase().replace(/ /g, '-').replace(/[^\\w-]+/g, '') + '_video_' + new Date().getTime(),\n myVid = {\n isUrl: true,\n name: name,\n url: mp4_url,\n type: 'video/mp4'\n },\n cas;\n if (mp4_url) {\n myCamera.reset();\n // Add video to archive\n if (localStorage.localArchive) {\n cas = JSON.parse(localStorage.localArchive);\n } else {\n cas = {};\n }\n cas[myVid.name] = myVid;\n localStorage.localArchive = JSON.stringify(cas);\n this.args.addFile(myVid);\n this.args.closeModal();\n }\n }, true);\n }\n toggleAdditionalSettings() {\n this.expandedSettings = !this.expandedSettings;\n }\n changeVideoQuality(event) {\n this.quality = event.target.value;\n this.startPreview();\n }\n chooseAudioSource(event) {\n this.audioInputId = event.target.value;\n this.startPreview();\n }\n chooseVideoSource(event) {\n this.videoInputId = event.target.value;\n this.startPreview();\n }\n toggleEchoCancellation() {\n this.echoCancellation = !this.echoCancellation;\n localStorage.setItem('bocceEchoCancellation', this.echoCancellation);\n this.startPreview();\n }\n startRecording() {\n this.recorder.record();\n this.recordingVideo = true;\n this.startTimer();\n }\n startTimer() {\n this.recordingStartTime = (0, _moment.default)().unix();\n this.timeInterval = setInterval(() => {\n let timeCalc = (0, _moment.default)().unix() - this.recordingStartTime;\n this.recordingCurrentTime = (0, _moment.default)(timeCalc * 1000).format('mm:ss');\n }, 1000);\n }\n stopRecording() {\n clearInterval(this.timeInterval);\n this.recorder.stop(blob => {\n try {\n getSeekableBlob(blob, seekableBlob => {\n if (!seekableBlob) {\n Ember.debug(\"Could not create seekable blob, creating non-seekable blob instead\");\n blob = blob.slice(0, blob.size, 'video/webm');\n this.blobUrl = URL.createObjectURL(blob);\n this.recordedVideo = blob;\n return;\n }\n // Forcibly assign webm mimetype to blob\n seekableBlob = seekableBlob.slice(0, blob.size, 'video/webm');\n this.blobUrl = URL.createObjectURL(seekableBlob);\n this.recordedVideo = seekableBlob;\n });\n } catch (e) {\n Ember.debug(\"Could not create seekable blob because: \", e, \"Creating non-seekable blob instead\");\n blob = blob.slice(0, blob.size, 'video/webm');\n this.blobUrl = URL.createObjectURL(blob);\n this.recordedVideo = blob;\n }\n this.stream.stop();\n this.recorder.clearRecordedData();\n });\n }\n attachToPost() {\n if (this.blobUrl) {\n this.args.addFile(this.recordedVideo);\n this.args.closeModal();\n }\n }\n showConfirmation() {\n this.showEraseConfirmation = true;\n }\n cancelErase() {\n this.showEraseConfirmation = false;\n }\n reRecord() {\n this.recordingVideo = false;\n this.showEraseConfirmation = false;\n this.recordingCurrentTime = '00:00';\n this.blobUrl = undefined;\n this.stream = undefined;\n this.recorder.clearRecordedData();\n this.startPreview();\n }\n hideLoadingWheel() {\n this.showLoadingWheel = false;\n }\n willDestroy() {\n super.willDestroy();\n if (this.recorder) {\n this.recorder.stop(() => {\n this.recorder.clearRecordedData();\n });\n }\n if (this.stream) {\n this.stream.stop();\n }\n if (this.timeInterval) {\n clearInterval(this.timeInterval);\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"expandedSettings\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"recordingVideo\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"showEraseConfirmation\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"showLoadingWheel\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"videoSources\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"audioSources\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, \"quality\", [_dec7], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor8 = _applyDecoratedDescriptor(_class.prototype, \"audioInputId\", [_dec8], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor9 = _applyDecoratedDescriptor(_class.prototype, \"videoInputId\", [_dec9], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor10 = _applyDecoratedDescriptor(_class.prototype, \"echoCancellation\", [_dec10], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor11 = _applyDecoratedDescriptor(_class.prototype, \"recordingStartTime\", [_dec11], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor12 = _applyDecoratedDescriptor(_class.prototype, \"recordingCurrentTime\", [_dec12], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor13 = _applyDecoratedDescriptor(_class.prototype, \"blobUrl\", [_dec13], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"toggleAdditionalSettings\", [_dec14], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleAdditionalSettings\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"changeVideoQuality\", [_dec15], Object.getOwnPropertyDescriptor(_class.prototype, \"changeVideoQuality\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"chooseAudioSource\", [_dec16], Object.getOwnPropertyDescriptor(_class.prototype, \"chooseAudioSource\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"chooseVideoSource\", [_dec17], Object.getOwnPropertyDescriptor(_class.prototype, \"chooseVideoSource\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"toggleEchoCancellation\", [_dec18], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleEchoCancellation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"startRecording\", [_dec19], Object.getOwnPropertyDescriptor(_class.prototype, \"startRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"stopRecording\", [_dec20], Object.getOwnPropertyDescriptor(_class.prototype, \"stopRecording\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"attachToPost\", [_dec21], Object.getOwnPropertyDescriptor(_class.prototype, \"attachToPost\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"showConfirmation\", [_dec22], Object.getOwnPropertyDescriptor(_class.prototype, \"showConfirmation\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"cancelErase\", [_dec23], Object.getOwnPropertyDescriptor(_class.prototype, \"cancelErase\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"reRecord\", [_dec24], Object.getOwnPropertyDescriptor(_class.prototype, \"reRecord\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"hideLoadingWheel\", [_dec25], Object.getOwnPropertyDescriptor(_class.prototype, \"hideLoadingWheel\"), _class.prototype)), _class));\n});","define(\"bocce/components/modals/attachments/attachments-upload-file\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _class, _descriptor, _descriptor2;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let ModalsAttachmentsAttachmentsUploadFileComponent = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._action, _dec4 = Ember._action, _dec5 = Ember._action, (_class = class ModalsAttachmentsAttachmentsUploadFileComponent extends _component.default {\n constructor(owner, args) {\n super(owner, args);\n _initializerDefineProperty(this, \"uploadedFiles\", _descriptor, this);\n _initializerDefineProperty(this, \"hasUploaded\", _descriptor2, this);\n this.uploadedFiles = [];\n this.hasUploaded = false;\n }\n uploadFile(event) {\n this.hasUploaded = true;\n this.uploadedFiles = [...this.uploadedFiles, ...event.target.files];\n }\n uploadMoreFiles() {\n this.hasUploaded = false;\n }\n attachToPost() {\n this.uploadedFiles.forEach(file => {\n this.args.addFile(file);\n });\n this.uploadedFiles = [];\n this.hasUploaded = false;\n this.args.closeModal();\n }\n willDestroy() {\n super.willDestroy();\n this.uploadedFiles = [];\n this.hasUploaded = false;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"uploadedFiles\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"hasUploaded\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"uploadFile\", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, \"uploadFile\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"uploadMoreFiles\", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, \"uploadMoreFiles\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"attachToPost\", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, \"attachToPost\"), _class.prototype)), _class));\n});","define(\"bocce/components/modals/cover-backdrop\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n classNames: ['cover-backdrop']\n });\n});","define(\"bocce/components/modals/dark-backdrop\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n classNames: ['dark-backdrop'],\n click() {\n this.action();\n }\n });\n});","define(\"bocce/components/new-activity-modal\", [\"exports\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/embed-parser\", \"bocce/mixins/uploadable\", \"bocce/mixins/editable\", \"bocce/mixins/polls\", \"bocce/mixins/nested-resources\", \"bocce/utilities/dialog\"], function (_exports, _audioRec, _videoRec, _rtcRec, _embedParser, _uploadable, _editable, _polls, _nestedResources, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n const __COLOCATED_TEMPLATE__ = Ember.HTMLBars.template(\n /*\n
\n
{{modalTitle}}
\n \n
\n {{#if this.multiSectionPicker}}\n \n {{/if}}\n
\n {{!-- The max length accepted by the Canvas api is 254, but 250 looks nicer --}}\n \n
\n `;\n if (type == 'graded') {\n scoresHtml += `\n
\n
User
\n
Score
\n
`;\n }\n for (let score of scores[type]) {\n scoresHtml += `
\n
${score.name}
\n ${type == 'graded' ? `
${score.score}
` : ''}\n
`;\n }\n scoresHtml += '
';\n (0, _dialog.default)('', ['OK'], scoresHtml, 0, [\"quiz-stats-essay-scores\"]);\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"session\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"showUsers\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"showUsers\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"showScores\", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, \"showScores\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz-stats/question-statistics-multiple\", [\"exports\", \"bocce/utilities/dialog\", \"bocce/mixins/boot\"], function (_exports, _dialog, _boot) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _class, _descriptor, _descriptor2;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n const QuestionStatisticsMultiple2 = Ember.Component.extend(_boot.default, {});\n\n //Used for Fill in Multiple Blanks and Multiple Dropdowns.\n\n //this.statistics gets passed in to the component. Origin is canvas quiz stats api.\n let QuestionStatisticsMultiple = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._action, _dec4 = Ember._action, (_class = class QuestionStatisticsMultiple extends QuestionStatisticsMultiple2 {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"placeholderId\", _descriptor, this);\n _initializerDefineProperty(this, \"answers\", _descriptor2, this);\n }\n init(...args) {\n super.init(...args);\n this.placeholderId = this.statistics.answer_sets[0].text;\n this.blankPercentages = this.getBlankPercentages();\n this.setAnswers();\n }\n didInsertElement() {\n const self = this;\n\n //When the user clicks on a placeholder, change the placeholder id. This triggers the stats to be recalculated\n //and then re-rendered. After this happens, show the stats html inside a modal.\n Ember.$(this.element).find(`.question-stats-placeholder`).off('click.questionStatsPlaceholder').on('click.questionStatsPlaceholder', event => {\n self.changePlaceholder(event.target.textContent);\n Ember.run.scheduleOnce('afterRender', this, function () {\n (0, _dialog.default)('', ['OK'], Ember.$(this.element).find('.question-statistics-multiple-blank-stats').html(), 0);\n });\n });\n this.boot_area(Ember.$(this.element).find(`.question-stats-question-text`), true, true, true, false, true);\n }\n\n /**\n * /(?<=\\[).+?(?=\\])/g) - lookbehind doesn't work in Safari <= 16.3\n \n get blanksOrdered() {\n let ret = []\n let question_text = this.statistics.question_text\n for(const m of question_text.matchAll(/(?<=\\[).+?(?=\\])/g)) {\n ret.push(m[0])\n }\n return ret\n }*/\n\n getBlankPercentages() {\n let stats = {};\n let totalResponses = this.statistics.responses;\n for (const answerSet of this.statistics.answer_sets) {\n stats[answerSet.text] = {};\n }\n for (const answerSet of this.statistics.answer_sets) {\n let percentageCorrect = `0%`;\n let usernames = [];\n let answerResponses = 0;\n for (const answer of answerSet.answers) {\n if (answer.correct) {\n percentageCorrect = `${+parseFloat(answer.responses / totalResponses * 100).toFixed(2)}`;\n usernames.push(...answer.user_names);\n }\n if (answer.id != 'none') {\n answerResponses += answer.responses;\n }\n }\n stats[answerSet.text].correct = percentageCorrect;\n stats[answerSet.text].usernames = usernames;\n stats[answerSet.text].answered = +parseFloat(answerResponses / totalResponses * 100).toFixed(2);\n }\n return stats;\n }\n get blanks() {\n return this.statistics.answer_sets.map(as => as.text);\n }\n get questionText() {\n let text = this.statistics.question_text;\n this.statistics.answer_sets.forEach(as => {\n text = text.replace(`[${as.text}]`, `${as.text}`);\n });\n return text;\n }\n changePlaceholder(placeholderId) {\n this.placeholderId = placeholderId;\n this.setAnswers();\n }\n showUsers(users) {\n let usersHtml = `
\n `;\n for (let user of users.sort()) {\n usersHtml += `
\n
${user}
\n
`;\n }\n usersHtml += '
';\n (0, _dialog.default)('', ['OK'], usersHtml, 0);\n }\n setAnswers() {\n let answerSet = this.statistics.answer_sets.find(as => as.text == this.placeholderId);\n if (answerSet) {\n this.answers = answerSet.answers;\n } else {\n this.answers = [];\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"placeholderId\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"answers\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"changePlaceholder\", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, \"changePlaceholder\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"showUsers\", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, \"showUsers\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz-stats/question-statistics-single\", [\"exports\", \"bocce/utilities/dialog\", \"bocce/mixins/boot\"], function (_exports, _dialog, _boot) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n const QuestionStatisticsSingle2 = Ember.Component.extend(_boot.default, {});\n\n //Used for multiple choice, multiple answer, fill in the blank, and true/false.\n\n //this.statistics gets passed in to the component. Origin is canvas quiz stats api.\n let QuestionStatisticsSingle = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._action, (_class = class QuestionStatisticsSingle extends QuestionStatisticsSingle2 {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"answersTracked\", _descriptor, this);\n }\n didInsertElement() {\n this.boot_area($(this.element).find(`.bootable-area`), true, true, true, false, true);\n }\n\n //Triggers the stats getter to recalculate and thus the UI to update with new stats.\n //So hacky, but it was the only way I could get this to re-render when the 'answers' arg changed\n //as in the case of fill-in-multiple-blanks or multiple-dropdowns\n willRender() {\n this.answersTracked = this.answers;\n }\n get stats() {\n let stats = {};\n let scores = this.quizzes.quiz.scores;\n stats.answerStats = {};\n for (const answer of this.answersTracked) {\n if (answer.correct) {\n //First, we run the calculation, then we convert it to a float, then we ensure we only have two decimal places, finally, \n //we strip off trailing zeros with the '+' at the front of this calculation. For example, the '+' makes sure that '100.00' gets stored \n //in the string as '100', '100.10' as '100.1', etc.\n stats.percentageCorrect = +parseFloat(answer.user_ids.length / this.statistics.responses * 100).toFixed(2);\n }\n if (!stats.answerStats[answer.id]) {\n stats.answerStats[answer.id] = {};\n }\n stats.answerStats[answer.id].correct = answer.correct;\n stats.answerStats[answer.id].text = answer.text;\n stats.answerStats[answer.id].responsePercentage = `${+parseFloat(answer.user_ids.length / this.statistics.responses * 100).toFixed(2)}%`;\n stats.answerStats[answer.id].users = answer.user_ids.map(ui => {\n return `${scores[ui].name}`;\n });\n }\n return stats;\n }\n showUsers(users) {\n let usersHtml = `
\n `;\n for (let user of users.sort()) {\n usersHtml += `
\n
${user}
\n
`;\n }\n usersHtml += '
';\n (0, _dialog.default)('', ['OK'], usersHtml, 0);\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"answersTracked\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"showUsers\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"showUsers\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz-stats/quiz-stats-actions\", [\"exports\", \"bocce/utilities/dialog\", \"bocce/js/utils\"], function (_exports, _dialog, _utils) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _class, _descriptor, _descriptor2, _descriptor3;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let QuizStatsActions = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._action, _dec5 = Ember._action, _dec6 = Ember._action, _dec7 = Ember._action, (_class = class QuizStatsActions extends Ember.Component {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"answersTracked\", _descriptor, this);\n _initializerDefineProperty(this, \"working\", _descriptor2, this);\n _initializerDefineProperty(this, \"action\", _descriptor3, this);\n }\n //So hacky, but it was the only way I could get this to re-render when the 'answers' arg changed\n //as in the case of fill-in-multiple-blanks or multiple-dropdowns\n willRender() {\n this.answersTracked = this.answers;\n }\n get hasAttachments() {\n return this.has('file_upload_question');\n }\n get hasEssays() {\n return this.has('essay_question');\n }\n has(type) {\n let retval = false;\n for (const questionStats of this.quizzes.quiz.quiz_statistics.question_statistics.toArray()) {\n if (questionStats.get('question_type') == type && questionStats.get('responses') > 0) {\n retval = true;\n break;\n }\n }\n return retval;\n }\n setAction(event) {\n this.action = event.target.value;\n }\n performAction() {\n if (this.action == 'download-attachments') {\n this.downloadAttachments();\n } else if (this.action == 'download-essays') {\n this.downloadEssays();\n } else if (this.action == 'show-quiz') {\n this.toggleShowStats();\n }\n }\n async downloadAttachments() {\n let numAttempts = 0;\n const getAttachments = async () => {\n this.working = true;\n try {\n let path = this.quizzes.quiz.quiz_statistics.quiz_submissions_zip_url;\n let response = await fetch(path, {\n cache: \"no-cache\"\n });\n if (!response.ok) {\n throw new Error();\n\n //assuming that s3 is being used to store the attachments\n } else if (!response.url.indexOf('s3.amazonaws')) {\n numAttempts++;\n if (numAttempts > 30) {\n this.working = false;\n (0, _dialog.default)('No attachments found');\n return;\n }\n setTimeout(getAttachments, 1000);\n return;\n }\n let blob = await response.blob();\n (0, _utils.saveFile)('attachments.zip', 'application/zip', blob);\n this.working = false;\n } catch (e) {\n this.working = false;\n (0, _dialog.default)('No attachments found.');\n }\n };\n getAttachments();\n }\n async downloadEssays() {\n try {\n this.working = true;\n let path = `/interface/sections/${session.get('section.id')}/quizzes/${this.quizzes.quiz.id}/get_essay_responses/-1`;\n let response = await fetch(path);\n if (!response.ok) {\n throw new Error();\n }\n let blob = await response.blob();\n (0, _utils.saveFile)(`${this.quizzes.quiz.title.substring(0, 20)}-essay-responses.zip`, 'application/zip', blob);\n this.working = false;\n } catch (e) {\n this.working = false;\n (0, _dialog.default)('No essays found.');\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"answersTracked\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"working\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"action\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return 'show-quiz';\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"setAction\", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, \"setAction\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"performAction\", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, \"performAction\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"downloadAttachments\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"downloadAttachments\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"downloadEssays\", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, \"downloadEssays\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz-stats/quiz-stats\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _class;\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n let QuizStatsActions = _exports.default = (_dec = Ember._action, (_class = class QuizStatsActions extends Ember.Component {\n goToQuestion(event) {\n if (+event.target.value != null) {\n //JS: I don't like hard-coding selectors, but I couldn't think of any other way to do this.\n document.querySelectorAll('.question-stats-index')[+event.target.value].scrollIntoView();\n }\n }\n }, (_applyDecoratedDescriptor(_class.prototype, \"goToQuestion\", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, \"goToQuestion\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz-stats/types/essay-question-statistics\", [\"exports\", \"bocce/utilities/dialog\", \"bocce/js/utils\"], function (_exports, _dialog, _utils) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let QuestionStatisticsSingle = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._action, (_class = class QuestionStatisticsSingle extends Ember.Component {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"downloadingEssays\", _descriptor, this);\n }\n get hasEssays() {\n return this.statistics.responses > 0;\n }\n async getEssayResponses() {\n try {\n this.downloadingEssays = true;\n let path = `/interface/sections/${session.section.id}/quizzes/${this.quizzes.quiz.id}/get_essay_responses/${this.statistics.id}`;\n let response = await fetch(path, {\n cache: \"no-cache\"\n });\n if (!response.ok) {\n throw new Error();\n }\n let blob = await response.blob();\n (0, _utils.saveFile)('essay.zip', 'application/zip', blob);\n this.downloadingEssays = false;\n } catch (e) {\n this.downloadingEssays = false;\n (0, _dialog.default)('No essays found.');\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"downloadingEssays\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"getEssayResponses\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"getEssayResponses\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz-stats/types/short-answer-question-statistics\", [\"exports\", \"bocce/utilities/dialog\", \"bocce/js/utils\"], function (_exports, _dialog, _utils) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _class;\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n let ShortAnswerQuestionStatistics = _exports.default = (_dec = Ember._action, (_class = class ShortAnswerQuestionStatistics extends Ember.Component {\n async showUsers(users) {\n let usersHtml = `
\n `;\n for (let user of users.sort()) {\n usersHtml += `
\n
${user}
\n
`;\n }\n usersHtml += '
';\n (0, _dialog.default)('', ['OK'], usersHtml, 0);\n }\n }, (_applyDecoratedDescriptor(_class.prototype, \"showUsers\", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, \"showUsers\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let Quiz = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._action, (_class = class Quiz extends Ember.Component {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"timedQuizStarted\", _descriptor, this);\n }\n startQuiz() {\n this.timedQuizStarted = true;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"timedQuizStarted\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"startQuiz\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"startQuiz\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz/previous-questions\", [\"exports\", \"lodash.isequal\"], function (_exports, _lodash) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _class;\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n let PreviousQuestions = _exports.default = (_dec = Ember._action, (_class = class PreviousQuestions extends Ember.Component {\n didInsertElement() {\n var maxWidth = 0;\n $('.previous-questions .previous-question-index').each(function (index, element) {\n var curWidth = $(element).width();\n if (maxWidth < curWidth) {\n maxWidth = curWidth;\n }\n });\n\n // Set all buttons to the max width\n $('.previous-questions .previous-question-index').width(maxWidth);\n }\n get dropdownDisabled() {\n return this.quizzes.gradingQuiz || this.quizzes.uploadingQuestion;\n }\n get questionData() {\n return this.quizzes.questions.map((question, index) => {\n let text = `Question ${index + 1}`;\n if (this.quizzes.hasScore) {\n /**\n * Possible values for question.correct: \n * undefined: manually graded question without a score\n * defined: manually graded question with a score\n * partial: partially right answer, ex: multiple fill in the blanks \n * where one of the blanks is right and the other is wrong\n * correct/incorrect: fully right or wrong answer, ex: multiple choice, multiple dropdowns\n */\n\n if (question.correct === 'undefined') {\n text += ` - pending`;\n } else if (question.correct === 'defined' || question.correct === 'partial') {\n text += ` - ${question.points_received} / ${question.points}`;\n } else {\n text += ` - ${question.correct}`;\n }\n } else if ((0, _lodash.default)(question.given_answer, question.default_answer) && index <= this.quizzes.maxQuestionIndexUsed && index != this.quizzes.questionIndex) {\n text += ` - unanswered`;\n }\n let disabled = this.quizzes.hasScore ? false : index > this.quizzes.maxQuestionIndexUsed;\n return {\n question,\n disabled,\n text\n };\n });\n }\n setQuestionIndex(event) {\n const index = +event.target.value;\n this.quizzes.setQuestionIndex(index);\n }\n }, (_applyDecoratedDescriptor(_class.prototype, \"setQuestionIndex\", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, \"setQuestionIndex\"), _class.prototype)), _class));\n});","define(\"bocce/components/quiz/quiz-overview\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n class QuizOverview extends Ember.Component {\n //@service quizzes;\n\n init(...args) {\n super.init(...args);\n }\n }\n _exports.default = QuizOverview;\n});","define(\"bocce/components/recipient-filter\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n filter: null,\n filteredList: null,\n recipientId: null,\n currentListPosition: null,\n // The reason there is a keyUp function AND a keyPress function is because\n // keyUp recognized the arrow keys but not the enter key. And keyPress recognizes\n // the enter key and not the arrow keys. At least that's how it was on my macbook\n // air work computer - JRW.\n keyUp: function (keyEvent) {\n if (keyEvent.key === 'ArrowUp' || keyEvent.key === 'ArrowDown') {\n /* eslint-disable-next-line ember/no-jquery */\n var listLength = Ember.$('.recipient-filter-list li').length;\n var currentListPosition = this.currentListPosition;\n if (keyEvent.key === 'ArrowUp') {\n if (currentListPosition === 0 || currentListPosition === null) {\n this.set('currentListPosition', listLength - 1);\n } else {\n this.set('currentListPosition', this.currentListPosition - 1);\n }\n } else {\n // it's the ArrowDown\n if (currentListPosition === listLength - 1) {\n // we're at the end, go back to the beginning\n this.set('currentListPosition', 0);\n } else {\n if (currentListPosition >= 0 && currentListPosition !== null) {\n this.set('currentListPosition', this.currentListPosition + 1);\n } else {\n this.set('currentListPosition', 0);\n }\n }\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list li').eq(this.currentListPosition).addClass('active');\n } else {\n // A new word has been autocompleted, set the list position\n // to zero.\n this.set('currentListPosition', null);\n }\n },\n keyPress: function (keyEvent) {\n if (keyEvent.which === 13) {\n // 13 is the enter key\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list li').eq(this.currentListPosition).click();\n }\n },\n actions: {\n autoComplete: function () {\n if (!this.filter) {\n this.set('filteredList', []);\n this.set('recipientId', null);\n }\n this.autoComplete(this.filter);\n },\n removeRecipient: function (recipient_id) {\n var recipients = this.recipients;\n recipients.removeObject(recipients.findBy('id', recipient_id));\n },\n focusRI: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-input').focus();\n },\n choose: function (recipient) {\n if (recipient.modelName === 'conversation') {\n this.viewConversation(recipient.id);\n return;\n }\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('recipients.isFulfilled')) {\n this.recipients.pushObject(recipient);\n } else {\n this.choose(recipient);\n }\n this.set('filteredList', []);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-input').val('').focus();\n this._super();\n }\n }\n });\n});","define(\"bocce/components/rte-input\", [\"exports\", \"sanitize-html\", \"bocce/utilities/dialog\"], function (_exports, _sanitizeHtml, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-observers */\n\n function sanitize(string) {\n string = cleanup(string);\n const sanitizeHtmlOptions = {\n // options for the sanitize-html package. see docs:\n // https://www.npmjs.com/package/sanitize-html#htmlparser2-options\n allowedTags: _sanitizeHtml.default.defaults.allowedTags.concat(['h2', 'u']),\n allowedAttributes: {\n ..._sanitizeHtml.default.defaults.allowedAttributes,\n span: ['data-cursor-marker'] //allows us to place the cursor after pasted content\n },\n transformTags: {\n 'h1': 'h2',\n // Word's header is h1, but our header is h2\n 'a': function (tagName, attribs) {\n // filter out useless links because apparently the geniuses\n // at Microsoft decided to start including THOSE now\n if (!attribs['href'] || attribs['href'] === '') {\n return false;\n } else {\n return {\n tagName: tagName,\n attribs: {\n href: attribs.href\n }\n };\n }\n }\n }\n };\n return (0, _sanitizeHtml.default)(string, sanitizeHtmlOptions);\n }\n function cleanup(string) {\n let cleaned = string;\n\n // convert curly quotes to straight quotes:\n cleaned = cleaned.replace(/[\\u2018\\u2019]/g, '\\'');\n cleaned = cleaned.replace(/[\\u201C\\u201D]/g, '\"');\n\n //get rid of certain strings\n let noNoWords = [];\n //Empty lines (doesn't affect display but HTML looks nicer)\n noNoWords.push(/^[\\r\\n]/gm);\n // empty comments\n noNoWords.push(//g);\n //get rid of all noNoWords\n for (let pattern of noNoWords) {\n cleaned = cleaned.replace(pattern, '');\n }\n return cleaned;\n }\n function archiverSaveAnimation() {\n /* eslint-disable-next-line ember/no-jquery */\n const archiver = Ember.$('#archiver');\n archiver.addClass('saving');\n archiver.one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function () {\n archiver.removeClass('saving');\n });\n }\n\n // NK: I'm putting this variable here because it should be treated as\n // a private property, inaccessible from outside of the component.\n let changedFromDOM = false;\n var _default = _exports.default = Ember.Component.extend({\n /*\n * initialization\n */\n\n classNames: ['rte-input'],\n editorExpanded: false,\n didInsertElement() {\n this.bindRepBox();\n },\n bindRepBox() {\n /* eslint-disable-next-line ember/no-jquery */\n let repBox = Ember.$(this.element).find('.rte-editor-input');\n let userInput = this.userInput || '';\n repBox.html(userInput);\n this.set('repBox', repBox);\n },\n /*\n * userInput/repBox value binding functions\n */\n\n // Two-Way Binding Info (NK)\n //\n // This component relies on a tricky pattern, and the current implementation\n // works but is fragile. Two-way bindings are no longer a feature of Ember\n // Octane, as Ember is increasingly using vanilla Javascript objects.\n // We need to be able to update the userInput value both from inside the\n // component (when the user types something or pastes something in), and from\n // the parent component (e.g. loading in a value if the user is updating a\n // post, or running the sanitizing function on pasted text.)\n //\n // The changedFromDOM boolean is how we distinguish what triggered the change.\n // When userInput is updated from the parent component, we want to update the\n // RTE so the user sees the current content. When it's updated from the DOM\n // (i.e. when the user is typing), we should leave the RTE alone.\n //\n // Since observers don't have timing guarantees, we can't use the old pattern\n // where updateUserInput() would set it, update userInput, and then unset it.\n // Instead, we have updateUserInput() set changedFromDOM, and leave it true.\n // The next time updateRepbox() runs, it will skip updating userInput, and\n // unset changedFromDOM.\n //\n // TODO (NK): Figure out a way to do this _without_ using an observer.\n // Observers are deprecated, and are generally a mess to debug.\n\n // Watches for changes to userInput and, unless changedFromDOM is\n // set, updates the editor to match\n /* eslint-disable-next-line ember/no-observers */\n updateRepbox: Ember.observer('userInput', function () {\n if (changedFromDOM) {\n changedFromDOM = false;\n } else {\n const newValue = this.userInput;\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$(this.element).find('.rte-editor-input').html(newValue);\n }\n }),\n cleanUpRepbox: Ember.observer('convoSent', function () {\n // Empty the repbox after the message is sent\n this.repBox.html('');\n }),\n // Update userInput without triggering the updateRepbox observer\n updateUserInput() {\n let repBoxHTML = this.repBox.get(0).innerHTML;\n /* eslint-disable-next-line ember/no-get */\n let userInput = this.get('userInput');\n if (repBoxHTML !== userInput) {\n changedFromDOM = true;\n this.set('userInput', repBoxHTML);\n }\n },\n // Clean repbox HTML after a change from the DOM\n sanitizeRepBox() {\n let repBox = this.repBox;\n let newHTML = repBox.html();\n let clean = sanitize(newHTML);\n this.set('userInput', clean);\n },\n /*\n * Misc config/utility\n */\n\n // used in the template to decide how to display buttons\n mobileUpload: Ember.computed(function () {\n /* eslint-disable-next-line ember/no-jquery */\n return Ember.$.isMobile;\n }),\n // what to display when input is empty\n placeholderVal: Ember.computed('placeholder', function () {\n if (!this.placeholderType || this.placeholderType == \"student-submission\") {\n return 'Please type something here to submit, even if you have already attached a file. The submit button will not work unless you have entered text into this box.';\n } else if (this.placeholderType == \"generic\") {\n return \"Say something (or paste a cool URL)...\";\n } else {\n return \"\";\n }\n }),\n // check cursor location and update formatting buttons to match\n // current styles\n updateButtonsStatus() {\n var sel = document.getSelection(),\n /* eslint-disable-next-line ember/no-jquery */\n boldBtn = Ember.$('.fa-bold'),\n /* eslint-disable-next-line ember/no-jquery */\n italicBtn = Ember.$('.fa-italic'),\n /* eslint-disable-next-line ember/no-jquery */\n underlineBtn = Ember.$('.fa-underline'),\n /* eslint-disable-next-line ember/no-jquery */\n ulBtn = Ember.$('.fa-list-ul'),\n /* eslint-disable-next-line ember/no-jquery */\n olBtn = Ember.$('.fa-list-ol'),\n /* eslint-disable-next-line ember/no-jquery */\n hBtn = Ember.$('.fa-heading'),\n /* eslint-disable-next-line ember/no-jquery */\n preBtn = Ember.$('.preformatted-button');\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$(sel.anchorNode).closest('b, strong').length > 0) {\n boldBtn.addClass('active');\n } else {\n boldBtn.removeClass('active');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$(sel.anchorNode).closest('i').length > 0) {\n italicBtn.addClass('active');\n } else {\n italicBtn.removeClass('active');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$(sel.anchorNode).closest('u').length > 0) {\n underlineBtn.addClass('active');\n } else {\n underlineBtn.removeClass('active');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$(sel.anchorNode).closest('ul').length > 0) {\n ulBtn.addClass('active');\n } else {\n ulBtn.removeClass('active');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$(sel.anchorNode).closest('ol').length > 0) {\n olBtn.addClass('active');\n } else {\n olBtn.removeClass('active');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$(sel.anchorNode).closest('h2').length > 0) {\n hBtn.addClass('active');\n } else {\n hBtn.removeClass('active');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$(sel.anchorNode).closest('pre').length > 0) {\n preBtn.addClass('active');\n } else {\n preBtn.removeClass('active');\n }\n },\n sendUpdateArchive() {\n if (this.updateArchive) {\n this.updateArchive();\n }\n },\n moveCursorToEndOfInsertedContent() {\n // Locate the marker\n let marker = document.querySelector('span[data-cursor-marker]');\n if (marker) {\n const range = document.createRange();\n const selection = window.getSelection();\n\n //Set the range to the position right after the marker\n range.setStartAfter(marker);\n //Collapse the range to its start, effectively moving the cursor to this position\n range.collapse(true);\n\n //Apply the range to the selection\n selection.removeAllRanges();\n selection.addRange(range);\n\n //Remove the marker from the document\n marker.parentNode.removeChild(marker);\n }\n },\n actions: {\n setEditorExpanded(isExpanded) {\n this.set('editorExpanded', isExpanded);\n },\n // clean up text on blur\n blurHandler() {\n this.sanitizeRepBox();\n },\n detectShiftEnter() {\n if (event.key === 'Enter' && event.shiftKey) {\n event.preventDefault(); // Prevents the default action of the enter key (new line)\n this.send('sendMessageAction');\n } else {\n return true;\n }\n },\n // clean up text and position cursor on paste\n pasteHandler() {\n // NK: I don't love using setTimeout here, but this is the best\n // way I've found so far to make sure that this bit of code runs\n // after the normal paste action finishes.\n setTimeout(() => {\n //First, place a marker after the inserted content.\n document.execCommand('insertHTML', false, '');\n\n //Then, clean up the content.\n this.sanitizeRepBox();\n setTimeout(() => {\n //Finally, after the content has been cleaned up, move the cursor to right after the \n //marker.\n this.moveCursorToEndOfInsertedContent();\n });\n });\n },\n mouseOrKeyUpHandler() {\n this.updateUserInput();\n this.updateButtonsStatus();\n },\n formatBlock(tag) {\n if (event.target.classList.contains('active')) {\n document.execCommand('formatBlock', false, '
');\n } else {\n document.execCommand('formatBlock', false, `<${tag}>`);\n }\n },\n formatInline(tag) {\n // target the 'active'class list now instead of waiting for\n // the user to type or move the cursor (triggering the keyUp\n // event). TODO: Can the 'active' tag come from a computed\n // property/observer instead of doing it this way?\n event.target.classList.toggle('active');\n document.execCommand(tag, false, null);\n },\n insertText(text) {\n document.execCommand('insertText', false, text);\n },\n saveArchive() {\n const bodyInput = this.userInput;\n archiverSaveAnimation();\n let namedDialog;\n if (localStorage.localDocRunner) {\n // if localDocRunner exists, then we already have a record name\n namedDialog = Promise.resolve({\n recName: localStorage.localDocRunner,\n name: localStorage.localDocRunnerName\n });\n } else {\n // ... otherwise, we need to get it from the user.\n namedDialog = (0, _dialog.default)('Please enter a short title for your draft.', ['Save and Continue', 'Cancel'], `
\n
Warning!
\n
\n \n Drafts can only save your text.\n \n \n All attachments (files, audio, video) will be lost. \n \n \n Note: Your draft will only be saved on this device. To avoid losing access, do not clear your browser cache.\n \n
\n
`, true, ['rte-draft']).then(name => {\n // dialog returns false if the user clicks \"cancel\"\n if (name === false) {\n return false;\n }\n\n // generate recName and assign it to the localStorage object\n const recName = name + '_' + new Date().getTime();\n localStorage.localDocRunner = recName;\n localStorage.localDocRunnerName = name;\n\n // return both name and the generated recName\n return {\n name,\n recName\n };\n });\n }\n namedDialog.then(result => {\n // result === false when user clicks Cancel\n if (!result) {\n return;\n }\n const {\n name,\n recName\n } = result;\n const myText = {\n isText: true,\n textContent: bodyInput,\n type: 'text',\n name,\n recName\n };\n let cas;\n if (localStorage.localDocs) {\n cas = JSON.parse(localStorage.localDocs);\n } else {\n cas = {};\n }\n cas[recName] = myText;\n localStorage.localDocs = JSON.stringify(cas);\n this.sendUpdateArchive();\n });\n },\n // NK: The rte-input component and the editable mixin are more\n // tightly coupled than I'd like when it comes to the drafts saving\n // feature, but I don't have time at the moment to untangle them.\n viewArchive() {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.pop-attachment-drawer').removeClass('hidden');\n this.sendUpdateArchive();\n // TODO (NK): We really don't need the runloop to handle this, but I want\n // to minimize the changes I'm making right now. the .video-archive\n // element class list has conditionals in the property list which get\n // updated after calling sendUpdateArchive(). This causes the entire class\n // list to be reset, and we lose any classes we added manually. We should\n // rewrite this so we declaratively apply the class as needed in the\n // template, rather than imperatively changing it via actions.\n Ember.run.schedule('afterRender', function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.video-archive').toggleClass('visible');\n });\n }\n }\n });\n});","define(\"bocce/components/rubric/assessment\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n const RubricAssessment2 = Ember.Component.extend({\n assessmentObserver: Ember.observer('submission.rubric_assessments.@each.{points,comments}', function () {\n this.setData();\n })\n });\n let RubricAssessment = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember.inject.service('rubric'), _dec4 = Ember.inject.service, _dec5 = Ember.inject.service, _dec6 = Ember._action, (_class = class RubricAssessment extends RubricAssessment2 {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"detailsShown\", _descriptor, this);\n _initializerDefineProperty(this, \"data\", _descriptor2, this);\n _initializerDefineProperty(this, \"rubricService\", _descriptor3, this);\n _initializerDefineProperty(this, \"store\", _descriptor4, this);\n _initializerDefineProperty(this, \"session\", _descriptor5, this);\n }\n toggleDetailsShown() {\n this.detailsShown = !this.detailsShown;\n }\n init() {\n super.init(...arguments);\n this.setData();\n }\n setData() {\n let data = [];\n this.get('criteria').forEach(criterion => {\n let assessment = this.store.peekRecord('assessment', `${this.get('submission_id')}_${this.get('submission.attempt')}_${criterion.get('id')}`);\n data.push({\n assessment,\n criterion\n });\n });\n this.data = data;\n }\n get hasAssessments() {\n let data = this.data;\n if (data) {\n return this.data.some(d => d.assessment?.points);\n } else {\n return false;\n }\n }\n get lateDeduction() {\n let assessment = this.store.peekRecord('assessment', `${this.get('submission_id')}_${this.get('submission.attempt')}_-1`);\n return assessment?.get('points');\n }\n get lateDeductionComment() {\n let assessment = this.store.peekRecord('assessment', `${this.get('submission_id')}_${this.get('submission.attempt')}_-1`);\n return assessment?.get('comments');\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"detailsShown\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return true;\n }\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"data\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"rubricService\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"store\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"session\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"toggleDetailsShown\", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleDetailsShown\"), _class.prototype)), _class));\n});","define(\"bocce/components/rubric/grading\", [\"exports\", \"bocce/utilities/dialog\", \"lodash.isequal\"], function (_exports, _dialog, _lodash) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7, _descriptor8;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n const RubricGrading2 = Ember.Component.extend({\n assessmentObserver: Ember.observer('submission.rubric_assessments.@each.{points}', 'lateDeductionAssessment.{points}', function () {\n const assessedPoints = this.assessedPoints;\n if (!assessedPoints) {\n this.set('grade', 0);\n return;\n }\n\n //Sanitizing\n this.data.forEach(d => {\n //Don't allow more points than the maximum.\n if (d.assessment.points > d.criterion.points) {\n d.assessment.set('points', d.criterion.points);\n } else if (d.assessment.points < 0) {\n //Don't allow negative points\n d.assessment.set('points', 0);\n }\n });\n\n //Late deduction points can't be more than the assessed points\n if (this.lateDeductionAssessment.get('points') > assessedPoints) {\n this.lateDeductionAssessment.set('points', assessedPoints);\n } else if (this.lateDeductionAssessment.get('points') < 0) {\n this.lateDeductionAssessment.set('points', 0);\n }\n this.set('grade', this.assessedPoints - (this.lateDeductionAssessment.get('points') || 0));\n }),\n //Keep track of whether the content is dirty.\n contentDirtyObserver: Ember.observer('submission.rubric_assessments.@each.{points,comments}', 'lateDeductionAssessment.{points}', function () {\n const assessment = {\n assessment: this.data.map(d => ({\n id: d.assessment.id,\n points: +d.assessment.points,\n comments: d.assessment.comments\n })),\n lateDeduction: +this.lateDeductionAssessment.get('points'),\n lateDeductionComment: this.lateDeductionAssessment.get('comments')\n };\n this.set('contentIsDirty', !(0, _lodash.default)(this.get('defaultAssessment'), assessment));\n })\n });\n let RubricGrading = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember.inject.service('rubric'), _dec5 = Ember._tracked, _dec6 = Ember.inject.service, _dec7 = Ember.inject.service('userprofile'), _dec8 = Ember._tracked, _dec9 = Ember._action, _dec10 = Ember._action, _dec11 = Ember._action, _dec12 = Ember._action, (_class = class RubricGrading extends RubricGrading2 {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"detailsShown\", _descriptor, this);\n _initializerDefineProperty(this, \"grade\", _descriptor2, this);\n _initializerDefineProperty(this, \"lateDeductionAssessment\", _descriptor3, this);\n _initializerDefineProperty(this, \"rubricService\", _descriptor4, this);\n _initializerDefineProperty(this, \"working\", _descriptor5, this);\n _initializerDefineProperty(this, \"store\", _descriptor6, this);\n _initializerDefineProperty(this, \"userprofileService\", _descriptor7, this);\n _initializerDefineProperty(this, \"initialized\", _descriptor8, this);\n //Seemed like the simplest way to not call resetAssessment when exiting the grader upon a grade being given.\n _defineProperty(this, \"ignoreResetAssessmentCall\", false);\n }\n init() {\n super.init(...arguments);\n //Sets submission controller callbacks. First one is used to determine if the content is dirty. Second\n //one is used to reset the rubric assessment when the editor is destroyed.\n this.setRubricAssessmentIsDirtyCallback(() => {\n return this.rubricService.isGrading && this.get('contentIsDirty');\n });\n let assessments = [];\n this.get('criteria').forEach(criterion => {\n const assessmentId = `${this.get('submission.db_id')}_${this.get('submission.attempt')}_${criterion.id}`;\n let assessment = this.store.peekRecord('assessment', assessmentId);\n if (!assessment) {\n assessment = this.store.createRecord('assessment', {\n id: assessmentId,\n points: '',\n comments: ''\n });\n }\n assessments.push(assessment);\n });\n this.get('submission').set('rubric_assessments', assessments);\n let deductionAssessmentId = `${this.get('submission.db_id')}_${this.get('submission.attempt')}_-1`;\n //The late deduction criteria's id ends in '-1'\n let assessment = this.store.peekRecord('assessment', deductionAssessmentId);\n\n //Do we have a late deduction?\n if (assessment) {\n this.lateDeductionAssessment = assessment;\n } else {\n this.lateDeductionAssessment = this.store.createRecord('assessment', {\n id: deductionAssessmentId,\n points: 0,\n comments: ''\n });\n }\n\n //Used when exiting the grader and also for determining when content is dirty.\n this.set('defaultAssessment', {\n assessment: this.data.map(d => ({\n id: d.assessment.id,\n points: +d.assessment.points,\n comments: d.assessment.comments\n })),\n lateDeduction: +this.lateDeductionAssessment.get('points'),\n lateDeductionComment: this.lateDeductionAssessment.get('comments')\n });\n }\n didInsertElement() {\n //Necessary to trigger the observers\n this.set('submission.rubric_assessments', this.get('submission.rubric_assessments') || []);\n $(this.element).find('.grading-rubric-grading-rubric-points, .grading-rubric-grading-deduction-amount').on('keypress', function (e) {\n if (e.which == 13) {\n //Not doing this via an @enter on the inputs because that is too slow.\n e.target.blur();\n }\n });\n }\n willDestroyElement() {\n //This method is called both when exiting the submission modal and also when assigning a grade. We don't want to reset the assessment\n //when assigning a grade. So, this is why this logic is in place.\n if (!this.ignoreResetAssessmentCall) {\n this.resetAssessment();\n }\n }\n get data() {\n let ret = [];\n this.get('criteria').forEach(criterion => {\n let assessment = this.store.peekRecord('assessment', `${this.get('submission.db_id')}_${this.get('submission.attempt')}_${criterion.get('id')}`);\n ret.push({\n assessment,\n criterion\n });\n });\n return ret;\n }\n get assessedPoints() {\n let total = 0;\n\n /**\n * return null if nothing has any points. i.e.: \n * this.data.some(d => !!+d.assessment.points) returns true if anything has points (+ converts to int, !! converts to boolean)\n * So, !this.data.some(d => !!+d.assessment.points) means that nothing has any points.\n */\n if (!this.data.some(d => !!+d.assessment.points)) {\n return null;\n }\n this.data.forEach(d => {\n if (+d.assessment.points) {\n total += +d.assessment.points;\n }\n });\n return total;\n }\n get percentage() {\n return Math.round(100 * this.get('grade') / this.get('submission.assignment.points_possible'));\n }\n resetAssessment() {\n const defaultAssessment = this.get('defaultAssessment');\n if (this.get('submission.rubric_assessments')) {\n this.get('submission.rubric_assessments').forEach(a => {\n const assessment = defaultAssessment.assessment.find(p => p.id == a.get('id'));\n if (assessment) {\n Ember.set(a, 'points', assessment.points);\n Ember.set(a, 'comments', assessment.comments);\n }\n });\n }\n this.set('lateDeductionAssessment.points', defaultAssessment.lateDeduction);\n this.set('lateDeductionAssessment.comments', defaultAssessment.lateDeductionComment);\n }\n onNoCredit() {\n this.data.forEach(d => {\n Ember.set(d.assessment, 'points', 0);\n });\n this.lateDeductionAssessment.set('points', 0);\n this.element.scrollIntoView(false);\n }\n submitGrade() {\n this.working = true;\n let hasAllCriteria = true;\n this.data.forEach(d => {\n if (d.assessment.points === '') {\n hasAllCriteria = false;\n return;\n }\n });\n if (!hasAllCriteria) {\n (0, _dialog.default)('Please make sure all criteria are given point values before submitting.').then(() => {\n $(this.element).find('.grading-rubric-grading-rubric-points').each(function () {\n if (!$(this).val()) {\n $(this).toggleClass(\"incomplete\");\n }\n });\n $(this.element).find('.grading-rubric-grading-rubric-points').on('input', function () {\n if (!$(this).val()) {\n $(this).addClass(\"incomplete\");\n } else {\n $(this).removeClass(\"incomplete\");\n }\n });\n });\n this.working = false;\n return;\n }\n if (this.lateDeductionAssessment.get('points') === '') {\n this.lateDeduction = 0;\n }\n\n //If the late deduction amount was changed.\n if (this.get('defaultAssessment.lateDeduction') != this.lateDeductionAssessment.get('points') || this.get('defaultAssessment.lateDeductionComment') != this.lateDeductionAssessment.get('comments')) {\n const id = `${this.submission_id}_${this.attempt}_-1`;\n const r = this.store.peekRecord('assessment', id);\n\n //We don't store a late deduction with every assessment. If there is no late deduction\n //assessment, create one. If we do have one, update the points.\n if (!r) {\n const lateDeductionAssessment = this.store.createRecord('assessment', {\n id,\n points: this.lateDeductionAssessment.get('points'),\n comments: this.lateDeductionAssessment.get('comments')\n });\n this.get('submission.rubric_assessments').pushObject(lateDeductionAssessment);\n } else {\n r.set('points', this.lateDeductionAssessment.get('points'));\n }\n }\n this.ignoreResetAssessmentCall = true;\n this.onSubmitGrade(this.get('grade'), () => {\n //On complete\n Ember.run.scheduleOnce('afterRender', () => {\n this.rubricService.isGrading = false;\n this.working = false;\n });\n });\n }\n showGuide() {\n const pointsPossible = this.get('submission.assignment.points_possible');\n const range = Math.floor(pointsPossible * .10);\n const ratings = [{\n name: 'Excellent'\n }, {\n name: 'Good'\n }, {\n name: 'Fair'\n }, {\n name: 'Needs Improvement'\n }, {\n name: 'Fail'\n }];\n for (let i = 0; i < ratings.length; i++) {\n ratings[i].range = `(${pointsPossible - range * (i + 1) - i}-${pointsPossible - range * i - i})`;\n }\n let html = `
${ratings.map(r => `
${r.name}
${r.range}
`).join('')}
`;\n (0, _dialog.default)(html);\n }\n async exitGrader() {\n let result = 'Exit Grader';\n if (this.get('contentIsDirty')) {\n result = await (0, _dialog.default)('', ['Go Back', 'Exit Grader'], `\n
\n
Are you sure you want to exit the grader?
\n
Your changes will not be saved.
\n
\n `, 0, ['grading-rubric-grading-exit']);\n }\n if (result == 'Exit Grader') {\n this.rubricService.isGrading = false;\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"detailsShown\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return true;\n }\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"grade\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return 0;\n }\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"lateDeductionAssessment\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"rubricService\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"working\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"store\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, \"userprofileService\", [_dec7], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor8 = _applyDecoratedDescriptor(_class.prototype, \"initialized\", [_dec8], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"onNoCredit\", [_dec9], Object.getOwnPropertyDescriptor(_class.prototype, \"onNoCredit\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"submitGrade\", [_dec10], Object.getOwnPropertyDescriptor(_class.prototype, \"submitGrade\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"showGuide\", [_dec11], Object.getOwnPropertyDescriptor(_class.prototype, \"showGuide\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"exitGrader\", [_dec12], Object.getOwnPropertyDescriptor(_class.prototype, \"exitGrader\"), _class.prototype)), _class));\n});","define(\"bocce/components/rubric/instructor-graded\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let RubricGraded = _exports.default = (_dec = Ember.inject.service('rubric'), _dec2 = Ember._action, (_class = class RubricGraded extends Ember.Component {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"rubricService\", _descriptor, this);\n }\n editGrade() {\n this.rubricService.toggleIsGrading();\n Ember.run.scheduleOnce('afterRender', this, function () {\n let $el = Ember.$('.grading-rubric-grading');\n if ($el.length) {\n $el[0].scrollIntoView();\n }\n });\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"rubricService\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"editGrade\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"editGrade\"), _class.prototype)), _class));\n});","define(\"bocce/components/rubric/rubric\", [\"exports\", \"@glimmer/component\", \"showdown\"], function (_exports, _component, _showdown) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let RubricComponent = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._action, (_class = class RubricComponent extends _component.default {\n toggleDetailsShown() {\n this.detailsShown = !this.detailsShown;\n }\n constructor() {\n super(...arguments);\n _initializerDefineProperty(this, \"detailsShown\", _descriptor, this);\n if (this.args.criteria && this.args.criteria.get('length')) {\n const converter = new _showdown.default.Converter();\n\n //For some reason, ember likes to dynamically mess with the criteria\n //under some circumstances if this property is tracked or a getter property. I have no idea why. \n //There could be some better, deeper fix than to make it static, but I have no idea what that is.\n this.data = this.args.criteria.map(c => ({\n points: c.points,\n description: converter.makeHtml(c.description ?? ''),\n title: c.title\n }));\n }\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"detailsShown\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return true;\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"toggleDetailsShown\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleDetailsShown\"), _class.prototype)), _class));\n});","define(\"bocce/components/rubric/ungraded\", [\"exports\", \"@glimmer/component\"], function (_exports, _component) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _class, _descriptor;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n let RubricUngraded = _exports.default = (_dec = Ember.inject.service, _dec2 = Ember._action, _dec3 = Ember._action, (_class = class RubricUngraded extends _component.default {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"rubric\", _descriptor, this);\n }\n viewPreviousAttempt() {\n //I don't like this. But the alternative seemed to be a non-trivial solution that might not\n //have been worth the effort.\n Ember.$('.assignment-version label')[0].click();\n Ember.$('.assignment-version label')[0].scrollIntoView();\n }\n startGrading() {\n this.rubric.toggleIsGrading();\n Ember.run.scheduleOnce('afterRender', this, function () {\n Ember.$('.grading-rubric-grading')[0].scrollIntoView();\n });\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"rubric\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _applyDecoratedDescriptor(_class.prototype, \"viewPreviousAttempt\", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, \"viewPreviousAttempt\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"startGrading\", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, \"startGrading\"), _class.prototype)), _class));\n});","define(\"bocce/components/side-panel/activity\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n session: Ember.inject.service(),\n // Element info\n\n classNames: ['activity-component'],\n // Default empty arrays for when assignments or discussions is falsey\n // These arrays are also responsible for filtering out locked items.\n discussionsArray: Ember.computed('discussions.@each.locked', function () {\n let discussions = this.discussions || Ember.ArrayProxy.create();\n return discussions.filter(d => !d.get('locked'));\n }),\n assignmentsArray: Ember.computed('assignments', function () {\n return this.assignments || Ember.ArrayProxy.create();\n }),\n // Since the criteria for \"unread\" is different for teachers vs. students,\n // this computed property attempts to hide that logic.\n assignmentsNeedingAttention: Ember.computed('session.isInstructor', 'assignmentsArray.@each.{needsTeacherAttention,needsStudentAttention}', function () {\n let assignments = this.assignmentsArray;\n /* eslint-disable-next-line ember/no-get */\n let filter = this.get('session.isInstructor') ? 'needsTeacherAttention' : 'needsStudentAttention';\n return assignments.filterBy(filter);\n }),\n // Sets the type of model to filter on.\n // possible values:\n // * 'all'\n // * 'assignments'\n // * 'discussions'\n // * 'requiredDiscussions'\n // * 'announcements'\n filterType: 'all',\n // Sets whether to show unread items (and what constitutes 'unread')\n // Possible values:\n // * 'all'\n // * 'unread'\n // * 'ungraded'\n // * 'resubmission'\n // * 'comments'\n // * 'new'\n filterUnread: 'all',\n // This computed property makes the filter template easier to read.\n filterString: Ember.computed('filterType', 'filterUnread', function () {\n let type = this.filterType;\n let unread = this.filterUnread;\n // For development/testing builds, this is a safety check to try and catch\n // if we're accidentally setting either filter option to an unsupported value.\n (true && !(['all', 'assignments', 'conversations', 'requiredDiscussions', 'announcements'].includes(type)) && Ember.assert(`filterType must be a supported type: ${type}`, ['all', 'assignments', 'conversations', 'requiredDiscussions', 'announcements'].includes(type)));\n (true && !(['all', 'unread', 'ungraded', 'resubmission', 'comments', 'new', 'unsubmitted'].includes(unread)) && Ember.assert(`filterUnread must be a supported type: ${unread}`, ['all', 'unread', 'ungraded', 'resubmission', 'comments', 'new', 'unsubmitted'].includes(unread)));\n return `${this.filterType}_${this.filterUnread}`;\n }),\n // Functions for filtered categories\n\n all_all: Ember.computed('discussionsArray.[]', 'assignmentsArray.[]', function () {\n let discussions = this.discussionsArray.toArray();\n let assignments = this.assignmentsArray.toArray();\n return Ember.ArrayProxy.create({\n content: [...discussions, ...assignments]\n });\n }),\n all_unread: Ember.computed('discussionsArray.@each.unread', 'assignmentsNeedingAttention.[]', function () {\n let discussions = this.discussionsArray.filterBy('unread');\n let assignments = this.assignmentsNeedingAttention;\n return Ember.ArrayProxy.create({\n content: [...discussions, ...assignments]\n });\n }),\n announcements_all: Ember.computed.filterBy('discussionsArray', 'is_announcement'),\n announcements_unread: Ember.computed.filterBy('announcements_all', 'unread'),\n assignments_all: Ember.computed.alias('assignmentsArray'),\n assignments_unsubmitted: Ember.computed.filterBy('assignments_all', 'needsStudentAttention'),\n assignments_ungraded: Ember.computed('assignmentsArray.@each.numUngradedSubmissions', function () {\n let ungraded = this.assignmentsArray.filterBy('numUngradedSubmissions');\n return ungraded;\n }),\n assignments_comments: Ember.computed.filterBy('assignments_all', 'numUnreadSubmissions'),\n assignments_resubmission: Ember.computed.filterBy('assignments_all', 'numResubmissionsPending'),\n nonAnnouncements: Ember.computed.filterBy('discussionsArray', 'isNotAnnouncement'),\n requiredDiscussions_all: Ember.computed('nonAnnouncements.@each.is_required', 'session.isInstructor', function () {\n let required = this.nonAnnouncements.filterBy('is_required');\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.isInstructor')) {\n required = required.filter(d => {\n let now = moment();\n let due = moment(d.get('due_at'));\n return !d.get('due_at') || due < now || d.get('dueWithinOneWeek') || d.get('dueWithinTwoWeeks');\n });\n }\n return required;\n }),\n requiredDiscussions_unread: Ember.computed.filterBy('requiredDiscussions_all', 'unread'),\n conversations_all: Ember.computed.filterBy('nonAnnouncements', 'isConversation'),\n conversations_unread: Ember.computed.filterBy('conversations_all', 'unread'),\n // activities computed property selects the correct filter computed property,\n // and returns that value. Activities is also responsible for sorting.\n activities: Ember.computed('filterString', 'all_all.[]', 'all_unread.[]', 'announcements_all.[]', 'announcements_unread.[]', 'assignments_all.[]', 'assignments_unsubmitted.[]', 'assignments_ungraded.[]', 'assignments_comments.[]', 'assignments_resubmission.[]', 'requiredDiscussions_all.[]', 'requiredDiscussions_unread.[]', 'conversations_all.[]', 'conversations_unread.[]', function () {\n let filter = this.filterString;\n let result = this.get(filter);\n result = this.filterBuggedAnnouncements(this.announcements_all, result);\n (true && !(result !== undefined) && Ember.assert(`${filter} must be defined on activity class`, result !== undefined));\n return result.toArray().sort((a, b) => {\n return new Date(b.get('sortDate')) - new Date(a.get('sortDate'));\n });\n }),\n filterBuggedAnnouncements(announcements_all, result) {\n let dupes = [];\n let find_dupes_helper = new Set();\n let dirty_announcements = announcements_all.filter(announcement => announcement.hasDirtyAttributes);\n dirty_announcements = dirty_announcements.map(item => item.idSlug);\n for (let item of announcements_all) {\n if (find_dupes_helper.has(item.db_id)) {\n dupes.push(item.db_id);\n }\n find_dupes_helper.add(item.db_id);\n }\n return result.filter(item => !(dirty_announcements.includes(item.idSlug) && dupes.includes(item.db_id)));\n },\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n totalUngradedSubmissions: Ember.computed('assignmentsArray.@each.numUngradedSubmissions', function () {\n let ungraded = 0;\n this.assignments_ungraded.forEach(assignment => {\n ungraded += assignment.get('numUngradedSubmissions');\n });\n return ungraded;\n }),\n actions: {\n changeFilter(type, unread = 'all') {\n if (typeof unread !== 'string') {\n (true && !(unread.target && unread.target.value && typeof unread.target.value === 'string') && Ember.assert('If not a string, unread must be an event', unread.target && unread.target.value && typeof unread.target.value === 'string'));\n unread = unread.target.value;\n }\n this.set('filterType', type);\n this.set('filterUnread', unread);\n }\n }\n });\n});","define(\"bocce/components/side-panel/late-grading-policy\", [\"exports\", \"bocce/config/environment\"], function (_exports, _environment) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n session: Ember.inject.service(),\n didInsertElement: function () {\n this.keyupCallback = evt => {\n evt = evt || window.event;\n var isEscape = false;\n if (\"key\" in evt) {\n isEscape = evt.key === \"Escape\" || evt.key === \"Esc\";\n } else {\n isEscape = evt.keyCode === 27;\n }\n if (isEscape) {\n this.closeLateGradingPolicy();\n }\n };\n document.addEventListener('keyup', this.keyupCallback);\n },\n willDestroyElement: function () {\n document.removeEventListener('keyup', this.keyupCallback);\n },\n lateGradingPolicy: Ember.computed('session', function () {\n return this.get('session.lateGradingPolicy') || _environment.default.APP.text.noLateGradingPolicy;\n })\n });\n});","define(\"bocce/components/side-panel/panel-list-item\", [\"exports\", \"bocce/models/discussion\", \"bocce/models/assignment\", \"bocce/models/quiz\"], function (_exports, _discussion, _assignment, _quiz) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n session: Ember.inject.service(),\n tagName: 'li',\n classNames: ['activity', 'fade-in', 'panel-list-item'],\n classNameBindings: ['unread'],\n ariaRole: 'presentation',\n iconComponent: Ember.computed('item', function () {\n let item = this.item;\n let type = 'portrait';\n if (item instanceof _discussion.default && item.get('is_required')) {\n type = 'discussion-icon';\n } else if (item instanceof _assignment.default) {\n type = 'assignment-icon';\n } else if (item instanceof _quiz.default) {\n type = 'quiz-icon';\n }\n return `side-panel/panel-list-item/${type}`;\n }),\n itemRoute: Ember.computed('item', function () {\n let item = this.item;\n if (item instanceof _discussion.default) {\n return 'classroom.lessons.discussion';\n }\n if (item instanceof _assignment.default) {\n return 'classroom.lessons.assignments';\n }\n if (item instanceof _quiz.default) {\n return 'classroom.lessons.quiz';\n }\n return null;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n unread: Ember.computed('item.{unread,numUngradedSubmissions}', 'session.isInstructor', function () {\n /* eslint-disable-next-line ember/no-get */\n let teacherHasUngraded = this.get('session.isInstructor') && /* eslint-disable-next-line ember/no-get */\n this.get('item.numUngradedSubmissions') > 0;\n /* eslint-disable-next-line ember/no-get */\n return this.get('item.hasUnreadResponses') || teacherHasUngraded;\n })\n });\n});","define(\"bocce/components/side-panel/panel-list-item/assignment-icon\", [\"exports\", \"bocce/components/side-panel/panel-list-item/icon\"], function (_exports, _icon) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _icon.default.extend({\n session: Ember.inject.service(),\n iconClass: Ember.computed('session.isInstructor', 'item.currentUserSubmission', function () {\n /* eslint-disable-next-line ember/no-get */\n if (this.get('item.currentUserSubmission')) {\n return 'fas fa-check';\n }\n return 'fas fa-pencil';\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n grade: Ember.computed('item.{currentUserSubmission,grade_matches_current_submission}', function () {\n /* eslint-disable-next-line ember/no-get */\n if (this.get('item.currentUserSubmission') && /* eslint-disable-next-line ember/no-get */\n this.get('item.grade_matches_current_submission')) {\n /* eslint-disable-next-line ember/no-get */\n return this.get('item.currentUserSubmissionGrade');\n }\n return null;\n })\n });\n});","define(\"bocce/components/side-panel/panel-list-item/discussion-icon\", [\"exports\", \"bocce/components/side-panel/panel-list-item/icon\"], function (_exports, _icon) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _icon.default.extend({\n session: Ember.inject.service(),\n iconClass: Ember.computed('item.i_submitted', 'session.isInstructor', function () {\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('session.isInstructor') && this.get('item.i_submitted')) {\n return 'fas fa-check';\n } else {\n return 'fas fa-users';\n }\n })\n });\n});","define(\"bocce/components/side-panel/panel-list-item/icon\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n classNames: ['icon', 'portraits']\n });\n});","define(\"bocce/components/side-panel/panel-list-item/portrait\", [\"exports\", \"bocce/components/side-panel/panel-list-item/icon\"], function (_exports, _icon) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _icon.default.extend({});\n});","define(\"bocce/components/side-panel/panel-list-item/quiz-icon\", [\"exports\", \"bocce/components/side-panel/panel-list-item/icon\"], function (_exports, _icon) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _icon.default.extend({\n iconClass: 'fas fa-question',\n latestAttempt: Ember.computed('item.attempts.[]', function () {\n /* eslint-disable-next-line ember/no-get */\n let attempts = this.get('item.attempts');\n return attempts ? attempts.objectAt(0) : null;\n })\n });\n});","define(\"bocce/components/side-panel/work\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n didInsertElement: function () {\n this.scrollCallback = () => {\n if (document.querySelector('.side-panel').scrollTop > 10) {\n $('.side-panel .panel-container.work-panel .late-grading-policy-cta-container').addClass('boxShadow');\n } else {\n $('.side-panel .panel-container.work-panel .late-grading-policy-cta-container').removeClass('boxShadow');\n }\n };\n document.querySelector('.side-panel').addEventListener('scroll', this.scrollCallback);\n },\n willDestroyElement: function () {\n document.querySelector('.side-panel').removeEventListener('scroll', this.scrollCallback);\n },\n classNames: ['activities', 'work'],\n assignmentsArray: Ember.computed('assignments', function () {\n return this.assignments || [];\n }),\n discussionsArray: Ember.computed('discussions', function () {\n return this.discussions || [];\n }),\n quizzesArray: Ember.computed('quizzes', function () {\n return this.quizzes || [];\n }),\n all: Ember.computed('quizzesArray.[]', 'assignmentsArray.@each.locked', 'discussionsArray.@each.locked', function () {\n let {\n quizzesArray,\n assignmentsArray,\n discussionsArray\n } = this;\n let all = [...quizzesArray.toArray(), ...assignmentsArray.toArray().filter(a => !a.get('locked')), ...discussionsArray.toArray().filter(a => !a.get('locked'))];\n all.sort((a, b) => {\n return new Date(b.get('sortDate')) - new Date(a.get('sortDate'));\n });\n return all;\n }),\n todo: Ember.computed('all.@each.todo', function () {\n let all = this.all;\n return all.filter(item => item.get('todo'));\n }),\n done: Ember.computed('all.@each.todo', function () {\n return this.all.filter(item => !item.get('todo'));\n }),\n actions: {\n setShowLateGradingPolicy(show) {\n this.set('showLateGradingPolicy', show);\n }\n }\n });\n});","define(\"bocce/components/submission/footer-interact\", [\"exports\", \"bocce/mixins/editable\"], function (_exports, _editable) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend(_editable.default, {\n actions: {\n postSubmissionComment() {\n //If we were to directly call the passed in postSubmissionComment function from the template,\n //Ember would include a pointer event. We don't want that, which is why we are calling it through \n //an action.\n this.postSubmissionComment();\n }\n }\n });\n});","define(\"bocce/components/upload-preview\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n classNames: ['upload-preview'],\n file: Ember.computed.reads('model.file'),\n uploadedId: Ember.computed.reads('model.uploaded_id'),\n type: Ember.computed.reads('file.type'),\n previewStopped: function () {\n let id = this.uploadedId;\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$(`#file-id-${id}`).removeClass('playing');\n this.currentPreviewAudio\n /* eslint-disable-next-line ember/no-jquery */.off('playing', Ember.$.proxy(this.previewPlaying, this))\n /* eslint-disable-next-line ember/no-jquery */.off('pause', Ember.$.proxy(this.previewStopped, this))\n /* eslint-disable-next-line ember/no-jquery */.off('ended', Ember.$.proxy(this.previewStopped, this));\n },\n previewPlaying: function () {\n let id = this.uploadedId;\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$(`#file-id-${id}`).addClass('playing');\n },\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n is_image: Ember.computed('attachment', function () {\n let type = this.type;\n return type === 'image/jpeg' || type === 'image/png' || type === 'image/gif';\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n is_video: Ember.computed('attachment', function () {\n let type = this.type;\n return type === 'video/mp4';\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n is_quicktime: Ember.computed('attachment', function () {\n let type = this.type;\n return type === 'video/quicktime';\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n is_audio: Ember.computed('attachment', function () {\n let type = this.type;\n return type === 'audio/mpeg3' || type === 'audio/mp3' || type === 'audio/mpeg';\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n is_word: Ember.computed('attachment', function () {\n let type = this.type;\n return type === 'application/msword';\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n is_pdf: Ember.computed('attachment', function () {\n let type = this.type;\n return type === 'application/pdf';\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n generatedUrl: Ember.computed('attachment', function () {\n if (!this.file.url) {\n return URL.createObjectURL(this.file);\n }\n\n // Grab Kaltura thumbnail\n /* eslint-disable-next-line ember/no-get */\n let url = this.get('file.url');\n let kaltura = url.split('https://cdnapisec.kaltura.com/p/2588802/sp/258880200/playManifest/entryId/');\n if (kaltura.length > 1) {\n kaltura = kaltura[1].split('/format/url/protocol/https/flavorParamId/4128/name/course_video.mp4');\n let thumbnailUrl = 'https://cdnsecakmi.kaltura.com/p/2588802/thumbnail/entry_id/' + kaltura[0] + '/width/250';\n return thumbnailUrl;\n }\n return url;\n }),\n actions: {\n audioPreview: function () {\n /* eslint-disable-next-line ember/no-jquery */\n var audio = Ember.$('#attachment-audio-player').get(0);\n audio.attributes.src.value = this.generatedUrl;\n audio.load();\n audio.play();\n\n /* eslint-disable-next-line ember/no-jquery */\n this.set('currentPreviewAudio', Ember.$(audio));\n this.currentPreviewAudio\n /* eslint-disable-next-line ember/no-jquery */.on('playing', Ember.$.proxy(this.previewPlaying, this))\n /* eslint-disable-next-line ember/no-jquery */.on('pause', Ember.$.proxy(this.previewStopped, this))\n /* eslint-disable-next-line ember/no-jquery */.on('ended', Ember.$.proxy(this.previewStopped, this));\n },\n audioPreviewStop: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#attachment-audio-player').get(0).pause();\n },\n videoPreview: function () {\n if (event.target.paused) {\n event.target.play();\n } else {\n event.target.pause();\n }\n },\n otherFilePreview: function () {\n let url = this.generatedUrl;\n window.open(url, '_blank');\n }\n }\n });\n});","define(\"bocce/components/user-dot\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n tagName: 'span'\n });\n});","define(\"bocce/components/user-icons\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n tagName: 'span',\n needsIcons: Ember.computed.reads('isInstructor')\n });\n});","define(\"bocce/components/user-portrait\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Component.extend({\n userprofileService: Ember.inject.service('userprofile'),\n tagName: 'button',\n classNames: ['portrait'],\n attributeBindings: ['ariaHidden:aria-hidden', 'ariaLabel:aria-label'],\n ariaHidden: 'false',\n ariaLabel: Ember.computed('user.name', function () {\n /* eslint-disable-next-line ember/no-get */\n return `View profile of ${this.get('user.name')}`;\n }),\n initials: Ember.computed('user.name', function () {\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('user.name')) {\n return '';\n }\n let username = this.get('user.name').split(' ').map(part => part.charAt(0)).join('');\n // cut down to two initials\n return username.slice(0, 2);\n }),\n click() {\n if (!this.suppressClick && this.user) {\n let user = this.user;\n if (user.get && user.get('id')) {\n this.userprofileService.toggleUserProfile(user.get('id'));\n } else {\n this.userprofileService.toggleUserProfile(user.id);\n }\n }\n }\n });\n});","define(\"bocce/controllers/announcement\", [\"exports\", \"bocce/mixins/audio-rec\", \"bocce/mixins/editable\", \"bocce/mixins/discussable\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/enmodal\", \"bocce/mixins/notify\", \"bocce/mixins/prefs\"], function (_exports, _audioRec, _editable, _discussable, _videoRec, _rtcRec, _enmodal, _notify, _prefs) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-observers */\n var _default = _exports.default = Ember.Controller.extend(_audioRec.default, _editable.default, _discussable.default, _enmodal.default, _videoRec.default, _rtcRec.default, _prefs.default, _notify.default, {\n init(...args) {\n this._super(...args);\n this.sortProperties = this.sortProperties || ['lastResponse.date:desc'];\n },\n discussions: Ember.inject.controller(),\n // required by EditableMixin\n classroom: Ember.inject.controller(),\n // required by DiscussableMixin and EnmodalMixin\n discussion: Ember.inject.controller('classroom.lessons.discussion'),\n // required by DiscussableMixin\n newDiscussion: Ember.inject.controller('classroom.lessons.discussion-new'),\n // required by DiscussableMixin\n sorted_model: Ember.computed.sort('model', 'sortProperties'),\n userProfileService: Ember.inject.service('userprofile'),\n announcements: Ember.computed.filterBy('sorted_model', 'is_announcement', true),\n /* eslint-disable-next-line ember/no-observers */\n contentObserver: Ember.observer('model.content.isLoaded', function () {\n let c = this.model;\n\n // content.content.isLoaded tells us when the array is fully loaded\n if (!(c = this.model) || !(c = c.get('content')) || !c.get('isLoaded') || this.announcementInitialLoad) {\n return;\n }\n let anns = this.announcements.sortBy('date'),\n anlen = anns.get('length');\n if (anlen === 0) {\n this.set('noAnnouncements', true);\n } else {\n for (let i = 0; i < anlen; i++) {\n let ann = anns.objectAt(i);\n if (!ann.get('read')) {\n this.send('notify', ann.get('title'), true, 'discussion', ann.id);\n }\n }\n }\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.user.profile.can_showcase')) {\n this.send('togglePrefPanel');\n }\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.user.profile.font_size')) {\n /* eslint-disable-next-line ember/no-get */\n this.send('adjustFont', this.get('session.user.profile.font_size'));\n }\n this.set('announcementInitialLoad', true);\n })\n });\n});","define(\"bocce/controllers/classroom\", [\"exports\", \"bocce/mixins/menus\", \"bocce/mixins/calendar-events\", \"bocce/utilities/dialog\", \"bocce/mixins/notify\", \"bocce/mixins/helpguide\", \"bocce/mixins/enmodal\"], function (_exports, _menus, _calendarEvents, _dialog, _notify, _helpguide, _enmodal) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-observers */\n var _default = _exports.default = Ember.Controller.extend(_menus.default, _calendarEvents.default, _notify.default, _helpguide.default, _enmodal.default, {\n lessons: Ember.inject.controller('classroom.lessons'),\n conversations: Ember.inject.controller(),\n discussions: Ember.inject.controller(),\n classroom: Ember.inject.controller(),\n // required by EnmodalMixin\n heartbeat: Ember.inject.service(),\n session: Ember.inject.service(),\n bookmarks: Ember.inject.service(),\n userprofileService: Ember.inject.service('userprofile'),\n bookmarksService: Ember.computed.alias('bookmarks'),\n queryParams: ['start_panel'],\n start_panel: false,\n bobMaximized: false,\n bobClosed: true,\n pianoMaximized: false,\n switch_panel: Ember.observer('start_panel', function () {\n let startPanel = this.get('start_panel');\n\n // Validate panel to make sure it is one of the following: syllabus, conversation, activity, roster\n if (startPanel && ['syllabus', 'conversation', 'activity', 'roster'].includes(startPanel)) {\n this.send('swapPanel', startPanel);\n }\n }),\n // Variation on the Service Injection design pattern.\n // Using jQuery in the controller's unit tests breaks tests, since there is no DOM\n // and AJAX doesn't work with no interface to connect to. Referencing jQuery through\n // a property lets us mock the jQuery API in our unit tests.\n jQuery: Ember.$,\n topicLoading: Ember.computed.reads('lessons.topicLoading'),\n isBCM: Ember.computed.alias('model.classroom.term.isBCM'),\n lessonLoaded: Ember.computed.alias('session.lessonLoaded'),\n /* eslint-disable-next-line ember/no-observers */\n convoScroller: Ember.observer('activeConversation', function () {\n if (!this.activeConversation) {\n /* eslint-disable-next-line ember/no-jquery */\n\n Ember.$('.modal-content').off('scrollstop', tgt => {\n if (tgt.target.scrollTop === 0) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.loading-prv').addClass('active');\n this.send('loadMoreMessages');\n }\n });\n }\n }),\n nextLessonMap: Ember.computed('model', 'model.classroom.lessons', function () {\n let map = {},\n model = this.model;\n let lessons = model.classroom.get('lessons');\n if (lessons) {\n return lessons.then(l_objs => {\n l_objs.forEach(lesson => {\n map[lesson.get('number')] = lesson;\n });\n return map;\n });\n }\n return map;\n }),\n bobSrc: Ember.computed(function () {\n let bobUrl = this.get('session.course.bobUrl');\n return 'https://' + bobUrl + '/?section=' + this.get('session.course.id');\n }),\n userSectionCodes: Ember.computed('model.classroom.id', 'session.user.sectionCodes', function () {\n // Returns the other sections the user is enrolled in,\n // including courses from other terms.\n /* eslint-disable-next-line ember/no-get */\n let sectionCodes = this.get('session.user.sectionCodes');\n if (sectionCodes === undefined) {\n return false;\n }\n sectionCodes = sectionCodes.filter(s => {\n /* eslint-disable-next-line ember/no-get */\n return Number(s.courseId) !== Number(this.get('model.classroom.id'));\n });\n if (sectionCodes.length === 0) {\n return false;\n }\n sectionCodes.sort((a, b) => {\n if (a.startsAt !== b.startsAt) {\n return a.startsAt > b.startsAt ? -1 : 1;\n } else if (a.courseTitle !== b.courseTitle) {\n return a.courseTitle < b.courseTitle ? -1 : 1;\n } else {\n return Number(a.sectionLabel) - Number(b.sectionLabel);\n }\n });\n return sectionCodes;\n }),\n otherCurrentTermSections: Ember.computed('userSectionCodes', 'session.course.term.name', function () {\n // Returns the other sections the user is enrolled in,\n // ONLY including the current course's term.\n let userSectionCodes = this.userSectionCodes || [];\n let otherCurrentTermSections = userSectionCodes.filter(section => {\n /* eslint-disable-next-line ember/no-get */\n return section.termLabel === this.get('session.course.term.name');\n });\n return otherCurrentTermSections;\n }),\n shouldOfferSurvey: Ember.computed('isBCM', 'model.classroom.term.weeksSinceStart', 'session.isStudent', function () {\n if (this.isBCM) {\n return false;\n }\n /* eslint-disable-next-line ember/no-get */\n let week = this.get('model.classroom.term.weeksSinceStart'),\n /* eslint-disable-next-line ember/no-get */\n max = this.get('session.isStudent') ? 14 : 15;\n let isFourWeekCourse = this.get('session.section.course.isFourWeekCourse');\n let surveyWeek = isFourWeekCourse ? 4 : 6;\n return week >= surveyWeek && week <= max;\n }),\n hasDiscourse: Ember.computed('model.classroom.discourseUrl', function () {\n // Check session to see course code\n /* eslint-disable-next-line ember/no-get */\n let courseCode = this.get('session.course.code');\n let limited = this.get(\"session.isLimitedStudentTerm\");\n // Check if the CourseCode contains 'OLART-220'\n if (courseCode.includes('OLART-220') && !limited) {\n return true;\n }\n return false;\n }),\n surveyLabel: Ember.computed('model.classroom.term.weeksSinceStart', function () {\n return 'Survey';\n }),\n loadConversations: function () {\n let conversationsLoaded = this.conversationsLoaded || false,\n conversationsController = this.conversations,\n that = this;\n if (!conversationsLoaded) {\n return this.store.findAll('conversation').then(conversations => {\n conversationsController.set('model', conversations);\n that.set('conversationsLoaded', true);\n });\n }\n },\n loadEvents: function () {\n let eventsLoaded = this.eventsLoaded || false;\n // Reload events if we find ourselves in a different course\n if (this.currentCourseId != this.get('session.course.id')) {\n eventsLoaded = false;\n // Setting eventsLoaded here to display the loading-message partial in events.hbs\n this.set('eventsLoaded', false);\n this.currentCourseId = this.get('session.course.id');\n }\n // If there was a previous heartbeat error, restart the\n // heartbeat if we're within 30 minutes of a scheduled\n // event start time.\n let restart_heartbeat_if_needed = events => {\n if (window.heartbeatFrequencyInMilliseconds > 0) {\n return;\n }\n let now = new Date();\n events.forEach(evt => {\n let start = new Date(evt.get('startAt'));\n if (Math.abs(start - now) < 30 * 60 * 1000) {\n Ember.debug('Restarting heartbeat');\n window.heartbeatFrequencyInMilliseconds = 30 * 1000;\n /* eslint-disable-next-line ember/no-get */\n Ember.run.later(this, this.get('heartbeat.heartbeat'), 100);\n }\n });\n };\n if (!eventsLoaded) {\n return this.store.findAll('event').then(events => {\n // this.set('session.course.events', events);\n this.set('eventsLoaded', true);\n restart_heartbeat_if_needed(events);\n });\n } else {\n /* eslint-disable-next-line ember/no-get */\n restart_heartbeat_if_needed(this.get('session.course.events'));\n }\n },\n // lessonNumber and lessonTitle are given to the\n // Classroom::CurrentLessonItem component\n lessonNumber: Ember.computed.reads('lessons.model.lesson.number'),\n lessonTitle: Ember.computed.reads('lessons.model.item.title'),\n // this gets set using updateShortcutsDrawer action\n shortcutsDrawerLabel: 'Loading TOC...',\n hCodeURL: Ember.computed('session.section.id', /* eslint-disable-next-line ember/no-get */\n function () {\n return '/interface/sections/' + this.get('session.section.id') + '/acceptHonorCode';\n }),\n // courseDiscussions and courseAssignments are null at first, and are set\n // by the Route once the assignments/discussions findAll promises resolve.\n // These values are then passed to components.\n courseDiscussions: null,\n courseAssignments: null,\n courseRequiredDiscussions: null,\n courseQuizzes: null,\n activityContentLoading: Ember.computed('courseDiscussions', 'courseAssignments', function () {\n return this.courseDiscussions === null || this.courseAssignments === null;\n }),\n newAdviceCards: Ember.computed('session.section.adviceCards', 'session.section.adviceCards.adviceCardsCount', function () {\n let adviceCards = this.get('session.section.adviceCards');\n if (adviceCards) {\n return adviceCards.adviceCardsCount;\n }\n return false;\n }),\n submitAdviceCardPrompt: Ember.computed('session.section.adviceCards', 'session.section.adviceCards.iSubmitted', function () {\n let prefs = this.get('session.user.profile');\n return !(prefs?.declinedAdviceCardSubmission || false);\n }),\n iSubmitted: Ember.computed('session.section.adviceCards', 'session.section.adviceCards.iSubmitted', function () {\n return this.get('session.section.adviceCards.iSubmitted');\n }),\n isLastWeekOfCourse: Ember.computed('session.section.ends_at', 'session.course.lessons', function () {\n const lessons = this.get('session.course.lessons');\n if (lessons?.objectAt(lessons.length - 1)?.locked) {\n return false;\n }\n // Last lesson week starts 35 days before the course ends\n const lastLessonStarts = 35 * 24 * 60 * 60 * 1000;\n const endsAt = new Date(this.get('session.section.ends_at'));\n const now = new Date();\n return endsAt - now < lastLessonStarts;\n }),\n // TODO: NK: unreadActivities duplicates the logic from the all_unread\n // property on components/side-panel/activity.js. I'm leaving this as\n // duplicated code for now, but I'd like to find a way of DRYing this,\n // because we also use similar logic on the Work panel. Once the Work\n // panel is component-ized, that'll mean this same logic is needed in three\n // different places.\n unreadActivities: Ember.computed('session.user.profile.declinedAdviceCardSubmission', 'session.section.adviceCards', 'session.section.adviceCards.{adviceCardsCount,iSubmitted,isLastWeek}', 'courseDiscussions.@each.unread', 'courseAssignments.@each.{needsStudentAttention,needsTeacherAttention}', 'session.isInstructor', function () {\n let discussions = this.courseDiscussions || Ember.ArrayProxy.create();\n discussions = discussions.filter(d => !d.get('locked'));\n let unreadDiscussions = discussions.filterBy('unread');\n let assignments = this.courseAssignments || Ember.ArrayProxy.create();\n /* eslint-disable-next-line ember/no-get */\n let filter = this.get('session.isInstructor') ? 'needsTeacherAttention' : 'needsStudentAttention';\n let unreadAssignments = assignments.filterBy(filter);\n let retval = Ember.ArrayProxy.create({\n content: [...unreadDiscussions, ...unreadAssignments]\n });\n if (this.get('session.isInstructor') && this.session.section.adviceCards.adviceCardsCount) {\n for (let i = 0; i < this.session.section.adviceCards.adviceCardsCount; i++) {\n retval.pushObject([]);\n }\n }\n return retval;\n }),\n incompleteWork: Ember.computed('courseRequiredDiscussions.@each.todo', 'courseAssignments.@each.todo', 'courseQuizzes.@each.todo', function () {\n let discussions = this.courseRequiredDiscussions || [];\n let assignments = this.courseAssignments || [];\n let quizzes = this.courseQuizzes || [];\n let all = [...discussions.toArray(), ...assignments.toArray(), ...quizzes.toArray()];\n return all.filter(item => !item.get('locked') && item.get('todo'));\n }),\n adviceCardsURL: Ember.computed('session.course.id', 'session.course.code', 'session.section.id', function () {\n const courseId = this.get('session.course.id'),\n courseCode = this.get('session.course.code'),\n sectionId = this.get('session.section.id'),\n lastLessonNum = this.get('session.course.lessons.length') - 1;\n return `/#/${courseId}/${courseCode}/${sectionId}/${lastLessonNum}/peer-to-peer-support-advice-cards`;\n }),\n showBobInfoText: Ember.computed('session.dismissedBobInfoText', function () {\n let dismissedInfoText = localStorage.getItem('dismissedBobInfoText');\n return !dismissedInfoText;\n }),\n actions: {\n setShowLateGradingPolicy(show) {\n this.set('showLateGradingPolicy', show);\n },\n acceptHonorCode: function (firstTime, skipCheck) {\n if (!skipCheck) {\n /* eslint-disable-next-line ember/no-jquery */\n if (!Ember.$('#hcode-accept').is(':checked')) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-accept-checkbox').addClass('attention');\n return;\n }\n } else {\n this.set('session.section.needsHonorCode', false);\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-accept').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-accept-checkbox').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-working').removeClass('hidden');\n this.jQuery.ajax({\n type: 'POST',\n url: this.hCodeURL,\n success: () => {\n Ember.debug('Honor code accepted');\n this.set('session.section.needsHonorCode', false);\n if (firstTime) {\n this.set('helpGuideOpen', true);\n }\n },\n error: error => {\n this.set('session.section.needsHonorCode', true);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-accept').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-accept-checkbox').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-working').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.hcode-accept-checkbox').removeClass('attention');\n (0, _dialog.default)('Unable to accept honor code. Something went wrong. Please try submitting again. If this error persists, contact support.');\n Ember.debug('Unable to accept honor code. Something went wrong: ');\n Ember.debug(error);\n }\n });\n },\n nextLesson: function (curLesson) {\n var model = this.model,\n lessons = model.classroom.get('lessons'),\n idx = model.classroom.get('lessons').mapBy('id').indexOf(curLesson.get('id')),\n nxt = idx + 1,\n nextLesson;\n if (nxt < lessons.get('length')) {\n nextLesson = lessons.objectAt(nxt);\n //$('.main-panel').scrollTop(0);\n this.transitionToRoute('classroom.lessons', nextLesson.get('id'), nextLesson.get('items.firstObject.id'));\n }\n },\n shortcut: function (lessonId) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').scrollTop(0)\n /* eslint-disable-next-line ember/no-jquery */.scrollTop(Ember.$('.lesson-divider:nth(' + lessonId + ')').offset().top);\n },\n scrollToHeader: function (item, lesson) {\n var y;\n if (typeof item === 'number') {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.syllabus-divider.lesson-' + item).get(0).scrollIntoView();\n /* eslint-disable-next-line ember/no-jquery */\n y = Ember.$('.side-panel').scrollTop(); //your current y position on the page\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').scrollTop(y + 20);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#shortcuts-drawer-handle').prop('checked', false);\n } else {\n event.target.scrollIntoView();\n }\n this.transitionToRoute('classroom.lessons', lesson.get('id'), lesson.get('items.firstObject.id'));\n },\n openTOC: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#shortcuts-drawer-handle').prop('checked', true);\n },\n closeTOC: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#shortcuts-drawer-handle').prop('checked', false);\n },\n syllabusTransport: function (lessonId, topicId) {\n /* eslint-disable-next-line ember/no-jquery */\n var tgt = Ember.$('#syllabus-item-' + lessonId + '-' + topicId);\n if (!tgt.parent().hasClass('active')) {\n // Remove UI flourish from sylabus items for now\n // $('.syllabus-entry').removeClass('pbar');\n // tgt.addClass('pbar');\n this.lessons.set('interactionsBooted', false);\n }\n },\n // Click to swap between different left-side panels\n swapPanel: function (panel) {\n let that = this,\n $sp,\n $n;\n\n /* eslint-disable-next-line ember/no-jquery */\n $sp = Ember.$('.side-panel');\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('session.panelPosition')) {\n this.set('session.panelPosition', {});\n }\n /* eslint-disable-next-line ember/no-get */\n this.set('session.panelPosition.' + this.get('session.panel'), $sp.scrollTop());\n $sp.scrollTop(0);\n $sp.removeClass('syllabus');\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.panel') === panel) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom').toggleClass('active');\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom').addClass('active');\n this.set('session.panel', panel);\n if (panel === 'syllabus') {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').addClass('syllabus');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.proxy(setTimeout(function () {\n $sp.scrollTop(that.get('session.panelPosition.' + panel) || 0);\n if (!that.get('session.panelPosition.' + panel)) {\n /* eslint-disable-next-line ember/no-jquery */\n $n = Ember.$('.side-panel .std-link.active');\n if ($n.length > 0) {\n $sp.scrollTop($n.position().top - $sp.height() + 100 + $sp.scrollTop());\n }\n }\n }, 100), this);\n }\n\n // JRW: Late load these types only when requested.\n if (panel === 'conversations') {\n this.loadConversations();\n } else if (panel === 'chat') {\n this.loadEvents();\n } else if (panel === 'activity') {\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('session.isInstructor') && !this.studentActivityPanelFirstClick) {\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$('.filter-unread-count.all').length > 0) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.filter-unread-count.all').click();\n }\n this.set('studentActivityPanelFirstClick', true);\n }\n }\n },\n gradeBook: function () {\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('session.allowOldGradebook')) {\n this.set('gradebookOpen', true);\n } else {\n /* eslint-disable-next-line ember/no-get */\n var courseId = this.get('model.classroom.id');\n window.open('/courses/' + courseId + '/gradebook', '_blank');\n }\n },\n viewShowcase: function () {\n /* eslint-disable-next-line ember/no-get */\n var showcaseCourseID = this.get('session.section.showcase_course_id');\n window.open('/#/' + showcaseCourseID + '/' + showcaseCourseID + '/' + showcaseCourseID + '/', '_blank');\n },\n viewDiscourse: function () {\n let discourseURL = this.get('session.discourseUrl');\n window.open(discourseURL, '_blank');\n },\n viewAdviceCards: function () {\n if (this.get('session.isInstructor')) {\n try {\n fetch(`/interface/submit-advice-card/${this.get('session.section.id')}/mark_seen`, {\n method: 'POST'\n });\n } catch (e) {\n console.log(e);\n }\n this.set('session.section.adviceCards.adviceCardsCount', 0);\n }\n document.querySelector('.classroom').classList.remove('active');\n },\n declineAdviceCardSubmission: function () {\n let dialogPromise = (0, _dialog.default)('Are you sure you want to remove this reminder? This cannot be undone.', ['Yes', 'No']);\n dialogPromise.then(selection => {\n if (selection === 'Yes') {\n console.log('Declining advice card submission');\n let prefs = this.get('session.user.profile') || {};\n let courseCode = this.get('session.course.code');\n prefs.declinedAdviceCardSubmission = courseCode;\n this.store.findRecord('user', this.get('session.user.id')).then(record => {\n record.set('profile_updated', true);\n record.set('profile', prefs);\n record.save();\n });\n this.set('classroom.submitAdviceCardPrompt', false);\n // this.set('session.section.adviceCards.iSubmitted', true);\n }\n });\n },\n closePopup: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-container').removeAttr('closeId');\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-container .content').html('');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-container').removeClass('active');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-backdrop').removeClass('active');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').removeClass('on-modal');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').removeClass('no-scroll');\n },\n toggleCourseList: function () {\n /* eslint-disable-next-line ember/no-jquery */\n var $chbx_toggle = Ember.$('#chbx-course-select-drawer-handle');\n /* eslint-disable-next-line ember/no-jquery */\n var $course_toggle = Ember.$('.course-select-drawer-handle');\n /* eslint-disable-next-line ember/no-jquery */\n var $course_list = Ember.$('.course-list-courses');\n if ($chbx_toggle.prop('checked')) {\n $chbx_toggle.prop('checked', false);\n $course_toggle.removeClass('open');\n $course_list.removeClass('open');\n } else {\n $chbx_toggle.prop('checked', true);\n $course_toggle.addClass('open');\n $course_list.addClass('open');\n }\n },\n closeCourseList: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#chbx-course-select-drawer-handle').prop('checked', false);\n },\n switchCourse: function (userSectionCode) {\n // TODO: Change from hard page refresh to load ember classroom correctly without refresh.\n let path = 'https://' + window.location.hostname + '/#/' + userSectionCode.courseId + '/' + userSectionCode.code + '/' + userSectionCode.sectionId;\n window.location.href = path;\n window.location.reload();\n },\n //Change playback speed of audio player\n changePlaybackSpeed: function (value) {\n var speedCalc = parseFloat(value) / 100;\n document.getElementById('publicPlayer').playbackRate = speedCalc;\n },\n // Submit bug report\n bugReport: function () {\n //window.open('https://docs.google.com/forms/d/1TNgug-nMiauaMUmzju4EeGO8_BttNNyTUaSXebyuN8g/viewform', '_blank');\n // Using Luke's Simpler form\n window.open('https://online.berklee.edu/student-support-contact', '_blank');\n },\n // qualtrics survey\n takeSurvey: function () {\n this.transitionToRoute('classroom.lessons.survey', 'course');\n },\n maxMinBob: function () {\n this.set('bobMaximized', !this.bobMaximized);\n this.set('bobClosed', false);\n let isBobMaximized = this.get('bobMaximized');\n if (isBobMaximized) {\n Ember.$('.classroom').removeClass('active');\n } else {\n Ember.$('.classroom').addClass('active');\n }\n },\n closeBob: function () {\n this.set('bobClosed', true);\n this.set('bobMaximized', false);\n Ember.$('.classroom').addClass('active');\n },\n openBob: function () {\n if (!this.get('bobSrc')) {\n let bobUrl = this.get('session.course.bobUrl');\n this.set('bobSrc', 'https://' + bobUrl + '/?section=' + this.get('session.course.id'));\n } else {\n this.send('sendUrlToBob');\n }\n this.set('bobClosed', false);\n this.set('bobMaximized', false);\n this.send('dismissBobInfoText');\n },\n sendUrlToBob() {\n const bobIframe = document.querySelector('#bob-contents iframe');\n if (bobIframe) {\n const fullUrl = window.location.href;\n const bobOrigin = new URL(this.get('bobSrc')).origin;\n bobIframe.contentWindow.postMessage({\n bocce_url: fullUrl\n }, bobOrigin);\n }\n },\n dismissBobInfoText() {\n localStorage.setItem(\"dismissedBobInfoText\", \"true\");\n this.set('session.dismissedBobInfoText', true);\n },\n maxMinPiano: function () {\n this.set('pianoMaximized', !this.pianoMaximized);\n }\n },\n sendFeedback: function () {\n // opens a form where the user can send feedback, which opens a Salesforce case.\n /* eslint-disable-next-line ember/no-get */\n let userEmail = this.get('session.user.email');\n window.open(`https://online.berklee.edu/form_assembly?id=4708322&URLemail=${userEmail}`, '_blank');\n },\n openAdminModal: function () {\n this.transitionToRoute('classroom.lessons.admin');\n },\n helpSection: function () {\n this.set('helpGuideOpen', true);\n }\n });\n});","define(\"bocce/controllers/classroom/lessons\", [\"exports\", \"ember-data\", \"bocce/mixins/editable\", \"bocce/mixins/assignments\", \"bocce/mixins/uploadable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/enmodal\", \"bocce/mixins/boot\", \"bocce/mixins/interactions/piano\", \"bocce/utilities/dialog\", \"bocce/mixins/notify\"], function (_exports, _emberData, _editable, _assignments, _uploadable, _audioRec, _videoRec, _rtcRec, _enmodal, _boot, _piano, _dialog, _notify) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/use-ember-data-rfc-395-imports */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-observers */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_notify.default, _editable.default, _assignments.default, _uploadable.default, _audioRec.default, _videoRec.default, _rtcRec.default, _boot.default, _enmodal.default, {\n classroom: Ember.inject.controller(),\n discussions: Ember.inject.controller(),\n // required by EditableMixin\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n submission: Ember.inject.controller('classroom.lessons.submission'),\n // loadHeartbeatData uses this\n\n heartbeat: Ember.inject.service(),\n features: Ember.inject.service(),\n bookmarks: Ember.inject.service(),\n router: Ember.inject.service(),\n bookmarksService: Ember.computed.alias('bookmarks'),\n userprofileService: Ember.inject.service('userprofile'),\n topicsQueue: false,\n // Temporary function that we can call to start the misc. boot functions. Everything called from this should get\n // refactored; right now it mostly works, but is really hard to understand what does what and what relies on what.\n // Also, not all of it works.\n getThingsRolling() {\n this.afterBoot();\n this.heartbeat.begin(this);\n },\n getItemNode() {\n /* eslint-disable-next-line ember/no-get */\n let itemId = `#${this.get('model.item.cssString')}`;\n /* eslint-disable-next-line ember/no-jquery */\n let itemNode = Ember.$(itemId);\n return itemNode;\n },\n afterBoot() {\n // Open up syllabus view on first load...\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$('.classroom').hasClass('loading')) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom').removeClass('loading').addClass('active');\n }\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').addClass('no-scroll');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel-container').off('resize');\n\n // Create a play listener for any videos with a \"post-video-embed\" class -- if the play fails, we'll replace the\n // source with a placeholder video and try again every 20 seconds until the video is fixed\n document.addEventListener('error', function (event) {\n // Check if the event target is a video source inside a .video-container\n if (event.target.matches('.video-container video source')) {\n let video = event.target.closest('.video-container').querySelector('video');\n let source = event.target;\n let oldSrc = source.getAttribute('src');\n let placeholderSrc = '/front_end/images/video_processing_placeholder.mp4';\n\n // If the video is not the placeholder video, replace the source with the placeholder video and try again\n if (oldSrc !== placeholderSrc) {\n source.setAttribute('src', placeholderSrc);\n video.load();\n video.play();\n }\n function checkVideoStatus() {\n let cacheBustSRC = oldSrc + '?cacheBust=' + new Date().getTime();\n Ember.$.ajax({\n url: cacheBustSRC,\n type: 'HEAD',\n cache: false,\n crossDomain: true,\n success: function (response, textStatus, xhr) {\n // If the video is fixed, clear the interval and play the video\n // Check for OK and 302\n if (xhr.status === 200 || xhr.status === 302) {\n let wasPlaying = !video.paused;\n // Set video source back to the original source\n source.setAttribute('src', cacheBustSRC);\n video.load();\n if (wasPlaying) {\n video.play();\n }\n clearInterval(interval);\n } else {\n console.log(\"Uploaded video is still processing...\");\n }\n },\n error: function (xhr, textStatus, errorThrown) {\n console.log(\"Uploaded video is still processing...\");\n }\n });\n }\n let interval = setInterval(checkVideoStatus, 20000);\n }\n }, true);\n setTimeout(() => {\n this.afterAfterBoot();\n }, 500);\n },\n afterAfterBoot() {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').removeClass('no-scroll');\n let current_item = this.getItemNode();\n if (current_item && current_item.length) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').scrollTop(0).scrollTop(current_item.position().top - 100);\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$('#bocce-piano .pianoContainer').length === 0) {\n this.setupPiano();\n }\n },\n setupPiano() {\n // Set up piano\n let config = {\n 'keyWidth': 30,\n 'noteLabels': 'true',\n 'middleC': 'true',\n 'hideKey': false,\n 'hotKeys': true,\n 'placementId': 'bocce-piano',\n 'noKeys': false,\n 'keyDirs': {}\n };\n for (let i = 0; i < 85; i++) {\n config['keyDirs']['key_' + i] = 'https://intro.online.berklee.edu/audio/piano/key_' + i + '.mp3';\n }\n window.piano = new _piano.default(config);\n window.piano.run();\n },\n nextLesson: Ember.computed('classroom.nextLessonMap', 'model.lesson.id', function () {\n return _emberData.default.PromiseObject.create({\n promise: new Promise(resolve => {\n /* eslint-disable-next-line ember/no-get */\n this.get('classroom.nextLessonMap').then(map => {\n /* eslint-disable-next-line ember/no-get */\n let nextLessonId = parseInt(this.get('model.lesson.id')) + 1;\n resolve(map[nextLessonId]);\n });\n })\n });\n }),\n showNextLesson: Ember.computed('nextLesson', 'features.staticContent', function () {\n return _emberData.default.PromiseObject.create({\n promise: new Promise(resolve => {\n /* eslint-disable-next-line ember/no-get */\n this.get('nextLesson').then(nextLesson => {\n resolve(Boolean(!this.get('features.staticContent') && nextLesson));\n });\n })\n });\n }),\n // this is sort of the equivalent to the modalId. It is needed because\n // there needs to be a parent for the response text and the lesson\n // can have an in page response\n replyContainerId: Ember.computed(function () {\n return 'in-page-discussion';\n }),\n nextLessonAvail: Ember.computed('nextLesson', function () {\n var retRes = false,\n nxt = this.nextLesson;\n if (nxt) {\n retRes = true;\n }\n return retRes;\n }),\n nextLessonLocked: Ember.computed('nextLesson', function () {\n return _emberData.default.PromiseObject.create({\n promise: new Promise(resolve => {\n this.nextLesson.then(next => {\n resolve(next && next.get('locked'));\n });\n })\n });\n }),\n lastLesson: Ember.computed('classroom.lastLesson', 'model.lesson.id', function () {\n var retRes = false,\n /* eslint-disable-next-line ember/no-get */\n last = this.get('classroom.lastLesson');\n if (last && this.model.lesson.get('id') === last.id) {\n retRes = true;\n }\n return retRes;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n discussionModel: Ember.computed.reads('model.discussion'),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n unlocks_at_formatted: Ember.computed('nextLesson', function () {\n /* eslint-disable-next-line ember/no-get */\n return moment(this.get('nextLesson.unlocks_at')).tz('America/New_York').format('MMM Do');\n }),\n /* eslint-disable-next-line ember/no-observers */\n currentPathChanged: Ember.observer('model.currentPath', function () {\n var current_user = this.session.get('user');\n\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('model.item')) {\n return;\n }\n this.model.course.set('lastLesson', this.model.lesson);\n this.model.course.set('lastItem', this.model.item);\n\n // Mark the item as viewed in canvas.\n // Re-save this, even if it's already read, so we can track activity.\n if (!this.session.get('course.isReadOnly')) {\n this.model.item.set('read', true);\n this.model.item.save();\n }\n current_user.then(user => {\n let lv = user.get('lastviewed');\n lv[this.model.course.get('course_id')] = {\n lesson: this.model.lesson.get('id'),\n item_id: this.model.item.get('id')\n };\n user.set('lastviewed', lv);\n user.save();\n });\n }),\n lessonsController: Ember.computed(function () {\n return this;\n }),\n actions: {\n nextLesson: async function (curLesson) {\n if (!(await this.nextLessonLocked)) {\n this.classroom.send('nextLesson', curLesson);\n }\n },\n showcaseDeleteItem: function (item) {\n (0, _dialog.default)('Are you sure you want to delete this lesson from the showcase? This action cannot be undone.', ['Yes, delete it.', 'Cancel']).then(selection => {\n if (selection === 'Yes, delete it.') {\n let lesson = item.get('lesson');\n let destroyLesson = lesson.get('items').get('length') === 1 || false;\n item.destroyRecord();\n if (destroyLesson) {\n lesson.destroyRecord();\n }\n }\n });\n },\n showcaseEditItem: function () {\n this.set('editingLessonTitle', true);\n },\n cancelEdit: function () {\n this.set('editingLessonTitle', false);\n },\n showcaseSaveItemTitle: function (item) {\n /* eslint-disable-next-line ember/no-jquery */\n let newTitle = Ember.$(`.lesson-edit-input-${item.id}`).val();\n item.set('title', newTitle);\n item.set('showcaseEdit', true);\n item.save();\n this.set('editingLessonTitle', false);\n },\n updateShortcutsDrawer(title) {\n this.classroom.set('shortcutsDrawerLabel', title);\n },\n submitAdviceCard() {\n this.set('session.section.adviceCards.iSubmitted', true);\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/admin\", [\"exports\", \"bocce/mixins/enmodal\"], function (_exports, _enmodal) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default);\n});","define(\"bocce/controllers/classroom/lessons/admin/assignments\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Controller.extend({});\n});","define(\"bocce/controllers/classroom/lessons/admin/banners\", [\"exports\", \"bocce/utilities/dialog\"], function (_exports, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n const bannerTypes = {\n 'global instructors': {\n dismiss_condition_id: 5,\n is_global: false\n },\n 'global students': {\n dismiss_condition_id: 6,\n is_global: false\n },\n 'global': {\n dismiss_condition_id: null,\n is_global: true\n },\n 'grading': {\n dismiss_condition_id: 1,\n is_global: false\n },\n 'survey': {\n dismiss_condition_id: 2,\n is_global: false\n },\n 'user': {\n dismiss_condition_id: 3,\n is_global: false\n },\n 'course': {\n dismiss_condition_id: 4,\n is_global: false\n },\n 'dashboard-banner': {\n dismiss_condition_id: 7,\n is_global: false\n }\n };\n\n // This is written as a fetch because the banners API doesn't\n // follow the structure that Ember expects:\n async function addRecipientsToBanner(bannerID, dataObject) {\n const url = `/interface/banners/${bannerID}/add_recipients`;\n const body = JSON.stringify(dataObject);\n let response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body\n });\n if (!response.ok) {\n return Promise.reject(response.status);\n }\n return await response.json();\n }\n function getTwoMonthsFromNow() {\n const twoMonths = (new Date().getMonth() + 1) % 12;\n const twoMonthsFromNow = new Date();\n twoMonthsFromNow.setMonth(twoMonths);\n return moment(twoMonthsFromNow);\n }\n function isValidUrl(url) {\n try {\n new URL(url);\n } catch (e) {\n return false;\n }\n return true;\n }\n var _default = _exports.default = Ember.Controller.extend({\n newBannerMessage: '',\n newBannerDismissCondition: 5,\n newBannerIsGlobal: false,\n newBannerDismissAt: getTwoMonthsFromNow(),\n newBannerHyperlink: '',\n bannerTypeNames: Object.keys(bannerTypes),\n courseUsers: [],\n showUserDropDown: false,\n individualUserId: 0,\n // Sort banners by ID in descending order:\n sortedBanners: Ember.computed('model.@each.id', function () {\n const modelArray = this.model.toArray();\n const sortedModel = modelArray.sort((a, b) => parseInt(b.id) - parseInt(a.id));\n const filteredModel = sortedModel.filter((banner, index) => {\n let type = banner.get('type');\n if (type === \"Course Banner\" || type === \"Individual User Banner\" || type === 'Dashboard Banner') {\n return false;\n } else {\n return true;\n }\n });\n return filteredModel;\n }),\n targetedBanners: Ember.computed('model.@each.id', function () {\n let models = this.model.toArray(),\n courseUserIds = this.store.peekAll('user').mapBy('id'),\n courseId = this.get('session.course.id');\n const filteredBanners = models.filter((banner, index) => {\n let type = banner.get('type'),\n context_id = banner.get('context_id');\n if ([\"Course Banner\", \"Dashboard Banner\"].includes(type) && courseId === context_id.toString()) {\n return true;\n }\n if (type === \"Individual User Banner\" && courseUserIds.includes(context_id.toString())) {\n return true;\n }\n return false;\n });\n return filteredBanners;\n }),\n init() {\n this._super(...arguments);\n let users = this.store.findAll('user');\n this.set('courseUsers', users);\n },\n actions: {\n createNewBanner() {\n const message = this.newBannerMessage,\n dismiss_at = this.newBannerDismissAt.toDate(),\n link = this.newBannerHyperlink || null;\n if (message.length === 0) {\n alert('Banner Message cannot be empty.');\n return;\n }\n if (dismiss_at <= new Date()) {\n alert('Dismiss date should be in the future.');\n return;\n }\n if (link && !isValidUrl(link)) {\n alert('Invalid link format');\n return;\n }\n const dismiss_condition_id = this.newBannerDismissCondition;\n const is_global = this.newBannerIsGlobal;\n let context_id = 0,\n targeted = false;\n if (dismiss_condition_id === 3) {\n context_id = this.get('individualUserId');\n targeted = true;\n }\n if (dismiss_condition_id === 4 || dismiss_condition_id == 7) {\n context_id = this.get('session.course.id');\n targeted = true;\n }\n const newBannerObject = {\n message,\n dismiss_condition_id,\n priority: 1,\n dismiss_at,\n is_global,\n link,\n context_id\n };\n\n // Create banner:\n const banner = this.store.createRecord('banner', newBannerObject);\n return banner.save().then(() => {\n // Add recipients to the banner\n /* eslint-disable-next-line ember/no-get */\n const term_id = this.get('session.termID');\n const data = {\n term_id,\n by_course: true,\n user_id: this.get('individualUserId'),\n course_id: this.get('session.course.id') || 0\n };\n switch (dismiss_condition_id) {\n case 1:\n data.type = 'TeacherEnrollment';\n break;\n case 2:\n data.type = 'StudentEnrollment';\n break;\n case 3:\n data.type = 'IndividualUser';\n break;\n case 4:\n data.type = 'Course';\n break;\n case 5:\n data.type = 'TeacherEnrollment';\n break;\n case 6:\n data.type = 'StudentEnrollment';\n break;\n case 7:\n data.type = 'Course';\n break;\n default:\n data.type = 'Global';\n break;\n }\n const id = banner.get('id');\n addRecipientsToBanner(id, data);\n if (targeted) {\n this.send('navigate', 'targeted');\n }\n this.set('newBannerMessage', '');\n });\n },\n async deleteBanner(banner) {\n await $.get(`/interface/banners/${banner.id}/dismissAll`);\n return banner.destroyRecord();\n },\n setNewBannerTypeID() {\n // 'event' is the click event\n const typeName = event.target.value;\n if (typeName === 'user') {\n this.set('showUserDropDown', true);\n this.set('individualUserId', this.get('courseUsers').get('firstObject').get('id'));\n } else {\n this.set('showUserDropDown', false);\n }\n (true && !(Object.keys(bannerTypes).includes(typeName)) && Ember.assert('typeName is in bannerTypes', Object.keys(bannerTypes).includes(typeName)));\n const type = bannerTypes[typeName];\n const {\n dismiss_condition_id,\n is_global\n } = type;\n (true && !(typeof dismiss_condition_id !== 'undefined') && Ember.assert('Banner type has dismiss_condition_id', typeof dismiss_condition_id !== 'undefined'));\n (true && !(typeof is_global !== 'undefined') && Ember.assert('Banner type has is_global', typeof is_global !== 'undefined'));\n this.set('newBannerDismissCondition', type.dismiss_condition_id);\n this.set('newBannerIsGlobal', type.is_global);\n },\n setIndividual() {\n const user_id = event.target.value;\n this.set('individualUserId', user_id);\n },\n navigate(tabName) {\n $(\".admin-banner-nav\").removeClass(\"selected\");\n $(\"table.global-banners\").removeClass(\"hidden\");\n $(\"table.targeted-banners\").removeClass(\"hidden\");\n if (tabName === \"global\") {\n $(\".admin-banner-nav.global\").addClass(\"selected\");\n $(\"table.targeted-banners\").addClass(\"hidden\");\n } else {\n $(\".admin-banner-nav.targeted\").addClass(\"selected\");\n $(\"table.global-banners\").addClass(\"hidden\");\n }\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/admin/cache\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Controller.extend({\n quizUsers: [],\n selectedQuizUser: {},\n selectedQuiz: {},\n canFlushQuizCache: Ember.computed('selectedQuizUser', function () {\n if (Object.keys(this.get('selectedQuizUser')).length !== 0) {\n return true;\n }\n return false;\n }),\n sendFlushRequest: function (path, type = \"course\") {\n showElements('.floating-modal.admin .cache-management .status .in-progress');\n let basePath = \"/interface/system/flush/\";\n if (type === \"quiz\") {\n basePath = \"/interface/\";\n }\n return fetch(`${basePath}${path}`, {\n method: 'POST'\n }).then(response => response.json()).then(responseJSON => {\n this.flushResponseHandler(responseJSON);\n });\n },\n flushResponseHandler(retval) {\n hideElements('.floating-modal.admin .cache-management .status .in-progress');\n // Success\n if (retval === 'success') {\n // The cache call was successful\n showElements('.floating-modal.admin .cache-management .status .success');\n hideElements('.floating-modal.admin .cache-management .status .failure');\n // Refresh the browser\n setTimeout(() => {\n location.reload();\n }, 3000);\n } else {\n // Unsuccessful\n showElements('.floating-modal.admin .cache-management .status .failure');\n hideElements('.floating-modal.admin .cache-management .status .success');\n }\n },\n loadQuizUsers: async function (quizId) {\n let path = `/interface/sections/${this.get('session.section.id')}/quizzes/${quizId}/submission_users`,\n data = await $.get(path);\n if (data.quiz_submission_users.users) {\n let firstUserId = data.quiz_submission_users.users[0].id;\n this.setSelectedQuizUser(firstUserId);\n this.set('quizUsers', data.quiz_submission_users.users);\n }\n },\n setSelectedQuizUser: function (userId) {\n let thisUser = this.store.findRecord('user', userId);\n this.set('selectedQuizUser', thisUser);\n },\n setSelectedQuiz: function (quizId) {\n let thisQuiz = this.store.findRecord('quiz', quizId);\n this.set('selectedQuiz', thisQuiz);\n },\n actions: {\n toggleCacheClearConfirm: function (selector) {\n selector = `.${selector}`;\n let element = document.querySelector(selector);\n if (element) {\n element.classList.toggle('hidden');\n }\n },\n clearCache: function (type) {\n /* eslint-disable-next-line ember/no-get */\n let courseId = this.get('session.course.id'),\n /* eslint-disable-next-line ember/no-get */\n sectionId = this.get('session.section.id'),\n path;\n switch (type) {\n case 'lessons':\n path = `courses/${courseId}/modules`;\n break;\n case 'assignment':\n path = `courses/${courseId}/assignments`;\n break;\n case 'discussion':\n path = `courses/${courseId}/discussion_topics`;\n break;\n case 'events':\n path = `calendar_events?context_codes[]=course_${courseId}&all_events=true`;\n break;\n case 'enrollments':\n path = `sections/${sectionId}/enrollments`;\n break;\n case 'course':\n path = `courses/${courseId}`;\n break;\n case 'quiz':\n path = `quiz-admin/flush-context/${courseId}/${this.get('selectedQuiz.search_id')}/${this.get('selectedQuizUser.id')}`;\n break;\n }\n this.sendFlushRequest(path, type);\n },\n selectQuiz: async function (quizId) {\n $(\".quizzes .quiz-options .quiz-users\").removeClass(\"hidden\");\n this.setSelectedQuiz(quizId);\n await this.loadQuizUsers(quizId);\n },\n selectQuizUser: function (userId) {\n this.setSelectedQuizUser(userId);\n }\n }\n });\n function hideElements(selector) {\n for (let element of document.querySelectorAll(selector)) {\n element.classList.add('hidden');\n }\n }\n function showElements(selector) {\n for (let element of document.querySelectorAll(selector)) {\n element.classList.remove('hidden');\n }\n }\n});","define(\"bocce/controllers/classroom/lessons/admin/live\", [\"exports\", \"bocce/utilities/dialog\"], function (_exports, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Controller.extend({\n meetingKeyRequest: async function (event, meetingKey, action) {\n let eventId = event.get('id');\n /* eslint-disable-next-line ember/no-get */\n let path = `/interface/courses/${this.get('session.course.id')}/events/${eventId}/${action}/${meetingKey}`,\n currentMeetingKeys = event.get('meetingKeys') || [];\n let response = await fetch(path, {\n method: 'POST'\n });\n let responseBody = await response.json();\n switch (responseBody) {\n case 'added':\n currentMeetingKeys.push(meetingKey);\n event.set('meetingKeys', currentMeetingKeys);\n break;\n case 'deleted':\n {\n let index = currentMeetingKeys.indexOf(meetingKey);\n if (index > -1) {\n currentMeetingKeys.splice(index, 1);\n event.set('meetingKeys', currentMeetingKeys);\n }\n break;\n }\n default:\n Ember.debug(`Error making meeting key request: ${action} ID: ${eventId} meetingKey: ${meetingKey}`);\n break;\n }\n },\n actions: {\n toggleRemoveMeetingKeyConfirm: function (meetingKey) {\n let selector = `.event-delete-${meetingKey}-confirm`;\n let element = document.querySelector(selector);\n if (element) {\n element.classList.toggle('hidden');\n }\n },\n addMeetingKey: function (event) {\n (0, _dialog.default)('Add Meeting Key', ['Submit', 'Cancel'], `Enter the nine digit meeting key to add to \"${event.get('title')}\" ID: ${event.get('id')}`, true).then(meetingKey => {\n if (meetingKey.length < 9 || meetingKey.length > 12) {\n alert('Meeting Key must contain between 9 and 12 integers');\n return;\n }\n if (!/^\\d+$/.test(meetingKey)) {\n alert('Meeting Key must only contain numbers.');\n return;\n }\n this.meetingKeyRequest(event, meetingKey, 'addMeetingKey');\n });\n },\n removeMeetingKey: function (event, meetingKey) {\n this.send('toggleRemoveMeetingKeyConfirm', meetingKey);\n this.meetingKeyRequest(event, meetingKey, 'deleteMeetingKey');\n },\n refreshZoomUser: async function (user) {\n let path = `/interface/zoom/refresh_user/${user.email}/${user.id}`,\n response = await fetch(path);\n let zoomID = await response.text();\n $('.reset-user-results').text(`Success: Zoom ID Set to ${zoomID}, please refresh`);\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/admin/quizzes\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Controller.extend({\n quizUsers: null,\n selectedQuizId: \"\",\n selectedQuiz: null,\n selectedUser: null,\n result: null,\n page: 1,\n init(...args) {\n this._super(...args);\n this.set('operations', ['quiz-admin/unlock-for-next-attempt'\n\n //commented out for now. When putting this back, consider restricting it to a subset of users. \n //Also, rename this to 'delete-all-attempts'...'quiz-admin/delete-latest-attempt'\n ]);\n },\n loadQuizUsers: async function (quizId) {\n let path = `/interface/sections/${this.get('session.section.id')}/quizzes/${quizId}/submission_users`,\n data = await $.get(path);\n this.set('quizUsers', data.quiz_submission_users.users);\n },\n actions: {\n selectQuiz: function (quizId) {\n this.set('selectedQuizId', quizId);\n this.loadQuizUsers(quizId);\n let thisQuiz = this.store.findRecord('quiz', quizId);\n this.set('selectedQuiz', thisQuiz);\n this.set('selectedUser', null);\n },\n expandOperations: async function (userId) {\n let user = await this.store.findRecord('user', userId);\n user.set('quizAttemptMade', this.get('quizUsers').find(u => u.id == userId).attempt_made);\n\n //Hacky, but this is the best way I could find to get the quiz operations to re-render when the selected user changes.\n this.set('selectedUser', null);\n Ember.run.schedule('afterRender', () => {\n this.set('selectedUser', user);\n });\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/announcement-new\", [\"exports\", \"bocce/controllers/classroom/lessons/discussion-new\"], function (_exports, _discussionNew) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _discussionNew.default.extend({});\n});","define(\"bocce/controllers/classroom/lessons/conversation-new-with\", [\"exports\", \"bocce/controllers/classroom/lessons/conversation\"], function (_exports, _conversation) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _conversation.default.extend();\n});","define(\"bocce/controllers/classroom/lessons/conversation-new\", [\"exports\", \"bocce/mixins/discussable\", \"bocce/mixins/enmodal\", \"bocce/mixins/conversable\", \"bocce/mixins/editable\", \"bocce/mixins/uploadable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\"], function (_exports, _discussable, _enmodal, _conversable, _editable, _uploadable, _audioRec, _videoRec, _rtcRec) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n window.conversationHeartbeatFrequency = 3 * 1000;\n var _default = _exports.default = Ember.Controller.extend(_conversable.default, _enmodal.default, _discussable.default, _editable.default, _uploadable.default, _audioRec.default, _videoRec.default, _rtcRec.default, {\n init(...args) {\n this._super(...args);\n this.recipients = this.recipients || [];\n },\n conversation: Ember.inject.controller('classroom.lessons.conversation'),\n conversations: Ember.inject.controller(),\n userprofileService: Ember.inject.service('userprofile'),\n discussions: Ember.inject.controller(),\n // required by EditableMixin\n classroom: Ember.inject.controller(),\n // required by DiscussableMixin and EnmodalMixin\n discussion: Ember.inject.controller('classroom.lessons.discussion'),\n // required by DiscussableMixin\n newDiscussion: Ember.inject.controller('classroom.lessons.discussion-new'),\n // required by DiscussableMixin\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n\n scrollAndUpdateLocalConversation: function (conversation) {\n Ember.run.later(function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.existing-conversation .modal-content').animate({\n scrollTop: Ember.$('.existing-conversation .modal-content').prop('scrollHeight')\n }, 1000);\n }, 500);\n conversation.set('last_authored_message_at', new Date());\n conversation.set('last_message_at', new Date());\n conversation.set('message_count', conversation.get('message_count') + 1);\n },\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n conversationModalContentMarginTop: Ember.computed('model', function () {\n /* eslint-disable-next-line ember/no-get */\n let participantRelationships = this.get('model.participantRelationships');\n return participantRelationships * 26;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n sharedEnrollments: Ember.computed('model', function () {\n let conversation = this.model,\n retval = [],\n /* eslint-disable-next-line ember/no-get */\n currentUser = this.get('session.user'),\n sectionCodes = currentUser.get('sectionCodes');\n if (conversation) {\n let participantRelationships = conversation.get('participantRelationships') || [];\n participantRelationships.forEach(participantRelationship => {\n if (!sectionCodes) {\n return;\n }\n sectionCodes.forEach(sectionCode => {\n if (sectionCode.sectionId === participantRelationship.section) {\n retval.push({\n sectionId: sectionCode.sectionId,\n relationship: participantRelationship.relationship,\n courseTitle: sectionCode.courseTitle,\n sectionLabel: sectionCode.sectionLabel\n });\n }\n });\n });\n }\n return retval;\n }),\n privateConversationHeartbeat: function () {\n let store = this.store,\n heartbeat = () => {\n /* eslint-disable-next-line ember/no-get */\n let currentConversationId = this.get('model.id');\n store.nestResources('conversation-message', [{\n 'conversation': currentConversationId\n }]);\n\n // Load in conversation messages\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.get('/interface/conversations/' + currentConversationId).then(resolvedData => {\n let data = resolvedData,\n loaded_messages = store.peekAll('conversation_message'),\n ids = loaded_messages.map(e => e.id),\n to_push = [];\n for (let msg of data.conversation_message) {\n if (ids.indexOf(msg.id.toString()) === -1) {\n to_push.push(msg);\n }\n }\n if (to_push.length > 0) {\n store.pushPayload({\n 'conversation_message': to_push\n });\n Ember.run.later(function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.existing-conversation .modal-content').animate({\n scrollTop: Ember.$('.existing-conversation .modal-content').prop('scrollHeight')\n }, 1000);\n }, 500);\n }\n }).always(() => {\n // When the modal is open, 'currentConversationId' is always set on the\n // conversation controller. When the modal is closed, this value is cleared out.\n // Only heartbeat if there is something stored in this value. - JRW\n if (this.currentConversationId) {\n Ember.run.later(this, heartbeat, window.conversationHeartbeatFrequency);\n }\n });\n };\n window.runPMHeartbeat = heartbeat;\n Ember.run.later(this, heartbeat, window.conversationHeartbeatFrequency);\n },\n actions: {\n autoComplete: function (param) {\n param = param.trim();\n if (param !== '' && param.length > 2) {\n let handle = this.autoCompleteTimeoutHandle;\n if (handle) {\n Ember.run.cancel(handle);\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list .loading-graphic').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list .no-results').addClass('hidden');\n this.set('filteredList', []);\n var that = this;\n\n // Wait a moment before we spam the server with searches for \"jam\", \"jame\", and \"james\"\n // all at once\n handle = Ember.run.later(() => {\n param = param.toLowerCase();\n param = encodeURIComponent(param);\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'get',\n param: param,\n url: '/interface/user-search/' + param,\n success: function (results) {\n let premd = that.store.peekAll('conversation').filter(conversation => {\n let matching_conversations = conversation.get('participants').filter(user => {\n if (typeof this.param !== 'string') {\n return false;\n }\n let lc = this.param.toLowerCase();\n return user.get('name').toLowerCase().indexOf(lc) > -1;\n });\n return matching_conversations > 0;\n });\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list .loading-graphic').addClass('hidden');\n let res = results.length < 8 ? results : results.slice(0, 7);\n res = premd.concat(res);\n that.set('filteredList', res);\n if (results.length === 0) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list .no-results').removeClass('hidden');\n }\n },\n error: function (error) {\n Ember.debug('Error in conversation recipient list.');\n Ember.debug(error);\n }\n });\n }, 333);\n this.set('autoCompleteTimeoutHandle', handle);\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list .loading-graphic').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.recipient-filter-list .no-results').addClass('hidden');\n this.set('filteredList', []);\n }\n },\n // This choose action represents a user picking a\n // recipient from the autocomplete list above the conversations list\n startConversation: function (recipient) {\n this.set('conversation.uploadInProgress', true);\n let recipArray = [],\n recipURL = '';\n if (recipient) {\n recipArray = [recipient];\n }\n this.recipients.forEach(function (ob) {\n recipArray.push(ob.id);\n });\n recipURL += recipArray.join('-');\n this.set('recipients', []);\n\n // Check if a conversation with the user already exists\n let conversations = this.store.peekAll('conversation'),\n existing = conversations.find(function (c) {\n let ob = c.get('conversationPartners');\n if (ob.get('length') > 0) {\n ob = ob.toArray();\n }\n if (!Array.isArray(ob) || !Array.isArray(recipArray) || recipArray.length !== ob.length) {\n return false;\n }\n let arr1 = ob.concat().sort(),\n arr2 = recipArray.concat().sort(),\n i;\n for (i = 0; i < arr1.length; i++) {\n if (parseInt(arr1[i].get('id')) !== parseInt(arr2[i])) {\n return false;\n }\n }\n return true;\n });\n if (existing) {\n if (existing.get('archived')) {\n existing.set('workflow_state', 'active');\n existing.save();\n }\n this.send('viewConversation', existing.id);\n } else {\n this.send('createConversation', recipURL);\n }\n this.set('conversation.uploadInProgress', false);\n },\n choose: function (recipient) {\n this.recipients.pushObject(recipient);\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/conversation\", [\"exports\", \"bocce/mixins/discussable\", \"bocce/mixins/enmodal\", \"bocce/mixins/conversable\", \"bocce/mixins/editable\", \"bocce/mixins/uploadable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/helpers/heartbeat\", \"bocce/utilities/dialog\"], function (_exports, _discussable, _enmodal, _conversable, _editable, _uploadable, _audioRec, _videoRec, _rtcRec, _heartbeat, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n window.conversationHeartbeatFrequency = 3 * 1000;\n var _default = _exports.default = Ember.Controller.extend(_conversable.default, _enmodal.default, _discussable.default, _editable.default, _uploadable.default, _audioRec.default, _videoRec.default, _rtcRec.default, {\n userprofileService: Ember.inject.service('userprofile'),\n kalturaService: Ember.inject.service('kaltura-upload'),\n conversationNew: Ember.inject.controller('classroom.lessons.conversation-new'),\n filteredList: Ember.computed.alias('conversationNew.filteredList'),\n discussions: Ember.inject.controller(),\n dashboard: Ember.inject.controller(),\n submission: Ember.inject.controller('classroom.lessons.submission'),\n // loadHeartbeatData uses this\n classroom: Ember.inject.controller(),\n // required by DiscussableMixin\n discussion: Ember.inject.controller('classroom.lessons.discussion'),\n // required by DiscussableMixin\n newDiscussion: Ember.inject.controller('classroom.lessons.discussion-new'),\n // required by DiscussableMixin\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n init(...args) {\n this._super(...args);\n },\n currentConversationId: Ember.computed.reads('model.id'),\n inlineInbox: Ember.computed('model', 'target.currentRouteName', function () {\n let currentRoute = this.get('target.currentRouteName');\n return currentRoute === \"dashboard\";\n }),\n scrollAndUpdateLocalConversation: function (conversation) {\n Ember.run.later(function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.existing-conversation .modal-content').animate({\n scrollTop: Ember.$('.existing-conversation .modal-content').prop('scrollHeight')\n }, 1000);\n }, 500);\n conversation.set('last_authored_message_at', new Date());\n conversation.set('last_message_at', new Date());\n conversation.set('message_count', conversation.get('message_count') + 1);\n },\n sortedMessages: Ember.computed.sort('model.messages', ['model.messages.[]'], (a, b) => {\n let id_a = parseInt(a.get('id'), 10),\n id_b = parseInt(b.get('id'), 10);\n return id_a < id_b ? -1 : 1;\n }),\n conversationModalContentMarginTop: Ember.computed('model.{participants.[],participantRelationships.[]}', function () {\n /* eslint-disable-next-line ember/no-get */\n if (this.get('model.participants.length') > 2) {\n return 0;\n }\n /* eslint-disable-next-line ember/no-get */\n let participantRelationships = this.get('model.participantRelationships.length');\n return participantRelationships * 26;\n }),\n sharedEnrollments: Ember.computed('model.{participants.[],participantRelationships.@each.section}', 'session.user.sectionCodes.[]', function () {\n let conversation = this.model,\n retval = [],\n /* eslint-disable-next-line ember/no-get */\n currentUser = this.get('session.user'),\n sectionCodes = currentUser.get('sectionCodes');\n if (conversation) {\n if (conversation.get('participants.length') > 2) {\n return [];\n }\n let participantRelationships = conversation.get('participantRelationships') || [];\n participantRelationships.forEach(participantRelationship => {\n if (!sectionCodes) {\n return;\n }\n sectionCodes.forEach(sectionCode => {\n if (sectionCode.sectionId === participantRelationship.section) {\n retval.push({\n sectionId: sectionCode.sectionId,\n relationship: participantRelationship.relationship,\n courseTitle: sectionCode.courseTitle,\n sectionLabel: sectionCode.sectionLabel\n });\n }\n });\n });\n }\n return retval;\n }),\n actions: {\n addFile: function (file) {\n if (file.type.includes('video')) {\n let encoding_video = {\n name: file.name,\n percent_uploaded: 0\n };\n this.encoding_videos.pushObject(encoding_video);\n let fileIndex = this.encoding_videos.length - 1;\n this.kalturaUploadVideo(file, async () => {\n Ember.debug('Unable to upload video to Kaltura. Uploading to S3...');\n await (0, _dialog.default)('Video embedding unsuccessful; this file will be available for download only.', ['OK']);\n this.encoding_videos.removeObject(encoding_video);\n file.ignoreDownloadOnlyPrompt = true;\n this.send('addValidFile', file);\n }, fileIndex, URL.createObjectURL(file), encoding_video);\n } else if (this.mimeTypes.indexOf(file.type) === -1) {\n this.send('addValidFile', file);\n } else {\n this.send('addInvalidFile', file);\n }\n },\n deleteAttachment: function (file) {\n this.send('deleteFile', file);\n },\n renameAttachment: function (attachment, newName) {\n if (attachment.file?.isUrl) {\n this.kalturaService.kalturaRenameVideo(attachment.uploaded_id, newName);\n Ember.set(attachment, 'name', newName);\n } else {\n Ember.set(attachment, 'name', newName);\n this.store.findRecord('attachment', attachment.uploaded_id).then(loadedAttachment => {\n loadedAttachment.set('name', newName);\n loadedAttachment.save();\n });\n }\n },\n closeModals: function () {\n this._super();\n if (this.target.currentRouteName === \"dashboard\") {\n this.dashboard.set('messagePanelOn', false);\n }\n return;\n },\n startPrivateConversationHeartbeat: function () {\n window.runPMHeartbeat = () => {\n const convo_id = this.currentConversationId,\n messages = this.sortedMessages;\n if (!convo_id || !messages) {\n return;\n }\n const since_id = messages[messages.length - 1]?.id || 0;\n (0, _heartbeat.default)([`/interface/conversations/${convo_id}/conversation-messages/since/${since_id}`], this).always(() => {\n // When the modal is open, 'currentConversationId' is always set on the\n // conversation controller. When the modal is closed, this value is cleared out.\n // Only heartbeat if there is something stored in this value. - JRW\n Ember.run.later(this, window.runPMHeartbeat, window.conversationHeartbeatFrequency);\n });\n };\n Ember.run.later(this, window.runPMHeartbeat, window.conversationHeartbeatFrequency);\n },\n loadMoreMessages: function (defer) {\n // call \"convoController.send('loadMoreMessages')\" in console to test\n window.convoController = this;\n\n /* eslint-disable-next-line ember/no-get */\n let loaded = this.get('session.loadedMessages') || [];\n\n /* eslint-disable-next-line ember/no-get */\n if (loaded.indexOf(this.get('model.id')) >= 0) {\n if (defer) {\n defer.resolve();\n }\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.loading-prv').removeClass('active');\n return;\n }\n\n /* eslint-disable-next-line ember/no-get */\n let messages = this.get('model.messages');\n if (!messages) {\n if (defer) {\n defer.resolve();\n }\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.loading-prv').removeClass('active');\n return;\n }\n let num_loaded = messages.get('length'),\n /* eslint-disable-next-line ember/no-get */\n user_id = this.get('session.user.id'),\n args = '';\n if (num_loaded > 0) {\n let min_id;\n for (let i = 0; i < num_loaded; i++) {\n let id = parseInt(messages.objectAt(i).get('id'), 10);\n if (!min_id || id < min_id) {\n min_id = id;\n }\n }\n args = `?page=${min_id}`;\n }\n setTimeout(() => {\n /* eslint-disable-next-line ember/no-jquery, ember/no-get */\n Ember.$.get(`/interface/conversations/${this.get('model.id')}/conversation-messages${args}`).then(data => {\n if (!data || !data.conversation_message || data.conversation_message.length === 0) {\n /* eslint-disable-next-line ember/no-get */\n loaded.push(this.get('model.id'));\n this.set('session.loadedMessages', loaded);\n } else {\n for (let m of data.conversation_message) {\n let auth_id = Number(m.author_id);\n if (auth_id === Number(user_id)) {\n m.belongsToSelf = true;\n }\n }\n this.store.pushPayload(data);\n }\n if (defer) {\n defer.resolve();\n }\n\n /* eslint-disable-next-line ember/no-jquery, ember/no-incorrect-calls-with-inline-anonymous-functions */\n Ember.run.scheduleOnce(() => {\n Ember.$('.loading-prv').removeClass('active');\n });\n });\n }, 250);\n },\n scrollToBottom: function () {\n /* eslint-disable-next-line ember/no-incorrect-calls-with-inline-anonymous-functions */\n Ember.run.scheduleOnce('afterRender', this, function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.existing-conversation .modal-content').animate({\n scrollTop: Ember.$('.existing-conversation .modal-content').prop('scrollHeight')\n }, 500);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.modal-content').on('scrollstop', tgt => {\n if (tgt.target.scrollTop === 0 && this.nxtScroll) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.loading-prv').addClass('active');\n this.send('loadMoreMessages');\n } else if (!this.nxtScroll) {\n this.set('nxtScroll', true);\n }\n });\n });\n },\n autoComplete: function (param) {\n this.conversationNew.send('autoComplete', param);\n },\n choose: function (recipient) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n /* eslint-disable-next-line ember/no-get */\n url: '/interface/conversations/' + this.get('model.id') + '/addRecipient/' + recipient.id,\n success: () => {\n this.model.reload();\n Ember.debug('Recipient Added sucessfully');\n },\n error: error => {\n Ember.debug('Unable to add recipient. Something went wrong: ');\n Ember.debug(error);\n }\n });\n this.set('adding_member', false);\n },\n addUserToggle: function () {\n if (this.adding_member) {\n this.set('adding_member', false);\n } else {\n this.set('adding_member', true);\n }\n },\n toggleArchived: function () {\n let convo = this.model;\n let isArchived = convo.get('archived');\n let un = isArchived ? 'un' : '';\n let dialogMessage = 'Are you sure you wish to ' + un + 'archive this conversation? To undo this action later, click the folder icon again.';\n (0, _dialog.default)(dialogMessage, ['Yes', 'No']).then(selection => {\n if (selection === 'Yes') {\n convo.set('workflow_state', isArchived ? 'active' : 'archived');\n convo.save();\n }\n });\n },\n updateConversation: function () {\n var message = this.parseEmbed(this.bodyInput);\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$.trim(message.replace(/ /g, ' ').replace(//g, ' ')).length === 0) {\n return;\n }\n message = message.replace('', '');\n message = message.trim();\n\n // Add any video embeds\n message = message + this.videoEmbedString;\n this.set('uploadInProgress', true);\n let current_conversation = this.model,\n conversationPartners = current_conversation.get('conversationPartners');\n\n // Uploading attachments\n this.set('uploadInProgress', true);\n\n // The first message is saved along with the original conversation\n if (current_conversation.get('isNew')) {\n current_conversation.set('lastActivity', new Date());\n current_conversation.set('new_message_body', message);\n if (!this.working && this.file_ids.length > 0) {\n current_conversation.set('attachments', this.file_ids);\n }\n current_conversation.save().then(savedConversation => {\n this.set('bodyInput', '');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.rte-editor-input').html('');\n this.send('clearAllFiles');\n this.set('uploadInProgress', false);\n savedConversation.set('last_authored_message', message);\n }, err => {\n Ember.debug(err, 'Create new conversation to ' + conversationPartners.id);\n this.set('uploadInProgress', false);\n window.alert('Unable to post message. Please try again.');\n }).then(() => {\n this.transitionToRoute('classroom.lessons.conversation', current_conversation.get('id'));\n });\n } else {\n this.store.nestResources('conversation-message', [{\n 'conversation': current_conversation.get('id')\n }]);\n let new_conversation_message = current_conversation.store.createRecord('conversation_message');\n new_conversation_message.set('body', message);\n new_conversation_message.set('createdAt', new Date());\n new_conversation_message.set('belongsToSelf', true);\n new_conversation_message.set('conversation', current_conversation.get('id'));\n new_conversation_message.set('conversationPartners', conversationPartners);\n if (!this.working && this.file_ids.length > 0) {\n new_conversation_message.set('attachments', this.file_ids);\n }\n new_conversation_message.save().then(() => {\n this.set('bodyInput', '');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.rte-editor-input').html('');\n this.send('clearAllFiles');\n this.set('uploadInProgress', false);\n current_conversation.set('last_authored_message', message);\n current_conversation.set('last_message', message);\n current_conversation.set('lastActivity', new Date());\n }, err => {\n Ember.debug({\n Location: 'Post conversation reply',\n Status: err.status,\n Message: err.message,\n Error: err.responseText\n });\n this.set('uploadInProgress', false);\n window.alert('Unable to post message. Please try again.');\n }).then(() => {\n this.scrollAndUpdateLocalConversation(current_conversation);\n });\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.pop-attachment-drawer').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.floating-modal.active').removeClass('drawer-open');\n },\n deleteUnsentConversation: function () {\n let current_conversation = this.model;\n if (current_conversation && current_conversation.get('isNew')) {\n current_conversation.destroyRecord();\n }\n },\n destroyEditor: function () {\n //This editorDestroyed logic is necessary because this method gets called twice,\n //once by 'closeModals', and the other time by 'destroyer'\n if (!this.editorDestroyed) {\n this._super();\n this.send('deleteUnsentConversation');\n\n // Ensure that the heartbeat stops\n this.set('model', null);\n this.editorDestroyed = true;\n }\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/discussion-new\", [\"exports\", \"bocce/mixins/enmodal\"], function (_exports, _enmodal) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default, {\n classroom: Ember.inject.controller(),\n discussions: Ember.inject.controller(),\n lessons: Ember.inject.controller('classroom.lessons') // required by UploadableMixin\n });\n});","define(\"bocce/controllers/classroom/lessons/discussion\", [\"exports\", \"bocce/mixins/editable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/uploadable\", \"bocce/mixins/discussable\", \"bocce/mixins/enmodal\", \"bocce/utilities/dialog\"], function (_exports, _editable, _audioRec, _videoRec, _rtcRec, _uploadable, _discussable, _enmodal, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-observers */\n var _default = _exports.default = Ember.Controller.extend(_editable.default, _audioRec.default, _videoRec.default, _rtcRec.default, _uploadable.default, _discussable.default, _enmodal.default, {\n init(...args) {\n this._super(...args);\n this.preExistingRespIds = this.preExistingRespIds || [];\n this.selectedPollIds = this.selectedPollIds || [];\n },\n userprofileService: Ember.inject.service('userprofile'),\n kalturaService: Ember.inject.service('kaltura-upload'),\n classroom: Ember.inject.controller(),\n // required by DiscussableMixin\n discussion: Ember.inject.controller('classroom.lessons.discussion'),\n // required by DiscussableMixin\n newDiscussion: Ember.inject.controller('classroom.lessons.discussion-new'),\n // required by DiscussableMixin\n discussions: Ember.inject.controller(),\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n\n prevResp: false,\n isWorking: false,\n parentsLoaded: false,\n childrenLoaded: false,\n /* eslint-disable-next-line ember/no-observers */\n pollLoader: Ember.observer('model.poll_id', function () {\n // EF Temp fix:\n // Wait on full render before tryign to gather poll data to ensure the entire record is loaded\n //this.model.reload().then(() => {\n if (!this.model || !this.model.id) {\n return;\n }\n // TODO (NK): This is a workaround to fix issues with timing/reloading the\n // I'm leaving it as-is for the ember 3 upgrade, but this should be rewritten\n // to depend on the normal route lifecycle instead of making another request\n // and using the runloop.\n /* eslint-disable-next-line ember/no-jquery */\n this.store.findRecord('discussion', this.model.id, {\n reload: true\n }).then(d => {\n Ember.run.schedule('afterRender', this, () => {\n let pr = d.poll_raw;\n if (pr && pr.length > 0) {\n pr = JSON.parse(pr);\n if (pr.did_vote) {\n pr.voter_info = JSON.parse(pr.did_vote);\n }\n this.set('model.poll', pr);\n }\n });\n });\n }),\n /* eslint-disable-next-line ember/no-observers */\n didVoteObserver: Ember.observer('model.poll.voters', function () {\n /* eslint-disable-next-line ember/no-get */\n let did_vote = this.get('model.poll.did_vote');\n if (typeof did_vote === 'string') {\n this.set('model.poll.did_vote', JSON.parse(did_vote));\n }\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n voterData: Ember.computed('model.poll.voters', function () {\n /* eslint-disable-next-line ember/no-get */\n let data = this.get('model.poll.voters');\n if (data && data.length > 0) {\n data = JSON.parse(data);\n for (let i = 0; i < data.length; i++) {\n data[i].user = this.store.findRecord('user', data[i].user_id);\n }\n return data;\n }\n return [];\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n myVote: Ember.computed('model.poll.voters', function () {\n /* eslint-disable-next-line ember/no-get */\n let data = this.get('model.poll.did_vote');\n if (data && data.length > 0) {\n data = JSON.parse(data);\n if (data.length > 0) {\n return data[0];\n }\n }\n return [];\n }),\n /* eslint-disable-next-line ember/no-observers */\n updateLesson: Ember.observer('model.message', function () {\n /* eslint-disable-next-line ember/no-get */\n if (this.get('model.id')) {\n /* eslint-disable-next-line ember/no-get */\n this.store.findRecord('item', this.get('model.id')).then(item => {\n var lessonId = item.get('lesson') ? item.get('lesson').id : false;\n if (lessonId) {\n this.set('isLesson', lessonId);\n /* eslint-disable-next-line ember/no-get */\n this.set('topicId', this.get('model.id'));\n }\n }, () => {\n // FAIL SILENTLY -- if no failure callback present, app borks\n });\n }\n }),\n // Returns an array of top-level responses, ie replies to the main discussion thread.\n topLevelResponses: Ember.computed('model.responses.@each.parent', 'session.user.id', 'session.{isInstructor,isLimitedStudentTerm}', function () {\n /* eslint-disable-next-line ember/no-get */\n let responses = this.get('model.responses').toArray();\n let limited = this.get(\"session.isLimitedStudentTerm\") && !this.get(\"session.isInstructor\");\n let currentUserId = this.get('session.user.id') ? parseInt(this.get('session.user.id')) : false;\n let top_level = responses.filter(response => {\n let parent = parseInt(response.get('parent'));\n let respUserId = response.get('user.id') ? parseInt(response.get('user.id')) : false;\n if (!parent) {\n let instructorIds = response.get('instructor_ids') || [];\n if (limited && respUserId !== currentUserId && !instructorIds.includes(respUserId)) {\n return false;\n }\n return true;\n } else {\n // If there's a parent ID, make sure that the parent exists.\n // response.some(...) here returns true if the parent is found.\n return !responses.some(r => parseInt(r.get('id')) === parent);\n }\n });\n top_level.sort((a, b) => {\n return a.get('date') - b.get('date');\n });\n this.set('parentsLoaded', true);\n return top_level;\n }),\n // Returns an object whose keys are top-level response IDs (see topLevelResponses),\n // and whose values are arrays of child responses. For example:\n // {\n // 1234: [\n // { /* child response */ },\n // { /* child response */ },\n // ]\n // }\n childResponses: Ember.computed('model.responses.@each.parent', 'session.user.id', 'session.{isInstructor,isLimitedStudentTerm}', 'topLevelResponses', function () {\n /* eslint-disable-next-line ember/no-get */\n let responses = this.get('model.responses').toArray();\n let parent_ids = this.topLevelResponses.map(r => parseInt(r.get('id')));\n let limited = this.get(\"session.isLimitedStudentTerm\") && !this.get(\"session.isInstructor\");\n let currentUserId = this.get('session.user.id') ? parseInt(this.get('session.user.id')) : false;\n let childResponses = {};\n // For each top level response, find all children of that response.\n for (let parent_id of parent_ids) {\n // look for responses that point to the current top-level response as a parent\n childResponses[parent_id] = responses.filter(function (response) {\n if (limited) {\n let instructorIds = response.get('instructor_ids') || [];\n let respUserID = response.get('user.id') ? parseInt(response.get('user.id')) : false;\n if (respUserID !== currentUserId && !instructorIds.includes(respUserID)) {\n return false;\n }\n }\n return parseInt(response.get('parent')) === parent_id;\n });\n // sort them by date!\n childResponses[parent_id].sort((a, b) => a.get('date') - b.get('date'));\n }\n this.set('childrenLoaded', true);\n return childResponses;\n }),\n responsesLoaded: Ember.computed.and('parentsLoaded', 'childrenLoaded'),\n message: Ember.computed.alias('model.message'),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n isInstructorAndOwner: Ember.computed('model.user', function () {\n /* eslint-disable-next-line ember/no-get */\n return this.get('model.user.id') === this.get('session.user.id') && this.get('session.isInstructor');\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n truncated: Ember.computed('message', function () {\n if (this.message.length > 1000) {\n return true;\n }\n return false;\n }),\n actions: {\n addFile: function (file) {\n if (file.type.includes('video')) {\n let encoding_video = {\n name: file.name,\n percent_uploaded: 0\n };\n this.encoding_videos.pushObject(encoding_video);\n let fileIndex = this.encoding_videos.length - 1;\n this.kalturaUploadVideo(file, async () => {\n Ember.debug('Unable to upload video to Kaltura. Uploading to S3...');\n await (0, _dialog.default)('Video embedding unsuccessful; this file will be available for download only.', ['OK']);\n this.encoding_videos.removeObject(encoding_video);\n file.ignoreDownloadOnlyPrompt = true;\n this.send('addValidFile', file);\n }, fileIndex, URL.createObjectURL(file), encoding_video);\n } else if (this.mimeTypes.indexOf(file.type) === -1) {\n this.send('addValidFile', file);\n } else {\n this.send('addInvalidFile', file);\n }\n },\n removeAttachment: function (file) {\n this.send('deleteFile', file);\n },\n renameAttachment: function (attachment, newName) {\n if (attachment.file?.isUrl) {\n this.kalturaService.kalturaRenameVideo(attachment.uploaded_id, newName);\n Ember.set(attachment, 'name', newName);\n } else {\n Ember.set(attachment, 'name', newName);\n this.store.findRecord('attachment', attachment.uploaded_id).then(loadedAttachment => {\n loadedAttachment.set('name', newName);\n loadedAttachment.save();\n });\n }\n },\n destroyEditor: function () {\n this._super();\n this.set('replyId', false);\n },\n deleteAttachment(courseId, discussionId, attachmentId, type, recordId, userId) {\n const message = 'Are you sure you wish to delete this attachment? This action CANNOT be undone!';\n const self = this;\n (0, _dialog.default)(message, [`Yes. Soft delete.`, `Yes. Leave no trace. But, before you click this button, manually delete the file from S3. Attachment id: ${attachmentId}.`, 'No']).then(choice => {\n if (choice.indexOf('Yes') === 0) {\n let destroy = choice === 'Yes. Leave no trace.';\n self.set('isWorking', true);\n this.store.findRecord('attachment', attachmentId).then(record => {\n this.store.nestResources('attachment', [{\n course: courseId\n }, {\n discussion: discussionId + \":\" + recordId\n }, {\n user: userId\n }, {\n destroy\n }, {\n type\n }]);\n try {\n record.destroyRecord().then(() => {\n this.store.findRecord(type, recordId).then(function (record) {\n let currentAttachmentIds = record.get('attachments');\n record.set('attachments', currentAttachmentIds.filter(a => a != attachmentId));\n self.set('isWorking', false);\n });\n this.store.nestResources('attachment', []);\n }).catch(() => {\n (0, _dialog.default)('Problem occurred deleting attachment. Please reload the page and try again.');\n self.set('isWorking', false);\n });\n } catch (e) {\n (0, _dialog.default)('Problem occurred deleting attachment. Please reload the page and try again.');\n self.set('isWorking', false);\n }\n });\n }\n });\n },\n viewInLesson: function (lesson) {\n this.transitionToRoute('classroom.lessons', lesson, this.topicId);\n },\n replyToDiscussion: function (arg) {\n this.set('replyId', arg.id);\n this.set('replyText', arg.text);\n this.set('replyAuthor', arg.user);\n let rte = document.querySelector('.rte-editor-input');\n if (rte) {\n rte.focus();\n }\n },\n replyCancel: function () {\n this.set('replyId', false);\n this.set('replyText', '');\n this.set('replyAuthor', '');\n },\n togglePollId: function (poll_choice_id, indx) {\n /* eslint-disable-next-line ember/no-get */\n if (this.get('model.poll.old_poll')) {\n this.set('selectedPollIds', []);\n this.set('selectedPollIds.' + indx, poll_choice_id);\n } else {\n if (!this.get('selectedPollIds.' + indx)) {\n this.set('selectedPollIds.' + indx, poll_choice_id);\n } else {\n this.set('selectedPollIds.' + indx, null);\n }\n }\n Ember.notifyPropertyChange(this, 'selectedPollIds');\n },\n votePoll: async function (poll_id) {\n let choices = this.selectedPollIds;\n let choicesArray = [];\n for (let i = 0; i < choices.length; i++) {\n choicesArray.push({\n poll_choice_id: choices[i]\n });\n }\n let voteInPollElem = document.querySelectorAll('.vote-in-poll');\n for (let e of voteInPollElem) {\n e.classList.add('submitting');\n }\n let response = await fetch(`/interface/vote_in_poll/${poll_id}`, {\n method: 'POST',\n body: JSON.stringify({\n poll_submissions: choicesArray\n })\n });\n if (!response.ok) {\n Ember.debug('Unable to vote in poll. Something went wrong:', response.statusText);\n }\n this.pollLoader();\n for (let e of voteInPollElem) {\n e.classList.remove('submitting');\n }\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/event-new\", [\"exports\", \"bocce/config/environment\", \"bocce/mixins/discussable\", \"bocce/mixins/editable\", \"bocce/mixins/uploadable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/nested-resources\", \"bocce/mixins/enmodal\", \"bocce/mixins/webex\", \"bocce/utilities/dialog\", \"lodash.isequal\"], function (_exports, _environment, _discussable, _editable, _uploadable, _audioRec, _videoRec, _rtcRec, _nestedResources, _enmodal, _webex, _dialog, _lodash) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.sendEventsToInterfaceAndSendEmail = sendEventsToInterfaceAndSendEmail;\n // app/controllers/classroom/lessons/event-new.js\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n // create the default start time; default\n // is the next half-hour increment\n function defaultTime() {\n let start = moment().tz('America/New_York').add(5, 'minutes');\n start.utcOffset(-5);\n if (start.minutes() < 30) {\n start.startOf('hour').minute(30);\n } else {\n start.startOf('hour').add(1, 'hour');\n }\n return start;\n }\n let baseEventDate = defaultTime();\n var _default = _exports.default = Ember.Controller.extend(_discussable.default, _enmodal.default, _editable.default, _uploadable.default, _audioRec.default, _videoRec.default, _rtcRec.default, _nestedResources.default, _webex.default, {\n init(...args) {\n this._super(...args);\n this.eventsModel = this.eventsModel || Ember.ArrayProxy.create({\n content: Ember.A([{\n bodyInput: '',\n titleInput: '',\n startDate: baseEventDate,\n additionalSectionsCourseIds: Ember.ArrayProxy.create({\n content: Ember.A([])\n }),\n shown: true\n }])\n });\n this.savedData = this.savedData || [{\n title: '',\n body: ''\n }];\n this.set('contentNotDirty', true);\n this.extraSectionToggled = false;\n this.showTemplateManager = false;\n this.templates.onTemplateChange = this.actions.changeTemplate.bind(this);\n this.templates.onDeleteTemplate = this.actions.onDeleteTemplate.bind(this);\n },\n //Calculated event properties\n events: Ember.computed('eventsModel.@each.{bodyInput,titleInput,startDate,shown,additionalSectionsCourseIds}', function () {\n for (let i = 0; i < this.eventsModel.length; i++) {\n let event = this.eventsModel.objectAt(i);\n if (event.startDate) {\n Ember.set(event, 'startDate_date', event.startDate.format('YYYY-MM-DD'));\n Ember.set(event, 'startDate_time', event.startDate.format('HH:mm'));\n }\n Ember.set(event, 'hideCascadeDate', event === this.eventsModel.lastObject);\n Ember.set(event, 'disableCascadeDate', !!!event.startDate);\n const bodyWithSpaceForNewLines = event.bodyInput.replaceAll(/<\\s*br\\s*\\/?>/g, ' ');\n Ember.set(event, 'bodyInputTextOnly', Ember.$('').html(bodyWithSpaceForNewLines).text());\n Ember.set(event, 'incomplete', !!!event.startDate || !event.titleInput || !event.bodyInput);\n }\n return this.eventsModel;\n }),\n //Keep the 'contentNotDirty' property up-to-date\n eventsUpdated: Ember.observer('eventsModel.@each.{bodyInput,titleInput,startDate,shown,additionalSectionsCourseIds}', function () {\n const contentNotDirty = (0, _lodash.default)(this.savedData, this.events.map(em => ({\n title: em.titleInput,\n body: em.bodyInput\n })));\n this.set('contentNotDirty', contentNotDirty);\n }),\n editorHasContent: Ember.computed('contentNotDirty', function () {\n return !this.get('contentNotDirty');\n }),\n application: Ember.inject.controller(),\n templates: Ember.inject.service(),\n classroom: Ember.inject.controller(),\n // required by DiscussableMixin\n discussion: Ember.inject.controller('classroom.lessons.discussion'),\n // required by DiscussableMixin\n newDiscussion: Ember.inject.controller('classroom.lessons.discussion-new'),\n // required by DiscussableMixin\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n\n modalTitle: Ember.computed('session.isInstructor', function () {\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.isInstructor')) {\n return 'New Live Class';\n } else {\n return 'New Live Session';\n }\n }),\n frequencyText: 'weeks',\n sectionDrawerToggled: false,\n readyToSave: Ember.computed('eventsModel.@each.{bodyInput,titleInput,startDate}', 'classroom.activeNewEvent', 'isWorking', function () {\n let readyToSave = true;\n const events = this.get('events');\n const self = this;\n events.forEach(event => {\n readyToSave = readyToSave && Ember.$.trim(event.titleInput.replace(/ /g, ' ').replace(//g, ' ')).length > 0 && /* eslint-disable-next-line ember/no-jquery */\n Ember.$.trim(event.bodyInput.replace(/ /g, ' ').replace(//g, ' ')).length > 0 && !!event.startDate && event.startDate.isValid() && !self.isWorking;\n });\n /* eslint-disable-next-line ember/no-jquery */\n return readyToSave;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n otherEnrolledSections: Ember.computed(function () {\n // returns the other current-term sections the user is enrolled in\n /* eslint-disable-next-line ember/no-get */\n let filteredCodes = this.get('classroom.otherCurrentTermSections');\n if (filteredCodes.length > 6) {\n /* eslint-disable-next-line ember/no-incorrect-calls-with-inline-anonymous-functions */\n\n Ember.run.scheduleOnce('afterRender', this, () => {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.floating-modal#new-event .rte-editor-input').addClass('height-limited');\n });\n }\n return filteredCodes || [];\n }),\n //Clear out the new event modal data.\n resetData() {\n this.eventsModel.setObjects([{\n bodyInput: '',\n titleInput: '',\n startDate: baseEventDate,\n additionalSectionsCourseIds: Ember.ArrayProxy.create({\n content: Ember.A([])\n }),\n shown: true\n }]);\n this.savedData = [{\n title: '',\n body: ''\n }];\n this.extraSectionToggled = false;\n this.set('contentNotDirty', true);\n this.templates.selectedTemplateIndex = -1;\n },\n saveEvents() {\n this.set('isWorking', true);\n const section = this.session.get('section');\n const course = section.get('course');\n const now = moment().tz('America/New_York'),\n self = this,\n courseId = course.get('id'),\n eventData = [];\n let priorDate = false,\n invalidDate = false,\n duplicateDates = [],\n eventDates = [];\n this.eventsModel.forEach((em, index) => {\n let start_time = em.startDate;\n const eventDataSingle = {\n courseIds: [],\n event: {}\n };\n if (start_time.unix() < now.unix()) {\n priorDate = true;\n this.set('isWorking', false);\n return;\n }\n if (!start_time.isValid()) {\n invalidDate = true;\n this.set('isWorking', false);\n return;\n }\n\n // Force the value of the time input to avoid DST conversion\n start_time = `${start_time.format('YYYY-MM-DD')}T${em.startDate_time}:00`;\n let start_time_moment = moment(start_time),\n end_time_moment = moment(start_time).add(\"1\", \"hours\");\n let startTimeMomentFormatted = start_time_moment.format('YYYY-MM-DDTHH:mm:ss');\n eventDataSingle.event = {\n title: em.titleInput,\n description: this.parseEmbed(em.bodyInput) ? this.parseEmbed(em.bodyInput) : '',\n futureEvent: true,\n startAt: startTimeMomentFormatted,\n endAt: end_time_moment.format('YYYY-MM-DDTHH:mm:ss')\n };\n\n //Keeping track of duplicate dates\n let duplicateDate = eventDates.find(ed => ed.date === startTimeMomentFormatted);\n if (duplicateDate) {\n let title1 = duplicateDate.title;\n let title2 = em.titleInput;\n duplicateDates.push({\n title1,\n title2,\n date: startTimeMomentFormatted\n });\n }\n eventDates.push({\n title: em.titleInput,\n date: startTimeMomentFormatted\n });\n eventDataSingle.courseIds.push(courseId);\n em.additionalSectionsCourseIds.forEach(additionalSectionCourseId => {\n eventDataSingle.courseIds.push(additionalSectionCourseId);\n });\n eventData.push(eventDataSingle);\n });\n if (priorDate) {\n alert('You can only schedule live classes in the future.');\n return;\n } else if (invalidDate) {\n alert('Please choose a valid date for the scheduled live class.');\n return;\n } else if (duplicateDates.length > 0) {\n let {\n title1,\n title2,\n date\n } = duplicateDates[0];\n (0, _dialog.default)(`You have two events scheduled for ${moment(date).format('MMM Do, YYYY h:mm a') + \" ET\"}: ${title1}, ${title2}. Are you sure you wish to proceed?`, ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n self.set('isWorking', false);\n self.send('destroyEditor');\n self.send('closeEventModal');\n sendEventsToInterfaceAndSendEmail.call(this, eventData);\n } else {\n this.set('isWorking', false);\n }\n });\n } else {\n self.set('isWorking', false);\n self.send('destroyEditor');\n self.send('closeEventModal');\n sendEventsToInterfaceAndSendEmail.call(this, eventData);\n }\n },\n setFullDate(event) {\n if (event.startDate_date && event.startDate_time) {\n let dateString = `${event.startDate_date}T${event.startDate_time}:00`,\n startDate = moment.tz(dateString, 'America/New_York');\n Ember.set(event, 'startDate', startDate);\n } else {\n Ember.set(event, 'startDate', \"\");\n }\n },\n setShowTemplateManager(show) {\n this.set('showTemplateManager', show);\n },\n actions: {\n openTutorial() {\n window.open(_environment.default.APP.multiSchedulerOverviewUrl, '_blank').focus();\n },\n onDeleteTemplate() {\n if (this.templates.selectedTemplateIndex === -1) {\n this.resetData();\n }\n if (this.templates.templates.length === 0) {\n this.setShowTemplateManager(false);\n }\n },\n changeTemplate(template) {\n if (template && template.events) {\n this.eventsModel.setObjects(template.events.map((te, index) => ({\n titleInput: te.title,\n bodyInput: te.body,\n additionalSectionsCourseIds: Ember.ArrayProxy.create({\n content: Ember.A([])\n })\n })));\n } else {\n this.resetData();\n }\n this.savedData = this.eventsModel.map(em => ({\n title: em.titleInput,\n body: em.bodyInput\n }));\n },\n updateDate(index, e) {\n const event = this.events.objectAt(index);\n Ember.set(event, 'startDate_date', e.target.value);\n this.setFullDate(event);\n },\n updateTime(index, e) {\n const event = this.events.objectAt(index);\n Ember.set(event, 'startDate_time', e.target.value);\n this.setFullDate(event);\n },\n resetData() {\n this.resetData();\n },\n cascadeDate(index) {\n if (index != null && index >= 0 && index < this.eventsModel.length) {\n const date = this.eventsModel.objectAt(index).startDate;\n if (!date) {\n return;\n }\n for (let i = index + 1; i < this.eventsModel.length; i++) {\n let event = this.eventsModel.objectAt(i);\n Ember.set(event, 'startDate', moment(date).add(7 * (i - index), 'd'));\n }\n }\n },\n cascadeSection(index) {\n if (index != null && index >= 0 && index < this.eventsModel.length) {\n const additionalSectionsCourseIds = this.eventsModel.objectAt(index).additionalSectionsCourseIds;\n for (let i = index + 1; i < this.eventsModel.length; i++) {\n let event = this.eventsModel.objectAt(i);\n event.additionalSectionsCourseIds.setObjects(additionalSectionsCourseIds.toArray());\n }\n }\n },\n updateTemplate() {\n this.templates.updateTemplate(this.eventsModel);\n },\n saveTemplate() {\n this.templates.saveTemplate(this.eventsModel);\n },\n deleteEvent: function (index) {\n if (index != null && index >= 0 && index < this.eventsModel.length) {\n const self = this;\n (0, _dialog.default)('Are you sure you wish to delete this event?', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n self.eventsModel.removeAt(index, 1);\n\n //Make sure that the single event is showing if it is the only one left.\n if (self.eventsModel.length === 1) {\n const event = self.eventsModel.objectAt(0);\n Ember.set(event, 'shown', true);\n }\n }\n });\n }\n },\n destroyEditor() {\n this.resetData();\n },\n resetData() {\n this.resetData();\n },\n toggleShowEventDetails: function (index) {\n if (index != null && index >= 0 && index < this.eventsModel.length) {\n const event = this.eventsModel.objectAt(index);\n Ember.set(event, 'shown', !event.shown);\n }\n },\n addEvent: function () {\n this.eventsModel.forEach(em => {\n Ember.set(em, 'shown', false);\n });\n this.eventsModel.pushObject({\n bodyInput: '',\n titleInput: '',\n startDate: moment(baseEventDate).add(7 * this.eventsModel.length, 'd'),\n additionalSectionsCourseIds: Ember.ArrayProxy.create({\n content: Ember.A([])\n }),\n shown: true\n });\n },\n saveEvents: function () {\n if (!this.extraSectionToggled && this.otherEnrolledSections.length > 0) {\n (0, _dialog.default)('You have not added any additional sections. Continue?', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n this.saveEvents();\n }\n });\n } else {\n this.saveEvents();\n }\n },\n toggleExtraSection: function (section, index) {\n if (index != null && index >= 0 && index < this.eventsModel.length) {\n const event = this.eventsModel.objectAt(index);\n let additionalSectionsCourseIds = event.additionalSectionsCourseIds,\n existingIndex = -1;\n additionalSectionsCourseIds.forEach((extraSection, esIndex) => {\n if (extraSection === section.courseId) {\n existingIndex = esIndex;\n }\n });\n if (existingIndex >= 0) {\n additionalSectionsCourseIds.removeAt(existingIndex, 1);\n } else {\n additionalSectionsCourseIds.pushObject(section.courseId);\n }\n this.extraSectionToggled = true;\n }\n },\n closeEventModal: function () {\n this.set('repBox', false);\n this.send('closeModals', false);\n this.set('flatpickrFirstOpened', false);\n },\n setShowTemplateManager(show) {\n this.setShowTemplateManager(show);\n }\n }\n });\n function sendEventsToInterfaceAndSendEmail(eventData, start_chat = false) {\n const self = this;\n const batchEventData = [];\n eventData.forEach(ed => {\n ed.courseIds.forEach(courseId => {\n batchEventData.push({\n event: {\n ...ed.event,\n course: courseId\n }\n });\n });\n });\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/batch-ops/batchEvent',\n data: JSON.stringify({\n events: batchEventData\n }),\n success(batchResult) {\n Ember.$('.activity-filters .filter-action .current').click();\n if (self.eventsModel) {\n self.eventsModel.setObjects([{\n bodyInput: '',\n titleInput: '',\n startDate: baseEventDate,\n additionalSectionsCourseIds: Ember.ArrayProxy.create({\n content: Ember.A([])\n }),\n shown: true\n }]);\n }\n //Key events off of course id\n const courseEvents = batchResult.events.reduce((aggregated, r) => {\n const event = r.event;\n if (aggregated[event.course]) {\n aggregated[event.course].push(event);\n } else {\n aggregated[event.course] = [event];\n }\n return aggregated;\n }, {});\n const sendEmail = () => {\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/event-emails/scheduled',\n data: JSON.stringify({\n events: courseEvents\n }),\n success(data) {\n if (data.failedEmails && data.failedEmails.length > 0) {\n (0, _dialog.default)(`E-mails for the following courses failed to send: ${data.failedEmails.join(',')}`);\n }\n },\n error(jqXHR, textStatus) {\n (0, _dialog.default)('Unknown error occurred sending e-mails. Try again? Warning: this may send duplicate e-mails.', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n sendEmail();\n }\n });\n }\n });\n };\n sendEmail();\n if (start_chat) {\n let any_course_events = courseEvents[Object.keys(courseEvents)[0]];\n let any_chat = any_course_events[0];\n self.send('startChat', any_chat, any_chat.id);\n self.set('isWorking', false);\n self.transitionToRoute('classroom.lessons');\n (0, _dialog.default)('Events successfully scheduled. It may be a minute or two before it starts up.');\n } else {\n self.transitionToRoute('classroom.lessons');\n (0, _dialog.default)('Events successfully scheduled. It may be a minute or two before you see them.');\n }\n },\n error(jqXHR, textStatus) {\n (0, _dialog.default)('Failed to schedule event. Please retry.');\n self.set('isWorking', false);\n }\n });\n }\n});","define(\"bocce/controllers/classroom/lessons/event-quick-new\", [\"exports\", \"bocce/controllers/classroom/lessons/event-new\"], function (_exports, _eventNew) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _eventNew.default.extend({\n init(...args) {\n this._super(...args);\n let baseEventDate = moment().tz('America/New_York');\n this.eventsModel = Ember.ArrayProxy.create({\n content: Ember.A([{\n bodyInput: '',\n titleInput: 'Live Class',\n startDate: baseEventDate,\n startDate_date: baseEventDate.format('YYYY-MM-DD'),\n startDate_time: baseEventDate.format('HH:mm'),\n additionalSectionsCourseIds: Ember.ArrayProxy.create({\n content: Ember.A([])\n }),\n shown: true\n }])\n });\n },\n isWorking: false,\n // saveEvents without invalid time check, since the time is now\n saveEvents() {\n const section = this.session.get('section');\n const course = section.get('course');\n const courseId = course.get('id'),\n eventData = [];\n this.eventsModel.forEach((em, index) => {\n let start_time = moment().tz('America/New_York');\n const eventDataSingle = {\n courseIds: [],\n event: {}\n };\n\n // Force the value of the time input to avoid DST conversion\n start_time = `${start_time.format('YYYY-MM-DD')}T${start_time.format('HH:mm')}:00`;\n let start_time_moment = moment(start_time),\n end_time_moment = moment(start_time).add(\"1\", \"hours\");\n let startTimeMomentFormatted = start_time_moment.format('YYYY-MM-DDTHH:mm:ss');\n eventDataSingle.event = {\n title: \"Live Class\",\n description: '',\n futureEvent: true,\n startAt: startTimeMomentFormatted,\n endAt: end_time_moment.format('YYYY-MM-DDTHH:mm:ss')\n };\n eventDataSingle.courseIds.push(courseId);\n em.additionalSectionsCourseIds.forEach(additionalSectionCourseId => {\n eventDataSingle.courseIds.push(additionalSectionCourseId);\n });\n eventData.push(eventDataSingle);\n });\n this.set('isWorking', true);\n _eventNew.sendEventsToInterfaceAndSendEmail.call(this, eventData, true);\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/event\", [\"exports\", \"bocce/config/environment\", \"bocce/mixins/enmodal\", \"bocce/mixins/editable\", \"bocce/mixins/webex\", \"bocce/mixins/nested-resources\", \"bocce/utilities/dialog\", \"sanitize-html\"], function (_exports, _environment, _enmodal, _editable, _webex, _nestedResources, _dialog, _sanitizeHtml) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-mixins */\n\n /* eslint-disable-next-line ember/no-observers */\n\n function sanitize(string) {\n const sanitizeHtmlOptions = {\n // options for the sanitize-html package. see docs:\n // https://www.npmjs.com/package/sanitize-html#htmlparser2-options\n allowedTags: _sanitizeHtml.default.defaults.allowedTags.concat(['h2', 'u']),\n transformTags: {\n 'h1': 'h2',\n // Word's header is h1, but our header is h2\n 'a': function (tagName, attribs) {\n // filter out useless links because apparently the geniuses\n // at Microsoft decided to start including THOSE now\n if (!attribs['href'] || attribs['href'] === '') {\n return false;\n } else {\n return {\n tagName: tagName,\n attribs: {\n href: attribs.href\n }\n };\n }\n }\n }\n };\n return (0, _sanitizeHtml.default)(string, sanitizeHtmlOptions);\n }\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default, _editable.default, _webex.default, _nestedResources.default, {\n classroom: Ember.inject.controller(),\n eventsService: Ember.inject.service('events'),\n userprofileService: Ember.inject.service('userprofile'),\n discussions: Ember.inject.controller(),\n event: Ember.inject.controller('classroom.lessons.event'),\n // required by WebexMixin\n events: Ember.inject.controller(),\n // required by WebexMixin\n newStudentEvent: Ember.inject.controller('classroom.lessons.student-event-new'),\n // required by WebexMixin\n\n queryParams: ['start_chat', 'join_chat', 'backup_code'],\n start_chat: false,\n join_chat: false,\n backup_code: false,\n hasOtherSectionChats: false,\n multiChatConnectionStatus: '',\n waitingForOtherSectionChats: true,\n sendCancelNotification: false,\n cancelRepeating: false,\n cancelAllSections: false,\n cancelEventConfirmedVisible: false,\n notEditingEvent: true,\n notifyStudents: true,\n tempEventStartAt: '',\n /**initialized in setupController */\n tempEventTitle: '',\n /**initialized in setupController */\n optionalEmailNote: '',\n isEventSaving: false,\n description: Ember.computed('model.description', function () {\n /* eslint-disable-next-line ember/no-get */\n let desc = this.get('model.description');\n if (desc && desc.indexOf('The instructor has started a live class!') === 0) {\n return '';\n }\n return desc;\n }),\n eventNotUpdated: Ember.computed('tempEventStartAt', 'tempEventTitle', 'bodyInput', function () {\n const tempStartAt = this.get('tempEventStartAt');\n const tempEventTitle = this.get('tempEventTitle');\n const bodyInput = this.get('bodyInput');\n return !tempStartAt || !tempEventTitle || !bodyInput || tempStartAt == this.model.get('startAt') && tempEventTitle == this.model.get('title') && bodyInput == this.model.get('description');\n }),\n future: Ember.computed('model.startAt', 'session.time', function () {\n /* eslint-disable-next-line ember/no-get */\n return moment() < moment(this.get('model.startAt'));\n }),\n past: Ember.computed('model.startAt', function () {\n /* eslint-disable-next-line ember/no-get */\n return moment() >= moment(this.get('model.startAt'));\n }),\n archived: Ember.computed('model.endAt', 'session.time', function () {\n /* eslint-disable-next-line ember/no-get */\n return moment() > moment(this.get('model.endAt'));\n }),\n // Hotfix\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n isMeetingSupposedToBeHappeningRightNow: Ember.computed('model.startAt', 'session.time', function () {\n let now = moment();\n\n /* eslint-disable-next-line ember/no-get */\n return moment(this.get('model.startAt')) <= now && /* eslint-disable-next-line ember/no-get */\n moment(this.get('model.endAt')) >= now;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n runnable: Ember.computed('model.startAt', 'session.time', function () {\n let now = moment();\n\n /* eslint-disable-next-line ember/no-get */\n return moment(this.get('model.startAt')) <= now + 10 * 60 * 1000 && /* eslint-disable-next-line ember/no-get */\n moment(this.get('model.endAt')) >= now;\n }),\n userIsAdminOrStaff: Ember.computed('session', function () {\n return this.get('session.isAdmin') || this.get('session.isStaff') ? true : false;\n }),\n resetTempEventDetails() {\n this.set('bodyInput', this.model.get('description'));\n this.set('tempEventTitle', this.model.get('title'));\n this.set('tempEventStartAt', this.model.get('startAt'));\n },\n // NK: Ideally this would be a computed property, but the current\n // implementation introduces side effects, which is a no-no for\n // computed properties. Rewriting this as an observer for now, but\n // at some point when there's time (hah) this should be refactored to\n // use computed properties, so Ember is happy, which will make us all\n // happy by proxy.\n /* eslint-disable-next-line ember/no-observers */\n otherSectionChatsObserver: Ember.observer('model', function () {\n this.set('waitingForOtherSectionChats', true);\n this.set('otherSectionChats', this.eventsService.getOtherSectionChats(this.classroom, this.get('model.title'), this.get('model.startAt')).then(events => {\n this.set('hasOtherSectionChats', events.length > 0 ? true : false);\n this.set('otherSectionChatsData', events || []);\n this.set('waitingForOtherSectionChats', false);\n\n // Start/Joint the chat if the corresponding query param is set\n if (this.get('start_chat')) {\n this.send('startChat');\n } else if (this.get('join_chat')) {\n this.send('joinChat', this.get('join_chat'));\n }\n return Promise.resolve(events || []);\n }));\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n otherRecurringChats: Ember.computed('model', function () {\n let otherRecurringChats = this.store.peekAll('event').filter(event => {\n // Compare titles, and the starting hours and minutes of the livechats.\n /* eslint-disable-next-line ember/no-get */\n let currentEventStartAt = moment(this.get('model.startAt')),\n filteredEventStartAt = moment(event.get('startAt')),\n /* eslint-disable-next-line ember/no-get */\n titlesMatch = event.get('title') === this.get('model.title'),\n timesMatch = currentEventStartAt.hours() === filteredEventStartAt.hours() && currentEventStartAt.minutes() === filteredEventStartAt.minutes(),\n /* eslint-disable-next-line ember/no-get */\n isSameEvent = event.get('id') === this.get('model.id');\n return titlesMatch && timesMatch && !isSameEvent;\n });\n return otherRecurringChats || [];\n }),\n editorHasContent: Ember.computed('eventNotUpdated', function () {\n return !this.get('notEditingEvent') && !this.get('eventNotUpdated');\n }),\n hasRecurringChats: Ember.computed('otherRecurringChats', function () {\n let otherRecurringChats = this.otherRecurringChats;\n return otherRecurringChats.length > 0;\n }),\n async sendEmail(event, subject, startAt, templateId) {\n const instructors = this.get('session.section.teachers').map(t => ({\n name: t.name,\n image_url: t.image_url\n }));\n\n //Used by SendGrid to fill in e-mail placeholders.\n const dynamicTemplateData = {\n changed: true,\n courseTitle: event.courseTitle,\n instructors,\n note: this.optionalEmailNote,\n subject,\n 'event': {\n title: {\n value: event.title,\n changed: !!this.titleChanged\n },\n startAt: {\n value: startAt,\n changed: !!this.startAtChanged\n },\n description: {\n value: event.description,\n changed: !!this.descriptionChanged\n },\n link: `${window.location.origin}/#/${event.courseId}/${event.courseCode}/${event.sectionId}/0/course-introduction/event/${event.id}`\n }\n };\n let studentEmails = [];\n if (this.model.privateLessonStudentId) {\n let students = this.get('session.section.students');\n let student = students.findBy('user.acsUserId', this.model.privateLessonStudentId);\n if (student) {\n studentEmails.push(student.get('user.email'));\n }\n } else {\n studentEmails = event.studentEmails;\n }\n console.log('sending e-mail to: ' + studentEmails);\n const self = this;\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/email',\n data: JSON.stringify({\n to: studentEmails,\n from_name: dynamicTemplateData.courseTitle,\n subject,\n templateId,\n dynamicTemplateData\n }),\n success() {\n console.log(\"email success!\");\n },\n error(jqXHR, textStatus) {\n (0, _dialog.default)('Notification e-mail failed to send. Try again?', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n this.sendEmail.call(self, event, subject, startAt, templateId);\n }\n });\n }\n });\n },\n actions: {\n copyToClipboard: function (...text) {\n text = text.join('');\n navigator.clipboard.writeText(text);\n },\n editEvent: function (eventDescription) {\n eventDescription = eventDescription || '';\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-save').removeClass('hidden');\n\n //Prevent description editing if the event is a private lesson event. Messes things up for Jitterbit.\n if (!this.model.privateLessonStudentId) {\n // Hide the description value\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description-edit').removeClass('hidden');\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description-edit-action').addClass('hidden');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n\n Ember.$('.modal-content .watch-button').addClass('hidden');\n this.set('notEditingEvent', false);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.floating-modal').removeClass('empty-evt');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.rte-editor-input').html(eventDescription);\n },\n cancelEditEventDescription: function () {\n Ember.$('.event-save').addClass('hidden');\n\n //Description editing was prevented if the event is a private lesson event. Messes things up for Jitterbit.\n if (!this.model.privateLessonStudentId) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description-edit').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description-edit-action').removeClass('hidden');\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.modal-content .watch-button').removeClass('hidden');\n this.set('notEditingEvent', true);\n this.set('bodyInput', this.model.get('description'));\n this.set('tempEventTitle', this.model.get('title'));\n this.set('tempEventStartAt', this.model.get('startAt'));\n },\n updateTempDate(e) {\n let formattedDateToSave = e.target.value ? moment.tz(e.target.value, 'America/New_York').utc().format(\"YYYY-MM-DDTHH:mm:ss\") + \"Z\" : \"\";\n this.set('tempEventStartAt', formattedDateToSave);\n },\n updateTempTitle(e) {\n this.set('tempEventTitle', e.target.value);\n },\n async saveEvent() {\n this.set('isEventSaving', true);\n let thisEvent = this.model,\n /* eslint-disable-next-line ember/no-jquery */\n newDescription = this.parseEmbed(Ember.$('.rte-editor-input').html()),\n promises = [],\n newTitle = this.get('tempEventTitle'),\n now = moment().tz('America/New_York'),\n newStartAt = this.get('tempEventStartAt');\n if (!newDescription) {\n (0, _dialog.default)('Description must not be blank.');\n this.set('isEventSaving', false);\n return;\n }\n if (!newTitle) {\n (0, _dialog.default)('Title must not be blank.');\n this.set('isEventSaving', false);\n return;\n }\n if (this.model.privateLessonStudentId && !newTitle.endsWith(_environment.default.APP.text.eventRescheduledPostfix)) {\n newTitle = newTitle + _environment.default.APP.text.eventRescheduledPostfix;\n }\n if (!moment(this.get('model.startAt')).isSame(moment(newStartAt)) && moment(newStartAt).unix() < moment(now).unix()) {\n (0, _dialog.default)('You cannot schedule a live class in the past.');\n this.set('isEventSaving', false);\n return;\n }\n const newEndAt = moment(newStartAt).add(\"1\", \"hours\").utc().format('YYYY-MM-DDTHH:mm:ss') + \"Z\";\n let updatedEvents = [];\n if (this.hasOtherSectionChats) {\n let otherChatsData = this.otherSectionChatsData.map(chatData => {\n //update the description\n chatData.description = newDescription;\n chatData.title = newTitle;\n chatData.startAt = newStartAt;\n chatData.endAt = newEndAt;\n // wrap the chat data in an object ready for the request\n return {\n event: chatData\n };\n });\n for (let otherSectionChatData of this.otherSectionChatsData) {\n let section = await this.store.findRecord('section', otherSectionChatData.sectionId);\n updatedEvents.push({\n id: otherSectionChatData.id,\n description: otherSectionChatData.description,\n title: otherSectionChatData.title,\n courseId: otherSectionChatData.course,\n courseCode: otherSectionChatData.code,\n courseTitle: otherSectionChatData.courseTitle,\n sectionId: otherSectionChatData.sectionId,\n studentEmails: section.get('studentEmails')\n });\n }\n promises.push(this.updateNestedResources(['event', 'events'], otherChatsData, ['course', 'courses']).then(() => {\n Ember.debug('Successfully updated event in other courses');\n }));\n }\n this.startAtChanged = false;\n this.titleChanged = false;\n this.descriptionChanged = false;\n if (this.get('tempEventStartAt') != thisEvent.get('startAt')) {\n this.startAtChanged = true;\n }\n if (this.get('tempEventTitle') != thisEvent.get('title')) {\n this.titleChanged = true;\n }\n if (sanitize(this.get('bodyInput')) != sanitize(thisEvent.get('description'))) {\n this.descriptionChanged = true;\n }\n thisEvent.set('description', newDescription);\n thisEvent.set('title', newTitle);\n thisEvent.set('startAt', newStartAt);\n thisEvent.set('endAt', newEndAt);\n let course = await thisEvent.get('course');\n let sections = await course.get('sections');\n let section = sections.objectAt(0);\n let sectionId = section.get('id');\n let studentEmails = section.get('studentEmails');\n promises.push(thisEvent.save());\n updatedEvents.push({\n id: thisEvent.get('id'),\n title: thisEvent.get('title'),\n description: thisEvent.get('description'),\n courseId: course.get('id'),\n courseCode: course.get('code'),\n courseTitle: course.get('title'),\n sectionId,\n studentEmails\n });\n Promise.all(promises).then(() => {\n // Hide the textbox and save button, show the value\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description-edit').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-save').addClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event-description-edit-action').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.modal-content .watch-button').removeClass('hidden');\n Ember.$('.rte-editor-input').text('');\n this.set('notEditingEvent', true);\n /* eslint-disable-next-line ember/no-jquery */\n\n if (this.notifyStudents && !this.archived && course.get('isOpen')) {\n const startAt = this.model.get('formattedStartTime');\n updatedEvents.forEach(event => {\n const subject = `Updated Live Class${this.optionalEmailNote ? ' with note' : ''}: ${event.title} @ ${startAt}`;\n this.sendEmail.call(this, event, subject, startAt, _environment.default.APP.email.templates.single_live_class_generic_id);\n });\n }\n this.set('isEventSaving', false);\n }).catch(() => {\n this.set('isEventSaving', false);\n (0, _dialog.default)('There was a problem updating the live class. Please try again later.');\n if (this.model.privateLessonStudentId && newTitle.endsWith(_environment.default.APP.text.eventRescheduledPostfix)) {\n newTitle = newTitle.replace(_environment.default.APP.text.eventRescheduledPostfix, \"\");\n thisEvent.set('title', newTitle);\n }\n });\n },\n cancelEvent: function () {\n this.set('cancelEventConfirmedVisible', true);\n this.set('notEditingEvent', true);\n },\n cancelEventConfirmed: function (event_id) {\n // Cancel the event\n // If needed, generate and send out an announcement.\n // If needed, cancel subsequent events in the series\n // If needed, cancel other sections' chats (and other sections' repeating chats)\n let discussionsCtl = this.discussions,\n otherRecurringChats = this.otherRecurringChats,\n that = this;\n this.store.findRecord('event', event_id).then(function (eventObject) {\n let announcementData = {},\n announcement = {},\n sendCancelNotification = that.get('sendCancelNotification'),\n cancelRepeating = that.get('cancelRepeating'),\n cancelAllSections = that.get('cancelAllSections'),\n currentEventStartAt = moment(eventObject.get('startAt'));\n\n // start generating announcement if checked (don't send it yet)\n if (sendCancelNotification) {\n if (cancelRepeating && that.get('otherRecurringChats').length > 0) {\n announcementData.title = 'Live classes have been cancelled';\n announcementData.message = 'Several live classes titled \\'' + eventObject.get('title') + '\\' have been cancelled.
';\n announcementData.message += 'The cancelled live classes were scheduled for:
' + currentEventStartAt.format('MMM, D YYYY h:mm A z') + ' ';\n announcementData.is_announcement = true;\n } else {\n announcementData = {\n message: 'A live class titled \\'' + eventObject.get('title') + '\\' occuring at ' + currentEventStartAt.format('MMM, D YYYY h:mm A z') + ' has been cancelled.',\n title: 'Live Class Cancelled',\n is_announcement: true\n };\n }\n }\n\n // delete repeating chats if checked\n if (cancelRepeating) {\n for (let i = 0; i < otherRecurringChats.length; i++) {\n let recurringEvent = otherRecurringChats[i],\n recurringEventStartAt = moment(recurringEvent.get('startAt'));\n if (currentEventStartAt < recurringEventStartAt) {\n announcementData.message += recurringEventStartAt.format('MMM, D YYYY h:mm A z') + ' ';\n recurringEvent.destroyRecord();\n }\n }\n }\n\n // delete the other section chats if checked\n if (cancelAllSections) {\n let eventsToDelete = [],\n promises = [];\n\n // add the other sections' versions of the current event to the kill list\n for (let i = 0; i < that.otherSectionChatsData.length; i++) {\n eventsToDelete.push({\n course: that.otherSectionChatsData[i].course,\n event: that.otherSectionChatsData[i].id\n });\n }\n\n // if recurring, get the other sections' subsequent events\n if (cancelRepeating && otherRecurringChats.length > 0) {\n let currentEventStartAt = moment(that.get('model.startAt')),\n currentEventTitle = that.get('model.title');\n that.otherSectionChatsData.forEach(otherSectionChat => {\n // iterate through each other section where the chat occurs and add the recurring events to the list.\n let courseId = otherSectionChat.course,\n currentEventId = otherSectionChat.id;\n /* eslint-disable-next-line ember/no-jquery */\n promises.push(Ember.$.get(`/interface/courses/${courseId}/events`).then(Ember.run.bind(results => {\n // For each event in the other section, check if it's an instance of the repeating event.\n let otherSectionChats = results.event,\n filteredOtherSectionChats = [];\n if (otherSectionChats) {\n filteredOtherSectionChats = otherSectionChats.filter(otherSectionChat => {\n let filteredEventStartAt = moment(otherSectionChat.startAt),\n titlesMatch = otherSectionChat.title === currentEventTitle,\n timesMatch = currentEventStartAt.hours() === filteredEventStartAt.hours() && currentEventStartAt.minutes() === filteredEventStartAt.minutes(),\n isSameEvent = otherSectionChat.id === currentEventId;\n return titlesMatch && timesMatch && !isSameEvent;\n });\n }\n\n // add the filtered events to the to-delete list.\n filteredOtherSectionChats.forEach(otherChat => {\n eventsToDelete.push({\n course: otherChat.course,\n event: otherChat.id\n });\n });\n })));\n });\n }\n\n // Once all other sections' event data is collected, delete all the other events.\n Promise.all(promises).then(() => {\n eventsToDelete.forEach(event => {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'DELETE',\n url: `/interface/courses/${event.course}/events/${event.event}`,\n success: () => {\n Ember.debug(`Deleted event ${event.event} in course ${event.course}`);\n },\n error: (jqXHR, textStatus, errorThrown) => {\n Ember.debug(`Failed to delete event ${event.event} in course ${event.course}, error: ${errorThrown}`);\n (0, _dialog.default)('There was a problem cancelling the live class in the other sections. Please cancel them manually.');\n }\n });\n });\n });\n }\n\n // send out the announcment if checked\n if (sendCancelNotification) {\n announcement = that.store.createRecord('discussion', announcementData);\n announcement.save();\n\n // send the announcement to the other sections if we're deleting it for them too\n if (cancelAllSections) {\n announcementData = {\n discussion: announcementData\n };\n for (let i = 0; i < that.otherSectionChatsData.length; i++) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/sections/' + that.otherSectionChatsData[i].sectionId + '/discussions',\n data: JSON.stringify(announcementData),\n dataType: 'json',\n contentType: 'application/json'\n });\n }\n }\n }\n\n // FINISH HIM\n eventObject.destroyRecord();\n });\n\n // clean up and close the modal\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').removeClass('on-modal');\n this.set('cancelEventConfirmedVisible', false);\n if (discussionsCtl.send) {\n discussionsCtl.send('closeModals');\n }\n },\n destroyEditor: function () {\n // this gets called from the 'closeModals' function in app/mixins/enmodal.js\n this._super();\n this.set('cancelEventConfirmedVisible', false);\n this.set('notEditingEvent', true);\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/no-submissions\", [\"exports\", \"bocce/mixins/editable\", \"bocce/mixins/assignments\", \"bocce/mixins/uploadable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/enmodal\", \"bocce/mixins/boot\", \"bocce/mixins/discussable\"], function (_exports, _editable, _assignments, _uploadable, _audioRec, _videoRec, _rtcRec, _enmodal, _boot, _discussable) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default, _editable.default, _assignments.default, _uploadable.default, _audioRec.default, _videoRec.default, _rtcRec.default, _boot.default, _discussable.default, {\n userprofileService: Ember.inject.service('userprofile'),\n discussions: Ember.inject.controller(),\n // required by EditableMixin\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n classroom: Ember.inject.controller(),\n // required by UploadableMixin and EnmodalMixin\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n assignments: Ember.computed(function () {\n return this.store.findAll('assignment');\n })\n });\n});","define(\"bocce/controllers/classroom/lessons/student-event-new\", [\"exports\", \"bocce/mixins/enmodal\", \"bocce/mixins/webex\"], function (_exports, _enmodal, _webex) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default, _webex.default, {\n titleInput: ''\n });\n});","define(\"bocce/controllers/classroom/lessons/submission-new\", [\"exports\", \"bocce/mixins/editable\", \"bocce/mixins/assignments\", \"bocce/mixins/uploadable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/enmodal\", \"bocce/mixins/boot\", \"bocce/utilities/dialog\"], function (_exports, _editable, _assignments, _uploadable, _audioRec, _videoRec, _rtcRec, _enmodal, _boot, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_editable.default, _assignments.default, _uploadable.default, _audioRec.default, _videoRec.default, _rtcRec.default, _boot.default, _enmodal.default, {\n init(...args) {\n this._super(...args);\n },\n userprofileService: Ember.inject.service('userprofile'),\n kalturaService: Ember.inject.service('kaltura-upload'),\n discussions: Ember.inject.controller(),\n // required by EditableMixin\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n classroom: Ember.inject.controller(),\n // required by UploadableMixin and EnmodalMixin\n\n actions: {\n addFile: function (file) {\n if (file.type.includes('video')) {\n let encoding_video = {\n name: file.name,\n percent_uploaded: 0\n };\n this.encoding_videos.pushObject(encoding_video);\n let fileIndex = this.encoding_videos.length - 1;\n this.kalturaUploadVideo(file, async () => {\n Ember.debug('Unable to upload video to Kaltura. Uploading to S3...');\n await (0, _dialog.default)('Video embedding unsuccessful; this file will be available for download only.', ['OK']);\n this.encoding_videos.removeObject(encoding_video);\n file.ignoreDownloadOnlyPrompt = true;\n this.send('addValidFile', file);\n }, fileIndex, URL.createObjectURL(file), encoding_video);\n } else if (this.mimeTypes.indexOf(file.type) === -1) {\n this.send('addValidFile', file);\n } else {\n this.send('addInvalidFile', file);\n }\n },\n deleteAttachment: function (file) {\n this.send('deleteFile', file);\n },\n renameAttachment: function (attachment, newName) {\n if (attachment.file?.isUrl) {\n this.kalturaService.kalturaRenameVideo(attachment.uploaded_id, newName);\n Ember.set(attachment, 'name', newName);\n } else {\n Ember.set(attachment, 'name', newName);\n this.store.findRecord('attachment', attachment.uploaded_id).then(loadedAttachment => {\n loadedAttachment.set('name', newName);\n loadedAttachment.save();\n });\n }\n }\n },\n rubric: Ember.computed('activeAssignment', function () {\n return this.get('activeAssignment.rubric');\n })\n });\n});","define(\"bocce/controllers/classroom/lessons/submission\", [\"exports\", \"bocce/mixins/editable\", \"bocce/mixins/assignments\", \"bocce/mixins/uploadable\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/enmodal\", \"bocce/mixins/boot\", \"bocce/mixins/discussable\", \"bocce/utilities/dialog\"], function (_exports, _editable, _assignments, _uploadable, _audioRec, _videoRec, _rtcRec, _enmodal, _boot, _discussable, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default, _editable.default, _assignments.default, _uploadable.default, _audioRec.default, _videoRec.default, _rtcRec.default, _boot.default, _discussable.default, {\n userprofileService: Ember.inject.service('userprofile'),\n kalturaService: Ember.inject.service('kaltura-upload'),\n rubricService: Ember.inject.service(\"rubric\"),\n discussions: Ember.inject.controller(),\n // required by EditableMixin\n lessons: Ember.inject.controller('classroom.lessons'),\n // required by UploadableMixin\n classroom: Ember.inject.controller(),\n // required by UploadableMixin and EnmodalMixin\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n assignments: Ember.computed(function () {\n return this.store.findAll('assignment');\n }),\n isShowingAssignmentDetails: false,\n bootContent() {\n /* eslint-disable-next-line ember/no-jquery */\n let node = Ember.$('.submission-body');\n if (node && node.length > 0) {\n this.boot_area(node, false, true, false, false, true);\n }\n },\n get editorHasContent() {\n const hasContent = this.bodyInputHasContent;\n const hasAttachments = this.hasNonDeletedAttachments;\n return hasContent || hasAttachments || this.isRubricAssessmentDirty?.();\n },\n actions: {\n addFile: function (file) {\n if (file.type.includes('video')) {\n let encoding_video = {\n name: file.name,\n percent_uploaded: 0\n };\n this.encoding_videos.pushObject(encoding_video);\n let fileIndex = this.encoding_videos.length - 1;\n this.kalturaUploadVideo(file, async () => {\n Ember.debug('Unable to upload video to Kaltura. Uploading to S3...');\n await (0, _dialog.default)('Video embedding unsuccessful; this file will be available for download only.', ['OK']);\n this.encoding_videos.removeObject(encoding_video);\n file.ignoreDownloadOnlyPrompt = true;\n this.send('addValidFile', file);\n }, fileIndex, URL.createObjectURL(file), encoding_video);\n } else if (this.mimeTypes.indexOf(file.type) === -1) {\n this.send('addValidFile', file);\n } else {\n this.send('addInvalidFile', file);\n }\n },\n deleteAttachment: function (file) {\n this.send('deleteFile', file);\n },\n renameAttachment: function (attachment, newName) {\n if (attachment.file?.isUrl) {\n this.kalturaService.kalturaRenameVideo(attachment.uploaded_id, newName);\n Ember.set(attachment, 'name', newName);\n } else {\n Ember.set(attachment, 'name', newName);\n this.store.findRecord('attachment', attachment.uploaded_id).then(loadedAttachment => {\n loadedAttachment.set('name', newName);\n loadedAttachment.save();\n });\n }\n },\n requestResubmit() {\n (0, _dialog.default)('Are you sure you want to request a resubmission?', ['Yes', 'No']).then(choice => {\n if (choice.indexOf('Yes') === 0) {\n this.postSubmissionGrade('r');\n }\n });\n },\n setRubricAssessmentIsDirtyCallback(isRubricAssessmentDirty) {\n this.isRubricAssessmentDirty = isRubricAssessmentDirty;\n },\n onSubmitGrade(grade, onCompleteCallback) {\n this.set('session.isWorking', true);\n this.postSubmissionGrade(grade, () => {\n this.actions.postSubmissionComment.call(this, () => {\n this.set('session.isWorking', false);\n this.set('bodyInput', '');\n onCompleteCallback();\n }, true);\n }, () => {\n this.set('session.isWorking', false);\n });\n },\n toggleIsShowingAssignmentDetails() {\n this.set('isShowingAssignmentDetails', !this.get('isShowingAssignmentDetails'));\n },\n deleteHistoricSubmission(attemptNum, historicAttachments, userId) {\n const message = 'Are you sure you wish to delete this past submission? This action CANNOT be undone!';\n const submissionDbId = this.get('openAssignmentView.db_id');\n (0, _dialog.default)(message, [`Yes. But, before you click this button, first perform any manual file deletion in S3 and Canvas (in that order). submission db id: ${submissionDbId}`, 'No']).then(choice => {\n if (choice.indexOf('Yes') === 0) {\n /* eslint-disable-next-line ember/no-get */\n const sectionId = this.get('session.section.id');\n /* eslint-disable-next-line ember/no-get */\n const assignmentId = this.get('openAssignmentView.assignment.content.id').split('-')[0];\n /* eslint-disable-next-line ember/no-get */\n\n const self = this;\n Ember.$.post(`/interface/sections/${sectionId}/assignments/${assignmentId}/submissions/${submissionDbId}/delete_historic_submission`, JSON.stringify({\n attempt: attemptNum,\n userId,\n attachments: historicAttachments ? historicAttachments.map(ha => ha.id).join(',') : []\n }), () => {\n const submission = self.submissions.find(item => {\n return item.id = submissionDbId && item.attempt === attemptNum;\n });\n self.submissions.removeObject(submission);\n }).fail(function () {\n (0, _dialog.default)('Problem occurred deleting historic submission. Please reload the page and try again.');\n });\n }\n });\n },\n deleteAssignmentComment(postID) {\n (true && !(Boolean(postID)) && Ember.assert('postID should exist', Boolean(postID)));\n /* eslint-disable-next-line ember/no-get */\n const section = this.get('session.section.id');\n /* eslint-disable-next-line ember/no-get */\n const assignment = this.get('openAssignmentView.assignment.content.id').split('-')[0];\n /* eslint-disable-next-line ember/no-get */\n const submission = this.get('openAssignmentView.id');\n (true && !(section) && Ember.assert('section id exists', section));\n (true && !(assignment) && Ember.assert('assignment id exists', assignment));\n (true && !(submission) && Ember.assert('submission id exists', submission));\n this.store.nestResources('comment', [{\n section\n }, {\n assignment\n }, {\n submission\n }]);\n this.store.findRecord('comment', postID).then(function (record) {\n record.set('is_deleted', true);\n record.save();\n });\n },\n deleteSubmissionAttachment(sectionId, submissionId, assignmentDbId, userId, attachmentId) {\n const message = 'Are you sure you wish to delete this attachment? This action CANNOT be undone!';\n const self = this;\n (0, _dialog.default)(message, [`Yes. But, before you click this button, first perform any manual file deletion in S3 and Canvas (in that order). attachment id: ${attachmentId}`, 'No']).then(choice => {\n if (choice.indexOf('Yes') === 0) {\n let destroy = false;\n self.set('session.isWorking', true);\n this.store.findRecord('attachment', attachmentId).then(record => {\n this.store.nestResources('attachment', [{\n section: sectionId\n }, {\n assignment: assignmentDbId\n }, {\n submission: submissionId\n }, {\n user: userId\n }, {\n destroy\n }]);\n try {\n record.destroyRecord().then(() => {\n if (submissionId) {\n this.store.findRecord('submission', submissionId).then(function (record) {\n let currentAttachmentIds = record.get('attachments');\n record.set('attachments', currentAttachmentIds.filter(a => a != attachmentId));\n self.set('session.isWorking', false);\n });\n } else {\n self.set('session.isWorking', false);\n //submissionId is not defined if we are deleting an attachment for submission history\n }\n this.store.nestResources('attachment', []);\n }).catch(() => {\n (0, _dialog.default)('Problem occurred deleting attachment. Please reload the page and try again.');\n self.set('session.isWorking', false);\n });\n } catch (e) {\n (0, _dialog.default)('Problem occurred deleting attachment. Please reload the page and try again.');\n self.set('session.isWorking', false);\n }\n });\n }\n });\n },\n destroyEditor: function () {\n /**\n * Note: this is currently called twice. Once in the assignments mixin and once in 'closeModals'.\n */\n this._super();\n\n //This is here so that the 'toggleAssignmentModal' observer gets triggered each time opening up the assignment modal. This\n //in turn ensures that the needed assignment data gets set.\n this.set('openAssignmentView', false);\n },\n deleteSubmissionCommentAttachment(sectionId, commentId, assignmentId, userId, attachmentId) {\n const message = 'Are you sure you wish to delete this attachment? This action CANNOT be undone!';\n const self = this;\n (0, _dialog.default)(message, [`Yes. Soft delete.`, `Yes. Leave no trace. But, before you click this button, permanent deletion in S3 must be performed manually. Attachment id: ${attachmentId}`, 'No']).then(choice => {\n if (choice.indexOf('Yes') === 0) {\n let destroy = choice === 'Yes. Leave no trace.';\n self.set('session.isWorking', true);\n this.store.findRecord('attachment', attachmentId).then(record => {\n this.store.nestResources('attachment', [{\n section: sectionId\n }, {\n assignment: assignmentId\n }, {\n comment: commentId\n }, {\n user: userId\n }, {\n destroy\n }]);\n try {\n record.destroyRecord().then(() => {\n this.store.findRecord('comment', commentId).then(function (record) {\n let currentAttachmentIds = record.get('attachments');\n record.set('attachments', currentAttachmentIds.filter(a => a != attachmentId));\n self.set('session.isWorking', false);\n });\n this.store.nestResources('attachment', []);\n }).catch(() => {\n (0, _dialog.default)('Problem occurred deleting attachment. Please reload the page and try again.');\n self.set('session.isWorking', false);\n });\n } catch (e) {\n (0, _dialog.default)('Problem occurred deleting attachment. Please reload the page and try again.');\n self.set('session.isWorking', false);\n }\n });\n }\n });\n }\n }\n });\n});","define(\"bocce/controllers/classroom/lessons/survey\", [\"exports\", \"bocce/mixins/enmodal\", \"bocce/mixins/notify\"], function (_exports, _enmodal, _notify) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default, _notify.default, {\n queryParams: ['nudge', 'concluded'],\n nudge: false,\n concluded: false,\n classroom: Ember.inject.controller(),\n // required by EnmodalMixin\n\n actions: {\n display: async function () {\n /* eslint-disable-next-line ember/no-get */\n let course_id = this.get('session.course.id');\n let response = await fetch(`/interface/survey/${course_id}`);\n let data = await response.json();\n let form = document.getElementById('survey_init');\n form.innerHTML = '';\n for (let key in data) {\n if (data[key] !== null) {\n let html = ``;\n form.insertAdjacentHTML('beforeend', html);\n }\n }\n form.submit();\n }\n }\n });\n});","define(\"bocce/controllers/conversations\", [\"exports\", \"bocce/mixins/discussable\", \"bocce/mixins/enmodal\", \"bocce/mixins/conversable\"], function (_exports, _discussable, _enmodal, _conversable) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_discussable.default, _conversable.default, _enmodal.default, {\n init(...args) {\n this._super(...args);\n this.sortProperties = this.sortProperties || ['lastActivity'];\n this.conversationSortRules = this.conversationSortRules || ['lastActivity:desc'];\n },\n classroom: Ember.inject.controller(),\n userprofileService: Ember.inject.service('userprofile'),\n conversation: Ember.inject.controller('classroom.lessons.conversation'),\n discussion: Ember.inject.controller('classroom.lessons.discussion'),\n // required by DiscussableMixin\n newDiscussion: Ember.inject.controller('classroom.lessons.discussion-new'),\n // required by DiscussableMixin\n\n sortAscending: false,\n filteredList: null,\n recipientId: null,\n courseInstructor: Ember.computed('session.teachingUser', function () {\n return this.get('session.teachingUser');\n }),\n sortedConversations: Ember.computed.sort('activeConversations', 'conversationSortRules'),\n activeConversations: Ember.computed('model.@each.{workflow_state,conversationPartners}', function () {\n let convos = this.model;\n if (convos) {\n return this.model.filterBy('active', true);\n }\n return [];\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n unreadCountInbox: Ember.computed('model.@each.workflow_state', function () {\n /* eslint-disable-next-line ember/no-get */\n let conversationsLoaded = this.get('classroom.conversationsLoaded') || false;\n if (!conversationsLoaded) {\n /* eslint-disable-next-line ember/no-get */\n return this.get('session.course.numUnreadConversations');\n } else {\n return this.model.filterBy('workflow_state', 'unread').length;\n }\n }),\n actions: {\n closeModals: function () {\n this._super();\n this.conversation.send('deleteUnsentConversation');\n },\n pmInstructor: function () {\n this.transitionToRoute('classroom.lessons.conversation-new-with', this.get('courseInstructor.id'));\n }\n }\n });\n});","define(\"bocce/controllers/dashboard\", [\"exports\", \"bocce/utilities/logout\", \"bocce/utilities/dialog\"], function (_exports, _logout, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Controller.extend({\n loginStreak: Ember.inject.service('login-streak'),\n dashboardSorter: Ember.inject.service('dashboard-sorter'),\n currentTab: 'courses',\n maximizedCard: false,\n isDYS: Ember.computed('model', 'model.[]', function () {\n let model = this.get('model');\n return model.get('firstObject.isDYS');\n }),\n unreadDiscoursePosts: Ember.computed('model', 'model.[]', function () {\n let model = this.get('model');\n return model.get('firstObject.unreadDiscoursePosts');\n }),\n showDiscourseSpeechBubble: Ember.computed('model', 'model.[]', function () {\n // Check the local storage var \"discourseSpeechBubbleClosed\" to see if it's true\n let discourseSpeechBubbleClosed = localStorage.getItem('discourseSpeechBubbleClosed');\n if (!!discourseSpeechBubbleClosed) {\n return false;\n }\n let model = this.get('model');\n let unreadPostsCounter = model.get('firstObject.unreadDiscoursePosts');\n if (unreadPostsCounter === \"!\") {\n return true;\n }\n return false;\n }),\n hasStudentEnrollment: false,\n showScrollToTop: true,\n lastUpdateTimestamp: null,\n displayStatsPopup: false,\n hideClassmateStats: false,\n experimentalFeaturesEnabled: false,\n startPolling() {\n // Set local storage var \"dashboardHB\" to true -- set to false to manually stop the heartbeat\n localStorage.setItem('dashboardHB', true);\n\n // Make sure user has the dashboard tab open\n let tabIsActive = !document.hidden;\n let lastMouseMovement = Date.now();\n const HEARTBEAT_INTERVAL = 30000; // polling interval in milliseconds\n const INACTIVITY_THRESHOLD = 60000; // after 60 seconds of inactivity, skip heartbeat\n\n document.addEventListener('visibilitychange', () => {\n tabIsActive = !document.hidden;\n });\n document.addEventListener('mousemove', () => {\n lastMouseMovement = Date.now();\n });\n this.polling = setInterval(() => {\n // Check local storage var \"dashboardHB\" to see if it's true\n let dashboardHB = localStorage.getItem('dashboardHB');\n let currentTime = Date.now();\n let doHB = dashboardHB && tabIsActive && currentTime - lastMouseMovement < INACTIVITY_THRESHOLD;\n if (doHB) {\n console.log(\"Beep.\");\n this.refreshModel();\n } else {\n console.log(\"Skip.\");\n }\n }, HEARTBEAT_INTERVAL); // 30 seconds\n },\n refreshModel() {\n this.set('lastUpdateTimestamp', new Date().getTime());\n this.store.queryRecord('dashboard', {\n fingerprint: this.get('model.firstObject.fingerprint')\n }).then(updatedModel => {\n if (!updatedModel || !updatedModel.get('length')) {\n return;\n }\n this.set('model', updatedModel);\n }).catch(error => {\n console.error(`Error while reloading the model: ${error}`);\n });\n },\n // Make sure to clear the polling when the controller is destroyed\n willDestroy() {\n super.willDestroy(...arguments);\n clearInterval(this.polling);\n },\n init() {\n this._super(...arguments);\n\n //Comment to stop heartbeat\n this.startPolling();\n\n //Check Local Storage for the dashboardMessageNoClassPerm\n let dashboardMessageNoClassPerm = localStorage.getItem('dashboardMessageNoClassPerm');\n if (dashboardMessageNoClassPerm) {\n (0, _dialog.default)(`You do not have permission to view this class. If you believe this is an error, please contact support@online.berklee.edu.`);\n localStorage.removeItem('dashboardMessageNoClassPerm');\n }\n },\n terms: Ember.computed('model', 'model.[]', function () {\n let prefs = this.get('session.user.profile');\n this.set('hideClassmateStats', prefs ? prefs.hideClassmateStats : false);\n this.set('experimentalFeaturesEnabled', prefs ? prefs.experimentalFeaturesEnabled : false);\n this.setSorterProperties();\n let model = this.get('model');\n if (!model) {\n return [];\n } else {\n let modelBackup = this.get('modelBackup');\n let fingerprint = model.get('firstObject.fingerprint');\n if (modelBackup && modelBackup.get('fingerprint') === fingerprint) {\n return modelBackup.get('terms');\n }\n this.set('modelBackup', model.firstObject);\n return model.get('firstObject.terms');\n }\n }),\n userId: Ember.computed('model', 'model.[]', function () {\n return this.get('model.firstObject.user_id');\n }),\n upcoming_live_class: Ember.computed('model', 'model.[]', function () {\n let model = this.get('model');\n return model.get('firstObject.next_live_class');\n }),\n currentTermLoginData: Ember.computed('model', 'model.userTermData', function () {\n let model = this.get('model');\n if (!model) {\n return;\n }\n let termData = model.get('firstObject.userTermData');\n const currentWeek = model.get('firstObject.terms.firstObject.week_num');\n if (termData && termData.login_days && currentWeek) {\n this.loginStreak.initialize(currentWeek, termData?.login_days || {});\n }\n return termData;\n }),\n displayIntroPopup: Ember.computed('session.user.profile', function () {\n let prefs = this.get('session.user.profile');\n if (!prefs) {\n return false;\n }\n\n // Check the local storage var \"accepteddashboardBeta2IntroPopup\" to see if it's true\n let acceptedDashboardBetaIntroPopup = prefs.acceptedDashboardBeta2IntroPopup || localStorage.getItem('acceptedDashboardBeta2IntroPopup');\n return !acceptedDashboardBetaIntroPopup;\n }),\n setSorterProperties() {\n this.dashboardSorter.setProperty('showUngraded', true);\n },\n savePrefs(prefs) {\n this.store.findRecord('user', this.get('session.user.id')).then(record => {\n record.set('profile_updated', true);\n record.set('profile', prefs);\n record.save();\n });\n },\n actions: {\n toggleSideMenu: function () {\n this.toggleProperty('showSideMenu');\n },\n toggleSideWidgets: function () {\n this.toggleProperty('showSideWidgets');\n },\n scrollElementIntoView: function (element, offset = 0) {\n if (!element) {\n return;\n }\n\n // Find the first scrollable parent\n let parent = element.parentNode;\n while (parent && (parent.scrollHeight === parent.clientHeight || getComputedStyle(parent).overflowY === 'visible')) {\n parent = parent.parentNode;\n }\n if (!parent || !(parent instanceof HTMLElement)) {\n return;\n }\n const parentRect = parent.getBoundingClientRect();\n const elementRect = element.getBoundingClientRect();\n\n // Calculate the distance the element needs to travel to be aligned with the top of the viewport + offset.\n const delta = elementRect.top - (parentRect.top + offset);\n\n // Adjust the parent's scrollTop by this delta\n parent.scrollTop += delta;\n },\n dismissIntroPopup: function () {\n let prefs = this.get('session.user.profile');\n if (!prefs) {\n return;\n }\n Ember.set(prefs, 'acceptedDashboardBeta2IntroPopup', true);\n localStorage.setItem('acceptedDashboardBeta2IntroPopup', 'true');\n this.savePrefs(prefs);\n this.set('displayIntroPopup', false);\n },\n toggleExperimentalFeatures: function () {\n let prefs = this.get('session.user.profile');\n if (!prefs) {\n return;\n }\n let experimentalOn = !this.get('experimentalFeaturesEnabled');\n this.setSorterProperties();\n if (!experimentalOn) {\n this.set('hideClassmateStats', true);\n }\n Ember.set(prefs, 'experimentalFeaturesEnabled', experimentalOn);\n this.set('experimentalFeaturesEnabled', experimentalOn);\n let element = document.getElementsByClassName('dashboard-intro-popup')[0];\n if (element) {\n element.scrollTo(0, element.scrollHeight);\n }\n this.savePrefs(prefs);\n },\n displayIntro: function () {\n this.set('displayIntroPopup', true);\n },\n tabNavigation: function (tab) {\n this.set('showSideMenu', false);\n this.set('currentTab', tab);\n },\n messageUser: function (id) {\n this.set('sendMessageTo', id);\n this.send('tabNavigation', 'inbox');\n },\n launchDiscourse: function () {\n this.send('closeDiscourseSpeechBubble');\n let discourseUrl = this.get('model.firstObject.discourseUrl');\n window.open(discourseUrl, '_blank');\n },\n closeDiscourseSpeechBubble: function () {\n event.stopPropagation();\n // Save the state to local storage, so the bubble doesn't show up again\n localStorage.setItem('discourseSpeechBubbleClosed', 'true');\n this.set('showDiscourseSpeechBubble', false);\n },\n logout: function () {\n (0, _logout.default)();\n },\n viewTerm: function (termNumber) {\n event.stopPropagation();\n // Scroll to element with id #term_{{termNumber}}\n let element = document.getElementById(`term_${termNumber}`);\n if (element) {\n this.scrollElementIntoView(element);\n document.getElementsByClassName('dashboard')[0].scrollBy(0, -100);\n }\n },\n navigate(courseClosed, startDate) {\n event.stopPropagation();\n // If the course is closed, show a dialog\n if (courseClosed) {\n // force date to be EST, formatted as MM/DD/YYYY\n let startDateFormatted = startDate ? new Date(startDate).toLocaleString('en-US', {\n timeZone: 'America/New_York',\n month: '2-digit',\n day: '2-digit',\n year: 'numeric'\n }) : 'the first day of the term';\n (0, _dialog.default)(`Please note this course hasn't opened yet. Sections open between 9 AM and 6 PM ET on ${startDateFormatted}. You will receive an email notification once this course becomes available. We hope you have a great semester!`);\n return;\n }\n\n // Check the href of the link clicked and navigate to this page in a new tab, unless sameWindow is true\n let href = event.target.href;\n if (!href) {\n let closetsLink = event.target.closest('a');\n if (closetsLink) {\n href = closetsLink.href;\n }\n }\n window.open(href, '_blank');\n },\n scrollToTop() {\n event.stopPropagation();\n // Scroll contents of .dashboard to top\n let element = document.getElementsByClassName('dashboard')[0];\n if (element) {\n element.scrollTo(0, 0);\n }\n },\n updateMaximizedCard(newIndex) {\n this.set('maximizedCard', newIndex);\n },\n toggleClassmateStats: function () {\n let prefs = this.get('session.user.profile');\n if (!prefs) {\n return;\n }\n let hideClassmateStats = this.get('hideClassmateStats');\n Ember.set(prefs, 'hideClassmateStats', !hideClassmateStats);\n this.set('hideClassmateStats', !hideClassmateStats);\n this.savePrefs(prefs);\n },\n dismissStatsInfo: function () {\n this.set('displayStatsPopup', false);\n },\n displayStatsInfo: function () {\n this.set('displayStatsPopup', true);\n }\n }\n });\n});","define(\"bocce/controllers/discussions\", [\"exports\", \"bocce/mixins/audio-rec\", \"bocce/mixins/discussable\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/enmodal\"], function (_exports, _audioRec, _discussable, _videoRec, _rtcRec, _enmodal) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_audioRec.default, _discussable.default, _videoRec.default, _rtcRec.default, _enmodal.default, {\n init(...args) {\n this._super(...args);\n this.sortProperties = this.sortProperties || ['sortDate:desc'];\n },\n classroom: Ember.inject.controller(),\n // required by DiscussableMixin\n discussion: Ember.inject.controller('classroom.lessons.discussion'),\n // required by DiscussableMixin\n newDiscussion: Ember.inject.controller('classroom.lessons.discussion-new'),\n // required by DiscussableMixin\n userprofileService: Ember.inject.service('userprofile'),\n // When the app first boots up, the discussions controller loads\n // without a model property. When that happens Ember crashes because\n // computed properties try and get data from the model. This property\n // returns the model if it exists, otherwise returns an empty array.\n all_model: Ember.computed('model', function () {\n return this.model || [];\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n numRequiredDiscussions: Ember.computed('discussions.{@each.read,responses.@each.read}', 'all_model.[]', 'activities.@each.read', function () {\n return this.requiredDiscussions.filter(discussion => {\n return (!discussion.get('read') || discussion.get('hasUnreadResponses')) && !discussion.get('locked');\n }).length;\n }),\n numUnrequiredDiscussions: Ember.computed('discussions.{@each.read,responses.@each.read}', 'all_model.[]', function () {\n let filteredConversations = this.discussions.filter(discussion => {\n return !discussion.get('is_required') && (!discussion.get('read') || discussion.get('hasUnreadResponses')) && !discussion.get('locked');\n });\n return filteredConversations.length;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n numSubmissionsNeedAttention: Ember.computed('submissions.@each.{needsStudentAttention,needsTeacherAttention}', function () {\n let subs = this.submissions,\n /* eslint-disable-next-line ember/no-get */\n filter = this.get('session.isInstructor') ? 'needsTeacherAttention' : 'needsStudentAttention';\n return subs.filterBy(filter, true).get('length');\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n numAssignmentsNeedAttention: Ember.computed('assignments.@each.{needsStudentAttention,needsTeacherAttention}', 'submissions.@each.{needsStudentAttention,needsTeacherAttention}', function () {\n let assignments = this.assignments,\n /* eslint-disable-next-line ember/no-get */\n filter = this.get('session.isInstructor') ? 'needsTeacherAttention' : 'needsStudentAttention';\n return assignments.filterBy(filter, true).get('length');\n }),\n discussions: Ember.computed.filterBy('all_model', 'is_announcement', false),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n requiredDiscussions: Ember.computed('all_model.@each.{is_announcement,responses}', function () {\n let retVal = this.all_model.filterBy('is_required', true);\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.isInstructor')) {\n retVal = retVal.filter(discussion => {\n let now = moment(),\n discussionDue = moment(discussion.get('due_at'));\n return !discussion.get('due_at') || discussionDue < now || discussion.get('dueWithinOneWeek') || discussion.get('dueWithinTwoWeeks');\n });\n }\n return retVal;\n }),\n announcements: Ember.computed.filterBy('all_model', 'is_announcement', true),\n assignments: Ember.computed('loadedAssignments', function () {\n if (this.loadedAssignments) {\n return this.loadedAssignments;\n } else {\n return [];\n }\n }),\n submissions: Ember.computed('assignments.@each.submissions', function () {\n let retval = Ember.A();\n this.assignments.forEach(a => retval.pushObjects(a.get('submissions').toArray()));\n return retval;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n quizzes: Ember.computed(function () {\n return this.store.findAll('quiz');\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n work: Ember.computed('quizzes.@each.attempts', 'discussions.@each.{id,unread_count}', 'assignments.@each.{id,numUngradedSubmissions,submissions,teacher_requires_resubmission}', function () {\n var discussions = this.requiredDiscussions || [],\n assignments = this.assignments || [],\n quizzes = this.quizzes || [],\n quizLength,\n stream = Ember.A(),\n i,\n current,\n emptyWeek = true,\n dividers = [{\n due_at: moment(new Date()).add(1, 'year').isoWeekday(6).toISOString(),\n is_divider: true,\n divider_message: 'To-Do',\n current: true,\n overdue: false\n }, {\n due_at: moment(new Date()).add(1, 'weeks').isoWeekday(1).toISOString(),\n is_divider: true,\n divider_message: 'Completed',\n overdue: true,\n current: false\n }];\n quizLength = quizzes.get('length');\n if (quizLength > 0) {\n for (i = 0; i < quizLength; i++) {\n if (quizzes.objectAt(i).get('attempts.length') > 0) {\n quizzes.objectAt(i).set('latestAttempt', quizzes.objectAt(i).get('attempts').objectAt(0));\n }\n }\n }\n stream.pushObjects(assignments.toArray());\n stream.pushObjects(discussions.toArray());\n stream.pushObjects(quizzes.toArray());\n for (i = 0; i < stream.length; i++) {\n if (!stream[i].get('is_divider')) {\n current = stream[i].get('i_submitted') || stream[i].get('currentUserSubmission') || stream[i].get('latestAttempt');\n if (stream[i].get('due_at')) {\n stream[i].set('due_at', moment(stream[i].get('due_at')).toISOString());\n }\n stream[i].set('overdue', false);\n\n // Mark overdue assignments\n if (!current && (!moment(stream[i].get('due_at')).isSame(Date(), 'week') && !moment(stream[i].get('due_at')).isSame(moment(new Date()).isoWeekday(7), 'day') || moment(stream[i].get('due_at')).isSame(moment(new Date()).isoWeekday(7).subtract(1, 'weeks'), 'day'))) {\n stream[i].set('current', true);\n stream[i].set('overdue', true);\n } else {\n emptyWeek = false;\n stream[i].set('current', !current);\n }\n\n // Force marked for resubmission assignments to the top of the pile\n if (stream[i].get('teacher_requires_resubmission') === 'needs_resubmit') {\n stream[i].set('current', true);\n stream[i].set('overdue', true);\n }\n }\n }\n if (emptyWeek) {\n dividers.push({\n due_at: moment(new Date()).add(1, 'days').isoWeekday(1).toISOString(),\n is_divider: true,\n empty_week: true,\n divider_message: 'Nothing due currently.',\n current: true\n });\n }\n stream.pushObjects(dividers);\n return Ember.ArrayProxy.extend({\n init(...args) {\n this._super(...args);\n // eslint-disable-next-line ember/no-side-effects\n this.sortProperties = this.sortProperties || ['current:desc', 'due_at:desc'];\n },\n content: stream,\n sorted: Ember.computed.sort('content', 'sortProperties')\n }).create();\n }),\n numUnreadAnnouncements: Ember.computed('announcements.@each.read', 'all_model.[]', function () {\n return this.announcements.filterBy('read', false).filterBy('locked', false).length;\n }),\n numUngradedSubmissions: Ember.computed('assignments.@each.numUngradedSubmissions', function () {\n let assignments = this.assignments,\n totalUngraded = 0;\n assignments.forEach(a => totalUngraded += a.get('numUngradedSubmissions'));\n return totalUngraded;\n }),\n numResubmissionsPending: Ember.computed('assignments.@each.numResubmissionsPending', function () {\n let assns = this.assignments;\n let retval = 0;\n assns.forEach(a => retval += a.get('numResubmissionsPending'));\n return retval;\n }),\n numUnreadSubmissions: Ember.computed('assignments.@each.numUnreadSubmissions', function () {\n let assns = this.assignments;\n return assns.reduce((total, assn) => total + assn.get('numUnreadSubmissions'), 0);\n }),\n numUnreadAssignments: Ember.computed('assignments.@each.hasUnreadSubmissions', function () {\n let assns = this.assignments;\n return assns.filterBy('hasUnreadSubmissions', true).get('length');\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n activities: Ember.computed('announcements.@each.{id,unread_count}', 'assignments.@each.{comments,grade,id,numUngradedSubmissions,submissions,teacher_requires_resubmission}', 'discussions.@each.{id,unread_count}', 'filterType', function () {\n var discussions = this.discussions || [],\n announcements = this.announcements || [],\n assignments = this.assignments || [],\n stream = Ember.A(),\n ac = assignments.get('content'),\n i,\n z,\n bc,\n cc,\n currentComment,\n comments = [],\n gDate,\n compareDates = function (a, b) {\n if (a.date < b.date) {\n return 1;\n } else if (a.date > b.date) {\n return -1;\n }\n return 0;\n };\n moment.tz.add(['EST|EST|50|0|', 'America/New_York|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 11B0 1qL0 1a10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x40 iv0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e6']);\n\n // Find latest response time for each assignment\n // If there are none, go with assignment update time\n // BUT - Only if the filter isn't set to 'assignments'\n // IF it is - assignments should only filter by due date\n if (this.filterType !== 'assignments') {\n if (!!ac && ac.length > 0) {\n for (i = 0; i < ac.length; i++) {\n if (!ac[i].record.get) {\n break;\n }\n bc = ac[i].record.get('submissions.arrangedContent.arrangedContent');\n if (bc) {\n for (z = 0; z < bc.length; z++) {\n cc = bc[z].get('comments.arrangedContent.arrangedContent');\n currentComment = cc[cc.length - 1] || false;\n if (currentComment) {\n comments.push({\n date: currentComment.get('created_at'),\n postTime: moment(currentComment.get('created_at')).tz('America/New_York').calendar()\n });\n }\n }\n }\n if (comments.length > 0) {\n comments.sort(compareDates);\n ac[i].record.set('lastResponse', comments[0]);\n }\n comments = [];\n }\n }\n }\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.isInstructor')) {\n assignments = assignments.filter(assignment => {\n let now = moment(),\n assignmentDue = moment(assignment.get('due_at'));\n return assignmentDue < now || assignment.get('dueWithinOneWeek') || assignment.get('dueWithinTwoWeeks') || !assignment.get('due_at');\n });\n discussions = discussions.filter(discussion => {\n let now = moment(),\n discussionDue = moment(discussion.get('due_at'));\n return !discussion.get('is_required') || !discussion.get('due_at') || discussionDue < now || discussion.get('dueWithinOneWeek') || discussion.get('dueWithinTwoWeeks');\n });\n }\n stream.pushObjects(discussions.toArray());\n stream.pushObjects(announcements.toArray());\n stream.pushObjects(assignments.toArray());\n\n // Conventionalize date format, so Ember can perform sorting properly\n for (i = 0; i < stream.length; i++) {\n if (stream[i].get('date') === stream[i].get('lastResponse.date')) {\n gDate = stream[i].get('due_at') || stream[i].get('lastResponse.date');\n } else {\n gDate = stream[i].get('lastResponse.date') || stream[i].get('due_at');\n }\n if (gDate) {\n stream[i].set('sortDate', moment(gDate).toISOString());\n }\n }\n return Ember.ArrayProxy.extend({\n content: stream,\n sorted: Ember.computed.sort('content', 'sortProperties'),\n sortProperties: this.sortProperties\n }).create();\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n unreadCountActivities: Ember.computed('numRequiredDiscussions', 'numUnrequiredDiscussions', 'numUnreadAnnouncements', 'numSubmissionsNeedAttention', 'numAssignmentsNeedAttention', function () {\n let retval = this.numRequiredDiscussions + this.numUnrequiredDiscussions + this.numUnreadAnnouncements;\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.isInstructor')) {\n return retval + this.numSubmissionsNeedAttention;\n } else {\n return retval + this.numAssignmentsNeedAttention;\n }\n }),\n unfinishedCountWork: Ember.computed('work', 'discussions.@each.i_submitted', 'assignments.@each.{currentUserSubmission,teacher_requires_resubmission}', function () {\n let work = this.work,\n num_assns = work.filterBy('activity_type', 'assignment').filter(a => !a.get('locked') && (!a.get('currentUserSubmission') || a.get('teacher_requires_resubmission') === 'needs_resubmit')).length,\n num_discs = work.filterBy('activity_type', 'discussion').filter(d => !d.get('locked') && !d.get('i_submitted')).length;\n return num_assns + num_discs;\n }),\n actions: {\n viewInLesson: function (assignment_id) {\n var that = this;\n this.store.findRecord('item', assignment_id).then(function (item) {\n var lessonId = item.get('lesson').id;\n if (lessonId) {\n that.transitionToRoute('classroom.lessons', lessonId, assignment_id);\n }\n }, function () {\n Ember.debug('Can\\'t find lesson that contains assignment.');\n });\n },\n filterActivities: function (filterType, unread) {\n // Grab value of select when a dropdown filter is used\n if (typeof unread === 'object') {\n unread = unread.target.value;\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .styled-select').addClass('current');\n }\n let ungraded = unread === 'ungraded';\n let resubmission = unread === 'resubmission';\n let comments = unread === 'comments';\n let ungraded_and_unread = unread === 'new';\n if (filterType === 'assignments' && !unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .styled-select option').removeAttr('selected');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .styled-select').removeClass('current');\n }\n if (unread === 'all') {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .styled-select').removeClass('current');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .filter-label.assignments').addClass('current');\n unread = false;\n }\n if (filterType !== 'assignments') {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .styled-select').removeClass('current');\n }\n unread = unread === 'unread' ? true : false;\n this.set('filterType', filterType);\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action button').removeClass('current');\n if (unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .filter-unread-count.' + filterType).addClass('current');\n } else if (ungraded) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .filter-ungraded-count.ungraded.' + filterType).addClass('current');\n } else if (resubmission) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .filter-ungraded-count.pending-resubmit.' + filterType).addClass('current');\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activity-filters .filter-action .filter-label.' + filterType).addClass('current');\n }\n switch (filterType) {\n case 'discussions':\n if (unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.unread:not(.requiredDiscussion)').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.read:not(.requiredDiscussion)').hide();\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion:not(.requiredDiscussion)').show();\n }\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.requiredDiscussion').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom .side-panel').scrollTop(0);\n break;\n case 'requiredDiscussions':\n if (unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.unread.requiredDiscussion').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.read.requiredDiscussion').hide();\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.requiredDiscussion').show();\n }\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion:not(.requiredDiscussion)').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom .side-panel').scrollTop(0);\n break;\n case 'announcements':\n if (unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement.unread').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement.read').hide();\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement').show();\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom .side-panel').scrollTop(0);\n break;\n case 'assignments':\n if (unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.requires-attention').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment:not(.requires-attention)').hide();\n } else if (ungraded) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.has_ungraded').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment:not(.has_ungraded)').hide();\n } else if (resubmission) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.needs_resubmit').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment:not(.needs_resubmit)').hide();\n } else if (comments) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.has_comments').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment:not(.has_comments)').hide();\n } else if (ungraded_and_unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.has_ungraded').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.has_comments').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment:not(.has_comments):not(.has_ungraded)').hide();\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment').show();\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom .side-panel').scrollTop(0);\n break;\n case 'all':\n if (unread) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.unread:not(.requiredDiscussion)').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.read:not(.requiredDiscussion)').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.unread.requiredDiscussion').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion.read.requiredDiscussion').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement.unread').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement.read').hide();\n\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('session.isInstructor')) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.requires-attention').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment:not(.requires-attention)').hide();\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment:not(.has_ungraded), .activities:not(.work) .assignment:not(.has_comments)').hide();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.has_ungraded, .activities:not(.work) .assignment.has_comments').show();\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment.needs_resubmit').show();\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .discussion').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .announcement').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.activities:not(.work) .assignment').show();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.classroom .side-panel').scrollTop(0);\n }\n break;\n default:\n break;\n }\n }\n }\n });\n});","define(\"bocce/controllers/events\", [\"exports\", \"bocce/mixins/enmodal\", \"bocce/mixins/webex\", \"bocce/controllers/classroom/lessons/event-new\", \"bocce/utilities/dialog\"], function (_exports, _enmodal, _webex, _eventNew, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-observers */\n var _default = _exports.default = Ember.Controller.extend(_enmodal.default, _webex.default, {\n init(...args) {\n this._super(...args);\n this.sortChatsBy = this.sortChatsBy || ['startAt'];\n this.sortPastChatsBy = this.sortPastChatsBy || ['startAt:desc'];\n },\n event: Ember.inject.controller('classroom.lessons.event'),\n classroom: Ember.inject.controller(),\n eventsService: Ember.inject.service('events'),\n userprofileService: Ember.inject.service('userprofile'),\n application: Ember.inject.controller(),\n // required by WebexMixin\n events: Ember.inject.controller(),\n // required by WebexMixin\n newStudentEvent: Ember.inject.controller('classroom.lessons.student-event-new'),\n // required by WebexMixin\n\n showOnlyInstructorChats: false,\n upcomingChatsExpanded: false,\n /* eslint-disable-next-line ember/no-observers */\n instructorChatsFilterObserver: Ember.observer('showOnlyInstructorChats', function () {\n if (!this.showOnlyInstructorChats) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.events-instructor-chat-toggle-container').removeClass('checked');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event.student-chat').removeClass('hidden');\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.events-instructor-chat-toggle-container').addClass('checked');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.event.student-chat').addClass('hidden');\n }\n }),\n //I can't get ember to update 'runnable' in the event model. So, using an observer here to force the property update.\n sessionTimeChanged: Ember.observer('session.time', function () {\n this.eventsList.forEach(e => {\n e.notifyPropertyChange('runnable');\n });\n }),\n pastChats: Ember.computed('eventsList.[]', 'session.time', 'sesssion.user', function () {\n let now = new Date(),\n currentUserAcsId = this.get('session.user.acsUserId'),\n isInstructor = this.get('session.isInstructor'),\n isStaff = this.get('session.isStaff'),\n canShow = true;\n let pastChats = this.eventsList.filter(function (e) {\n let hasMedia = e.get('meetings.length') > 0 || e.get('s3Recordings.length') > 0 || e.get('kalturaRecordings.length') > 0,\n isPastTime = moment(e.get('endAt')) < now;\n if (e.get('privateLessonStudentId')) {\n canShow = e.get('privateLessonStudentId') === currentUserAcsId || isInstructor || isStaff;\n }\n return isPastTime && canShow && hasMedia;\n });\n return pastChats;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n currentChat: Ember.computed('eventsList.[]', 'session.{time,runningChatMeetingKey}', function () {\n if (!this.session.runningChatMeetingKey) {\n return null;\n }\n let chats = this.sortedChats;\n if (!chats || chats.get('length') === 0) {\n return null;\n }\n chats = chats.filter(chat => {\n if (chat.meeting_key != this.session.runningChatMeetingKey) {\n return false;\n }\n if (chat.get('privateLessonStudentId')) {\n return chat.get('privateLessonStudentId') === this.get('session.user.acsUserId') || this.get('session.isInstructor') || this.get('session.isStaff');\n }\n return true;\n });\n if (chats.get('length') === 0) {\n return null;\n }\n return chats.objectAt(0);\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n upcomingChats: Ember.computed('eventsList.[]', 'session.time', 'currentChat', function () {\n let now = new Date(),\n chats = this.eventsList,\n cur = this.currentChat,\n teachingUser = this.get('session.teachingUser'),\n currentUserAcsId = this.get('session.user.acsUserId'),\n isInstructor = this.get('session.isInstructor'),\n isStaff = this.get('session.isStaff'),\n canShow = true;\n if (chats.get('length') === 0) {\n return [];\n }\n\n // JRW: If it's a Private Lesson, set the late start time to 5 minutes\n // If not, broaden the window to 30 minutes\n /* eslint-disable-next-line ember/no-get */\n let chatWindowInMinutes = this.get('session.course.isPrivateLesson') ? 5 : 30;\n return chats.filter(e => {\n let canStart = moment(e.get('startAt')) > now - chatWindowInMinutes * 60 * 1000,\n isNotCurrent = !cur || !cur.id || e.id != cur.id;\n if (e.get('privateLessonStudentId')) {\n canShow = e.get('privateLessonStudentId') === currentUserAcsId || isInstructor || isStaff;\n }\n // JRW: sometimes staff edit events and it changes the\n // owner to admin, this sets the event to the instructor\n // user.\n if (e.get('user').get('id') === \"1\") {\n e.set('user', teachingUser);\n }\n return canStart && isNotCurrent && canShow;\n });\n }),\n // NK: I'm reworking otherSectionChats to be an observer here, because\n // the computed property introduces side effects. Ideally we'll be\n // able to rewrite this at some point so that we can use only\n // computed properties.\n /* eslint-disable-next-line ember/no-observers */\n otherSectionChatsObserver: Ember.observer('upcomingChats', function () {\n // JRW: If the next chat starts within +/-10 minutes, check if it's a multisection chat.\n // If it is, set the model of the event controller to get the otherSectionChats\n // computed property.\n let chats = this.sortedUpcomingChats.filter(e => !e.get('isStudentChat')),\n now = moment(),\n tenMinutesInMilliseconds = 1000 * 60 * 10,\n nextUpcomingChat = chats.objectAt(0),\n otherSectionChatsPromise;\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.isInstructor') && nextUpcomingChat) {\n let start = moment(nextUpcomingChat.get('startAt'));\n if (start - tenMinutesInMilliseconds < now && start + tenMinutesInMilliseconds > now) {\n this.set('isWorking', true);\n otherSectionChatsPromise = this.eventsService.getOtherSectionChats(this.classroom, nextUpcomingChat.get('title'), nextUpcomingChat.get('startAt')).then(otherSectionChats => {\n // Attach the otherSectionChats event to the events list controller and send the length\n // of the array to the otherSectionChatsPromise handler\n this.set('isWorking', false);\n if (Array.isArray(otherSectionChats) && otherSectionChats.length !== 0) {\n this.set('otherSectionChats', otherSectionChats);\n return otherSectionChats.length;\n } else {\n return 0;\n }\n });\n\n //return the DS.PromiseObject (and its result)\n return otherSectionChatsPromise.then(result => {\n this.set('hasOtherSectionChats', result !== 0);\n });\n }\n }\n\n // default to false - doesn't get here if isInstructor is true and there's a chat to check\n return this.set('hasOtherSectionChats', false);\n }),\n // Returns the next chat, if it starts within 30 minutes from now\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n chatStartingSoon: Ember.computed('upcomingChats', 'session.time', function () {\n let candidate = this.sortedUpcomingChats.objectAt(0);\n if (!candidate) {\n return null;\n }\n let start = moment(candidate.get('startAt'));\n if (start - moment() < 1800000) {\n return candidate;\n }\n return null;\n }),\n eventsList: Ember.computed.reads('session.course.events'),\n eventsLoaded: Ember.computed.reads('classroom.eventsLoaded'),\n otherCurrentTermSections: Ember.computed.reads('classroom.otherCurrentTermSections'),\n sortedChats: Ember.computed.sort('eventsList', 'sortChatsBy'),\n sortedUpcomingChats: Ember.computed.sort('upcomingChats', 'sortChatsBy'),\n sortedPastChats: Ember.computed.sort('pastChats', 'sortPastChatsBy'),\n hasChats: Ember.computed('eventsList.[]', function () {\n /* eslint-disable-next-line ember/no-get */\n return this.get('eventsList.length') - this.eventsList.filterBy('meetings').filterBy('endAt').length > 0;\n }),\n actions: {\n createSingleSectionQuickEvent() {\n let now = moment().tz('America/New_York');\n this.set('isWorking', true);\n const section = this.session.get('section');\n const course = section.get('course');\n const courseId = course.get('id');\n let eventDataSingle = {\n event: {\n title: 'Live Class',\n description: '',\n futureEvent: true,\n startAt: now.format('YYYY-MM-DDTHH:mm:ss'),\n endAt: now.add(\"1\", \"hours\").format('YYYY-MM-DDTHH:mm:ss'),\n course\n },\n courseIds: [courseId]\n };\n _eventNew.sendEventsToInterfaceAndSendEmail.call(this, [eventDataSingle]);\n },\n viewEvent: function (event_id) {\n this.transitionToRoute('classroom.lessons.event', event_id, {\n queryParams: {\n backup_code: false\n }\n });\n },\n filterEvents: function (filterType) {\n switch (filterType) {\n case 'all':\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel .chat-panel .events .past-event').show();\n break;\n case 'current':\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel .chat-panel .events .past-event').hide();\n break;\n default:\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel .chat-panel .events .past-event').hide();\n }\n },\n dismissStartChatConfirmation: function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.chat-confirmation').addClass('hidden');\n },\n toggleExpandUpcomingChats() {\n this.toggleProperty('upcomingChatsExpanded');\n },\n toggleInstructorChats: function () {\n this.set('showOnlyInstructorChats', !this.showOnlyInstructorChats);\n }\n }\n });\n});","define(\"bocce/controllers/gradebook\", [\"exports\", \"bocce/utilities/dialog\"], function (_exports, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Controller.extend({\n init(...args) {\n this._super(...args);\n this.filters = this.filters || ['All'];\n },\n classroom: Ember.inject.controller(),\n discussions: Ember.inject.controller(),\n userprofileService: Ember.inject.service('userprofile'),\n gradeCalculator: Ember.inject.service('grade-calculator'),\n ungradedAsZero: true,\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n model: Ember.computed('classroom.gradebookOpen', 'discussions.unreadCountActivities', function () {\n return this.store.findAll('gradebook', {\n reload: true\n });\n }),\n assignmentHeaders: Ember.computed('model', 'model.content', function () {\n let headers = this.model.toArray()[0];\n if (headers) {\n for (let column of headers.columns) {\n if (column.muted) {\n column.muted = false;\n }\n }\n }\n return headers;\n }),\n assignmentUsers: Ember.computed('model', 'model.content', function () {\n return this.model.toArray().slice(1);\n }),\n grades: Ember.computed(function () {\n return ['-', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'I'];\n }),\n passfail: Ember.computed('session.aFNonCredit', function () {\n let retval = [{\n display: '-',\n value: '-'\n }, {\n display: 'Pass',\n value: 'A'\n }, {\n display: 'Fail',\n value: 'F'\n }];\n\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.aFNonCredit')) {\n retval = [{\n display: '-',\n value: '-'\n }, {\n display: 'Pass',\n value: 'A'\n }, {\n display: 'Fail',\n value: 'F'\n }, {\n display: 'A',\n value: 'A'\n }, {\n display: 'A-',\n value: 'A-'\n }, {\n display: 'B+',\n value: 'B+'\n }, {\n display: 'B',\n value: 'B'\n }, {\n display: 'B-',\n value: 'B-'\n }, {\n display: 'C+',\n value: 'C+'\n }, {\n display: 'C',\n value: 'C'\n }, {\n display: 'C-',\n value: 'C-'\n }, {\n display: 'D',\n value: 'D'\n }, {\n display: 'F',\n value: 'F'\n }, {\n display: 'I',\n value: 'I'\n }];\n }\n return retval;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n gradesDueAt: Ember.computed(function () {\n /* eslint-disable-next-line ember/no-get */\n return moment(this.get('session.gradeLockAt')).format('MMMM DD');\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n showGradesDueAt: Ember.computed('session.gradeLockAt', function () {\n /**\n * When interface receives a term update without a grade cutoff date, it is intended that there is no cutoff date. In this case, \n * the date is set to the year 9999, which is a date that more than likely will never be reached. Javascript's date's .getYear() returns\n * the number of years since 1900. So, in checking equality to 9999 - 1900, we are checking if the year is 9999, and thus if there is a grade \n * cutoff.\n */\n return this.get('session.gradeLockAt') && !(this.get('session.gradeLockAt').getYear() == 9999 - 1900);\n }),\n unmuteAllGrades: false,\n hideMutedGrades: false,\n /* eslint-disable-next-line ember/no-observers */\n calcGrades: Ember.observer('assignmentHeaders.@each.muted', 'ungradedAsZero', 'unmuteAllGrades', 'filters', function () {\n let usersPrp = this.assignmentUsers,\n unmuteAllGrades = this.unmuteAllGrades,\n ungradedAsZero = this.ungradedAsZero;\n for (let h = 0; h < usersPrp.length; h++) {\n let allUserAssignments = usersPrp[h].get('columns');\n let finalGrades = this.get('gradeCalculator').calculateFinalGrade(allUserAssignments, unmuteAllGrades, ungradedAsZero);\n\n // Assign total grades to each category (assignment type)\n for (let i = 0; i < finalGrades.categoryGrades.length; i++) {\n let indx = usersPrp[h].get('columns').findIndex(m => m && m.calcType && m.calcType.indexOf(finalGrades.categoryGrades[i].category) > -1);\n let scoreCalc = finalGrades.categoryGrades[i].score;\n let gradeCalc = finalGrades.categoryGrades[i].grade;\n this.set('assignmentUsers.' + h + '.columns.' + indx + '.score', scoreCalc);\n this.set('assignmentUsers.' + h + '.columns.' + indx + '.grade', gradeCalc);\n }\n\n // Assign total grades to the final grade columns\n let totalIndx = usersPrp[h].get('columns').findIndex(m => m && m.calcType === 'Total');\n this.set('assignmentUsers.' + h + '.columns.' + totalIndx + '.score', finalGrades.weightedScore);\n this.set('assignmentUsers.' + h + '.columns.' + totalIndx + '.grade', finalGrades.finalGrade);\n }\n }),\n actions: {\n weighNotYetDueGrades: function () {\n this.toggleProperty('unmuteAllGrades');\n },\n hideMuted: function () {\n this.toggleProperty('hideMutedGrades');\n },\n muteToggleAssignment: function (assignment_index) {\n let usersPrp = this.assignmentUsers;\n for (let i = 0; i < usersPrp.length; i++) {\n let isMuted = this.get('assignmentUsers.' + i + '.columns.' + assignment_index + '.muted');\n this.set('assignmentUsers.' + i + '.columns.' + assignment_index + '.muted', !isMuted);\n }\n let isMuted = this.get('assignmentHeaders.columns.' + assignment_index + '.muted');\n this.set('assignmentHeaders.columns.' + assignment_index + '.muted', !isMuted);\n this.calcGrades();\n },\n ungradedAsZeroToggle: function () {\n let asZero = this.ungradedAsZero;\n this.set('ungradedAsZero', !asZero);\n },\n gradeEditOn: function (column) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.gradebook-container .content .tg-wrap table td#cell-' + column).addClass('editing');\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.gradebook-container .content .tg-wrap table td#cell-' + column + ' input').focus();\n },\n gradeEditOff: function (column) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.gradebook-container .content .tg-wrap table td#cell-' + column).removeClass('editing');\n },\n assignGrade: function (assignment_id, submission_id, student_id, score) {\n let usr = this.assignmentUsers,\n inx = usr.findIndex(function (m) {\n return m && m.id === student_id;\n }),\n indx = usr[inx].get('columns').findIndex(function (m) {\n return m && m.assignment_id === assignment_id;\n });\n if (!score || typeof score === 'object') {\n score = event.target.value;\n }\n usr[inx].set('columns.' + indx + '.score', score);\n this.set('assignmentUsers', usr);\n\n /* eslint-disable-next-line ember/no-get */\n this.store.nestResources('submission', [{\n section: this.get('session.section.id')\n }, {\n assignment: assignment_id\n }]);\n this.store.findRecord('submission', submission_id + '_' + student_id).then(sub => {\n sub.set('posted_grade', score);\n sub.save().then(() => {\n // PRM: I hate these confirmations _so_ much.\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('session.user.hideGradebookConfirmations')) {\n (0, _dialog.default)('Your grade has been successfully submitted.');\n }\n this.calcGrades();\n Ember.notifyPropertyChange(this, 'assignmentUsers');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.gradebook-container .content .tg-wrap table td').removeClass('editing');\n }, err => {\n let prompt = 'Please try again in a few minutes.';\n if (err.errors && err.errors.length > 0) {\n let status = err.errors[0].status;\n if (status === '401') {\n prompt = 'You do not have permission to grade in this course. Please make sure you are enrolled as an instructor.';\n } else if (status === '501') {\n prompt = 'Something went wrong with the server. Please contact support.';\n } else if (status === '503') {\n prompt = 'Please try again in a few minutes.';\n } else {\n prompt = 'Please contact suppot with the following error: ' + err.errors[0].detail;\n }\n }\n (0, _dialog.default)('Unable to save your grade. ' + prompt);\n });\n });\n },\n assignFinalGrade: function (assignment_id, submission_id, student_id) {\n let grade = event.target.value;\n\n // Lock out grading after session ends\n /* eslint-disable-next-line ember/no-get */\n if (this.get('session.readOnly')) {\n (0, _dialog.default)('Final grades for this course are now locked. Please contact the registrar directly to modify any final student grades.');\n return;\n }\n let usr = this.assignmentUsers,\n inx = usr.findIndex(function (m) {\n return m && m.id === student_id;\n }),\n indx = usr[inx].get('columns').findIndex(function (m) {\n return m && m.assignment_id === assignment_id;\n });\n usr[inx].set('columns.' + indx + '.grade', grade);\n\n /* eslint-disable-next-line ember/no-get */\n this.store.nestResources('submission', [{\n section: this.get('session.section.id')\n }, {\n assignment: assignment_id\n }]);\n this.store.findRecord('submission', submission_id + '_' + student_id).then(sub => {\n sub.set('posted_grade', grade);\n sub.save().then(() => {\n (0, _dialog.default)('Your grade has been successfully submitted.');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.gradebook-container .content .tg-wrap table td').removeClass('editing');\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/final-grade-conf',\n data: JSON.stringify({\n section_id: this.get('session.section.id'),\n term_name: this.get('session.course.term.name')\n }),\n success(data) {\n if (data.success) {\n console.log('Grade Conf: ' + data.message);\n }\n },\n error(jqXHR, textStatus) {\n console.log('Error: ' + textStatus);\n }\n });\n }, err => {\n let prompt = 'Please try again in a few minutes.';\n if (err.errors && err.errors.length > 0) {\n let status = err.errors[0].status;\n if (status === '401') {\n prompt = 'You do not have permission to grade in this course. Please make sure you are enrolled as an instructor.';\n } else if (status === '501') {\n prompt = 'Something went wrong with the server. Please contact support.';\n } else if (status === '503') {\n prompt = 'Please try again in a few minutes.';\n } else {\n prompt = 'Please contact suppot with the following error: ' + err.errors[0].detail;\n }\n }\n (0, _dialog.default)('Unable to save your grade. ' + prompt);\n });\n });\n },\n generateCSV: function () {\n let csvArr = [],\n rowsTemp = [];\n\n /* eslint-disable-next-line ember/no-get */\n let headersPrp = this.get('assignmentHeaders.columns'),\n usersPrp = this.assignmentUsers;\n for (let i = 0; i < headersPrp.length; i++) {\n let text = headersPrp[i].name || headersPrp[i].title || headersPrp[i];\n //surround with couples of double quotes in case the header title has a comma in it\n rowsTemp.push('\"' + text.replaceAll(/\"/gm, '\"\"') + '\"');\n }\n csvArr.push(rowsTemp);\n for (let i = 0; i < usersPrp.length; i++) {\n rowsTemp = [];\n for (let j = 0; j < usersPrp[i].get('columns').length; j++) {\n let current = usersPrp[i].get('columns')[j];\n let text = '';\n if (!current) {\n text = '-';\n } else if (current.grading_standard_id === 2) {\n // final grade column\n text = current.grade ? current.grade : \"-\";\n } else if (current.grade) {\n if (current.grade === 'Complete') {\n text = '+';\n } else if (current.grade === 'N/A' || !current.score || current.score === 'N/A') {\n text = '0% (F)';\n } else {\n text = current.score + (current.score.toString().includes('%') ? '' : '%') + ' (' + current.grade + ')';\n }\n } else if (current.score) {\n text = current.score;\n } else if (current.content) {\n // live class attendance column\n text = current.content;\n } else if (current.name) {\n text = current.name;\n } else {\n text = '-';\n }\n if (current && current.late) {\n text += ' (LATE)';\n }\n rowsTemp.push('\"' + text.replaceAll(/\"/gm, '\"\"') + '\"');\n }\n csvArr.push(rowsTemp);\n }\n let csvContent = 'data:text/csv;charset=utf-8,' + csvArr.map(e => e.join(',')).join('\\n');\n let encodedUri = encodeURI(csvContent);\n let link = document.createElement('a');\n link.setAttribute('href', encodedUri);\n /* eslint-disable-next-line ember/no-get */\n link.setAttribute('download', this.get('session.course.code') + '_' + this.get('session.course.term.name') + '_gradebook.csv');\n document.body.appendChild(link); // Required for FF\n link.click(); // This will download the data file named \"my_data.csv\".\n },\n closeGradebook: function () {\n this.classroom.set('gradebookOpen', false);\n },\n changeFilter: function (filter) {\n let currentFilters = this.filters;\n if (filter === 'All') {\n this.set('filters', ['All']);\n } else {\n if (currentFilters.includes(filter)) {\n currentFilters = currentFilters.filter(curFilter => curFilter !== filter);\n } else {\n currentFilters.push(filter);\n }\n currentFilters = currentFilters.filter(curFilter => curFilter !== 'All');\n if (currentFilters.length === 0) {\n currentFilters.push('All');\n }\n this.set('filters', currentFilters);\n }\n\n // set columns to be muted based on the current filter\n let usersPrp = this.assignmentUsers;\n for (let i = 0; i < usersPrp.length; i++) {\n for (let j = 0; j < usersPrp[i].columns.length; j++) {\n let itm = usersPrp[i].columns[j];\n if (itm.assignment_type && !this.filters.includes('All')) {\n let filterAssignments = itm.assignment_type.includes(\"Assignment\") && !this.filters.includes(\"Assignments\");\n let filterQuizzes = itm.assignment_type.includes(\"Quiz\") && !this.filters.includes(\"Quizzes\");\n // filterDiscussions is a catch-all for the rest of the assignments not included in assignments or quizzes\n let filterDiscussions = !itm.assignment_type.includes(\"Assignment\") && !itm.assignment_type.includes(\"Quiz\") && !this.filters.includes(\"Discussions\");\n let isFiltered = filterAssignments || filterDiscussions || filterQuizzes;\n this.set('assignmentUsers.' + i + '.columns.' + j + '.muted', isFiltered);\n this.set('assignmentHeaders.columns.' + j + '.muted', isFiltered);\n } else if (itm.assignment_type && this.filters.includes('All')) {\n this.set('assignmentUsers.' + i + '.columns.' + j + '.muted', false);\n this.set('assignmentHeaders.columns.' + j + '.muted', false);\n }\n }\n }\n },\n view: async function (quizId) {\n let quiz = await store.findRecord('quiz', quizId);\n let item = await store.findRecord('item', quiz.id);\n this.transitionToRoute('classroom.lessons', item.lesson.id, item.id);\n this.send('closeGradebook');\n }\n }\n });\n});","define(\"bocce/controllers/index\", [\"exports\", \"bocce/mixins/menus\"], function (_exports, _menus) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_menus.default, {\n userprofileService: Ember.inject.service('userprofile'),\n nextTerm: Ember.computed('model.terms.length', function () {\n return {};\n }),\n previousTerm: Ember.computed('model.terms.length', function () {\n return {};\n }),\n currentTerm: Ember.computed('model.terms.length', function () {\n /* eslint-disable-next-line ember/no-get */\n var terms = this.get('model.terms'),\n best;\n if (!terms || terms.get('length') === 0) {\n return {};\n }\n best = terms.objectAt(0);\n if (terms.get('length') > 1) {\n // do something here with startsOn.\n }\n return best;\n })\n });\n});","define(\"bocce/controllers/lobby\", [\"exports\", \"bocce/mixins/prefs\", \"bocce/mixins/menus\", \"bocce/utilities/logout\"], function (_exports, _prefs, _menus, _logout) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_prefs.default, _menus.default, {\n sortedCourses: Ember.computed.sort('model', function (a, b) {\n let as = a.get('start_at'),\n bs = b.get('start_at');\n if (as > bs) {\n return -1;\n } else if (as < bs) {\n return 1;\n } else if (a.get('section') < b.get('section')) {\n return -1;\n } else {\n return 1;\n }\n }),\n current: Ember.computed('sortedCourses', function () {\n let model = this.sortedCourses;\n let current = model.filter(function (itm) {\n return itm.get('current');\n });\n return current;\n }),\n past: Ember.computed('sortedCourses', function () {\n let model = this.sortedCourses;\n let past = model.filter(function (itm) {\n return !itm.get('current');\n });\n return past;\n }),\n loadConversations: function () {\n let conversationsLoaded = this.conversationsLoaded || false,\n conversationsController = this.conversations;\n if (!conversationsLoaded) {\n return this.store.findAll('conversation').then(conversations => {\n conversationsController.set('model', conversations);\n this.set('conversationsLoaded', true);\n });\n }\n },\n actions: {\n toggleMessagePanel: function (open) {\n if (open) {\n this.set('messagePanelOn', true);\n } else {\n this.set('messagePanelOn', false);\n }\n },\n logout: function () {\n (0, _logout.default)();\n }\n }\n });\n});","define(\"bocce/controllers/notifications\", [\"exports\", \"bocce/mixins/prefs\", \"bocce/mixins/menus\", \"bocce/utilities/dialog\", \"bocce/utilities/logout\"], function (_exports, _prefs, _menus, _dialog, _logout) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n var _default = _exports.default = Ember.Controller.extend(_prefs.default, _menus.default, {\n notifications: Ember.computed(function () {\n // Uncomment to display notifications!\n // return this.store.findAll('notification');\n return false;\n }),\n totalNotifications: Ember.computed.reads('notifications.length'),\n actions: {\n clearAll: function () {\n var that = this;\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n url: '/interface/notifications/',\n type: 'DELETE',\n success: function () {\n that.get('notifications').clear();\n that.set('haveNotifications', false);\n }\n });\n },\n logOut: function () {\n (0, _logout.default)();\n },\n /* eslint-disable-next-line ember/no-get */\n goLink: function (type) {\n /* eslint-disable-next-line ember/no-get */\n let currentUser = this.get('session.user');\n switch (type) {\n case 'home':\n window.open('https://online.berklee.edu/my_home', '_blank');\n break;\n case 'forum':\n let discourseUrl = this.get('session.discourseUrl');\n window.open(discourseUrl, '_blank');\n break;\n case 'dashboard':\n window.open('https://' + window.location.hostname + '/#/dashboard', '_blank');\n break;\n case 'profile':\n window.open('http://network.online.berklee.edu/home', '_blank');\n break;\n case 'help':\n window.open('https://online.berklee.edu/help', '_blank');\n break;\n case 'advisor':\n window.open('https://online.berklee.edu/about/contact-us', '_blank');\n break;\n case 'library':\n window.open('https://online.berklee.edu/school/lms/library-links', '_blank');\n break;\n case 'materials':\n window.open('https://online.berklee.edu/school/lms/requirements', '_blank');\n break;\n case 'policies':\n window.open('https://online.berklee.edu/about/berklee-online-general-policies', '_blank');\n break;\n case 'doodle':\n window.open('http://www.doodle.com', '_blank');\n break;\n case 'meetup':\n window.open('http://www.meetup.com', '_blank');\n break;\n case 'calendar':\n var icsLink = 'https://' + window.location.hostname + '/interface/calendar-export/' + currentUser.get('calendarHash'),\n bodyMarkup = '';\n /* eslint-disable-next-line ember/no-jquery */\n if (!Ember.$.isMobile) {\n bodyMarkup += '
';\n }\n bodyMarkup += ' or download the feed.';\n var message = 'Copy the link below and paste it into any calendar app that takes iCal feeds (Google Calendar, iCal, Outlook, etc.)';\n (0, _dialog.default)(message, ['Dismiss'], bodyMarkup);\n break;\n default:\n break;\n }\n },\n // Delete the record for the given notification id\n dismissNotification: function (id) {\n this.store.findRecord('notification', id).then(post => {\n post.destroyRecord();\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.user-menu').click();\n }, err => {\n Ember.debug('Could not delete notification.', err);\n });\n },\n // Transition to the Route for the given modal type+id\n travelToNotification: function (type, id) {\n this.transitionToRoute(`classroom.lessons.${type}`, id);\n }\n }\n });\n});","define(\"bocce/controllers/userprofile\", [\"exports\", \"bocce/mixins/editable\", \"bocce/mixins/uploadable\"], function (_exports, _editable, _uploadable) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Controller.extend(_editable.default, _uploadable.default, {\n classroom: Ember.inject.controller(),\n userprofileService: Ember.inject.service('userprofile'),\n activeUser: false,\n assignmentsLocked: true,\n unlockWeek: 2,\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n self: Ember.computed('userprofileService.activeUser.content', function () {\n /* eslint-disable-next-line ember/no-get */\n if (this.get('userprofileService.activeUser.content.id') === this.get('session.user.id')) {\n return true;\n }\n return false;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n degree: Ember.computed('userprofileService.activeUser.content', function () {\n /* eslint-disable-next-line ember/no-get */\n var allEnrolled = this.get('userprofileService.activeUser.enrolled'),\n enrolledUser;\n if (allEnrolled) {\n /* eslint-disable-next-line ember/no-get */\n enrolledUser = allEnrolled.findBy('section.id', this.get('classroom.model.section.id'));\n if (enrolledUser) {\n return enrolledUser.get('degree');\n }\n }\n return '';\n }),\n /* eslint-disable-next-line ember/no-observers */\n earlyUnlockCheck: Ember.observer('userprofileService.activeUser.content', function () {\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('session.isStaff')) {\n return;\n }\n /* eslint-disable-next-line ember/no-get */\n let userId = this.get('userprofileService.activeUser.content.id');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/early_unlock_check/',\n data: JSON.stringify({\n user_id: userId,\n term_id: this.get('session.termID')\n }),\n success: latestUnlockWeek => {\n this.set('earlyUnlock', latestUnlockWeek);\n }\n });\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n accommodation: Ember.computed('userprofileService.activeUser.content', function () {\n /* eslint-disable-next-line ember/no-get */\n let allEnrolled = this.get('userprofileService.activeUser.enrolled'),\n enrolledUser;\n if (allEnrolled) {\n /* eslint-disable-next-line ember/no-get */\n enrolledUser = allEnrolled.findBy('section.id', this.get('classroom.model.section.id'));\n if (enrolledUser && enrolledUser.get('accommodation')) {\n return enrolledUser.get('accommodation');\n }\n }\n return false;\n }),\n actions: {\n unlockAssignments: function (userId) {\n this.set('assignmentsLocked', false);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n url: `/interface/sections/${this.session.get('section.id')}/assignments/0/user/${userId}/purge_assignment/`,\n data: {},\n dataType: 'text',\n success: () => {\n this.set('unlockingAssignments', false);\n Ember.debug('Assignments purged successfully');\n },\n error: error => {\n this.set('assignmentsLocked', true);\n Ember.debug('Unable to purge assignments. Something went wrong: ');\n Ember.debug(error);\n }\n });\n },\n stripAssignmentTimers: function (userId) {\n this.set('timersStripped', false);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n url: `/interface/sections/${this.session.get('section.id')}/assignments/0/user/${userId}/strip_timer/`,\n data: {},\n dataType: 'text',\n success: () => {\n this.set('strippingTimers', false);\n Ember.debug('Assignment timers stripped successfully');\n },\n error: error => {\n this.set('timersStripped', true);\n Ember.debug('Unable to strip assignment timers. Something went wrong: ');\n Ember.debug(error);\n }\n });\n },\n refreshZoomId: function (userId) {\n /* eslint-disable-next-line ember/no-jquery */\n this.set('refreshingZoom', true);\n Ember.$.ajax({\n type: 'POST',\n url: `/interface/users/${userId}/refreshZoomId/`,\n data: {},\n dataType: 'text',\n success: () => {\n this.set('refreshingZoom', false);\n this.set('zoomRefreshed', true);\n Ember.debug('Zoom Credentials refreshed successfully!');\n },\n error: error => {\n this.set('refreshingZoom', false);\n Ember.debug('Unable to refresh user Zoom Credentials');\n }\n });\n },\n // Early lesson unlocking\n // Only usable by admins (requires auth)\n changeUnlockWeek: function (weekNumber) {\n this.set('unlockWeek', weekNumber);\n },\n // Revoke selected user's early access\n earlyLock: function (userId) {\n this.set(\"working\", \"Locking...\");\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/early_lock/',\n data: JSON.stringify({\n user_id: userId,\n course_id: this.get('session.course.id'),\n term_id: this.get('session.termID')\n }),\n success: () => {\n console.log('User\\'s early access has been revoked.');\n this.set('earlyUnlock', false);\n this.set(\"working\", false);\n },\n error: e => {\n this.set(\"working\", false);\n Ember.debug('error occurred while locking');\n Ember.debug(e);\n }\n });\n },\n // Grant selected user early access up until week # \"unlockWeek\"\n earlyUnlock: function (userId) {\n // This functionality is only available for admins\n // Interface will reject teacher (and student) attempts\n let unlockWeek = this.unlockWeek;\n /* eslint-disable-next-line ember/no-get */\n let termId = this.get('session.course.term.id');\n /* eslint-disable-next-line ember/no-get */\n let courseId = this.get('session.course.id');\n this.set('working', \"Unlocking...\");\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n url: '/interface/early_unlock/',\n data: JSON.stringify({\n user_id: userId,\n term_id: termId,\n unlock_week: unlockWeek,\n course_id: courseId\n }),\n success: () => {\n this.set('working', false);\n this.set('earlyUnlock', this.unlockWeek);\n },\n error: e => {\n this.set('earlyUnlock', false);\n this.set('working', false);\n Ember.debug('error occurred while unlocking');\n Ember.debug(e);\n }\n });\n },\n closeProfile: function () {\n this.userprofileService.activeUser = false;\n this.userprofileService.isEditing = false;\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.user-profile').removeClass('active floating-modal');\n /* eslint-disable-next-line ember/no-jquery */\n },\n pm: function () {\n /* eslint-disable-next-line ember/no-get */\n let id = this.get('userprofileService.activeUser.id');\n this.send('closeProfile');\n this.transitionToRoute('classroom.lessons.conversation-new-with', id);\n },\n activityReport: function () {\n /* eslint-disable-next-line ember/no-get */\n var courseId = this.get('session.course.id'),\n /* eslint-disable-next-line ember/no-get */\n userId = this.get('userprofileService.activeUser.content.id');\n window.open('/courses/' + courseId + '/users/' + userId + '/usage', '_blank');\n },\n gradeReport: function () {\n /* eslint-disable-next-line ember/no-get */\n var courseId = this.get('session.course.id'),\n /* eslint-disable-next-line ember/no-get */\n userId = this.get('userprofileService.activeUser.content.id');\n window.open('/courses/' + courseId + '/grades/' + userId + '#tab-assignments', '_blank');\n },\n reportAtRisk: user => {\n let name = user.get('name'),\n email = user.get('email');\n if (name && email) {\n let atRiskWindow = window.open('', '_blank'),\n path = 'https://online.berklee.edu/at-risk-student-notification-form?student_name=';\n path += name + '&student_email=' + email;\n atRiskWindow.location = path;\n }\n },\n masquerade() {\n /* eslint-disable-next-line ember/no-get */\n let id = this.get('userprofileService.activeUser.id');\n let path = `/users/${id}/masquerade`;\n window.open(path, '_blank');\n }\n }\n });\n});","define(\"bocce/data-adapter\", [\"exports\", \"@ember-data/debug\"], function (_exports, _debug) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _debug.default;\n }\n });\n});","define(\"bocce/helpers/activity-count\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/activity-count.js\n var _default = _exports.default = Ember.Helper.helper(function (activity) {\n var total = activity[0].get('responses.length');\n if (total > 1) {\n return total + ' replies';\n } else if (total === 1) {\n return '1 reply';\n }\n });\n});","define(\"bocce/helpers/and\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.and = and;\n _exports.default = void 0;\n function and(params) {\n for (let p of params) {\n if (!p) {\n return false;\n }\n }\n return true;\n }\n var _default = _exports.default = Ember.Helper.helper(and);\n});","define(\"bocce/helpers/app-version\", [\"exports\", \"bocce/config/environment\", \"ember-cli-app-version/utils/regexp\"], function (_exports, _environment, _regexp) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.appVersion = appVersion;\n _exports.default = void 0;\n function appVersion(_, hash = {}) {\n const version = _environment.default.APP.version;\n // e.g. 1.0.0-alpha.1+4jds75hf\n\n // Allow use of 'hideSha' and 'hideVersion' For backwards compatibility\n let versionOnly = hash.versionOnly || hash.hideSha;\n let shaOnly = hash.shaOnly || hash.hideVersion;\n let match = null;\n if (versionOnly) {\n if (hash.showExtended) {\n match = version.match(_regexp.versionExtendedRegExp); // 1.0.0-alpha.1\n }\n // Fallback to just version\n if (!match) {\n match = version.match(_regexp.versionRegExp); // 1.0.0\n }\n }\n if (shaOnly) {\n match = version.match(_regexp.shaRegExp); // 4jds75hf\n }\n return match ? match[0] : version;\n }\n var _default = _exports.default = Ember.Helper.helper(appVersion);\n});","define(\"bocce/helpers/array-item\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.arrayItem = arrayItem;\n _exports.default = void 0;\n function arrayItem(params /*, hash*/) {\n let array = params[0];\n let index = params[1];\n return array[index];\n }\n var _default = _exports.default = Ember.Helper.helper(arrayItem);\n});","define(\"bocce/helpers/calendar\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/calendar.js\n // Convert date into calendar form with Moment.JS\n var _default = _exports.default = Ember.Helper.helper(function (date) {\n if (date.length === 0) {\n return 'Date Incorrect';\n }\n return moment(date[0]).tz('America/New_York').calendar();\n });\n});","define(\"bocce/helpers/cancel-all\", [\"exports\", \"ember-concurrency/helpers/cancel-all\"], function (_exports, _cancelAll) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _cancelAll.default;\n }\n });\n});","define(\"bocce/helpers/contains\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/same.js\n // Check if two provided strings are the same\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n var compare = inp[0],\n search = inp[1],\n compareTo = inp[2];\n if (search && search.length > 0) {\n return String(search[compareTo]) === String(compare);\n }\n return false;\n });\n});","define(\"bocce/helpers/correct\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/correct.js\n // Check if question is correct\n var _default = _exports.default = Ember.Helper.helper(function (mod) {\n var answer;\n if (!!mod[0] && !!mod[0].findBy) {\n if (!mod[1]) {\n answer = mod[0].filterBy('correct', true);\n return answer.length;\n }\n answer = mod[0].findBy('question_id', parseInt(mod[1]));\n if (answer && answer.correct) {\n return answer.correct ? 'correct' : 'incorrect';\n } else {\n return 'pending';\n }\n } else {\n return 'pending';\n }\n });\n});","define(\"bocce/helpers/dashify\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/dashify.js\n // Turn string to dashed string\n var _default = _exports.default = Ember.Helper.helper(function (dec) {\n var item = dec[0];\n return item.replace(/\\s+/g, '-').toLowerCase();\n });\n});","define(\"bocce/helpers/decode\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/decode.js\n // Decode URI-style properties\n var _default = _exports.default = Ember.Helper.helper(function (dec) {\n var retval = '',\n item = dec.join(' ');\n try {\n retval = decodeURIComponent(item);\n } catch (err) {\n retval = unescape(item);\n }\n return retval;\n });\n});","define(\"bocce/helpers/diff\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.diff = diff;\n function diff([a, b] /*, hash*/) {\n return a !== b;\n }\n var _default = _exports.default = Ember.Helper.helper(diff);\n});","define(\"bocce/helpers/difference\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/sum.js\n // Check if two provided strings are the same\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n var first = inp[0],\n second = inp[1];\n return parseInt(first) - parseInt(second);\n });\n});","define(\"bocce/helpers/dropdown-scale\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/dropdown-scale.js\n // Produce an array of sequential numbers\n var _default = _exports.default = Ember.Helper.helper(function (counter) {\n var total = parseInt(counter[0]),\n arr = ['-'];\n if (typeof total !== 'number') {\n return false;\n }\n for (let i = 0; i < total; i++) {\n arr.push(i + 1);\n }\n return arr;\n });\n});","define(\"bocce/helpers/epoch-to-dayhr\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/epoch-to-dayhr.js\n // Convert epoch time to days and hours\n var _default = _exports.default = Ember.Helper.helper(function (item) {\n if (!item) {\n return false;\n }\n // Convert seconds to hours/days in string form (e.g. 1d 2h)\n let days = Math.floor(item / 86400);\n let hours = Math.floor(item % 86400 / 3600);\n let dayString = days > 0 ? days + 'd ' : '';\n let hourString = hours > 0 ? hours + 'h' : '';\n return dayString + hourString;\n });\n});","define(\"bocce/helpers/eq\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/eq.js\n // Compare two values in a way that {{#if}} is happy\n var _default = _exports.default = Ember.Helper.helper(params => params[0] === params[1]);\n});","define(\"bocce/helpers/feature-flag\", [\"exports\", \"ember-feature-flags/helpers/feature-flag\"], function (_exports, _featureFlag) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _featureFlag.default;\n }\n });\n});","define(\"bocce/helpers/file-extension\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.fileExtension = fileExtension;\n // Take a filename and return it's file extension\n function fileExtension([filename]) {\n if (!filename) {\n return;\n }\n let splitFilename = filename.split('.');\n if (splitFilename.length == 1) {\n return;\n }\n return '.' + splitFilename[splitFilename.length - 1] || '';\n }\n var _default = _exports.default = Ember.Helper.helper(fileExtension);\n});","define(\"bocce/helpers/file-name\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.fileName = fileName;\n // app/helpers/file-name.js\n // Take a filename and return it's file name without the extension\n function fileName([filename]) {\n if (!filename) {\n return;\n }\n let split = filename.split('.');\n let extension = '.' + split[split.length - 1];\n return filename.replace(extension, '');\n }\n var _default = _exports.default = Ember.Helper.helper(fileName);\n});","define(\"bocce/helpers/format-grade\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.formatGrade = formatGrade;\n function formatGrade(gradeScore) {\n if (isNaN(gradeScore) || !gradeScore) {\n return gradeScore;\n }\n gradeScore = Number(gradeScore);\n let decPlaces = 0;\n if (Math.floor(gradeScore) !== gradeScore) {\n decPlaces = gradeScore.toString().split(\".\")[1].length || 0;\n }\n if (decPlaces > 4) {\n return gradeScore.toFixed(4) + \"...\";\n } else {\n return gradeScore;\n }\n }\n var _default = _exports.default = Ember.Helper.helper(fullGrade => {\n let grade = fullGrade[0];\n return formatGrade(grade);\n });\n});","define(\"bocce/helpers/heartbeat\", [\"exports\", \"bocce/js/store-util\"], function (_exports, _storeUtil) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = loadHeartbeatData;\n function loadHeartbeatData(routes, ctl, view = null) {\n const keys = ['attachment', 'discussion', 'response', 'submission', 'conversation', 'conversation_message', 'event', 'comment', 'user'];\n const deleble_types = ['comment'];\n const store = ctl.get('store');\n const user_id = parseInt(ctl.session.get('user.id'), 10);\n const router = ctl.get('router');\n /* eslint-disable-next-line ember/no-jquery */\n let promises = routes.map(url => Ember.$.get(url)),\n course_hb_index = -1,\n full_reload_p = false;\n for (let i = 0; i < routes.length; i++) {\n if (routes[i].match(/courses\\/.*\\/heartbeat/)) {\n course_hb_index = i;\n }\n if (routes[i].indexOf('heartbeat') === -1) {\n full_reload_p = true;\n }\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n return Ember.$.when.apply(Ember.$, promises).done(function (...data) {\n // Data is an array of array of the parameters that the done function\n // would normally get. data[nn][0] is the actual data we're looking\n // for from each hit.\n\n if (routes.length === 1) {\n data = [data];\n }\n for (let i = 0; i < data.length; i++) {\n if (data[i][2].status === 500) {\n Ember.debug('Heartbeat request returned 500 error, disabling');\n window.heartbeatFrequencyInMilliseconds = 0;\n return;\n }\n }\n if (view && course_hb_index >= 0) {\n let course_hb_data = data[course_hb_index][0];\n ctl.set('session.section.adviceCards', course_hb_data.adviceCards);\n ctl.set('session.runningChatMeetingKey', course_hb_data?.meeting_details?.meeting_key);\n ctl.set('session.runningChatMeetingDetails', course_hb_data.meeting_details);\n ctl.set('session.anyChatsRunning', ctl.get('session.runningChatMeetingKey'));\n ctl.set('session.canMergeChats', !!course_hb_data.other_section_meeting_key);\n if (course_hb_data.meeting_details) {\n delete course_hb_data.meeting_details;\n }\n ctl.set('session.globalReadOnly', !!course_hb_data.readonly);\n if (course_hb_data.readonly) {\n delete course_hb_data.readonly;\n }\n if (course_hb_data.banner && course_hb_data.banner.serial) {\n let ban = course_hb_data.banner;\n if (session.last_banner_serial !== ban.serial) {\n view.send('notify', ban.banner, ban.dismissable, ban.modal_type || null, ban.modal_id || null, 100, ctl.get('session'), false, ban.link, ban.serial, \"banner\", ban.dismiss_condition_id);\n session.last_banner_serial = ban.serial;\n }\n delete course_hb_data.banner;\n } else if (ctl.get('session.notification') && session.last_banner_serial) {\n //TODO: This 'else if' might not even be used...\n view.send('dismissNotif', ctl.get('session'));\n session.last_banner_serial = null;\n }\n if (course_hb_data.activity) {\n let activity = course_hb_data.activity;\n let fiveMinutesAgo = moment().subtract(5, 'minutes');\n for (let uid in activity) {\n let user = store.peekRecord('user', uid);\n if (user) {\n user.set('whenPresent', activity[uid]);\n if (activity[uid]) {\n let isPresent = new Date(activity[uid]) > fiveMinutesAgo;\n user.set('isPresent', isPresent);\n }\n }\n }\n }\n }\n for (let key of keys) {\n let objs = getData(data, key);\n\n // Use .peekAll instead of find to avoid a REST hit\n let loaded_objs = store.peekAll(key);\n let ids = loaded_objs.map(function (e) {\n return '' + e.id;\n });\n let to_push = [],\n interface_ids = [];\n for (let j = 0; j < objs.length; j++) {\n if (ids.indexOf('' + objs[j].id) === -1) {\n // TODO: make my_submission a computed property instead of computing it\n // in the inteface's toEmber function.\n if (key === 'submission') {\n let isSubmissionOwner = parseInt(objs[j].user) === user_id,\n grade = objs[j].grade;\n objs[j].my_submission = isSubmissionOwner;\n if (isSubmissionOwner) {\n store.findRecord('assignment', objs[j].assignment).then(assignment => {\n assignment.set('currentUserSubmissionGrade', grade);\n });\n }\n } else if (key === 'comment') {\n let submissionCtl = ctl.get('submission');\n let open_id = submissionCtl.get('openAssignmentView.id');\n\n // If the comment is one we didn't make ourselves and we're not looking at\n // the submission right now, set it to unread\n if (objs[j].user !== user_id && open_id !== objs[j].submission) {\n let submission = store.peekRecord('submission', objs[j].submission);\n if (submission) {\n submission.set('read', false);\n submission.set('workflow_state', 'unread');\n }\n }\n }\n // If the item's id doesn't exist in the store, add the item\n // to the to_push array.\n to_push.push(objs[j]);\n } else if (objs[j].is_deleted || objs[j].is_updated) {\n // If object remarked as deleted or updated, push too\n\n to_push.push(objs[j]);\n }\n interface_ids.push(objs[j].id);\n }\n\n // If this is a deleble type, and we're doing a full reload,\n // delete any objects that weren't included in the data dump\n if (full_reload_p && deleble_types.indexOf(key) >= 0) {\n loaded_objs.filter(obj => interface_ids.indexOf(obj.id) === -1).forEach(obj => {\n Ember.debug('Removing orphan ' + key + ' ' + obj.id);\n store.unloadRecord(obj);\n });\n }\n if (to_push.length > 0) {\n Ember.debug('Pushing ' + to_push.length + ' new ' + key);\n Ember.debug(to_push.map(function (e) {\n return e.id;\n }));\n\n // Uncomment to bring back growler\n /*\n growler\n .attr('message', messages[i])\n .removeClass('growl')\n .addClass('growl');\n */\n let payload = {};\n payload[key] = to_push;\n store.pushPayload(key, payload);\n if (key === 'response') {\n for (let resp of to_push) {\n let disc = store.peekRecord('discussion', resp.discussion);\n if (disc) {\n let unread_count = disc.get('unread_count');\n disc.set('unread_count', unread_count + 1);\n }\n }\n }\n if (key === 'conversation_message') {\n for (let conversation_message of to_push) {\n let conversation = store.peekRecord('conversation', conversation_message.conversation);\n if (conversation) {\n conversation.set('workflow_state', 'unread');\n conversation.set('last_message', conversation_message.body);\n }\n }\n }\n\n // JRW: Sometimes announcements come back as already read.\n if (key === 'discussion') {\n for (let discussion of to_push) {\n let loadedDiscussion = store.peekRecord('discussion', discussion.id);\n if (loadedDiscussion.get('is_announcement')) {\n loadedDiscussion.set('read', false);\n }\n }\n }\n }\n }\n doDeletes(store, router, data);\n // Pop the ongoing event modal up once per session\n let meeting_details = ctl.get('session.runningChatMeetingDetails'),\n shown = sessionStorage.getItem('popupShownForMeetingId'),\n event_id = meeting_details?.event_id || meeting_details?.id || false,\n willTransition;\n willTransition = event_id && shown != event_id;\n if (willTransition) {\n sessionStorage.setItem('popupShownForMeetingId', event_id);\n router.transitionTo('classroom.lessons.event', event_id);\n }\n });\n }\n function getData(data, key) {\n let objs = [];\n data.map(d => {\n if (d[0][key] && d[0][key].length > 0) {\n objs = objs.concat(d[0][key]);\n }\n });\n return objs;\n }\n function doDeletes(store, router, data) {\n let deletes = getData(data, 'deletes');\n for (let d of deletes) {\n if (d.type && d.id) {\n let typeMatch;\n\n //These deletes come back from heartbeat in the form of _attachment:id. \n //The type is the type of attachment that was deleted (discussion, submission, etc) and the id is for the model\n //that had that attachment.\n if (typeMatch = d.type.match(/^\\w+(?=_attachment)/)) {\n let type = typeMatch[0];\n const typeSplit = d.type.split(':');\n if (typeSplit.length >= 2) {\n const id = typeSplit[1];\n const record = store.peekRecord(type, id);\n if (record) {\n let currentAttachmentIds = record.get('attachments');\n if (currentAttachmentIds) {\n record.set('attachments', currentAttachmentIds.filter(a => {\n //'a' might be an on object if the item was added by heartbeat within the same session\n if (a && a.id) {\n return a.id != d.id;\n } else {\n return a != d.id;\n }\n }));\n }\n }\n }\n } else {\n let loaded_objs = store.peekAll(d.type);\n let ids = loaded_objs.map(function (e) {\n if (d.type == 'response') {\n return +e.id;\n } else {\n return e.id;\n }\n });\n\n //Delete the record if it exists\n if (ids.indexOf(d.id) >= 0) {\n const r = store.peekRecord(d.type, d.id);\n if (r) {\n (0, _storeUtil.pushDeletion)(store, d.type, d.id);\n const currentRoute = router.currentRoute;\n\n //Exit out of the discussion if it is currently being viewed.\n if (currentRoute && currentRoute.params && currentRoute.params['discussion_id']) {\n if (currentRoute.params['discussion_id'] == d.id) {\n router.transitionTo('classroom.lessons');\n }\n }\n }\n }\n }\n }\n }\n }\n});","define(\"bocce/helpers/image\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.BocceImage = void 0;\n class BocceImage {\n constructor(data_uri) {\n this._data_uri = new Promise(acc => {\n acc(data_uri);\n });\n }\n static from_file(file) {\n return new Promise((acc, rej) => {\n let fr = new FileReader();\n fr.onload = e => {\n acc(new BocceImage(e.target.result));\n };\n fr.onerror = err => {\n rej(err);\n };\n fr.readAsDataURL(file);\n });\n }\n data() {\n return this._data_uri;\n }\n load() {\n return this._data_uri.then(data_uri => {\n if (this._img) {\n return this._img;\n }\n let count = 0;\n this._img = new Image();\n this._img.src = data_uri;\n return new Promise(acc => {\n let int_id = setInterval(() => {\n if (this._img.complete || count > 10) {\n clearInterval(int_id);\n acc(this._img);\n }\n count++;\n }, 50);\n });\n });\n }\n\n // https://yellowpencil.com/blog/cropping-images-with-javascript/\n crop(x, y, width, height, scale) {\n this._data_uri = this.load().then(img => {\n let can = document.createElement('canvas'),\n can_con = can.getContext('2d');\n can.width = width;\n can.height = height;\n let buf = document.createElement('canvas'),\n buf_con = buf.getContext('2d');\n buf.width = img.width;\n buf.height = img.height;\n buf_con.drawImage(img, 0, 0);\n can_con.drawImage(buf, x, y, width, height, 0, 0, width, height);\n if (scale && Number(scale) !== 1.0) {\n let sca = document.createElement('canvas'),\n sca_con = sca.getContext('2d');\n sca.width = width * scale;\n sca.height = height * scale;\n sca_con.drawImage(can, 0, 0, sca.width, sca.height);\n can = sca;\n }\n return can.toDataURL('image/png');\n });\n return this._data_uri;\n }\n crop_to_square(max_size) {\n return this.load().then(img => {\n let h = img.height,\n w = img.width,\n size,\n h_offset = 0,\n w_offset = 0,\n scale = 1.0;\n if (h > w) {\n size = w;\n h_offset = (h - w) / 2;\n } else {\n size = h;\n w_offset = (w - h) / 2;\n }\n if (max_size && size > max_size) {\n scale = max_size / size;\n }\n return this.crop(w_offset, h_offset, size, size, scale);\n });\n }\n\n // https://stackoverflow.com/a/15754051\n to_blob() {\n return this.data().then(uri => {\n let byteString = atob(uri.split(',')[1]);\n let ab = new ArrayBuffer(byteString.length);\n let ia = new Uint8Array(ab);\n for (var i = 0; i < byteString.length; i++) {\n ia[i] = byteString.charCodeAt(i);\n }\n return new Blob([ab], {\n type: 'image/png'\n });\n });\n }\n }\n _exports.BocceImage = BocceImage;\n});","define(\"bocce/helpers/inarray\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/sum.js\n // Check if two provided strings are the same\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n var first = inp[0],\n second = inp[1];\n return second ? second.indexOf(first) > -1 : false;\n });\n});","define(\"bocce/helpers/isnull\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Helper.helper(function (val) {\n return !val || val[0] == null;\n });\n});","define(\"bocce/helpers/lessthan\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.lessthan = lessthan;\n // app/helpers/lessthan.js\n // Check if first provided number is less (!) than the second\n // Example: If number of assignments is less than 0\n // {{#if (lessthan assignments.length '0')}}\n\n function lessthan(inp) {\n var first = inp[0],\n second = inp[1];\n if (!first || !second) {\n return false;\n }\n return Number(first) < Number(second);\n }\n var _default = _exports.default = Ember.Helper.helper(lessthan);\n});","define(\"bocce/helpers/letter-grade\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/letter-grade.js\n // Convert points to letter grade\n var _default = _exports.default = Ember.Helper.helper(function (scores) {\n if (!scores[1]) {\n return '';\n }\n let pct = scores[0] / scores[1];\n if (pct >= 0.93) {\n return 'A';\n }\n if (pct >= 0.90) {\n return 'A-';\n }\n if (pct >= 0.87) {\n return 'B+';\n }\n if (pct >= 0.83) {\n return 'B';\n }\n if (pct >= 0.80) {\n return 'B-';\n }\n if (pct >= 0.77) {\n return 'C+';\n }\n if (pct >= 0.73) {\n return 'C';\n }\n if (pct >= 0.70) {\n return 'C-';\n }\n if (pct >= 0.6) {\n return 'D';\n }\n return 'F';\n });\n});","define(\"bocce/helpers/letter\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/letter.js\n // Convert number to letter\n var _default = _exports.default = Ember.Helper.helper(function (item) {\n if (!item) {\n return false;\n }\n return String.fromCharCode(65 + item[0]);\n });\n});","define(\"bocce/helpers/longdate\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/longdate.js\n // Convert date into long-form with Moment.JS\n var _default = _exports.default = Ember.Helper.helper(function (date) {\n if (date.length === 0) {\n return 'Date Incorrect';\n }\n return moment(date[0]).tz('America/New_York').format('MMMM Do, h:mmA') + ' ET';\n });\n});","define(\"bocce/helpers/moment\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/moment.js\n // Convert date into relative time with Moment.JS\n var _default = _exports.default = Ember.Helper.helper(function (args) {\n if (args.length === 0) {\n return 'Date Incorrect';\n }\n let [date, format] = args;\n let dateObj = new Date(date);\n let val = moment(dateObj).tz('America/New_York');\n if (format) {\n return val.format(format);\n } else {\n return val.calendar();\n }\n });\n});","define(\"bocce/helpers/morethan\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.morethan = morethan;\n // app/helpers/morethan.js\n // Check if first provided number is more than the second\n // Example: If number of assignments is more than 0\n // {{#if (morethan assignments.length '0')}}\n function morethan(inp) {\n var first = inp[0],\n second = inp[1];\n if (!first || !second) {\n return false;\n }\n return Number(first) > Number(second);\n }\n var _default = _exports.default = Ember.Helper.helper(morethan);\n});","define(\"bocce/helpers/morethanzero\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/morethanzero.js\n // Checks if the array has elements\n var _default = _exports.default = Ember.Helper.helper(function (item) {\n if (!item || !item[0] || !item[0].get) {\n return false;\n }\n return item[0].get('length') > 0;\n });\n});","define(\"bocce/helpers/not\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Helper.helper(function (val) {\n if (val == null || val[0] == null) {\n return true;\n }\n return !!!val[0];\n });\n});","define(\"bocce/helpers/notnull\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Helper.helper(function (val) {\n return val && val[0] != null && val[0] != undefined;\n });\n});","define(\"bocce/helpers/null\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Helper.helper(function (val) {\n return !val || val[0] == null;\n });\n});","define(\"bocce/helpers/or\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.or = or;\n function or(params) {\n for (let p of params) {\n if (p) {\n return true;\n }\n }\n return false;\n }\n var _default = _exports.default = Ember.Helper.helper(or);\n});","define(\"bocce/helpers/past-due\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/past-due.js\n // Check if date is past due or not\n var _default = _exports.default = Ember.Helper.helper(function (date, options) {\n if (new Date(date).getTime() < new Date().getTime()) {\n return options.fn(this);\n }\n return options.inverse(this);\n });\n});","define(\"bocce/helpers/percentage\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/percentage.js\n // Calculate percentage\n var _default = _exports.default = Ember.Helper.helper(function (mod) {\n if (!mod[0] || !mod[1]) {\n return 0;\n }\n let percentRaw = mod[0] / mod[1] * 100;\n if (parseInt(percentRaw) === parseFloat(percentRaw)) {\n return percentRaw;\n }\n return percentRaw.toFixed(2);\n });\n});","define(\"bocce/helpers/perform\", [\"exports\", \"ember-concurrency/helpers/perform\"], function (_exports, _perform) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _perform.default;\n }\n });\n});","define(\"bocce/helpers/pluralize\", [\"exports\", \"ember-inflector/lib/helpers/pluralize\"], function (_exports, _pluralize) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _pluralize.default;\n});","define(\"bocce/helpers/reply-count\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/reply-count.js\n // pluralize new replies or not\n var _default = _exports.default = Ember.Helper.helper(function (replies) {\n // We're calling this now with the number of replies, instead of the replies themselves\n let total = replies[0];\n if (total > 1) {\n return total + ' new replies';\n } else if (total === 1) {\n return '1 new reply';\n }\n });\n});","define(\"bocce/helpers/safehtml\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.safehtml = safehtml;\n function safehtml([input] /*, hash*/) {\n return Ember.String.htmlSafe(input);\n }\n var _default = _exports.default = Ember.Helper.helper(safehtml);\n});","define(\"bocce/helpers/same\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/same.js\n // Check if two provided strings are the same\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n var first = inp[0],\n second = inp[1];\n return String(first) === String(second);\n });\n});","define(\"bocce/helpers/show-discussions\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/show-ungraded.js\n // Show ungraded items in the dashboard\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n var isDiscussion = inp[0],\n showDiscussions = inp[1];\n if (!isDiscussion) {\n return true;\n }\n if (isDiscussion && showDiscussions) {\n return true;\n }\n return false;\n });\n});","define(\"bocce/helpers/show-ungraded\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/show-ungraded.js\n // Show ungraded items in the dashboard\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n var grade = inp[0],\n showUngraded = inp[1];\n if (grade) {\n return true;\n }\n if (!grade && showUngraded) {\n return true;\n }\n return false;\n });\n});","define(\"bocce/helpers/singularize\", [\"exports\", \"ember-inflector/lib/helpers/singularize\"], function (_exports, _singularize) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _singularize.default;\n});","define(\"bocce/helpers/spacelist\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/spacelist.js\n // Turn string to dashed string\n var _default = _exports.default = Ember.Helper.helper(function (dec) {\n var item = dec[0];\n return item.join(' ');\n });\n});","define(\"bocce/helpers/startswith\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.startswith = startswith;\n // app/helpers/startswith.js\n // Check if the first provided string begins with the second.\n // {{#if (startswith assignments.name 'Assignment 2')}}\n\n function startswith(inp) {\n var first = inp[0],\n second = inp[1];\n if (!first || !second) {\n return false;\n }\n return first.indexOf(second) === 0;\n }\n var _default = _exports.default = Ember.Helper.helper(startswith);\n});","define(\"bocce/helpers/substring\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/sum.js\n // Check if two provided strings are the same\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n let str = inp[0],\n start = inp[1],\n end = inp[2];\n if (!str || start == null) {\n return '';\n }\n if (end) {\n return str.substring(start, end);\n } else {\n return str.substring(start);\n }\n });\n});","define(\"bocce/helpers/sum\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/sum.js\n // Check if two provided strings are the same\n var _default = _exports.default = Ember.Helper.helper(function (inp) {\n var first = inp[0],\n second = inp[1];\n return parseInt(first) + parseInt(second);\n });\n});","define(\"bocce/helpers/task\", [\"exports\", \"ember-concurrency/helpers/task\"], function (_exports, _task) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _task.default;\n }\n });\n});","define(\"bocce/helpers/teaser\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/teaser.js\n // Trims the string to first or 100 characters\n var _default = _exports.default = Ember.Helper.helper(function (item) {\n if (!item || !item[0] || typeof item[0] !== 'string') {\n return false;\n }\n let str = item[0];\n let br = str.indexOf(' ');\n if (br > 0) {\n return str.substring(0, br);\n } else {\n return str.substring(0, 100);\n }\n });\n});","define(\"bocce/helpers/total\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // app/helpers/total.js\n // Return total number of elements in array\n var _default = _exports.default = Ember.Helper.helper(function (item) {\n if (!item || !item[0] || !item[0].get) {\n return false;\n }\n return item[0].get('length');\n });\n});","define(\"bocce/helpers/trim\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Helper.helper(function (item) {\n if (!item || !item[0] || typeof item[0] !== 'string') {\n return false;\n }\n return item[0].trim();\n });\n});","define(\"bocce/helpers/two-decimals\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = Ember.Helper.helper(function (val) {\n if (!val || val[0] == null) {\n return 'n/a';\n }\n\n // round val[0] to two decimal places\n return Math.round(val[0] * 100) / 100;\n });\n});","define(\"bocce/helpers/upload\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = uploadFile;\n _exports.uploadURL = uploadURL;\n function attemptUpload(url, data, return_url, on_progress) {\n // Using XMLHttpRequest instead of fetch here, because we ened to have a\n // callback for updating the upload progress bar.\n let request = new XMLHttpRequest();\n request.open('POST', url);\n if (on_progress) {\n request.addEventListener('progress', event => {\n if (event.lengthComputable) {\n on_progress(Math.ceil(100 * event.loaded / event.total));\n }\n });\n }\n return new Promise((resolve, reject) => {\n request.addEventListener('load', () => {\n if (request.status !== 200) {\n reject(request.status);\n return;\n }\n let raw = request.responseText;\n let obj = parseBrokenCanvasJSON(raw);\n if (return_url) {\n resolve(obj.url);\n } else {\n resolve(obj.id);\n }\n });\n request.send(data);\n });\n }\n function proxyUpload(store, file, path, return_url) {\n let att = store.createRecord('attachment');\n att.set('attachment', file);\n if (path) {\n att.set('file_context', path);\n }\n return att.save().then(obj => {\n if (return_url) {\n return obj.get('thumbnail_url');\n }\n return obj.get('id');\n }, err => {\n throw err;\n });\n }\n let force_proxy_uploads = false;\n\n // Returns a promise that resolves to the ID of the new attachent\n async function uploadFile(file, user_id, store, destination_folder, return_url, on_progress, postUrl) {\n if (force_proxy_uploads) {\n Ember.debug('Uploading via proxy due to previous failures');\n return proxyUpload(store, file, destination_folder, return_url);\n }\n if (file.isUrl) {\n return uploadURL(file, destination_folder);\n }\n let data = {\n name: file.name,\n content_type: file.type,\n on_duplicate: 'rename'\n };\n if (destination_folder) {\n data.parent_folder_path = destination_folder;\n }\n Ember.debug('Beginning direct upload of ' + data.name);\n let target = await postFileDataToCanvas(data, postUrl);\n if (!target.upload_url) {\n if (target.attachments instanceof Array && target.attachments.length > 0) {\n target = target.attachments[0];\n }\n }\n let fd = buildUploadFormData(target, file);\n Ember.debug('Sending file to ' + target.upload_url);\n let attachment_id = null;\n try {\n for (let i = 0; i < 2; ++i) {\n try {\n attachment_id = await attemptUpload(target.upload_url, fd, return_url, on_progress);\n break;\n } catch (err) {\n Ember.debug({\n Location: 'Initial upload attempt failed',\n Status: err,\n Message: 'User ' + user_id + ' uploading ' + data.name + ', ' + data.content_type,\n URL: target.upload_url,\n formdata: fd\n });\n }\n }\n // If we're still having errors, try uploading through the interface and\n // make a note to do that in the future.\n if (attachment_id === null) {\n force_proxy_uploads = true;\n attachment_id = await proxyUpload(store, file, destination_folder, return_url);\n }\n } catch (err) {\n Ember.debug('Failed uploading ' + data.name + ': ', err);\n Ember.debug({\n Location: 'Upload failed',\n Status: err.status,\n Message: 'User ' + user_id + ' uploading ' + data.name + ', ' + data.content_type,\n URL: target.upload_url,\n formdata: fd\n });\n throw err;\n }\n Ember.debug('Successfully uploaded ' + data.name + ' as attachment ' + attachment_id);\n return attachment_id;\n }\n function buildUploadFormData(target, file) {\n let fd = new FormData();\n for (let key in target.upload_params) {\n fd.append(key, target.upload_params[key]);\n }\n fd.append(target.file_param, file);\n return fd;\n }\n async function uploadURL(file, destination_folder) {\n let data = {\n url: file.url,\n content_type: file.type,\n on_duplicate: 'rename'\n };\n if (destination_folder) {\n data.parent_folder_path = destination_folder;\n }\n Ember.debug('Beginning upload of ' + data.name);\n let result = await postFileDataToCanvas(data);\n let id = result.id;\n let filePending = true;\n while (filePending) {\n filePending = await fetchFilePendingFlag(result.status_url);\n if (filePending) {\n await pauseOneSecond();\n }\n }\n return id;\n }\n async function fetchFilePendingFlag(statusURL) {\n let statusRequest = await fetch(statusURL);\n if (!statusRequest.ok) {\n throw statusRequest.status;\n }\n let stat = await statusRequest.text();\n let tmp = parseBrokenCanvasJSON(stat);\n return tmp && tmp.upload_status === 'pending';\n }\n async function postFileDataToCanvas(fileData, url) {\n let response = await fetch(url ?? '/api/v1/users/self/files', {\n method: 'POST',\n body: new URLSearchParams(fileData)\n });\n if (!response.ok) {\n throw response.status;\n }\n return response.json();\n }\n function parseBrokenCanvasJSON(text) {\n return JSON.parse(text.replace(/^while\\(1\\);/, ''));\n }\n function pauseOneSecond() {\n return new Promise(res => {\n setTimeout(() => res(), 1000);\n });\n }\n});","define(\"bocce/initializers/app-version\", [\"exports\", \"ember-cli-app-version/initializer-factory\", \"bocce/config/environment\"], function (_exports, _initializerFactory, _environment) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n let name, version;\n if (_environment.default.APP) {\n name = _environment.default.APP.name;\n version = _environment.default.APP.version;\n }\n var _default = _exports.default = {\n name: 'App Version',\n initialize: (0, _initializerFactory.default)(name, version)\n };\n});","define(\"bocce/initializers/container-debug-adapter\", [\"exports\", \"ember-resolver/resolvers/classic/container-debug-adapter\"], function (_exports, _containerDebugAdapter) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = {\n name: 'container-debug-adapter',\n initialize() {\n let app = arguments[1] || arguments[0];\n app.register('container-debug-adapter:main', _containerDebugAdapter.default);\n app.inject('container-debug-adapter:main', 'namespace', 'application:main');\n }\n };\n});","define(\"bocce/initializers/ember-cli-mirage\", [\"exports\", \"bocce/config/environment\", \"bocce/mirage/config\", \"ember-cli-mirage/get-rfc232-test-context\", \"ember-cli-mirage/start-mirage\"], function (_exports, _environment, _config, _getRfc232TestContext, _startMirage) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.startMirage = startMirage;\n //\n // This initializer does two things:\n //\n // 1. Pulls the mirage config objects from the application's config and\n // registers them in the container so `ember-cli-mirage/start-mirage` can\n // find them (since it doesn't have access to the app's namespace).\n // 2. Provides legacy support for auto-starting mirage in pre-rfc268 acceptance\n // tests.\n //\n var _default = _exports.default = {\n name: 'ember-cli-mirage',\n initialize(application) {\n if (_config.default) {\n application.register('mirage:base-config', _config.default, {\n instantiate: false\n });\n }\n if (_config.testConfig) {\n application.register('mirage:test-config', _config.testConfig, {\n instantiate: false\n });\n }\n _environment.default['ember-cli-mirage'] = _environment.default['ember-cli-mirage'] || {};\n if (_shouldUseMirage(_environment.default.environment, _environment.default['ember-cli-mirage'])) {\n startMirage(_environment.default);\n }\n }\n };\n function startMirage(env = _environment.default) {\n return (0, _startMirage.default)(null, {\n env,\n baseConfig: _config.default,\n testConfig: _config.testConfig\n });\n }\n function _shouldUseMirage(env, addonConfig) {\n if (typeof FastBoot !== 'undefined') {\n return false;\n }\n if ((0, _getRfc232TestContext.default)()) {\n return false;\n }\n let userDeclaredEnabled = typeof addonConfig.enabled !== 'undefined';\n let defaultEnabled = _defaultEnabled(env, addonConfig);\n return userDeclaredEnabled ? addonConfig.enabled : defaultEnabled;\n }\n\n /*\n Returns a boolean specifying the default behavior for whether\n to initialize Mirage.\n */\n function _defaultEnabled(env, addonConfig) {\n let usingInDev = env === 'development' && !addonConfig.usingProxy;\n let usingInTest = env === 'test';\n return usingInDev || usingInTest;\n }\n});","define(\"bocce/initializers/ember-data-data-adapter\", [\"exports\", \"@ember-data/debug/setup\"], function (_exports, _setup) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _setup.default;\n }\n });\n});","define(\"bocce/initializers/ember-data\", [\"exports\", \"ember-data\", \"ember-data/setup-container\"], function (_exports, _emberData, _setupContainer) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /*\n This code initializes EmberData in an Ember application.\n \n It ensures that the `store` service is automatically injected\n as the `store` property on all routes and controllers.\n */\n var _default = _exports.default = {\n name: 'ember-data',\n initialize: _setupContainer.default\n };\n});","define(\"bocce/initializers/export-application-global\", [\"exports\", \"bocce/config/environment\"], function (_exports, _environment) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n _exports.initialize = initialize;\n function initialize() {\n var application = arguments[1] || arguments[0];\n if (_environment.default.exportApplicationGlobal !== false) {\n var theGlobal;\n if (typeof window !== 'undefined') {\n theGlobal = window;\n } else if (typeof global !== 'undefined') {\n theGlobal = global;\n } else if (typeof self !== 'undefined') {\n theGlobal = self;\n } else {\n // no reasonable global, just bail\n return;\n }\n var value = _environment.default.exportApplicationGlobal;\n var globalName;\n if (typeof value === 'string') {\n globalName = value;\n } else {\n globalName = Ember.String.classify(_environment.default.modulePrefix);\n }\n if (!theGlobal[globalName]) {\n theGlobal[globalName] = application;\n application.reopen({\n willDestroy: function () {\n this._super.apply(this, arguments);\n delete theGlobal[globalName];\n }\n });\n }\n }\n }\n var _default = _exports.default = {\n name: 'export-application-global',\n initialize: initialize\n };\n});","define(\"bocce/initializers/global-session\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n // from http://brewhouse.io/blog/2015/02/12/ember-vs-angular-authentication\n var _default = _exports.default = {\n name: 'global-session',\n initialize: function initialize(application) {\n // Make the session service available to all routes and controller\n application.inject('route', 'session', 'service:session');\n application.inject('controller', 'session', 'service:session');\n application.inject('adapter', 'session', 'service:session');\n\n // NOTE: If needed, we can also inject the session service on-the-fly to other Ember objects (e.g. Components)\n // See: https://guides.emberjs.com/v2.18.0/applications/services/#toc_accessing-services\n\n application.inject('route:application', 'login-refresh', 'service:login-refresh');\n }\n };\n});","define(\"bocce/instance-initializers/ember-cli-mirage-autostart\", [\"exports\", \"ember-cli-mirage/instance-initializers/ember-cli-mirage-autostart\"], function (_exports, _emberCliMirageAutostart) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n Object.defineProperty(_exports, \"default\", {\n enumerable: true,\n get: function () {\n return _emberCliMirageAutostart.default;\n }\n });\n});","define(\"bocce/instance-initializers/ember-data\", [\"exports\", \"ember-data/initialize-store-service\"], function (_exports, _initializeStoreService) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = {\n name: 'ember-data',\n initialize: _initializeStoreService.default\n };\n});","define(\"bocce/js/quizzes/quizzes-factory\", [\"exports\", \"bocce/js/quizzes/quizzes\"], function (_exports, _quizzes) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n class QuizzesFactory {\n static get(quizId) {\n if (this.quizzesServices[quizId]) {\n return this.quizzesServices[quizId];\n } else {\n const quizzes = new _quizzes.default();\n this.quizzesServices[quizId] = quizzes;\n return quizzes;\n }\n }\n }\n _exports.default = QuizzesFactory;\n _defineProperty(QuizzesFactory, \"quizzesServices\", {});\n});","define(\"bocce/js/quizzes/quizzes\", [\"exports\", \"bocce/utilities/timer\", \"bocce/utilities/dialog\", \"bocce/utilities/promise-queue\", \"bocce/config/environment\", \"lodash.isequal\"], function (_exports, _timer, _dialog, _promiseQueue, _environment, _lodash) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7, _descriptor8, _descriptor9, _descriptor10, _descriptor11, _descriptor12, _descriptor13, _descriptor14, _descriptor15;\n function _initializerDefineProperty(e, i, r, l) { r && Object.defineProperty(e, i, { enumerable: r.enumerable, configurable: r.configurable, writable: r.writable, value: r.initializer ? r.initializer.call(l) : void 0 }); }\n function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\n function _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == typeof i ? i : i + \"\"; }\n function _toPrimitive(t, r) { if (\"object\" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != typeof i) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, (\"value\" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }\n function _initializerWarningHelper(r, e) { throw Error(\"Decorating class property failed. Please ensure that transform-class-properties is enabled and runs after the decorators transform.\"); }\n function shuffle(arr) {\n for (let i = arr.length; i > 0; --i) {\n let j = Math.floor(Math.random() * i);\n let x = arr[i - 1];\n arr[i - 1] = arr[j];\n arr[j] = x;\n }\n }\n let QuizzesService = _exports.default = (_dec = Ember._tracked, _dec2 = Ember._tracked, _dec3 = Ember._tracked, _dec4 = Ember._tracked, _dec5 = Ember._tracked, _dec6 = Ember._tracked, _dec7 = Ember._tracked, _dec8 = Ember._tracked, _dec9 = Ember._tracked, _dec10 = Ember._tracked, _dec11 = Ember._tracked, _dec12 = Ember._tracked, _dec13 = Ember._tracked, _dec14 = Ember._tracked, _dec15 = Ember._tracked, _dec16 = Ember._action, _dec17 = Ember._action, _dec18 = Ember._action, _dec19 = Ember._action, _dec20 = Ember._action, _dec21 = Ember._action, _dec22 = Ember._action, (_class = class QuizzesService extends Ember.Service {\n constructor(...args) {\n super(...args);\n _initializerDefineProperty(this, \"quiz\", _descriptor, this);\n _initializerDefineProperty(this, \"questionIndex\", _descriptor2, this);\n _initializerDefineProperty(this, \"gradingQuiz\", _descriptor3, this);\n _initializerDefineProperty(this, \"questionShown\", _descriptor4, this);\n _initializerDefineProperty(this, \"maxQuestionIndexUsed\", _descriptor5, this);\n _initializerDefineProperty(this, \"hasShownQuizIntercept\", _descriptor6, this);\n _initializerDefineProperty(this, \"timeRemaining\", _descriptor7, this);\n _initializerDefineProperty(this, \"quizSubmittedInText\", _descriptor8, this);\n _initializerDefineProperty(this, \"startingQuiz\", _descriptor9, this);\n _initializerDefineProperty(this, \"questionPromises\", _descriptor10, this);\n _initializerDefineProperty(this, \"savedAt\", _descriptor11, this);\n _initializerDefineProperty(this, \"initialized\", _descriptor12, this);\n _initializerDefineProperty(this, \"uploadingQuestion\", _descriptor13, this);\n _initializerDefineProperty(this, \"uploadFailed\", _descriptor14, this);\n _defineProperty(this, \"nextQuestionCallbacks\", []);\n _initializerDefineProperty(this, \"showStats\", _descriptor15, this);\n _defineProperty(this, \"questions\", void 0);\n _defineProperty(this, \"timedQuizSubmissionData\", void 0);\n _defineProperty(this, \"performedInitialRender\", void 0);\n _defineProperty(this, \"session\", void 0);\n _defineProperty(this, \"store\", void 0);\n }\n aggregateQuestions() {\n let questions = [];\n let models = ['multiple_choices', 'true_falses', 'short_answers', 'multiple_answers', 'multiple_dropdowns', 'essays', 'file_uploads', 'fill_in_multiple_blanks'];\n for (const model of models) {\n if (this.quiz.get(model)) {\n const m = this.quiz.get(model);\n m.forEach(m2 => {\n questions.push(m2);\n });\n }\n }\n this.quiz.quiz_questions = questions;\n }\n initialize(quiz, session, store, gs_service) {\n this.quiz = quiz;\n this.gainsight = gs_service;\n if (this.quiz.get('attempts.length') > 0) {\n this.quiz.set('quiz_attempt', {\n seeking: true\n });\n this.quiz.set('quiz_attempt', this.quiz.get('attempts').objectAt(0));\n }\n this.session = session;\n\n //Quizzes prior to the revamp had quiz questions stored in 'quiz_questions'. Post \n //revamp, they get stored in 'multiple_choices', 'fill_multiple_blanks', etc. So, \n //if there are 'quiz_questions', the quiz is probably archived or on the intro server. In this case, \n //don't copy 'multiple_choices', etc. into 'quiz_questions' as they are already there.\n if (this.quiz.get('quiz_questions.length') == 0) {\n this.aggregateQuestions();\n }\n if (this.quiz.get('shuffle')) {\n let arr = this.quiz.get('quiz_questions.content.currentState');\n if (arr) {\n shuffle(arr);\n this.quiz.set('quiz_questions.content.currentState', arr);\n this.quiz.set('quiz_questions.content.canonicalState', arr);\n }\n }\n this.questions = this.get('quiz.quiz_questions').sortBy('position');\n this.questionShown = true;\n this.store = store;\n let quizAttempt = this.quiz.get('quiz_attempt');\n this.resetGivenAnswers();\n if (quizAttempt) {\n let answers = quizAttempt.get('answers') ?? null;\n if (answers) {\n this.setQuestionData(answers, true);\n }\n this.timedQuizSubmissionData = {\n id: quizAttempt.get('id'),\n token: quizAttempt.get('token'),\n number: quizAttempt.get('number')\n };\n\n //this.quiz.set('quiz_attempt', {left: this.quiz.get('quiz_attempt.left')});\n\n if (!this.hasTimeLeft || this.hasScore) {\n this.setQuestionIndex(null);\n } else {\n //find the first un-answered question and set the index equal to that.\n let firstUnansweredQuestionIndex = null;\n let lastAnsweredQuestionIndex = null;\n this.questions.forEach((question, index) => {\n if (question.hasAnswer) {\n lastAnsweredQuestionIndex = index;\n } else if (!firstUnansweredQuestionIndex) {\n firstUnansweredQuestionIndex = index;\n }\n });\n if (firstUnansweredQuestionIndex) {\n this.setQuestionIndex(firstUnansweredQuestionIndex);\n } else {\n this.setQuestionIndex(0);\n }\n if (lastAnsweredQuestionIndex && lastAnsweredQuestionIndex + 1 > firstUnansweredQuestionIndex) {\n this.maxQuestionIndexUsed = lastAnsweredQuestionIndex + 1;\n if (this.maxQuestionIndexUsed == this.questions.length) {\n this.maxQuestionIndexUsed = this.questions.length - 1;\n }\n }\n }\n } else {\n this.setQuestionIndex(this.quiz.presentation === 'single' ? 0 : null);\n }\n\n //Quiz is resuming. Start the timer.\n if (this.isTimedQuiz && this.hasTimeLeft && !this.hasScore) {\n this.startTimer();\n }\n this.questions.forEach(question => {\n question.quizzes = this;\n });\n Ember.run.schedule('afterRender', () => {\n this.initialized = true;\n });\n }\n resetGivenAnswers() {\n this.quiz.quiz_questions.forEach(question => {\n if (question) {\n let defaultAnswer = question.get('default_answer');\n if (defaultAnswer) {\n defaultAnswer = JSON.parse(JSON.stringify(question.get('default_answer')));\n }\n question.set('given_answer', defaultAnswer);\n }\n });\n }\n get answerStats() {\n const stats = this.quiz.question_statistics.find(qs => {\n qs.position = this.question.position;\n });\n let numResponses = stats.responses;\n let correctResponses = 0;\n let answerStats = {};\n for (let answer of stats.answers) {\n if (answer.correct) {\n correctResponses += answer.user_ids.length;\n }\n answerStats[answer.id] = {};\n answerStats[answer.id].responsePercentage = answer.user_ids.length / numResponses;\n answerStats[answer.id].userNames = answer.user_names;\n }\n return answerStats;\n }\n getAnswerStats(answerId) {\n return this.questionStatistics.answers[answerId];\n }\n get showQuizIntercept() {\n return (this.isTimedQuiz && !this.hasTimeLeft || this.previousQuestionsLocked) && !this.hasScore && !this.hasShownQuizIntercept;\n }\n get hasAttempt() {\n return this.quiz.get('quiz_attempt') != null;\n }\n get hasTimeLeft() {\n return this.quiz.get('quiz_attempt.started_at') && this.quiz.get('quiz_attempt.end_at') && moment().unix() >= moment(this.quiz.get('quiz_attempt.started_at')).unix() && moment().unix() < moment(this.quiz.get('quiz_attempt.end_at')).unix();\n }\n get isRetrying() {\n return this.get('quiz.quiz_attempt.retrying');\n }\n get quizTimeLimit() {\n const timeLimit = this.quiz.time_limit_minutes;\n return `${timeLimit} Minute${timeLimit > 1 ? 's' : ''}`;\n }\n get gradedQuestions() {\n return this.questions.filter(v => v.correct && v.correct != 'undefined');\n }\n get correctQuestions() {\n return this.questions.filter(v => v.correct && v.correct == 'correct');\n }\n get notGradedQuestions() {\n return this.questions.filter(v => v.correct && v.correct == 'undefined');\n }\n get quizNotFullyGraded() {\n return this.notGradedQuestions.length > 0;\n }\n get question() {\n if (this.questionIndex != null && this.questionIndex >= 0 && this.questionIndex < this.questions.length) {\n return this.questions.objectAt(this.questionIndex);\n } else {\n return null;\n }\n }\n get questionIndexPostfix() {\n return this.get('quiz.presentation') === 'single' ? ` of ${this.questions.length}` : '';\n }\n get isGraded() {\n return this.get('quiz.is_graded');\n }\n get score() {\n return this.get('quiz.quiz_attempt.score');\n }\n async getQuizAnswers() {\n let answers = [];\n for (let question of this.questions) {\n let answer = await question.getAnswer();\n if (!(0, _lodash.default)(answer, question.default_answer)) {\n answers.push({\n question_id: question.id,\n answer\n });\n }\n }\n return answers;\n }\n get hasScore() {\n return this.score != null;\n }\n get isTimedQuiz() {\n return this.get('quiz.time_limit_minutes') > 0;\n }\n get isScoredTimedQuizWithRemainingAttempts() {\n return this.isTimedQuiz && this.hasScore && (this.quiz.quiz_attempt.left > 0 || this.quiz.allowed_attempts == -1);\n }\n get previousQuestionsLocked() {\n return this.get('quiz.cant_go_back') && this.get('quiz.presentation') === 'single';\n }\n get timeRemainingMinutes() {\n return this.timeRemaining != null ? Math.floor(this.timeRemaining / 60) : null;\n }\n get timeRemainingSeconds() {\n return this.timeRemaining != null ? this.timeRemaining % 60 : null;\n }\n get timeRemainingFormatted() {\n if (this.timeRemainingMinutes != null && this.timeRemainingSeconds != null) {\n let minutesFormatted = new String(this.timeRemainingMinutes).padStart(2, '0');\n let secondsFormatted = new String(this.timeRemainingSeconds).padStart(2, '0');\n return `${minutesFormatted}:${secondsFormatted}`;\n } else {\n return '';\n }\n }\n\n /**\n * This is used by the root-level quiz template (app/templates/quiz.hbs) to render the specific\n * quiz type, of which there are five:\n * - quiz-scenarios/quiz-all-graded.hbs: all questions are shown at once for a graded quiz.\n * - quiz-scenarios/quiz-all.hbs: all questions are shown at once for a not-graded quiz.\n * - quiz-scenarios/quiz-single-graded-results.hbs: results are shown for a single-question-at-a-time graded quiz.\n * - quiz-scenarios/quiz-single-graded-reviewing.hbs: student is reviewing individual question results for a\n * single-questions-at-a-time graded quiz.\n * - quiz-scenarios/quiz-single.hbs: one question shown at a time for a not-graded quiz.\n **/\n get component() {\n const type = this.quizType;\n return type == 'dummy-component' ? type : `quiz-scenarios/${type}`;\n }\n get quizType() {\n let graded = this.hasScore;\n let presentation = this.quiz.presentation;\n let questionIndex = this.questionIndex;\n let type = 'dummy-component';\n if (this.showQuizIntercept) {\n type = 'quiz-intercept';\n } else if (presentation == 'all') {\n if (graded) {\n type = 'quiz-all-graded';\n } else {\n type = 'quiz-all';\n }\n } else if (presentation == 'single') {\n if (!graded) {\n type = 'quiz-single';\n } else if (graded && questionIndex == null) {\n type = 'quiz-single-graded-results';\n } else if (graded && questionIndex >= 0) {\n type = 'quiz-single-graded-reviewing';\n }\n }\n return type;\n }\n\n /**\n * Sets question data given 'answers' returned by interface.\n */\n setQuestionData(answers, updateGivenAnswer) {\n if (answers) {\n this.questions.forEach(question => {\n let answer = answers.find(({\n question_id\n }) => question_id == question.id);\n if (answer) {\n if (updateGivenAnswer && answer.answer !== undefined) {\n question.set('given_answer', answer.answer);\n }\n\n /**\n * Possible values for answer.correct:\n * undefined: manually graded question without a score\n * defined: manually graded question with a score\n * partial: partially right answer, ex: multiple fill in the blanks\n * where one of the blanks is right and the other is wrong\n * correct/incorrect: fully right or wrong answer, ex: multiple choice, multiple dropdowns\n */\n question.set('correct', answer.correct);\n question.set('points_received', answer.points);\n let attachments = [];\n if (answer.attachments && answer.attachments instanceof Array && answer.attachments.length > 0) {\n attachments = answer.attachments;\n }\n let attemptData = question.get('attempt_data');\n question.set('attempt_data', {\n ...attemptData,\n attachments\n });\n }\n });\n }\n }\n addNextQuestionCallback(callback) {\n this.nextQuestionCallbacks.push(callback);\n }\n async executeNextQuestionCallbacks() {\n for (const callback of this.nextQuestionCallbacks) {\n _promiseQueue.default.enqueue(async () => {\n await callback();\n });\n }\n await _promiseQueue.default.enqueue(async () => {});\n this.nextQuestionCallbacks = [];\n }\n toggleShowStats() {\n this.showStats = !this.showStats;\n }\n async nextQuestion(index) {\n if (!this.hasScore) {\n await this.executeNextQuestionCallbacks();\n }\n this.setQuestionIndex(index);\n }\n setQuestionIndex(index) {\n this.questionIndex = index;\n if (this.maxQuestionIndexUsed == null) {\n this.maxQuestionIndexUsed = this.questionIndex;\n } else if (index > this.maxQuestionIndexUsed) {\n this.maxQuestionIndexUsed = index;\n }\n\n //This is a hack to ensure that didInsertElement is called when two of the same question types are repeated in a row.\n // Wasn't sure how else to make it happen.\n this.questionShown = false;\n Ember.run.schedule('afterRender', () => {\n this.questionShown = true;\n });\n }\n stopTimers() {\n if (this.autosaveTimerInterval) {\n clearInterval(this.autosaveTimerInterval);\n }\n if (this.countdownTimerInterval) {\n clearInterval(this.countdownTimerInterval);\n }\n }\n get quizLengthSeconds() {\n const timeRemaining = this.quiz.get('time_limit_minutes');\n if (this.quiz.get('quiz_attempt.end_at')) {\n //This should only be true if the user is continuing a previously started quiz\n return Math.floor((new Date(this.quiz.get('quiz_attempt.end_at')).getTime() - new Date().getTime()) / 1000);\n } else if (Number.isInteger(timeRemaining)) {\n return timeRemaining * 60;\n } else {\n return null;\n }\n }\n startTimer() {\n let submissionWarningEvents = [];\n const quizLengthSeconds = this.quizLengthSeconds;\n for (let timeLeft of _environment.default.APP.quizSubmissionWarningTimesLeft) {\n if (timeLeft < quizLengthSeconds) {\n submissionWarningEvents.push({\n time: timeLeft,\n callback: () => {\n this.quizSubmittedInText = `The quiz will be submitted in ${this.timeRemainingFormatted}`;\n this.timeRemainingSnapshot = this.timeRemaining;\n setTimeout(() => {\n this.quizSubmittedInText = undefined;\n }, 10000);\n }\n });\n }\n }\n this.countdownTimerInterval = (0, _timer.default)(this.quizLengthSeconds, 1000, (minutes, seconds) => {\n this.timeRemaining = 60 * minutes + seconds;\n }, [...submissionWarningEvents, {\n time: 0,\n callback: async () => {\n this.stopTimers();\n\n /**\n * Remove the retry-question-upload dialog if it is there so that we don't have two dialogs showing\n * at once when the time-is-up dialog shows.\n */\n Ember.$('.conf-dialog.retry-question-upload, .conf-dialog-mask.retry-question-upload').remove();\n await _promiseQueue.default.enqueue(async () => {\n await (0, _dialog.default)('Time is up! Click OK to submit the quiz.', ['OK'], false, false, 'quiz-time-is-up');\n });\n await this.submitQuiz(false);\n }\n }]);\n }\n async updateCanvasQuestionAnswer(question) {\n let retries = 0;\n this.uploadingQuestion = true;\n this.uploadFailed = false;\n const upload = async () => {\n try {\n const answer = await question.get('given_answer');\n const section = this.get('session.section.id');\n this.store.nestResources('attempt', [{\n section\n }, {\n quiz: this.quiz.get('id')\n }]);\n let resp = await this.store.findRecord('attempt', this.timedQuizSubmissionData.id);\n resp.set('token', this.timedQuizSubmissionData.token);\n resp.set('number', this.timedQuizSubmissionData.number);\n resp.set('answers', [{\n question_id: question.id,\n answer\n }]);\n resp.set('action', 'update_questions');\n await resp.save();\n this.savedAt = new Date().toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit'\n });\n } catch (e) {\n this.uploadFailed = true;\n if (retries < 3) {\n retries = retries + 1;\n setTimeout(upload, 1000);\n } else if (this.hasTimeLeft) {\n await (0, _dialog.default)('Unable to upload question answer. Please try again.', ['Retry'], false, false, 'retry-question-upload');\n upload();\n }\n }\n this.uploadingQuestion = false;\n this.uploadFailed = false;\n };\n await upload();\n }\n startSingleCantGoBackQuiz() {\n (0, _dialog.default)('When you answer questions, you cannot go back.', ['OK', 'Cancel']).then(choice => {\n if (choice === 'Cancel') {\n return false;\n }\n this.hasShownQuizIntercept = true;\n });\n }\n startQuiz() {\n const sectionId = this.session.get('section.id');\n const _startQuiz = () => {\n this.startingQuiz = true;\n const start = () => {\n this.hasShownQuizIntercept = true;\n this.startingQuiz = false;\n };\n if (this.isTimedQuiz) {\n Ember.$.post('/interface/sections/' + sectionId + '/quizzes/' + this.quiz.get('id') + '/attempts', data => {\n this.timedQuizSubmissionData = data;\n this.startTimer();\n start();\n }).fail(async () => {\n this.startingQuiz = false;\n const response = await (0, _dialog.default)('Problem occurred starting the quiz. Try again?', ['OK', 'Cancel']);\n if (response == 'OK') {\n _startQuiz();\n }\n });\n } else {\n start();\n }\n };\n if (this.previousQuestionsLocked) {\n (0, _dialog.default)('When you answer questions, you cannot go back.', ['OK', 'Cancel']).then(choice => {\n if (choice === 'Cancel') {\n return false;\n }\n _startQuiz();\n });\n } else {\n _startQuiz();\n }\n }\n retryQuiz() {\n Ember.set(this.quiz, 'quiz_attempt', {\n retrying: true,\n left: this.quiz.get('quiz_attempt.left')\n });\n this.setQuestionIndex(0);\n this.maxQuestionIndexUsed = 0;\n this.resetGivenAnswers();\n this.quiz.quiz_questions.forEach(question => {\n question.set('attempt_data', {});\n });\n this.hasShownQuizIntercept = false;\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').scrollTop(0)\n /* eslint-disable-next-line ember/no-jquery */.scrollTop(Ember.$(`#${this.get('quiz.id')}`).position().top - 10);\n }\n closeTimeRemaining() {\n this.quizSubmittedInText = undefined;\n }\n async submitQuizAttempt(data) {\n const submit = async () => {\n try {\n const section = this.get('session.section.id');\n this.store.nestResources('attempt', [{\n section\n }, {\n quiz: this.quiz.get('id')\n }]);\n let resp = await this.store.findRecord('attempt', data.id);\n let answers = await this.getQuizAnswers();\n resp.set('token', data.token);\n resp.set('number', data.number);\n resp.set('answers', answers);\n resp.set('state', 'complete');\n if (this.isTimedQuiz) {\n resp.set('action', 'submit');\n } else {\n resp.set('action', 'update_questions_and_submit');\n }\n return await resp.save();\n } catch (error) {\n console.log(`error: ${JSON.stringify(error.message)}`);\n const response = await (0, _dialog.default)('A problem occurred grading the quiz. Try again?', ['OK', 'Cancel']);\n if (response == 'OK') {\n await submit();\n }\n return null;\n }\n };\n return await submit();\n }\n async _submitQuiz() {\n if (!this.isTimedQuiz) {\n let unansweredQuestions = [];\n this.get('quiz.quiz_questions').forEach((question, index) => {\n if (!question.hasAnswer) {\n unansweredQuestions.push(index + 1);\n }\n });\n if (unansweredQuestions.length) {\n (0, _dialog.default)(`Question${unansweredQuestions.length > 1 ? 's' : ''} ${unansweredQuestions.join(', ')} ${unansweredQuestions.length == 1 ? 'is' : 'are'} unanswered.`);\n return;\n }\n }\n\n /* eslint-disable-next-line ember/no-get */\n var section = this.get('session.section.id');\n\n /* eslint-disable-next-line ember/no-jquery */\n // if ($('#' + quiz + ' .quiz-questions' + ' input[type=checkbox]:not(\\'.question-checker-handle\\'):not(\\'.question-handle\\'):checked, #' + quiz + ' .quiz-questions' + ' input[type=radio]:not(\\'.question-handle\\'):checked').length === 0) {\n // return;\n // }\n\n let dialogPromise;\n /* eslint-disable-next-line ember/no-get */\n if (this.get('quiz.allowed_attempts') === 1) {\n dialogPromise = (0, _dialog.default)('This is your last attempt. Are you sure you want to submit? TIP: Make sure to double-check your answers!', ['Submit', 'No']);\n } else {\n dialogPromise = Promise.resolve('Submit');\n }\n let choice = await dialogPromise;\n if (choice === 'Submit') {\n /* eslint-disable-next-line ember/no-jquery */\n // $('#' + quiz + ' .question-handle:last').prop('checked', true);\n // /* eslint-disable-next-line ember/no-jquery */\n\n Ember.debug('Requesting token...');\n /* eslint-disable-next-line ember/no-jquery */\n const self = this;\n if (this.isTimedQuiz) {\n return await this.submitQuizAttempt(this.timedQuizSubmissionData);\n } else {\n const submit = async () => {\n return new Promise((resolve, reject) => {\n Ember.$.ajax({\n url: '/interface/sections/' + section + '/quizzes/' + this.quiz.get('id') + '/attempts',\n type: \"POST\",\n data: {},\n dataType: \"json\",\n success: async data => {\n resolve(await this.submitQuizAttempt(data));\n },\n error: async err => {\n self.gradingQuiz = false;\n Ember.debug(err, 'Post quiz');\n const response = await (0, _dialog.default)('Unable to grade quiz. Try again?', ['OK', 'Cancel']);\n if (response == 'OK') {\n self.gradingQuiz = true;\n await submit();\n } else {\n resolve(false);\n }\n }\n });\n });\n };\n return await submit();\n }\n }\n }\n async submitQuiz(stopTimers = true) {\n this.gradingQuiz = true;\n if (stopTimers) {\n this.stopTimers();\n }\n await this.executeNextQuestionCallbacks();\n const scrollIntoViewIfNeeded = target => {\n // Target is outside the viewport from the bottom\n if (target.getBoundingClientRect().bottom > window.innerHeight) {\n // The bottom of the target will be aligned to the bottom of the visible area of the scrollable ancestor.\n target.scrollIntoView(false);\n }\n\n // Target is outside the view from the top\n if (target.getBoundingClientRect().top < 0) {\n // The top of the target will be aligned to the top of the visible area of the scrollable ancestor\n target.scrollIntoView();\n }\n };\n let finishQuizElem = document.querySelector(`#quiz-${this.quiz.id}-body .question-next-or-finish`);\n if (finishQuizElem) {\n scrollIntoViewIfNeeded(finishQuizElem);\n }\n const submit = async () => {\n try {\n let result = await this._submitQuiz();\n this.gradingQuiz = false;\n if (result) {\n this.set('quiz.quiz_attempt', result);\n this.set('quiz.todo', false);\n this.get('quiz').reload();\n if (this.questionIndex != null) {\n this.setQuestionIndex(null);\n }\n this.setQuestionData(this.get('quiz.quiz_attempt.answers'), false);\n this.quizStarted = false;\n\n // Gainsight\n let course = this.session.course,\n user = this.session.user,\n type = 'Quiz';\n this.gainsight.renderWorkGainsightPxTag(user.get('id'), course.get('id'), type, new Date());\n }\n } catch (e) {\n this.gradingQuiz = false;\n const response = await (0, _dialog.default)('Problem occurred grading quiz. Try again?', ['OK', 'Cancel']);\n if (response == 'OK') {\n await submit();\n }\n }\n };\n await submit();\n this.savedAt = null;\n this.timeRemaining = null;\n }\n }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, \"quiz\", [_dec], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, \"questionIndex\", [_dec2], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, \"gradingQuiz\", [_dec3], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, \"questionShown\", [_dec4], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, \"maxQuestionIndexUsed\", [_dec5], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, \"hasShownQuizIntercept\", [_dec6], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, \"timeRemaining\", [_dec7], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor8 = _applyDecoratedDescriptor(_class.prototype, \"quizSubmittedInText\", [_dec8], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: null\n }), _descriptor9 = _applyDecoratedDescriptor(_class.prototype, \"startingQuiz\", [_dec9], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor10 = _applyDecoratedDescriptor(_class.prototype, \"questionPromises\", [_dec10], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return [];\n }\n }), _descriptor11 = _applyDecoratedDescriptor(_class.prototype, \"savedAt\", [_dec11], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return null;\n }\n }), _descriptor12 = _applyDecoratedDescriptor(_class.prototype, \"initialized\", [_dec12], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor13 = _applyDecoratedDescriptor(_class.prototype, \"uploadingQuestion\", [_dec13], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor14 = _applyDecoratedDescriptor(_class.prototype, \"uploadFailed\", [_dec14], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return false;\n }\n }), _descriptor15 = _applyDecoratedDescriptor(_class.prototype, \"showStats\", [_dec15], {\n configurable: true,\n enumerable: true,\n writable: true,\n initializer: function () {\n return true;\n }\n }), _applyDecoratedDescriptor(_class.prototype, \"toggleShowStats\", [_dec16], Object.getOwnPropertyDescriptor(_class.prototype, \"toggleShowStats\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"nextQuestion\", [_dec17], Object.getOwnPropertyDescriptor(_class.prototype, \"nextQuestion\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"setQuestionIndex\", [_dec18], Object.getOwnPropertyDescriptor(_class.prototype, \"setQuestionIndex\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"startQuiz\", [_dec19], Object.getOwnPropertyDescriptor(_class.prototype, \"startQuiz\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"retryQuiz\", [_dec20], Object.getOwnPropertyDescriptor(_class.prototype, \"retryQuiz\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"closeTimeRemaining\", [_dec21], Object.getOwnPropertyDescriptor(_class.prototype, \"closeTimeRemaining\"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, \"submitQuiz\", [_dec22], Object.getOwnPropertyDescriptor(_class.prototype, \"submitQuiz\"), _class.prototype)), _class));\n});","define(\"bocce/js/store-util\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.pushDeletion = pushDeletion;\n //Taken from: https://gist.github.com/runspired/96618af26fb1c687a74eb30bf15e58b6/#file-push-deletion-js\n function pushDeletion(store, type, id) {\n let record = store.peekRecord(type, id);\n if (record !== null) {\n let relationships = {};\n let hasRelationships = false;\n record.eachRelationship((name, {\n kind\n }) => {\n hasRelationships = true;\n relationships[name] = {\n data: kind === 'hasMany' ? [] : null\n };\n });\n if (hasRelationships) {\n store.push({\n data: {\n type,\n id,\n relationships\n }\n });\n }\n record.unloadRecord();\n }\n }\n});","define(\"bocce/js/utils\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.saveFile = saveFile;\n _exports.scoreToLetter = scoreToLetter;\n _exports.xmlToJson = void 0;\n //Taken from https://stackoverflow.com/a/36899900\n function saveFile(name, type, data) {\n if (data !== null && navigator.msSaveBlob) return navigator.msSaveBlob(new Blob([data], {\n type: type\n }), name);\n var a = $(\"\");\n var url = window.URL.createObjectURL(new Blob([data], {\n type: type\n }));\n a.attr(\"href\", url);\n a.attr(\"download\", name);\n $(\"body\").append(a);\n a[0].click();\n window.URL.revokeObjectURL(url);\n a.remove();\n }\n const xmlToJson = _exports.xmlToJson = function xmlToJson(xml) {\n let obj = {};\n if (xml.nodeType == 1) {\n // element\n if (xml.attributes.length > 0) {\n obj['@attributes'] = {};\n for (let j = 0; j < xml.attributes.length; j++) {\n let attribute = xml.attributes.item(j);\n obj['@attributes'][attribute.nodeName] = attribute.nodeValue;\n }\n }\n } else if (xml.nodeType == 3) {\n // text\n obj = xml.nodeValue;\n }\n\n // do children\n if (xml.hasChildNodes()) {\n let children = xml.childNodes;\n if (children.length === 1 && children[0].nodeName === '#text') {\n obj = children[0].nodeValue;\n } else {\n for (let i = 0; i < children.length; i++) {\n let item = children.item(i);\n let nodeName = item.nodeName;\n if (typeof obj[nodeName] === 'undefined') {\n obj[nodeName] = xmlToJson(item); // Call the function directly\n } else {\n if (!Array.isArray(obj[nodeName])) {\n obj[nodeName] = [obj[nodeName]];\n }\n obj[nodeName].push(xmlToJson(item)); // Call the function directly\n }\n }\n }\n }\n return obj;\n };\n function scoreToLetter(score) {\n let grade = '';\n if (score >= 93) {\n grade = 'A';\n } else if (score >= 90) {\n grade = 'A-';\n } else if (score >= 87) {\n grade = 'B+';\n } else if (score >= 83) {\n grade = 'B';\n } else if (score >= 80) {\n grade = 'B-';\n } else if (score >= 77) {\n grade = 'C+';\n } else if (score >= 73) {\n grade = 'C';\n } else if (score >= 70) {\n grade = 'C-';\n } else if (score >= 60) {\n grade = 'D';\n } else {\n grade = 'F';\n }\n return grade;\n }\n});","define(\"bocce/mirage/config\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = _default;\n function _default() {\n // These comments are here to help you get started. Feel free to delete them.\n\n /*\n Config (with defaults).\n Note: these only affect routes defined *after* them!\n */\n\n // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server\n // this.namespace = ''; // make this `/api`, for example, if your API is namespaced\n // this.timing = 400; // delay for each request, automatically set to 0 during testing\n\n /*\n Shorthand cheatsheet:\n this.get('/posts');\n this.post('/posts');\n this.get('/posts/:id');\n this.put('/posts/:id'); // or this.patch\n this.del('/posts/:id');\n https://www.ember-cli-mirage.com/docs/route-handlers/shorthands\n */\n\n // This passthrough is needed for the ember-cli-code-coverage addon to work properly;\n // see the docs for details:\n //\n // https://www.npmjs.com/package/ember-cli-code-coverage#create-a-passthrough-when-intercepting-all-ajax-requests-in-tests\n this.passthrough('/write-coverage');\n this.namespace = 'interface';\n this.get('/sessions/:id');\n this.get('/users/:id');\n this.get('/courses/:id');\n this.get('/sections/:id');\n this.get('/courses/:course_id/sections/:section_id', (schema, request) => {\n return schema.sections.find(request.params.section_id);\n });\n this.get('/courses/:course_id/gradebooks', (schema, request) => {\n let course = schema.courses.find(request.params.course_id);\n return {\n gradebook: course.gradebooks\n };\n });\n this.get('/courses/:course_id/lessons/:lesson_id', (schema, request) => {\n let course = schema.courses.find(request.params.course_id);\n let lessons = course.lessons;\n let lesson = lessons.filter(l => l.id === request.params.lesson_id).models[0];\n let items = lesson.items.models;\n return {\n lesson,\n item: items\n };\n });\n this.get('/sections/:section_id/discussions', (schema, request) => {\n let discussion = schema.sections.find(request.params.section_id).discussions;\n return discussion;\n });\n this.get('/sections/:id/assignments', (schema, request) => {\n return schema.sections.find(request.params.id).assignments;\n });\n this.get('/sections/:sec_id/assignments/:assign_id', (schema, request) => {\n return schema.assignments.find(request.params.assign_id);\n });\n // TODO: get things working so that this route isn't necessary. The interface\n // doesn't have a real endpoint that matches this, but our current method of\n // setting up nested resources is unreliable in the test environment. This\n // catches certain cases where submissions don't nest correctly during\n // acceptance test.\n this.get('/submissions/:submission_id', (schema, request) => {\n return schema.submissions.find(request.params.submission_id);\n });\n this.get('/sections/:sec_id/assignments/:assign_id/submissions/:sub_id', (schema, request) => {\n return schema.submissions.find(request.params.sub_id);\n });\n this.get('/sections/:id/quizzes', (schema, request) => {\n return schema.sections.find(request.params.id).quizzes;\n });\n this.get('/courses/:course_id/lessons/:lesson_id/items/:item_id', (schema, request) => {\n let course = schema.courses.find(request.params.id);\n let lesson = course.lessons.filter(l => l.id === request.params.lesson_id).models[0];\n let item = lesson.items.filter(i => i.id === request.params.item_id).models[0];\n return item;\n });\n this.put('/courses/:course_id/lessons/:lesson_id/items/:item_id', () => {\n return {};\n });\n this.put('/users/:id');\n this.get('/dashboards');\n }\n});","define(\"bocce/mirage/factories/assignment\", [\"exports\", \"ember-cli-mirage\"], function (_exports, _emberCliMirage) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _emberCliMirage.Factory.extend({\n submission_requirements() {\n return {\n text: true\n };\n }\n });\n});","define(\"bocce/mirage/factories/course\", [\"exports\", \"ember-cli-mirage\"], function (_exports, _emberCliMirage) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _emberCliMirage.Factory.extend({\n code(i) {\n return `course-${i}`;\n },\n afterCreate(course, server) {\n course.update({\n sections: server.createList('section', 1),\n lessons: server.createList('lesson', 1)\n });\n }\n });\n});","define(\"bocce/mirage/factories/lesson\", [\"exports\", \"ember-cli-mirage\"], function (_exports, _emberCliMirage) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _emberCliMirage.Factory.extend({\n afterCreate(lesson, server) {\n lesson.update({\n items: server.createList('item', 1)\n });\n }\n });\n});","define(\"bocce/mirage/factories/user\", [\"exports\", \"ember-cli-mirage\"], function (_exports, _emberCliMirage) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _emberCliMirage.Factory.extend({});\n});","define(\"bocce/mirage/scenarios/default\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = _default;\n function _default(server) {\n // Create the test course\n let course = server.create('course');\n let section = server.create('section', {\n course\n });\n server.create('discussion', {\n section\n });\n server.create('assignment', {\n section\n });\n server.create('quiz', {\n section\n });\n let lesson = server.create('lesson', {\n course\n });\n let item = server.create('item', {\n lesson,\n title: 'something',\n type: 'Page'\n });\n server.create('page', {\n item,\n title: item.title,\n body: 'bla bla bla bla'\n });\n\n // NK: I'm not sure why this particular relationship needs to be\n // explicitly defined both ways, but it does I guess.\n course.lessons.add(lesson);\n course.save();\n\n // Create a user with 'lastviewed' data\n let lastviewed = {};\n lastviewed[course.id] = {\n lesson: lesson.id,\n item_id: item.id\n };\n let user = server.create('user', {\n lastviewed\n });\n\n // Create the one session to rule them all\n server.create('session', {\n user\n });\n }\n});","define(\"bocce/mirage/scenarios/test-assignment\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = _default;\n function _default(server) {\n let user = server.create('user', {\n lastviewed: {}\n });\n server.create('session', {\n user\n });\n let course = server.create('course');\n let section = course.sections.models[0];\n server.create('assignment', {\n course,\n section\n });\n }\n});","define(\"bocce/mirage/serializers/application\", [\"exports\", \"ember-cli-mirage\"], function (_exports, _emberCliMirage) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _emberCliMirage.RestSerializer.extend({});\n});","define(\"bocce/mirage/serializers/course\", [\"exports\", \"bocce/mirage/serializers/application\"], function (_exports, _application) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = _exports.default = _application.default.extend({});\n});","define(\"bocce/mixins/assignments\", [\"exports\", \"bocce/mixins/audio-rec\", \"bocce/mixins/video-rec\", \"bocce/mixins/rtc-rec\", \"bocce/mixins/boot\", \"bocce/mixins/showcase\", \"bocce/utilities/dialog\", \"bocce/js/utils\"], function (_exports, _audioRec, _videoRec, _rtcRec, _boot, _showcase, _dialog, _utils) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create(_audioRec.default, _videoRec.default, _rtcRec.default, _showcase.default, _boot.default, {\n init(...args) {\n this._super(...args);\n this.currentTimeMetronome();\n },\n bootContent() {\n /* eslint-disable-next-line ember/no-jquery */\n let node = Ember.$('.timed-assignment-instructions');\n if (node && node.length > 0) {\n this.boot_area(node, false, true, false, false, true);\n }\n },\n grades: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F'],\n rubric: Ember.inject.service(),\n curgrade: false,\n // NK: I'm making this an observer for now, because this function\n // introduces side effects, which computed properties should not do.\n /* eslint-disable-next-line ember/no-observers */\n assignmentTimerObserver: Ember.observer('activeAssignment.{timed_assignment_data,timed_assignment,timed_assignment.startTime,timed_assignment.endTime}', 'currentTimePulse', function () {\n //This gets called sometimes right after 'activeAssignment' was set to false. \n //We don't want to do anything in this case.\n if (!this.get('activeAssignment')) {\n return;\n }\n\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('activeAssignment.timed_assignment') || !this.instructionsBooted) {\n // Force timed assignment reload\n this.activeAssignment.reload().then(() => {\n /* eslint-disable-next-line ember/no-incorrect-calls-with-inline-anonymous-functions */\n Ember.run.scheduleOnce('afterRender', this, function () {\n this.bootContent();\n this.instructionsBooted = true;\n });\n });\n }\n\n /* eslint-disable-next-line ember/no-get */\n let endTime = this.get('activeAssignment.timed_assignment.endTime');\n if (!endTime) {\n this.set('lockdown', false);\n this.set('videoRec', false);\n this.set('assignmentTimer', false);\n return;\n }\n let ms = new Date(endTime).getTime() - new Date().getTime();\n\n /* eslint-disable-next-line ember/no-get */\n let noTimer = this.get('activeAssignment.timed_assignment.no_timer');\n if (!this.get('lockdown') && ms < 0 && !noTimer) {\n this.set('lockdown', true);\n }\n if (!noTimer) {\n // Sync every 60 sec, to let the interface know the user is still active\n if (!this.syncInterval || this.syncInterval > 60) {\n this.set('syncInterval', 1);\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n /* eslint-disable-next-line ember/no-get */\n url: `/interface/sections/${this.get('session.section.id')}/assignments/${this.get('activeAssignment.id')}/timer/sync_timer/`,\n dataType: 'text',\n success: () => {\n Ember.debug('Timer Synced');\n },\n error: error => {\n Ember.debug('Unable to sync timer. Something went wrong: ');\n Ember.debug(error);\n }\n });\n } else {\n this.set('syncInterval', this.syncInterval + 1);\n }\n if (!this.timerOn) {\n this.set('timerOn', true);\n this.currentTimeMetronome();\n }\n }\n if (!noTimer) {\n let seconds = Math.abs(ms / 1000);\n let hours = parseInt(seconds / 3600); // 3,600 seconds in 1 hour\n seconds = seconds % 3600;\n let minutes = parseInt(seconds / 60);\n hours = hours > 0 ? hours + ':' : '';\n if (minutes < 10 && hours > 1) {\n minutes = '0' + minutes;\n }\n minutes = minutes + ':';\n seconds = Math.floor(seconds % 3600 % 60);\n if (seconds < 10) {\n seconds = '0' + seconds;\n }\n this.set('assignmentTimer', hours + minutes + seconds);\n return;\n }\n }),\n currentTimeMetronome: function () {\n var interval = 1000;\n if (this.timerOn) {\n Ember.run.later(this, function () {\n this.notifyPropertyChange('currentTimePulse');\n this.currentTimeMetronome();\n }, interval);\n }\n },\n requirementsMet: Ember.computed('activeAssignment', 'files.@each.isDone', 'postable', function () {\n return this.postable;\n }),\n /* eslint-disable-next-line ember/no-observers */\n recalcGrade: Ember.observer('openAssignmentView', 'openAssignmentView.score', function () {\n /* eslint-disable-next-line ember/no-get */\n let score = parseFloat(this.get('openAssignmentView.score'));\n if (event && event.target && event.target.getAttribute && event.target.getAttribute('type') === 'text') {\n const scoreGrade = (0, _utils.scoreToLetter)(score);\n this.set('openAssignment.score', score);\n this.set('currentGrade', score);\n this.set('openAssignmentView.grade', scoreGrade);\n }\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n canResubmit: Ember.computed('openAssignmentView', function () {\n /* eslint-disable-next-line ember/no-get */\n if (!this.classroom || !this.get('classroom.model.section.teachers').findBy('profile')) {\n return true;\n }\n\n /* eslint-disable-next-line ember/no-get */\n if (!this.get('classroom.model.section.teachers').findBy('profile').get('profile.can_resubmit')) {\n return true;\n }\n\n /* eslint-disable-next-line ember/no-get */\n return this.get('classroom.model.section.teachers').findBy('profile').get('profile.can_resubmit')[this.get('session.section.id')] !== 'no';\n }),\n // bind attachment model data to each element of an array\n bindAttachmentObjects(dataArray) {\n dataArray = dataArray ? dataArray.toArray() : [];\n for (let i = 0; i < dataArray.length; ++i) {\n let item = dataArray[i];\n // Use Ember.set and Ember.get because it's not always an EmberObject\n Ember.set(item, 'attachment_objects', []);\n if (item.attachments) {\n for (let a = 0; a < item.attachments.length; ++a) {\n let file = item.attachments[a];\n if (typeof file === 'object' && file.url) {\n Ember.set(file, 'name', file.name || file.display_name || file.filename);\n Ember.set(file, 'type', file.type || item['content-type']);\n Ember.get(item, 'attachment_objects').push(file);\n } else {\n let id = file.id || file;\n let model = this.store.findRecord('attachment', id);\n Ember.get(item, 'attachment_objects').push(model);\n }\n }\n }\n }\n },\n submissions: Ember.computed('openAssignmentView.submission_history', function () {\n let submissions = this.get('openAssignmentView.submission_history');\n if (submissions) {\n this.bindAttachmentObjects(submissions);\n return submissions;\n } else {\n return false;\n }\n }),\n // All submissions prior to the newest submission\n submissionHistory: Ember.computed('openAssignmentView.assignment.points_possible', 'submissions.[]', function () {\n var hist = this.submissions ? this.submissions.toArray().slice(0, -1) : false;\n if (!hist) {\n return false;\n }\n hist.forEach(h => {\n h.percentage = Math.round(100 * h.score / this.get('openAssignmentView.assignment.points_possible'));\n });\n return hist.reverse();\n }),\n lastSubmission: Ember.computed('submissionHistory', function () {\n let subs = this.get('submissionHistory');\n if (subs) {\n return subs[subs.length - 1];\n } else {\n return false;\n }\n }),\n // All submissions after the first submission\n resubmissions: Ember.computed('submissions.[]', function () {\n const submissions = this.submissions;\n\n // remove the first submission; we only want REsubmissions:\n const resubmissions = submissions.slice(1);\n\n // Add an index to each item, to be used to scroll to\n // different assignment submission versions\n resubmissions.forEach((submission, i) => submission.counter = i);\n return resubmissions;\n }),\n comments: Ember.computed('openAssignmentView.comments.@each.{attachments}', function () {\n /* eslint-disable-next-line ember/no-get */\n const comments = this.get('openAssignmentView.comments') || [];\n // TODO (NK): unroll this so that getting attachments isn't done\n // inside the getter. Maybe in the route?\n this.bindAttachmentObjects(comments);\n return comments;\n }),\n // A sorted array of comments and resubmissions.\n commentTimeline: Ember.computed('comments.@each.{is_deleted}', 'resubmissions.[]', function () {\n const comments = this.comments.toArray().filter(c => !c.is_deleted);\n const history = this.resubmissions.toArray();\n\n // Combine the two arrays\n const stream = comments.concat(history);\n\n // comments and submissions have different names for their\n // \"created\" dates. This gives us a unified sort date.\n stream.forEach(item => {\n // Uses Ember.get and Ember.set instead of item.get because not\n // all items are EmberObjects\n const date = Ember.get(item, 'created_at') || Ember.get(item, 'submitted_at');\n item.sortDate = moment(date);\n });\n stream.sort((a, b) => {\n // Uses Ember.get instead of item.get because not all items\n // are EmberObjects\n const aDate = Ember.get(a, 'sortDate');\n const bDate = Ember.get(b, 'sortDate');\n return aDate - bDate;\n });\n return stream;\n }),\n next_submission: Ember.computed('openAssignmentView', function () {\n if (!this.openAssignmentView || !this.openAssignmentView.get('data.assignment.submissions.content.content')) {\n return;\n }\n var current = this.openAssignmentView.get('data.assignment.submissions.content.content'),\n next_sub = current[current.indexOf(this.openAssignmentView) + 1] || false,\n next = next_sub.id || false;\n return next;\n }),\n prev_submission: Ember.computed('openAssignmentView', function () {\n if (!this.openAssignmentView || !this.openAssignmentView.get('data.assignment.submissions.content.content')) {\n return;\n }\n var current = this.openAssignmentView.get('data.assignment.submissions.content.content'),\n prev_sub = current[current.indexOf(this.openAssignmentView) - 1] || false,\n prev = prev_sub.id || false;\n return prev;\n }),\n openAssignmentModal: Ember.computed('activeAssignment', function () {\n return !!this.activeAssignment;\n }),\n // Router Business\n /* eslint-disable-next-line ember/no-observers */\n toggleAssignmentModal: Ember.observer('openAssignmentView', 'activeAssignment', function () {\n var assignment = this.openAssignmentView,\n activeAssignment = this.activeAssignment,\n openAssignment = this.openAssignment;\n if (assignment && openAssignment) {\n if (assignment.get('assignment').get('id') !== openAssignment.get('id')) {\n openAssignment = false;\n }\n }\n if (!!activeAssignment || !!assignment) {\n // \"assignment\" here is actually the submission we're viewing...\n if (assignment) {\n if (!openAssignment) {\n this.set('author', assignment.get('data.user.short_name'));\n this.set('openAssignment', assignment.get('assignment'));\n this.store.nestResources('comment', [{\n course: assignment.get('data.course.id')\n }, {\n assignment: assignment.get('data.id')\n }, {\n submission: assignment.get('data.id')\n }]);\n }\n }\n /* eslint-disable-next-line ember/no-incorrect-calls-with-inline-anonymous-functions */\n Ember.run.scheduleOnce('afterRender', this, function () {\n if (window.CameraTag) {\n window.CameraTag.setup();\n }\n });\n } else if (!activeAssignment && !assignment) {\n this.send('destroyEditor');\n }\n this.send('clearAllFiles');\n }),\n // \"user\" and \"section\" are referenced in ShowcaseMixin.\n user: Ember.computed.reads('session.user'),\n section: Ember.computed.reads('session.section'),\n postSubmissionGrade: function (grade, onSuccessCallback, onFailureCallback) {\n let sub = this.openAssignmentView,\n that = this;\n this.store.nestResources('submission', [{\n section: that.get('session.section.id')\n }, {\n assignment: sub.get('assignment.id')\n }]);\n var old_grade = sub.get('grade');\n if (grade === 'n') {\n grade = null;\n sub.set('posted_grade', '');\n sub.set('workflow_state', 'submitted');\n } else if (grade === 'r') {\n grade = null;\n sub.set('posted_grade', null);\n sub.set('teacher_requires_resubmission', 'needs_resubmit');\n\n // Purge timer for assignment\n Ember.$.ajax({\n type: 'POST',\n url: `/interface/sections/${that.get('session.section.id')}/assignments/${sub.get('assignment.id')}/user/${sub.get('user.id')}/purge_assignment/`,\n data: {},\n dataType: 'text',\n success: () => {\n Ember.debug('Assignments purged successfully');\n },\n error: error => {\n Ember.debug('Unable to purge assignments. Something went wrong: ');\n Ember.debug(error);\n }\n });\n } else {\n sub.set('rubric_assessment_objects', sub.get('rubric_assessments').toArray().map(a => ({\n criteria_id: a.id.split('_')[2],\n points: a.points,\n comments: a.comments\n })));\n sub.set('posted_grade', grade == 'x' ? 0 : grade);\n sub.set('workflow_state', 'graded');\n sub.set('teacher_requires_resubmission', 'submitted');\n }\n sub.save().then(sub_with_score => {\n // Success\n\n if (grade !== old_grade) {\n // See if we need to update the gradebook\n var gbcol = this.store.peekRecord('gradebook', sub.get('user.id')),\n assn_id = parseInt(sub.get('assignment.id'), 10);\n if (gbcol) {\n var ndx = gbcol.get('columns').findIndex(function (m) {\n return m && parseInt(m.assignment_id, 10) === assn_id;\n });\n if (ndx >= 0) {\n gbcol.set('columns.' + ndx + '.grade', grade);\n gbcol.set('columns.' + ndx + '.score', sub_with_score.get('score'));\n }\n }\n }\n onSuccessCallback?.call(this);\n }, error => {\n // Failure\n that.send('switchSubmissionGrade', grade);\n Ember.debug('Failure');\n Ember.debug(error);\n (0, _dialog.default)('Error grading assignment.');\n onFailureCallback?.call(this);\n });\n if (+grade >= 0) {\n grade = (0, _utils.scoreToLetter)(Math.round(100 * grade / sub.get('assignment.points_possible')));\n }\n sub.set('grade', grade);\n sub.set('archive_body', false);\n },\n actions: {\n assignmentSubmission: function (assignment_id) {\n this.transitionToRoute('classroom.lessons.submission-new', assignment_id);\n },\n downloadAllSubmissions: function () {\n /* eslint-disable-next-line ember/no-get */\n let url = '/courses/' + this.get('session.course.id') + '/assignments/' + this.get('openAssignmentView.assignment.id').split('-')[0] + '/submissions?zip=1';\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.bulk_dl_message_status').text('Creating zip file');\n let requestedCompile = false,\n checkZip = () => {\n let req_url = url;\n if (!requestedCompile) {\n req_url += '&compile=1';\n requestedCompile = true;\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n url: req_url,\n dataType: 'json',\n success: data => {\n if (data && data.attachment) {\n if (data.attachment.workflow_state === 'zipped') {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.bulk_dl_message_status').text('');\n window.open(url);\n return;\n }\n if (data.attachment.workflow_state.indexOf('error') >= 0) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.bulk_dl_message_status').text('Failed');\n\n /* eslint-disable-next-line ember/no-jquery */\n setTimeout(() => Ember.$('.bulk_dl_message_status').text(''), 2000);\n return;\n }\n }\n setTimeout(checkZip, 250);\n },\n error: () => {\n setTimeout(checkZip, 250);\n }\n });\n };\n checkZip();\n },\n markSubmissionRead: function () {\n let submission = this.openAssignmentView;\n if (!submission) return;\n\n // Set this locally, but use the route to save (to avoid\n // saving becoming complicated and having to guess our\n // desired operation)\n if (!submission.get('read')) {\n submission.set('read', true);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.get(`/interface/sections/${this.session.get('section.id')}/assignments/${submission.get('assignment.id')}/submissions/${submission.get('id')}/read`);\n }\n },\n teardown() {\n this.send('markSubmissionRead');\n },\n viewAssignmentSubmission: function (submission_id) {\n const transition = () => {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.drawer-instructions').attr('checked', false);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#current-student-handle').attr('checked', false);\n this.set('showcaseOpen', false);\n this.set('assetsRecovered', true);\n\n // Mark the submission comments as read for the submission that we're _leaving_\n this.send('markSubmissionRead');\n this.transitionToRoute('classroom.lessons.submission', submission_id);\n };\n if (this.isRubricAssessmentDirty?.()) {\n (0, _dialog.default)('Are you sure you want to switch submissions? Your changes will be lost.', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n transition();\n }\n });\n } else {\n transition();\n }\n },\n deleteSubmissionBody: function () {\n (0, _dialog.default)('Are you sure you want to delete the submission body? This will also mark the submission as needing resubmission.', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n this.openAssignmentView.set('archive_body', true);\n this.postSubmissionGrade('r');\n }\n });\n },\n viewInLesson: function (assignment_id) {\n var that = this;\n /* eslint-disable-next-line ember/no-get */\n this.store.findRecord('item', this.get('activeAssignment.id')).then(function (item) {\n var lessonId = item.get('lesson').id;\n if (lessonId) {\n that.transitionToRoute('classroom.lessons', lessonId, assignment_id);\n }\n }, function () {\n Ember.debug('Can\\'t find lesson that contains assignment.');\n });\n },\n startTimer: function () {\n this.set('timerStarting', true);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'POST',\n /* eslint-disable-next-line ember/no-get */\n url: `/interface/sections/${this.get('session.section.id')}/assignments/${this.get('activeAssignment.id')}/timer/start_timer/`,\n data: {},\n dataType: 'text',\n success: () => {\n this.set('timerStarting', false);\n this.set('timerOn', true);\n this.set('lockdown', false);\n this.set('videoRec', false);\n this.activeAssignment.reload().then(() => {\n /* eslint-disable-next-line ember/no-get */\n let endTime = this.get('activeAssignment.timed_assignment.endTime');\n\n // Timer duraction calculated incorrectly - attempt to restart\n if (!endTime) {\n this.send('startTimer');\n return;\n }\n\n // Timer duration started at lesss than 0 seconds - attempt to restart\n let ms = new Date(endTime).getTime() - new Date().getTime();\n if (ms < 0) {\n this.send('startTimer');\n return;\n }\n this.currentTimeMetronome();\n\n /* eslint-disable-next-line ember/no-incorrect-calls-with-inline-anonymous-functions */\n Ember.run.later(this, function () {\n this.bootContent();\n }, 2000);\n });\n Ember.debug('Timed assignment started sucessfully');\n },\n error: error => {\n Ember.debug('Unable to start timed assignment. Something went wrong: ');\n Ember.debug(error);\n }\n });\n },\n goToAssignment: function (assignment_id) {\n let commentBody = this.bodyInput;\n let confirm;\n if (commentBody.length > 0) {\n confirm = (0, _dialog.default)('Are you sure you want to switch submissions? You will lose all unsaved data.', ['Yes', 'No']);\n } else {\n confirm = Promise.resolve('Yes');\n }\n confirm.then(choice => {\n if (choice === 'Yes') {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.drawer-select').attr('checked', false);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.submission-container').scrollTop(0);\n this.lessons.set('activeAssignment', false);\n this.lessons.set('openAssignment', false);\n this.lessons.set('openAssignmentView', false);\n this.send('treatAssignment', assignment_id);\n }\n });\n },\n gradeAssignmentSubmission: function (submission_url) {\n if (submission_url) {\n window.open(submission_url, '_blank');\n }\n },\n submitAssignment: function (section_id, assignment_id) {\n var content = this.bodyInput,\n sub,\n that = this;\n\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$.trim(content.replace(/ /g, ' ')).length === 0) {\n // Text is all whitespace. Don't post.\n return;\n }\n this.store.nestResources('submission', [{\n section: section_id\n }, {\n assignment: assignment_id\n }]);\n this.set('uploadInProgress', true);\n\n /* eslint-disable-next-line ember/no-get */\n sub = this.get('activeAssignment.data.submission');\n if (!sub) {\n sub = this.activeAssignment.store.createRecord('submission');\n }\n if (!this.working && this.file_ids.length > 0) {\n sub.set('file_ids', this.file_ids);\n sub.set('submission_type', 'online_upload');\n } else {\n sub.set('submission_type', 'online_text_entry');\n }\n\n // Add any video embeds\n content = this.parseEmbed(content);\n content = content + this.videoEmbedString;\n sub.set('teacher_requires_resubmission', 'submitted');\n sub.set('body', content);\n\n // The submission ID is derived from the assignment/user, so if\n // this is a resubmission then saving a new submission will give\n // us two models with identical IDs. To account for that, unload\n // all submissions for the current object before saving\n /* eslint-disable-next-line ember/no-get */\n let user_id = this.get('session.user.id');\n /* eslint-disable-next-line ember/no-get */\n let submissions = this.get('model.submissions');\n let my_submissions = submissions.filter(s => s.get('user.id') === user_id);\n my_submissions.forEach(s => s.unloadRecord());\n sub.save().then(function (sub) {\n that.set('timerOn', false);\n that.activeAssignment.reload();\n that.set('uploadInProgress', false);\n that.set('activeAssignment.did_submit', true);\n that.set('activeAssignment.needsStudentAttention', false);\n that.send('clearAllFiles');\n that.set('inEditor', false);\n that.set('bodyInput', '');\n that.send('destroyEditor');\n that.send('viewAssignmentSubmission', sub.id);\n\n // Gainsight\n let course = that.session.course,\n user = that.session.user,\n type = 'Assignment';\n that.gainsight.renderWorkGainsightPxTag(user.get('id'), course.get('id'), type, new Date());\n }, err => {\n (0, _dialog.default)('There was a problem submitting your assignment. Please retry in a moment.');\n Ember.debug(content);\n Ember.debug(this.bodyInput);\n Ember.debug(err);\n that.set('uploadInProgress', false);\n });\n },\n switchSubmissionGrade: function (grade) {\n let gradeScoreRubric = {\n 'A': 100,\n 'A-': 92,\n 'B+': 89,\n 'B': 86,\n 'B-': 82,\n 'C+': 79,\n 'C': 76,\n 'C-': 72,\n 'D': 69,\n 'F': 0,\n 'R': 0\n };\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.quickgrader input').removeClass('checked');\n if (grade) {\n // TODO: rewrite this feature so that we don't imperatively change the\n // selection class here. This should be doable without the runloop.\n Ember.run.schedule('afterRender', function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.quickgrader input[value=\"' + grade + '\"]').addClass('checked');\n });\n this.set('currentGrade', grade);\n this.set('openAssignmentView.score', gradeScoreRubric[grade]);\n } else {\n this.set('currentGrade', false);\n }\n },\n postSubmissionGrade: function (grade, onSuccessCallback, onFailureCallback) {\n this.postSubmissionGrade(grade, onSuccessCallback, onFailureCallback);\n },\n //dontClearBodyInput is needed because this function is called in two different places,\n //and one of them doesn't need bodyInput to be cleared.\n postSubmissionComment: function (onSuccessCallback, dontClearBodyInput) {\n var content = this.bodyInput,\n sub = this.openAssignmentView,\n submission_id = sub.get('id'),\n comment = sub.store.createRecord('comment'),\n /* eslint-disable-next-line ember/no-get */\n usr = this.get('session.user'),\n that = this,\n currentGrade;\n if (!content || content === '') {\n return;\n }\n\n // Add any video embeds\n content = this.parseEmbed(content);\n content = content + this.videoEmbedString;\n this.store.nestResources('submission', [{\n section: that.get('session.section.id')\n }, {\n assignment: sub.get('assignment.id')\n }]);\n this.store.nestResources('comment', [{\n section: that.get('session.section.id')\n }, {\n assignment: sub.get('assignment.id')\n }, {\n submission: submission_id\n }]);\n if (!this.working && this.file_ids.length > 0) {\n comment.set('file_ids', this.file_ids);\n comment.set('attachments', this.file_ids);\n }\n comment.set('comment', content);\n comment.set('user', usr);\n sub.get('comments').pushObject(comment);\n that.set('inEditor', false);\n if (!dontClearBodyInput) {\n that.set('bodyInput', '');\n }\n comment.save().then(function (savedComment) {\n comment.set('db_id', savedComment.get('db_id'));\n that.set('openAssignmentView', sub);\n\n // If there's a grade in the pipeline, post it\n let grade_to_assign = that.get('currentGrade');\n if (grade_to_assign) {\n that.set('currentGrade', false);\n } else if (!sub.get('grade_matches_current_submission')) {\n // If an instructor is leaving a comment on a resub and not changing\n // the grade, preserve it for the new submission\n grade_to_assign = sub.get('grade');\n }\n if (grade_to_assign) {\n // If the grade is a number, add a percentage sign to it, so it submits correctly...\n if (!isNaN(parseInt(grade_to_assign)) && toString(grade_to_assign).split('%').length === 1) {\n grade_to_assign = grade_to_assign + '%';\n }\n that.send('postSubmissionGrade', grade_to_assign, () => {\n onSuccessCallback?.();\n });\n } else {\n onSuccessCallback?.();\n }\n that.send('clearAllFiles');\n }, function () {\n var conv = sub.get('comments').objectAt(sub.get('comments.length') - 1);\n currentGrade = that.get('currentGrade');\n conv.set('needs_repost', true);\n conv.set('attached_grade', currentGrade);\n that.set('currentGrade', false);\n });\n },\n scrollToAssignment: function (version) {\n /* eslint-disable-next-line ember/no-jquery */\n var verNode = Ember.$('.submission-container li[version=' + version + ']');\n\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.submission-container').scrollTop(0);\n if (verNode.length > 0) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.submission-container input[version=' + version + ']').attr('checked', true);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.submission-container').scrollTop(verNode.offset().top);\n }\n },\n repostSubmissionComment: function (comment) {\n var that = this;\n comment.set('needs_repost', false);\n comment.set('is_reposting', true);\n comment.save().then(function () {\n comment.set('needs_repost', false);\n comment.set('is_reposting', false);\n let gr = comment.get('attached_grade');\n if (gr) {\n that.send('postSubmissionGrade', gr);\n }\n }, function () {\n comment.set('needs_repost', true);\n comment.set('is_reposting', false);\n });\n },\n setAssignmentSubmissionsHiddenStatus: function (assignment, status) {\n (0, _dialog.default)('Are you sure you want to make all student submissions for this assignment viewable to all students in the class?', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n let submissions = assignment.get('submissions');\n submissions.forEach(submission => {\n this.store.nestResources('submission', [{\n section: assignment.get('section.id')\n }, {\n assignment: assignment.get('id')\n }]);\n submission.set('hidden_from_students', status);\n submission.save();\n });\n }\n });\n }\n }\n });\n});","define(\"bocce/mixins/attachments-mixin\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create({\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n attachment_objects: Ember.computed('attachments.[]', function () {\n let attachments = this.attachments;\n let retval = [];\n for (let item of attachments) {\n // If the item is an object with a URL, that should be all we need to render the attachment component. If there\n // is no URL, assume that item either is or has an ID that we can use to get the attachment model from the store.\n if (typeof item === 'object' && item.url) {\n // If item doesn't already have a name or type, copy other property values that can work instead.\n Ember.set(item, 'name', item.name || item.display_name || item.filename);\n Ember.set(item, 'type', item.type || item['content-type']);\n retval.push(item);\n } else {\n // item can either be an ID, or an object with an ID property. If it's an object with an ID, use that.\n let id;\n if (Object.prototype.hasOwnProperty.call(item, 'id')) {\n id = item.id;\n } else {\n id = item;\n }\n if (id) {\n // This works because the attachment adapter (see adapters/attachment.js) prevents re-fetching attachment models\n // from the server. Attachment models should almost never be fetched individually; instead they should be\n // included as sideloaded models when fetching discussions, messages, submissions, etc. When they're sideloaded\n // the URL property includes a query string that Canvas uses to verify that the user has permission to view it.\n retval.push(this.store.findRecord('attachment', id));\n }\n }\n }\n return retval;\n })\n });\n});","define(\"bocce/mixins/audio-rec\", [\"exports\", \"bocce/mixins/legacy-attachment-manager\"], function (_exports, _legacyAttachmentManager) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create(_legacyAttachmentManager.default, {\n recorder: false,\n panel: false,\n echoCancellationEnabled: Ember.computed(function () {\n return localStorage.getItem('bocceEchoCancellation') === 'true';\n }),\n isChrome: Ember.computed(function () {\n return navigator.userAgent.toLowerCase().indexOf('chrome') > -1;\n }),\n noRec: Ember.computed(function () {\n // Check if getUserMedia is available. Otherwise, kill mic.\n navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;\n return !navigator.getUserMedia;\n }),\n startUserMedia: function (stream) {\n this.set('recorder', new Recorder(stream));\n Ember.debug('Recorder initialised.');\n },\n createDownloadLink: function () {\n let panel = this.panel;\n this.recorder.exportMP3(function (blob, mp3Name, context) {\n let file = new File([blob], mp3Name, {\n type: 'audio/mpeg3'\n });\n if (context.routeName) {\n //If context ISN'T a controller...\n context = context.get('controller');\n }\n panel.attributes.class.value = 'audio-rec-panel active stopped';\n context.send('addValidFile', file);\n context.send('toggleAudioRecPanel');\n }, this);\n },\n actions: {\n toggleAudioRecPanel: function (close) {\n let panel;\n this.set('panel', document.querySelector('.floating-modal.active .audio-rec-panel'));\n this.set('timeCode', document.querySelector('.floating-modal.active .audio-rec-panel .time-code'));\n panel = this.panel;\n if (!!close || panel.classList.contains('active') || this.noRec) {\n panel.attributes.class.value = 'audio-rec-panel standby';\n return false;\n }\n if (panel.classList.contains('active') || !!close && !!this.currStream) {\n this.recorder.destroy();\n this.set('recorder', false);\n } else {\n panel.attributes.class.value = 'audio-rec-panel standby active';\n this.send('toggleCameraPanel', true);\n try {\n // WebKit nonsense\n window.AudioContext = window.AudioContext || window.webkitAudioContext;\n window.URL = window.URL || window.webkitURL;\n Ember.debug('Audio context set up.');\n Ember.debug('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));\n } catch (e) {\n Ember.debug('No web audio support in this browser!');\n }\n }\n },\n audioRecRecord: function () {\n let constraints = {\n audio: true\n };\n if (!(localStorage.getItem('bocceEchoCancellation') === 'true')) {\n constraints.audio = {\n echoCancellation: false,\n mozAutoGainControl: false,\n mozNoiseSuppression: false,\n googEchoCancellation: false,\n googAutoGainControl: false,\n googNoiseSuppression: false,\n googHighpassFilter: false\n };\n }\n navigator.mediaDevices.getUserMedia(constraints).then(stream => {\n this.startUserMedia(stream);\n let timeCode = this.timeCode,\n rec = this.recorder.record(function (time) {\n timeCode.setAttribute('time_code', time);\n });\n if (rec) {\n this.panel.attributes.class.value = 'audio-rec-panel active recording';\n }\n }).catch(error => {\n Ember.debug('No live audio input: ' + e);\n this.panel.attributes.class.value = 'audio-rec-panel permission';\n });\n },\n audioRecStop: function () {\n let rec = this.recorder.stop();\n if (rec) {\n // create MP3 download link using audio data blob\n this.createDownloadLink();\n this.panel.attributes.class.value = 'audio-rec-panel active encoding';\n }\n },\n echoCancellationToggleAudio: function () {\n let echoCancellation = localStorage.getItem('bocceEchoCancellation') === 'true';\n if (echoCancellation) {\n localStorage.setItem('bocceEchoCancellation', false);\n this.set('echoCancellationEnabled', false);\n } else {\n localStorage.setItem('bocceEchoCancellation', true);\n this.set('echoCancellationEnabled', true);\n }\n }\n }\n });\n});","define(\"bocce/mixins/av-players\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create({\n session: Ember.inject.service(),\n //Keep track of the currently playing audio so that playback can be tracked with a progress bar.\n playingAudio: {\n id: '',\n item: null\n },\n //Keep track of the currently playing video so that playback can be tracked with a progress bar.\n playingVideo: {\n id: '',\n item: null,\n player: null\n },\n ticking: false,\n startAV: function () {\n this.session.currentAbortControllers.forEach(listener => listener.abort());\n this.session.clearAbortControllers();\n this.mainPanel = document.querySelector('.main-panel');\n this.avPlayerOnClickListener = e => {\n if (e.target.classList.contains('track-handle') || e.target.classList.contains('audio-asset')) {\n this.playAudioFromOb(e);\n } else if (e.target.classList.contains('video-asset')) {\n this.playVideoFromOb(e);\n } else if (e.target.classList.contains('kaltura-asset')) {\n this.playKalturaFromOb(e);\n }\n let compPath = e.composedPath();\n let hasVideoPlayer = this.playingVideo.player ? compPath.includes(this.playingVideo.player) : false;\n let hasAudioPlayer = this.audioPlayer ? compPath.includes(this.audioPlayer) : false;\n\n // If the evt target path does not contain the video player or audio player, disable the keyboard shortcuts\n if (hasVideoPlayer) {\n this.videoUnfocus = false;\n } else if (hasAudioPlayer) {\n this.videoUnfocus = false;\n } else {\n this.videoUnfocus = true;\n }\n };\n document.addEventListener('click', this.avPlayerOnClickListener);\n\n // Check if document already has a click event listener\n if (!this.mainPanel.hasAttribute('data-click-listener')) {\n this.mainPanel.setAttribute('data-click-listener', true);\n this.audio = document.querySelector('.audio-player .player');\n this.audioSeek = document.querySelector('.audio-player .audio-controls #player-seek');\n this.audioVolume = document.querySelector('.audio-player #player-vol');\n this.audioTimeCode = document.querySelector('.audio-player .audio-timecode');\n this.audioControls = document.querySelectorAll('.audio-controls > i:not(.fa-repeat), .audio-controls > label:not(.fa-repeat)');\n this.audioControlsPlayPause = document.querySelectorAll('.audio-controls > i.fa-play, .audio-controls > i.fa-pause');\n this.audioRepeatBtn = document.querySelector('.audio-controls > .fa-repeat');\n this.audioPlayer = document.querySelector('.audio-player');\n this.audioPlayerLesson = document.querySelector('.audio-player .topic-title');\n if (Ember.$.isMobile) {\n document.querySelector('.video-player .fa-window-restore').classList.add('ipad-control');\n } else {\n document.removeEventListener('keydown', this.keydown.bind(this));\n\n // When user focuses out of the text input, enable the keyboard shortcuts again\n document.addEventListener('focusin', e => {\n // If there's neither a video or audio player active, ignore\n if (!this.playingVideo.player && !this.audioPlayer) {\n this.videoUnfocus = true;\n return;\n }\n // If focused element path contains the video player, enable the keyboard shortcuts\n let compPath = e.composedPath();\n if (this.playingVideo.player && compPath.includes(this.playingVideo.player) || this.audioPlayer && compPath.includes(this.audioPlayer)) {\n this.videoUnfocus = false;\n } else {\n this.videoUnfocus = true;\n }\n });\n }\n this.audioSeek.addEventListener('input', this.seekAudioAction.bind(this));\n this.audioVolume.addEventListener('input', this.audioVolumeAction.bind(this));\n document.querySelector('.ap-close').addEventListener('click', this.audioClose.bind(this));\n this.timeupdateEventHandler = this.timeupdateEventHandler ?? this.updateAudioSeek;\n this.endedEventHandler = this.endedEventHandler ?? this.audioEnded;\n this.pauseEventHandler = this.pauseEventHandler ?? this.audioPaused;\n this.playingEventHandler = this.playingEventHandler ?? this.audioIsPlaying;\n this.audio.addEventListener('timeupdate', this.timeupdateEventHandler.bind(this));\n this.audio.addEventListener('ended', this.endedEventHandler.bind(this));\n this.audio.addEventListener('pause', this.pauseEventHandler.bind(this));\n this.audio.addEventListener('playing', this.playingEventHandler.bind(this));\n this.audioRepeatBtn.addEventListener('click', this.toggleRepeatAudioAction.bind(this));\n this.audioControlsPlayPause.forEach(btn => {\n btn.addEventListener('click', this.playPauseAudioAction.bind(this));\n });\n }\n },\n keydown(e) {\n // IF user is typing in a text field, don't do anything\n if (this.videoUnfocus) {\n return;\n }\n let item;\n // Check for currently playing audio or video\n if (this.playingAudio.item) {\n item = this.playingAudio.item;\n } else if (this.playingVideo) {\n item = this.playingVideo.item;\n } else {\n return;\n }\n\n // Switch on key code\n switch (e.keyCode) {\n case 32:\n // space - play/pause\n e.preventDefault();\n if (this.playingAudio.item) {\n this.playPauseAudioAction(item);\n } else if (this.playingVideo) {\n this.videoPlayPauseAction(item);\n }\n break;\n case 37:\n // left arrow - rewind 5 seconds\n e.preventDefault();\n if (this.playingAudio.item) {\n this.seekAudioAction(item, -5);\n } else if (this.playingVideo) {\n this.videoSeekEvent(item, -5);\n }\n break;\n case 39:\n // right arrow - forward 5 seconds\n e.preventDefault();\n if (this.playingAudio.item) {\n this.seekAudioAction(item, 5);\n } else if (this.playingVideo) {\n this.videoSeekEvent(item, 5);\n }\n break;\n case 38:\n // up arrow - increase volume\n e.preventDefault();\n if (this.playingAudio.item) {\n this.audioVolumeAction(item, 0.1);\n } else if (this.playingVideo) {\n this.videoVolumeAction(item, 0.1);\n }\n break;\n case 40:\n // down arrow - decrease volume\n e.preventDefault();\n if (this.playingAudio.item) {\n this.audioVolumeAction(item, -0.1);\n } else if (this.playingVideo) {\n this.videoVolumeAction(item, -0.1);\n }\n break;\n case 70:\n // f - toggle fullscreen\n e.preventDefault();\n if (this.playingVideo) {\n this.toggleFullScreenAction(item);\n }\n break;\n case 67:\n // c - toggle captions\n e.preventDefault();\n if (this.playingVideo) {\n this.toggleCCAction(item);\n }\n break;\n case 77:\n // m - toggle mute\n e.preventDefault();\n if (this.playingAudio.item) {\n this.toggleAudioMuteAction(item);\n } else if (this.playingVideo) {\n this.toggleVideoVolumeAction(item);\n }\n break;\n case 74:\n // j - rewind 10 seconds\n e.preventDefault();\n if (this.playingVideo) {\n this.videoSeekEvent(item, 10);\n } else if (this.playingAudio.item) {\n this.seekAudioAction(item, 10);\n }\n break;\n case 73:\n // i - forward 10 seconds\n e.preventDefault();\n if (this.playingVideo) {\n this.videoSeekEvent(item, -10);\n } else if (this.playingAudio.item) {\n this.seekAudioAction(item, -10);\n }\n break;\n case 190:\n // . - Increase playback speed\n e.preventDefault();\n if (this.playingVideo) {\n this.changeVideoSpeedAction(item, 1);\n }\n break;\n case 188:\n // , - Decrease playback speed\n e.preventDefault();\n if (this.playingVideo) {\n this.changeVideoSpeedAction(item, -1);\n }\n break;\n case 221:\n // ] - Jump to next CC cue\n e.preventDefault();\n if (this.playingVideo) {\n this.jumpCueAction(item);\n }\n break;\n case 219:\n // [ - Jump to previous CC cue\n e.preventDefault();\n if (this.playingVideo) {\n this.jumpCueAction(item, true);\n }\n break;\n // no default\n }\n },\n //Audio Player\n audioClose: function () {\n //Remove the playing audio's progress bar\n if (this.playingAudio.id && this.playingAudio.item && this.playingAudio.item.target) {\n this.playingAudio.item.target.closest('#' + this.playingAudio.id).style['background-image'] = '';\n this.playingAudio.id = '';\n this.playingAudio.item = null;\n }\n this.audioPlayer.classList.remove('active');\n this.audioPlayer.querySelectorAll('audio').forEach(function (audio) {\n audio.pause();\n });\n },\n seekAudioAction: function (item, time) {\n let length = this.audio ? this.audio.duration : 0;\n if (time) {\n this.audio.currentTime = this.audio.currentTime + time;\n } else if (length) {\n this.audio.currentTime = parseInt(item.target.value) / 1000 * length;\n }\n },\n audioVolumeAction: function (item, vol) {\n if (vol) {\n let newVol = this.audio.volume + vol;\n if (newVol > 1) {\n newVol = 1;\n } else if (newVol < 0) {\n newVol = 0;\n }\n this.audio.volume = newVol;\n // Adjust the volume slider\n this.audioVolume.value = this.audio.volume * 100;\n return;\n }\n this.audio.volume = parseInt(item.target.value) / 100;\n },\n toggleAudioMuteAction: function () {\n if (this.audio.muted) {\n this.audio.muted = false;\n this.audioVolume.value = this.audio.volume * 100;\n } else {\n this.audio.muted = true;\n this.audioVolume.value = 0;\n }\n },\n updateAudioSeek: function (item) {\n let time = item.target.currentTime,\n minutes = Math.floor(time / 60),\n seconds = Math.floor(time) % 60,\n curTime;\n seconds = seconds < 10 ? '0' + seconds.toString() : seconds.toString();\n minutes = minutes < 10 ? '0' + minutes.toString() : minutes.toString();\n curTime = minutes + ':' + seconds;\n const audioPercentComplete = Math.round(1000 * (time / item.target.duration)) / 10;\n\n //Scale is 0 to 1000\n this.audioSeek.value = audioPercentComplete * 10;\n\n //Update the playing audio's progress bar.`\n if (this.playingAudio.id && this.playingAudio.item && this.playingAudio.item.target) {\n this.playingAudio.item.target.closest('#' + this.playingAudio.id).style['background-image'] = 'linear-gradient(to right, #00bcd4 ' + audioPercentComplete + '%, #fff ' + audioPercentComplete + '%)';\n }\n this.audioTimeCode.setAttribute('time_code', curTime);\n },\n playPauseAudioAction: function (pause) {\n if (!pause || this.audio.paused) {\n document.querySelectorAll('audio.public_audio_player').forEach(function (ob) {\n ob.pause();\n });\n\n // This is for Safari -- Safari freaks out if there's a play command without a promise\n let promise = this.audio.play();\n if (promise !== undefined) {\n promise.catch(() => {\n // Auto-play was prevented\n }).then(() => {\n // Auto-play started\n });\n }\n } else {\n this.audio.pause();\n }\n },\n toggleRepeatAudioAction: function () {\n if (this.audioRepeatBtn.classList.contains('active')) {\n this.audioRepeatBtn.classList.remove('active');\n this.audio.loop = false;\n } else {\n this.audio.loop = true;\n this.audioRepeatBtn.classList.add('active');\n }\n },\n checkDom: function () {\n // Check to make sure the DOM player objects are correctly defined\n if (this.audio && this.audioSeek && this.audioVolume && this.audioTimeCode && this.audioControls && this.audioControlsPlayPause && this.audioRepeatBtn && this.audioPlayer && this.audioPlayerLesson) {\n return;\n }\n\n // If any are missing, define them again\n this.mainPanel = document.querySelector('.main-panel');\n this.audio = document.querySelector('.audio-player .player');\n this.audioSeek = document.querySelector('.audio-player .audio-controls #player-seek');\n this.audioVolume = document.querySelector('.audio-player #player-vol');\n this.audioTimeCode = document.querySelector('.audio-player .audio-timecode');\n this.audioControls = document.querySelectorAll('.audio-controls > i:not(.fa-repeat), .audio-controls > label:not(.fa-repeat)');\n this.audioControlsPlayPause = document.querySelectorAll('.audio-controls > i.fa-play, .audio-controls > i.fa-pause');\n this.audioRepeatBtn = document.querySelector('.audio-controls > .fa-repeat');\n this.audioPlayer = document.querySelector('.audio-player');\n this.audioPlayerLesson = document.querySelector('.audio-player .topic-title');\n },\n playAudioFromOb: function (item) {\n this.checkDom();\n let tgt = item.target.attributes.source || item.target.parentNode.attributes.source,\n title = item.target.attributes.title,\n next = item.target.attributes.next,\n prev = item.target.attributes.prev,\n prevBtn = this.audioPlayer.querySelector('.prevBtn'),\n nextBtn = this.audioPlayer.querySelector('.nextBtn'),\n relocate;\n this.playingVideo = {\n id: '',\n item: null,\n player: null\n };\n this.videoUnfocus = false;\n\n //Clear the previously played audio's progress bar.\n if (this.playingAudio.id && this.playingAudio.item && this.playingAudio.item.target) {\n this.playingAudio.item.target.closest('#' + this.playingAudio.id).style['background-image'] = '';\n }\n if (!this.keyDownListener && !Ember.$.isMobile) {\n this.keyDownListener = true;\n let controller = new AbortController();\n document.addEventListener('keydown', this.keydown.bind(this), {\n signal: controller.signal\n });\n this.session.addAbortController(controller);\n }\n\n //Set the id for the playing audio.\n //The html attribute 'data-playback-tracking-id' is added to the parsed topic body's html from canvas.\n this.playingAudio.id = item.target.attributes['data-playback-tracking-id'] ? item.target.attributes['data-playback-tracking-id'].value : false;\n this.playingAudio.item = item;\n title = title ? title.value : document.querySelector('.lesson-content').previousSibling.nodeValue;\n let kCont = document.getElementById('kalturaContainer');\n if (kCont.sendNotification) {\n kCont.sendNotification('pause');\n }\n prevBtn.classList.add('inactive');\n nextBtn.classList.add('inactive');\n this.closeVideoPlayer();\n if (prev) {\n prevBtn.classList.remove('inactive');\n prevBtn.setAttribute('for', prev.value);\n }\n if (next) {\n nextBtn.classList.remove('inactive');\n nextBtn.setAttribute('for', next.value);\n }\n let activeVideo = document.querySelector('.video-player.active');\n if (activeVideo) {\n activeVideo.classList.remove('active');\n }\n const previousPlaybackRate = this.audio.playbackRate;\n this.audio.attributes.src.value = tgt.value;\n this.audio.load();\n this.audio.playbackRate = previousPlaybackRate;\n let promise = this.audio.play();\n if (promise !== undefined) {\n promise.catch(() => {\n // Auto-play was prevented\n }).then(() => {\n // Auto-play started\n });\n }\n this.audioPlayer.classList.add('active');\n this.audioPlayerLesson.setAttribute('title', title);\n relocate = item.target.closest('.audio-asset');\n if (relocate) {\n relocate = relocate.getAttribute('id');\n } else {\n relocate = item.target.closest('.audio_multi').getAttribute('id');\n }\n this.audioPlayer.setAttribute('relocate', relocate);\n },\n audioEnded: function (event) {\n let player = document.getElementById('publicPlayer');\n // Activate 1 (play button)\n this.audioControls[1].classList.remove('inactive');\n // Deactivate 2 (pause button)\n this.audioControls[2].classList.add('inactive');\n\n // If 3 (Next button) is active, click it\n if (!this.audioControls[3].classList.contains('inactive')) {\n this.audioControls[3].click();\n } else if (player.loop && player.currentTime > Math.floor(player.duration - 1) && event.eventPhase === 2) {\n player.currentTime = 0;\n let promise = player.play();\n if (promise !== undefined) {\n promise.catch(() => {\n // Auto-play was prevented\n }).then(() => {\n // Auto-play started\n });\n }\n }\n },\n audioPaused: function () {\n // Activate 1 (play button)\n this.audioControls[1].classList.remove('inactive');\n // Deactivate 2 (pause button)\n this.audioControls[2].classList.add('inactive');\n },\n audioIsPlaying: function () {\n // Activate 2 (pause button)\n this.audioControls[2].classList.remove('inactive');\n // Deactivate 1 (play button)\n this.audioControls[1].classList.add('inactive');\n },\n closeAudioPlayer: function () {\n if (this.playingAudio.id && this.playingAudio.item && this.playingAudio.item.target) {\n this.playingAudio.item.target.closest('#' + this.playingAudio.id).style['background-image'] = '';\n }\n this.audio.pause();\n this.audioPlayer.classList.remove('active');\n },\n //Video Player\n playVideoFromOb: function (item) {\n // If the button clicked contains the fa-window-restore class, just maximitize the video player.\n if (item.target.classList.contains('fa-window-restore') || !!item.target.querySelector('.fa-window-restore')) {\n this.minmaxVideoPlayer(item, false);\n return;\n }\n this.checkDom();\n let rect = item.target.parentElement.lastElementChild.getBoundingClientRect(),\n cprnt = item.target.tagName.toLowerCase() === 'button' ? item.target : false;\n cprnt = !cprnt && (item.target.tagName.toLowerCase() === 'img' || item.target.tagName.toLowerCase() === 'i') ? item.target.parentNode : item.target;\n this.closeVideoPlayer(this);\n this.closeAudioPlayer();\n this.videoTranscriptItems = [];\n if (this.videoPlayerObserver) {\n this.videoPlayerObserver.disconnect();\n }\n if (!this.keyDownListener && !Ember.$.isMobile) {\n this.keyDownListener = true;\n let controller = new AbortController();\n document.addEventListener('keydown', this.keydown.bind(this), {\n signal: controller.signal\n });\n this.session.addAbortController(controller);\n }\n this.playingAudio.id = '';\n this.playingAudio.item = null;\n this.playingVideo = {\n id: '',\n item: null,\n player: null\n };\n let inf = cprnt.querySelector('i');\n let video_player;\n inf.classList.remove('fa-play');\n inf.classList.add('fa-spinner-third');\n inf.classList.add('fa-spin');\n video_player = document.querySelector('.video-player:not(#kalturaPlayer):not(.standalone)').cloneNode(true);\n video_player.classList.add('standalone');\n video_player.style.width = rect.width + 'px';\n video_player.style.height = rect.height + 'px';\n item.target.closest('.video-asset').parentElement.prepend(video_player);\n video_player.querySelector('.play-button').focus();\n this.playingVideo.id = item.target.attributes['data-playback-tracking-id'] ? item.target.attributes['data-playback-tracking-id'].value : false;\n this.playingVideo.item = item;\n this.playingVideo.player = video_player;\n\n // Disable right click on video player\n video_player.addEventListener('contextmenu', function (e) {\n e.preventDefault();\n });\n let video = video_player.querySelector('video');\n if (cprnt.attributes.noFlavors) {\n video_player.classList.add('noFlavors');\n } else {\n // Start a timer. IF the video doesn't load in 5 seconds, bump it down to 480p\n this.videoResTimer = setTimeout(() => {\n this.changeVideoResAction(false, '480p', video_player);\n }, 8000);\n }\n if (cprnt.attributes.uncompressed) {\n video_player.classList.add('uncompressed');\n }\n function brokenCCAlert(videoID, error) {\n //Turn video id into a url-encoded string\n let videoIDString = encodeURIComponent(videoID);\n let errorString = encodeURIComponent(error);\n Ember.$.ajax({\n type: 'POST',\n /* eslint-disable-next-line ember/no-get */\n url: `/interface/system/broken-cc?vidURL=${videoIDString}&vidError=${errorString}`,\n success: () => {\n console.log('BCC Alert sent');\n },\n error: error => {\n console.log('BCC Alert failed!');\n console.log(error);\n }\n });\n }\n function brokenVideoAlert(videoID, error) {\n let videoIDString = encodeURIComponent(videoID);\n let errorString = encodeURIComponent(error);\n // eslint-disable-next-line ember/no-jquery\n Ember.$.ajax({\n type: 'POST',\n url: `/interface/system/broken-video?vidURL=${videoIDString}&vidError=${errorString}`,\n success: () => {\n console.log('Broken Video Alert sent');\n },\n error: error => {\n console.log('Broken Video Alert failed!');\n console.log(error);\n }\n });\n }\n\n // Convert track from SRT to VTT\n function srt2vtt(responseText, context) {\n // Return if no CC data\n if (responseText.length === 0) {\n return false;\n }\n\n // Convert the srt to vtt format if necessary\n let isVTT = responseText.indexOf('WEBVTT') > -1;\n let vtt = isVTT ? responseText : 'WEBVTT\\n\\n';\n\n // remove all instances of \\r\\n and replace with \\n -- also replace all single \\r with \\n\n // Carriage returns are not allowed in WebVTT files\n responseText = responseText.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n let srt_lines = responseText.split('\\n');\n let transcriptRaw = [];\n\n // If the file is VTT, remove the first 2 lines from the SRT_lines array\n // That's extra guff we don't need\n if (isVTT) {\n srt_lines.splice(0, 2);\n }\n let srt_lines_length = srt_lines.length;\n\n // Split the CC file apart into lines and time codes for transcript rendering\n for (let i = 0; i < srt_lines_length; i++) {\n let srt_line = srt_lines[i];\n if (srt_line.indexOf('-->') > -1 && i < srt_lines_length - 1) {\n // Remove the last item in the transcriptRaw array if it's empty\n // This is to prevent empty lines from showing up in the transcript\n if (transcriptRaw.length > 0 && (!transcriptRaw[transcriptRaw.length - 1].text || transcriptRaw[transcriptRaw.length - 1].text === '')) {\n transcriptRaw.pop();\n }\n if (!isVTT) {\n srt_line = srt_line.replaceAll(',', '.');\n if (srt_line.split('.')[1].length < 3) {\n srt_line = srt_line + '0';\n }\n // Add line styling for captions here\n srt_line += 'align:middle line:84%';\n vtt += srt_line + '\\n';\n }\n let convertedTime = srt_line.split(' --> ');\n // If the previous item in the array doesn't have text, delete it\n if (transcriptRaw.length > 0 && !transcriptRaw[transcriptRaw.length - 1].text) {\n transcriptRaw.pop();\n }\n // If there's a time code, convert to seconds\n if (convertedTime.length > 1) {\n convertedTime = convertedTime[0];\n convertedTime = convertedTime.split(':');\n // Convert HH:MM:SS.MMM to seconds\n convertedTime = parseInt(convertedTime[0]) * 3600 + parseInt(convertedTime[1]) * 60 + parseInt(convertedTime[2].split('.')[0]) + parseInt(convertedTime[2].split('.')[1]) / 1000;\n transcriptRaw.push({\n time: convertedTime\n });\n }\n } else if (srt_line.indexOf('-->') === -1 && srt_line.length > 0 && isNaN(srt_line)) {\n if (transcriptRaw.length > 0 && !transcriptRaw[transcriptRaw.length - 1].text) {\n transcriptRaw[transcriptRaw.length - 1].text = srt_line + '\\n';\n } else {\n transcriptRaw[transcriptRaw.length - 1].text += srt_line + '\\n';\n }\n if (!isVTT) {\n vtt += srt_line + '\\n';\n }\n }\n }\n\n // If transcriptRaw is not empty, create an HTML paragraph element for each item in the array\n if (transcriptRaw.length > 0) {\n let transcript = document.createElement('p');\n transcript.classList.add('transcript');\n for (let i = 0; i < transcriptRaw.length; i++) {\n let transcriptItem = document.createElement('span');\n transcriptItem.classList.add('transcriptItem');\n transcriptItem.setAttribute('data-time', transcriptRaw[i].time);\n transcriptItem.innerHTML = transcriptRaw[i].text;\n transcript.appendChild(transcriptItem);\n }\n // Add a show/hide button to the end of the transcript\n let transcriptShowHide = document.createElement('button');\n transcriptShowHide.classList.add('transcriptShowHide');\n transcriptShowHide.innerHTML = 'Show Transcript';\n transcript.appendChild(transcriptShowHide);\n // Add the transcript to the video player\n video_player.after(transcript);\n\n // Add listener for transcript show/hide\n transcriptShowHide.addEventListener('click', () => {\n if (transcript.classList.contains('active')) {\n transcript.classList.remove('active');\n transcriptShowHide.innerHTML = 'Show Transcript';\n } else {\n transcript.classList.add('active');\n transcriptShowHide.innerHTML = 'Hide Transcript';\n }\n });\n\n // Add listeners for transcript items and chane the video time when clicked\n context.videoTranscriptItems = transcript.querySelectorAll('.transcriptItem');\n for (let i = 0; i < context.videoTranscriptItems.length; i++) {\n context.videoTranscriptItems[i].addEventListener('click', () => {\n video.currentTime = parseFloat(context.videoTranscriptItems[i].getAttribute('data-time')) + 0.01;\n });\n }\n }\n\n // turn vtt into blob\n let blob = new Blob([vtt], {\n type: 'text/vtt'\n });\n\n // create a url for the blob\n return window.URL.createObjectURL(blob);\n }\n\n // Add listener for video player play button\n video.addEventListener('playing', this.videoPlayBtn.bind(this));\n // Add listener for video player pause button\n video.addEventListener('pause', this.videoPauseBtn.bind(this));\n // Add listener for video player ended event\n video.addEventListener('ended', this.videoEnded.bind(this));\n // Add listener for video player timeupdate event\n video.addEventListener('timeupdate', this.updateVideoTime.bind(this));\n\n // Add listener for help button\n video_player.querySelector('.help-button-open').addEventListener('click', evt => {\n evt.preventDefault();\n video_player.querySelector('.video-player-help').classList.add('active');\n });\n video_player.querySelector('.help-button-close').addEventListener('click', evt => {\n evt.preventDefault();\n video_player.querySelector('.video-player-help').classList.remove('active');\n });\n video_player.querySelector('.minmax-button').addEventListener('click', this.minmaxVideoPlayer.bind(this));\n video_player.querySelector('.video-close-button').addEventListener('click', this.closeVideoPlayer.bind(this));\n video_player.querySelectorAll('.play-button').forEach(button => {\n button.addEventListener('click', this.playVideoAction.bind(this));\n });\n video_player.querySelectorAll('.pause-button').forEach(button => {\n button.addEventListener('click', this.pauseVideoAction.bind(this));\n });\n video_player.querySelectorAll('.video-rewind-button').forEach(button => {\n button.addEventListener('click', this.rewindVideoAction.bind(this));\n });\n video_player.querySelectorAll('.video-forward-button').forEach(button => {\n button.addEventListener('click', this.forwardVideoAction.bind(this));\n });\n video_player.querySelector('.volume-button-mute').addEventListener('click', this.toggleVideoVolumeAction);\n video_player.querySelector('.video-speed').addEventListener('click', this.changeVideoSpeedAction);\n video_player.querySelector('.video-resolution').addEventListener('click', this.changeVideoResAction);\n video_player.querySelector('.video-loop-toggle').addEventListener('click', this.toggleLoopAction);\n video_player.querySelector('.video-cc-toggle').addEventListener('click', this.toggleCCAction);\n video_player.querySelector('.video-fullscreen').addEventListener('click', this.toggleFullScreenAction);\n let video_seek_slider = video_player.querySelector('.play-slider-input');\n // Add listener for video player seek event\n video_seek_slider.addEventListener('input', this.videoSeekEvent);\n let video_volume_slider = video_player.querySelector('.volume-slider-input');\n // Add listener for video player volume event\n video_volume_slider.addEventListener('input', this.videoVolumeAction);\n\n // Go through all buttons and controls inside video_player and add an aria-controls attribute with a randomly generated ID like video_player_1\n // This helps screenreaders solve controls and content relationships\n let video_player_controls = video_player.querySelectorAll('button, input, select');\n let video_player_id = 'video_player_' + Math.floor(Math.random() * 1000000);\n for (let i = 0; i < video_player_controls.length; i++) {\n video_player_controls[i].setAttribute('aria-controls', video_player_id);\n }\n let startVideo = () => {\n document.querySelectorAll('.video-asset i').forEach(icon => {\n icon.classList.remove('fa-window-restore');\n icon.classList.remove('paused');\n icon.classList.add('fa-play');\n });\n video.load();\n\n // Add the randomly generated ID to the video player\n video.setAttribute('id', video_player_id);\n let promise = video.play();\n if (promise !== undefined) {\n promise.catch(() => {\n // cancel the resolution fallback timer\n if (this.videoResTimer) {\n clearTimeout(this.videoResTimer);\n }\n // Auto-play was prevented\n inf.classList.add('fa-wind-window-restore');\n inf.classList.remove('fa-spinner-third');\n inf.classList.remove('fa-spin');\n inf.classList.remove('paused');\n video_player.classList.remove('hidden');\n this.videoUnfocus = false;\n }).then(() => {\n // cancel the resolution fallback timer\n clearTimeout(this.videoResTimer);\n // Auto-play started\n inf.classList.add('fa-window-restore');\n inf.classList.remove('fa-spinner-third');\n inf.classList.remove('fa-spin');\n inf.classList.remove('paused');\n video_player.classList.remove('hidden');\n this.videoUnfocus = false;\n });\n }\n };\n\n /**\n * @typedef Status\n * @property {string} description\n * @property {string} indicator \"none\" means no error. Else \"major\" or \"minor\"\n *\n * @typedef StatusData\n * @property {Status}\n */\n\n /**\n * Get the status of Kaltura's API\n * @returns {Promise}\n */\n const kalturaStatus = async () => {\n return (await fetch(\"https://status.kaltura.com/api/v2/status.json\")).json();\n };\n let source = video.getElementsByTagName('source')[0];\n if (cprnt.attributes.source.value !== this.get('currentVideo') || cprnt.attributes.source.value !== source.getAttribute('src')) {\n let flavor = session.kaltura_flavors[session.currentVideoRes],\n tracks = video.getElementsByTagName('track'),\n poster = cprnt.attributes.poster.value,\n kaltura_session = session.get('kaltura_session'),\n track_url = !cprnt.attributes.kaltura ? cprnt.attributes.track.value : `https://www.kaltura.com/api_v3/service/caption_captionasset/action/serveByEntryId?entryId=${cprnt.attributes.source.value}&ks=${kaltura_session}`,\n source_url = !cprnt.attributes.kaltura ? cprnt.attributes.source.value : `https://cdnapisec.kaltura.com/p/2588802/sp/0/playManifest/entryId/${cprnt.attributes.source.value}/format/url/protocol/https/flavorParamId/${flavor}/video.mp4`;\n source.addEventListener('error', () => {\n let error_background = video_player.querySelector(\".player-error-background\");\n kalturaStatus().then(data => {\n let indicator = data.status.indicator;\n if (indicator !== \"none\") {\n // notify slack channel\n brokenVideoAlert(cprnt.attributes.source.value, data.status.description);\n inf.classList.add('fa-wind-window-restore');\n inf.classList.remove('fa-spinner-third');\n inf.classList.remove('fa-spin');\n inf.classList.remove('paused');\n video_player.classList.remove('hidden');\n error_background.classList.remove('hidden');\n }\n\n // create timeout for 5 minutes, then check again\n setTimeout(() => {\n kalturaStatus().then(data => {\n if (data.status.indicator === \"none\") {\n error_background.classList.add('hidden');\n startVideo();\n }\n });\n }, 300000);\n });\n });\n this.set('currentVideo', source_url);\n source.setAttribute('src', source_url);\n\n // Set video poster\n if (poster && poster !== '') {\n video.setAttribute('poster', poster);\n }\n\n // PRM: We can't just reuse the existing track node -- Chrome, at least,\n // doesn't reload the files when src changes\n if (tracks.length > 0) {\n video.removeChild(tracks[0]);\n }\n\n // Fetch track data from the track URL\n // Convert the track data from SRT to VTT format\n // Add the VTT track to the video player\n if (track_url && track_url !== '' && track_url.indexOf('undefined') === -1) {\n let xhr = new XMLHttpRequest();\n xhr.open('GET', track_url, true);\n xhr.onload = () => {\n if (xhr.status === 200) {\n // Try to convert the track data from SRT to VTT format -- catch any errors\n let vtt;\n try {\n vtt = srt2vtt(xhr.responseText, this);\n } catch (e) {\n brokenCCAlert(cprnt.attributes.source.value, e);\n }\n if (vtt) {\n let vtt_track = document.createElement('track');\n vtt_track.setAttribute('src', vtt);\n vtt_track.setAttribute('kind', 'subtitles');\n vtt_track.setAttribute('srclang', 'en');\n vtt_track.setAttribute('label', 'English');\n video.appendChild(vtt_track);\n }\n } else {\n console.log('Error loading CC track');\n video_player.classList.add('noCC');\n }\n startVideo();\n };\n xhr.send();\n } else {\n video_player.classList.add('noCC');\n startVideo();\n }\n }\n video_player.classList.remove('minified');\n video_player.classList.add('active');\n\n // Don't perform video movement on iPhone -- that goes straight to full screen\n if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i)) {\n // iPhones prefer to play video with their own controls (and in full-screen only)\n // so we need to hide the video player controls and show the native controls\n video_player.classList.add('small-mobile');\n video.controls = true;\n } else {\n // Clicking the video should toggle play/pause\n video.addEventListener('click', () => {\n this.videoUnfocus = false;\n if (video.paused) {\n video.play();\n } else {\n video.pause();\n }\n });\n if (!cprnt.attributes.standalone) {\n // Create the intersection observer\n let options = {\n root: document.querySelector('.main-panel'),\n rootMargin: \"0px\",\n threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]\n };\n this.videoPlayerObserver = new IntersectionObserver(this.videoPlayerObserverCallback.bind(this), options);\n this.videoPlayerObserver.observe(video_player);\n }\n video_player.attributes.playsInline = true;\n }\n },\n closeVideoPlayer: function () {\n function closeVideo(video, context, destroy) {\n if (video) {\n let video_player = video.closest('.video-player');\n // Unload the video player\n video.pause();\n video.firstElementChild.attributes.getNamedItem('src').value = '';\n video.load();\n context.set('currentVideo', false);\n if (context.videoPlayerObserver) {\n context.videoPlayerObserver.disconnect();\n }\n if (destroy) {\n let transcript = document.querySelector('.transcript');\n if (transcript) {\n transcript.remove();\n }\n let video_seek_slider = video_player.querySelector('.play-slider-input');\n video_seek_slider.removeEventListener('change', context.videoSeekEvent);\n let video_volume_slider = video_player.querySelector('.volume-slider-input');\n video_volume_slider.removeEventListener('change', context.videoVolumeAction);\n }\n }\n }\n\n // Destroy any standalone players\n document.querySelectorAll('.video-player.standalone').forEach(function (player) {\n let video = player.querySelector('video');\n closeVideo(video, this, true);\n player.remove();\n }.bind(this));\n\n // Minimize the floating player\n document.querySelectorAll('.video-player.active').forEach(function (player) {\n player.classList.remove('active');\n player.classList.add('minified');\n let video = player.querySelector('video');\n closeVideo(video, this, false);\n }.bind(this));\n\n // Reset play cue buttons\n document.querySelectorAll('.video-asset > i').forEach(function (icon) {\n icon.classList.remove('fa-window-restore');\n icon.classList.remove('paused');\n icon.classList.add('fa-play');\n });\n\n // Pause any kaltura players\n let kCont = document.getElementById('kalturaContainer');\n if (kCont.sendNotification) {\n kCont.sendNotification('pause');\n }\n },\n updateVideoTime: function (item) {\n let video_player = item.target.closest('.video-player').querySelector('video');\n let video_time_code = item.target.closest('.video-player').querySelector('.video-time-code-current');\n let currentVideoTime = video_player.currentTime;\n if (video_time_code) {\n video_time_code.innerHTML = this.formatTime(currentVideoTime);\n }\n let video_scrubber = item.target.closest('.video-player').querySelector('.play-slider-input');\n if (video_scrubber) {\n video_scrubber.value = currentVideoTime / video_player.duration * 1000;\n // Set aria-valuetext in \"X minutes Y seconds of Z minutes W seconds\" format\n video_scrubber.setAttribute('aria-valuetext', this.formatTime(currentVideoTime) + ' of ' + this.formatTime(video_player.duration));\n let currentTranscriptLine;\n for (let i = 0; i < this.videoTranscriptItems.length; i++) {\n let transcriptItem = this.videoTranscriptItems[i];\n let transcriptTime = transcriptItem.getAttribute('data-time');\n transcriptTime = parseFloat(transcriptTime);\n transcriptItem.classList.remove('current');\n if (transcriptTime <= currentVideoTime) {\n currentTranscriptLine = i;\n }\n }\n if (currentTranscriptLine || currentTranscriptLine === 0) {\n this.videoTranscriptItems[currentTranscriptLine].classList.add('current');\n }\n }\n },\n formatTime: function (time) {\n // Format time as hh:mm:ss\n let h = Math.floor(time / 3600),\n m = Math.floor(time % 3600 / 60),\n s = Math.floor(time % 3600 % 60);\n return (h > 0 ? h + \":\" + (m < 10 ? \"0\" : \"\") : \"\") + m + \":\" + (s < 10 ? \"0\" : \"\") + s;\n },\n changeVideoSpeedAction: function (item, speed) {\n if (speed) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n let speed_selector = video_player.querySelector('.video-speed-selector');\n let current_speed = speed_selector.querySelector('.current');\n let playback_speed = current_speed.attributes['speed'].value;\n if (speed === -1) {\n // Find option one ahead of current_speed\n let next_speed = current_speed.nextElementSibling;\n if (next_speed) {\n current_speed.classList.remove('current');\n next_speed.classList.add('current');\n }\n } else if (speed === 1) {\n // Find option one behind current_speed\n let prev_speed = current_speed.previousElementSibling;\n if (prev_speed) {\n current_speed.classList.remove('current');\n prev_speed.classList.add('current');\n }\n }\n\n // Get the current speed\n current_speed = speed_selector.querySelector('.current').attributes['speed'].value;\n if (current_speed !== playback_speed) {\n // Update the speed\n let player = this.playingVideo.item.target.parentElement.querySelector('.video-player');\n if (!player) {\n player = this.playingVideo.item.target.closest('.video-player');\n }\n let video = player.querySelector('video');\n video.playbackRate = current_speed;\n }\n return;\n }\n let video_player = item.target.closest('.video-player');\n let video = video_player.querySelector('video');\n if (!video_player) {\n return;\n }\n speed = parseFloat(item.target.attributes.speed.value);\n video_player.querySelectorAll('.video-speed-selector .option').forEach(function (option) {\n option.classList.remove('current');\n });\n item.target.classList.add('current');\n video.playbackRate = speed;\n },\n changeVideoResAction: function (item, manualRes, videoPlayer) {\n if (manualRes) {\n this.set('currentVideoRes', manualRes);\n // get the resolution button that matches the manualRes\n item = {\n target: videoPlayer.querySelector(`.video-resolution-selector #video-resolution-${manualRes}`)\n };\n // Check if the res selected is the same as the current res\n if (item.target.classList.contains('current')) {\n return;\n }\n videoPlayer.classList.add('hidden');\n }\n\n // Change the video resolution by swapping out the source, referencing session.kaltura_flavors\n let videoRes = item.target.getAttribute('resolution');\n session.currentVideoRes = videoRes;\n localStorage.setItem('bocceVideoRes', videoRes);\n item.target.parentElement.querySelectorAll('.option').forEach(function (option) {\n option.classList.remove('current');\n });\n item.target.classList.add('current');\n let video = item.target.closest('.video-player').querySelector('video');\n // get current time of video\n let currentTime = video.currentTime;\n // get current volume of video\n let volume = video.volume;\n // get current playback rate of video\n let playbackRate = video.playbackRate;\n // get current muted state of video\n let muted = video.muted;\n // Pause the video\n video.pause();\n\n // Pull video source url from source HTML element child of video player\n let videoSource = video.querySelector('source');\n // Get the video source url\n let videoSourceUrl = videoSource.getAttribute('src');\n let video_source_flavor = videoSourceUrl ? videoSourceUrl.split('/flavorParamId/')[1].split('/')[0] : null;\n if (!video_source_flavor) {\n console.log('no video source flavor');\n return;\n }\n let new_video_source = videoSourceUrl.replace(video_source_flavor, session.kaltura_flavors[videoRes]);\n video.setAttribute('src', new_video_source);\n // reload video with new source\n video.load();\n // set current time of video to previous time\n video.currentTime = currentTime;\n // set current volume of video to previous volume\n video.volume = volume;\n // set current playback rate of video to previous playback rate\n video.playbackRate = playbackRate;\n // set current muted state of video to previous muted state\n video.muted = muted;\n // play video\n let promise = video.play();\n if (promise !== undefined) {\n promise.catch(() => {\n // Auto-play was prevented\n if (videoPlayer) {\n videoPlayer.classList.remove('hidden');\n }\n }).then(() => {\n // Auto-play started\n if (videoPlayer) {\n videoPlayer.classList.remove('hidden');\n }\n });\n }\n },\n toggleCCAction: function (item) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let ccButton = video_player.querySelector('.video-cc-toggle');\n let video = video_player.querySelector('video');\n if (!video) {\n return;\n }\n if (video.textTracks.length > 0) {\n video.textTracks[0].mode = video.textTracks[0].mode === 'showing' ? 'hidden' : 'showing';\n ccButton.classList.toggle('active');\n }\n },\n jumpCueAction: function (item, prev) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n if (!video) {\n return;\n }\n let cues = this.videoTranscriptItems;\n if (!cues || cues.length === 0) {\n return;\n }\n let currentTime = video.currentTime;\n let cueIndex = Ember.$(cues).filter('.current').index();\n if (prev) {\n cueIndex--;\n } else {\n cueIndex++;\n }\n if (cueIndex < 0) {\n cueIndex = 0;\n } else if (cueIndex >= cues.length - 1) {\n cueIndex = cues.length - 1;\n }\n let timeAttr = cues[cueIndex].attributes['data-time'];\n let time = timeAttr ? timeAttr.value : 0;\n video.currentTime = time;\n },\n toggleFullScreenAction: function (item, exitFullScreen) {\n // check if video is in fullscreen mode\n if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) {\n if (document.exitFullscreen) {\n document.exitFullscreen();\n } else if (document.webkitExitFullscreen) {\n document.webkitExitFullscreen();\n } else if (document.mozCancelFullScreen) {\n document.mozCancelFullScreen();\n } else if (document.msExitFullscreen) {\n document.msExitFullscreen();\n }\n document.querySelectorAll('.video-player').forEach(function (videoPlayer) {\n videoPlayer.classList.remove('fullscreen');\n });\n return;\n }\n if (exitFullScreen) {\n return;\n }\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n if (!video_player) {\n return;\n }\n if (video_player.requestFullscreen) {\n video_player.requestFullscreen();\n } else if (video_player.mozRequestFullScreen) {\n video_player.mozRequestFullScreen();\n } else if (video_player.webkitRequestFullscreen) {\n video_player.webkitRequestFullscreen();\n } else if (video_player.msRequestFullscreen) {\n video_player.msRequestFullscreen();\n } else if (video_player.webkitEnterFullscreen) {\n video_player.webkitEnterFullscreen();\n } else if (video_player.enterFullscreen) {\n video_player.enterFullscreen();\n }\n document.querySelectorAll('.video-player').forEach(function (videoPlayer) {\n videoPlayer.classList.add('fullscreen');\n });\n },\n videoEnded: function (item) {\n // If the video ended, reset all the video player elements\n let video = item.target;\n if (!video) {\n return;\n }\n video.load();\n // A little hack to clear captions that get stuck on the screen at the end of the video when video.load() is called\n this.toggleCCAction(item);\n this.toggleCCAction(item);\n },\n //Kaltura Video Player\n playKalturaFromOb: function (item) {\n this.checkDom();\n let rect = item.target.parentElement.lastElementChild.getBoundingClientRect(),\n mainRect = document.querySelector('.main-panel').getBoundingClientRect(),\n cprnt = item.target[0].tagName.toLowerCase() === 'button' ? item.target : false,\n cpent = !cprnt && (item.target[0].tagName.toLowerCase() === 'img' || item.target[0].tagName.toLowerCase() === 'i') ? item.target.parentElement : item.target;\n cprnt = !cprnt && (item.target[0].tagName.toLowerCase() === 'img' || item.target[0].tagName.toLowerCase() === 'i') ? item.target.parentNode : item.target;\n\n //call closevideoplayer. Pass current context\n this.closeVideoPlayer();\n this.closeAudioPlayer();\n let inf = cprnt.querySelector('i');\n let video = document.querySelector('#kalturaPlayer');\n let vidWidth = rect.width + 'px';\n let vidHeight = rect.height + 'px';\n video.style.top = item.target.parentElement.offsetTop + 'px';\n video.style.height = vidHeight;\n video.style.left = rect.left - mainRect.left + 'px';\n video.style.width = vidWidth;\n this.audioPlayer.removeClass('active');\n document.querySelector('#kalturaPlayer').classList.remove('minified');\n if (inf.classList.contains('fa-window-restore')) {\n inf.classList.remove('fa-window-restore');\n this.minmaxVideoPlayer(document.querySelector('#kalturaPlayer'), false);\n return;\n }\n document.querySelectorAll('.video-asset .fa-window-restore, .kaltura-asset .fa-window-restore').forEach(function (item) {\n item.classList.remove('fa-window-restore');\n item.classList.remove('paused');\n item.classList.add('fa-play');\n });\n inf.classList.add('fa-window-restore');\n inf.classList.remove('fa-play');\n inf.classList.remove('paused');\n\n // Don't perform video movement on iPhone -- that goes straight to full screen\n if (!navigator.userAgent.match(/iPhone/i) && !navigator.userAgent.match(/iPod/i)) {\n video.classList.remove('minified');\n video.classList.add('active');\n\n // Create the intersection observer\n let options = {\n root: null,\n rootMargin: \"0px\",\n threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]\n };\n this.videoPlayerObserver = new IntersectionObserver(this.videoPlayerObserverCallback.bind(this), options);\n this.videoPlayerObserver.observe(video);\n this.mainPanel.addEventListener('resize', () => {\n let rect = cpent.getBoundingClientRect(),\n mainRect = document.querySelector('.main-panel').getBoundingClientRect();\n video.style.top = cpent.offsetTop + 'px';\n video.style.height = rect.height + 'px';\n video.style.left = rect.left - mainRect.left + 'px';\n video.style.width = rect.width + 'px';\n });\n }\n if (!item.target.classList.contains('paused')) {\n let kCont = document.getElementById('kalturaContainer');\n let source_id = cprnt.attributes.source.value;\n let account = cprnt.attributes.account.value;\n let player_id = cprnt.attributes.player_id.value;\n let player_multi = !!cprnt.attributes.multi;\n if (kCont.sendNotification) {\n kCont.sendNotification('changeMedia', {\n 'entryId': source_id\n });\n } else {\n try {\n if (player_multi) {\n kWidget.embed({\n targetId: 'kalturaContainer',\n flashvars: {\n 'autoPlay': true,\n 'autoMute': false,\n 'playlistAPI.kpl0Id': source_id\n },\n wid: '_' + account,\n uiconf_id: player_id\n });\n } else {\n kWidget.embed({\n targetId: 'kalturaContainer',\n flashvars: {\n 'autoPlay': true,\n 'autoMute': false\n },\n wid: '_' + account,\n uiconf_id: player_id,\n entry_id: source_id\n });\n }\n kWidget.addReadyCallback(playerId => {\n let kdp = document.getElementById(playerId);\n kdp.kBind('playerPaused', data => {\n this.videoPlayBtn(data, true);\n });\n kdp.kBind('playerPlayed', data => {\n this.videoPauseBtn(data, true);\n });\n kdp.kBind('doPlay', function () {\n kdp.sendNotification('changeVolume', 1);\n kdp.kUnbind('doPlay');\n });\n });\n } catch (e) {\n debug(e.message);\n }\n }\n }\n },\n videoPlayerObserverCallback: function (entries) {\n entries.forEach(entry => {\n if (!entry.isIntersecting && !entry.target.classList.contains('minified') && !this.get('minmaxing')) {\n // Check if video player is inside any hidden element parents\n let hidden = false;\n let parent = entry.target.parentElement;\n while (parent) {\n // if parent has a hidden class or is hidden inline, then we don't want to minimize\n if (parent.classList.contains('hidden') || parent.style.display === 'none') {\n hidden = true;\n break;\n } else if (parent.classList.contains('interaction_content')) {\n // If parent is an interaction, pause the video\n this.pauseVideoAction(entry);\n hidden = true;\n break;\n }\n parent = parent.parentElement;\n }\n if (hidden) {\n return;\n }\n this.minmaxVideoPlayer(entry.target, true);\n } else if (this.get('minmaxing')) {\n this.set('minmaxing', false);\n }\n });\n },\n minmaxVideoPlayer: function (item, min) {\n let video_player = item.classList ? item : item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let isFullScreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;\n if (video_player.classList.contains('minified') && !min && !isFullScreen) {\n this.set('minmaxing', true);\n video_player.classList.remove('minified');\n // Scroll video into view\n video_player.scrollIntoView(true);\n this.videoUnfocus = false;\n } else if (!video_player.classList.contains('minified') && !isFullScreen) {\n if (!video_player.classList.contains('paused')) {\n video_player.classList.add('minified');\n }\n }\n },\n videoPlayBtn: function (item, kaltura) {\n let video_player = item.target.closest('.video-player') || item.target.previousSibling.closest('.video-player');\n let video = video_player.querySelector('video');\n if (video_player) {\n video_player.classList.remove('paused');\n video_player.classList.add('playing');\n }\n if (kaltura) {\n let videoAssetPause = video_player.closest('.video_component, .video-component') ? [video_player.closest('.video_component, .video-component').querySelector('.kaltura-asset .fa-play')] : document.querySelectorAll('.kaltura-asset .fa-play');\n if (videoAssetPause && !!videoAssetPause[0]) {\n videoAssetPause.forEach(pause => {\n pause.classList.remove('fa-play');\n pause.classList.add('fa-window-restore');\n pause.classList.remove('paused');\n });\n }\n } else {\n let videoAssetPause = video_player.closest('.video_component, .video-component') ? [video_player.closest('.video_component, .video-component').querySelector('.video-asset .fa-play')] : document.querySelectorAll('.video-asset .fa-play');\n if (videoAssetPause && !!videoAssetPause[0]) {\n videoAssetPause.forEach(pause => {\n pause.classList.remove('fa-play');\n pause.classList.add('fa-window-restore');\n pause.classList.remove('paused');\n });\n }\n\n // set video-time-code-total to the duration of the video formatted as hh:mm:ss\n let video_time_code_total = video_player.querySelector('.video-time-code-total');\n video_time_code_total.innerHTML = this.formatTime(video.duration);\n }\n },\n videoPauseBtn: function (item, kaltura) {\n let video_player = item.target.closest('.video-player') || item.target.previousSibling.closest('.video-player');\n if (video_player) {\n video_player.classList.add('paused');\n video_player.classList.remove('playing');\n }\n if (kaltura) {\n let videoAssetPause = video_player.closest('.video_component, .video-component') ? [video_player.closest('.video_component, .video-component').querySelector('.kaltura-asset .fa-window-restore')] : document.querySelectorAll('.video-asset .fa-window-restore');\n if (videoAssetPause && !!videoAssetPause[0]) {\n videoAssetPause.forEach(pause => {\n pause.classList.add('fa-play');\n pause.classList.remove('fa-window-restore');\n pause.classList.add('paused');\n });\n }\n } else {\n let videoAssetPause = video_player.closest('.video_component, .video-component') ? [video_player.closest('.video_component, .video-component').querySelector('.video-asset .fa-window-restore')] : document.querySelectorAll('.video-asset .fa-window-restore');\n if (videoAssetPause && !!videoAssetPause[0]) {\n videoAssetPause.forEach(pause => {\n pause.classList.add('fa-play');\n pause.classList.remove('fa-window-restore');\n pause.classList.add('paused');\n });\n }\n }\n },\n rewindVideoAction: function (item) {\n // Rewind the video by 5 seconds\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n if (!video) {\n return;\n }\n video.currentTime -= 5;\n },\n forwardVideoAction: function (item) {\n // Fast-forward the video by 5 seconds\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n if (!video) {\n return;\n }\n video.currentTime += 10;\n },\n videoVolumeAction: function (item, volume) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n if (!video_player) {\n return;\n }\n let video = video_player.querySelector('video');\n if (!volume) {\n video.volume = item.target.value / 100;\n } else {\n let newVolume = video.volume + volume;\n if (newVolume > 1) {\n newVolume = 1;\n } else if (newVolume < 0) {\n newVolume = 0;\n }\n video.volume = newVolume;\n // Adjust the volume slider\n let volume_slider = video_player.querySelector('.volume-slider-input');\n volume_slider.value = video.volume * 100;\n }\n let volume_button = video_player.querySelector('.volume-button-mute');\n if (video.volume === 0) {\n volume_button.classList.add('fa-volume-off');\n volume_button.classList.remove('fa-volume');\n video.muted = true;\n } else {\n volume_button.classList.remove('fa-volume-off');\n volume_button.classList.add('fa-volume');\n video.muted = false;\n }\n },\n toggleVideoVolumeAction: function (item) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n if (!video) {\n return;\n }\n let mute_button = video_player.querySelector('.volume-button-mute');\n if (video.muted === true || video.volume === 0) {\n mute_button.classList.remove('fa-volume-off');\n mute_button.classList.add('fa-volume');\n video.muted = false;\n } else {\n mute_button.classList.remove('fa-volume');\n mute_button.classList.add('fa-volume-off');\n video.muted = true;\n }\n },\n playVideoAction: function (item) {\n let kCont = document.getElementById('kalturaContainer');\n if (kCont.sendNotification) {\n kCont.sendNotification('play');\n } else {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n let promise = video.play();\n if (promise !== undefined) {\n promise.catch(() => {\n // Auto-play was prevented\n }).then(() => {\n // Auto-play started\n });\n }\n }\n },\n pauseVideoAction: function (item) {\n let kCont = document.getElementById('kalturaContainer');\n if (kCont.sendNotification) {\n kCont.sendNotification('pause');\n } else {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n video.pause();\n }\n },\n videoPlayPauseAction: function (item) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n if (video_player) {\n if (video_player.classList.contains('paused')) {\n this.playVideoAction(item);\n } else {\n this.pauseVideoAction(item);\n }\n }\n },\n toggleLoopAction: function (item) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n if (!video) {\n return;\n }\n if (video.loop) {\n video.loop = false;\n item.target.classList.remove('active');\n } else {\n video.loop = true;\n item.target.classList.add('active');\n }\n },\n videoSeekEvent: function (item, time) {\n let video_player = item.target.closest('.video-player') || item.target.parentElement.querySelector('.video-player');\n let video = video_player.querySelector('video');\n if (time) {\n video.currentTime = video.currentTime + time;\n return;\n }\n video.currentTime = item.target.value * video.duration / 1000;\n }\n });\n});","define(\"bocce/mixins/boot\", [\"exports\", \"bocce/mixins/embed-parser\", \"bocce/mixins/support/render-template\", \"bocce/config/environment\", \"bocce/mixins/interactions/audio_comments\", \"bocce/mixins/interactions/audio_markers_quiz\", \"bocce/mixins/interactions/bpm_tap_pad\", \"bocce/mixins/interactions/content_container\", \"bocce/mixins/interactions/content_framing\", \"bocce/mixins/interactions/codec_comparison\", \"bocce/mixins/interactions/cycle_5\", \"bocce/mixins/interactions/fill_in_the_blanks_quiz\", \"bocce/mixins/interactions/drag_and_drop_quiz\", \"bocce/mixins/interactions/embedder\", \"bocce/mixins/interactions/flash_cards\", \"bocce/mixins/interactions/form_builder\", \"bocce/mixins/interactions/guitar\", \"bocce/mixins/interactions/hot_spot_quiz\", \"bocce/mixins/interactions/image_explorer\", \"bocce/mixins/interactions/javascript\", \"bocce/mixins/interactions/list_rollover\", \"bocce/mixins/interactions/mix_visualizer\", \"bocce/mixins/interactions/piano_quiz\", \"bocce/mixins/interactions/requirements\", \"bocce/mixins/interactions/reveal_content\", \"bocce/mixins/interactions/reveal_text_quiz\", \"bocce/mixins/interactions/rive\", \"bocce/mixins/interactions/simple_slideshow\", \"bocce/mixins/interactions/slideshow\", \"bocce/mixins/interactions/solfege_ladder\", \"bocce/mixins/interactions/sound_mixer\", \"bocce/mixins/interactions/text_line_and_text_select_hybrid\", \"bocce/mixins/interactions/timed_writing\", \"bocce/mixins/interactions/timeline\", \"bocce/mixins/interactions/timeline_knightlab\", \"bocce/mixins/interactions/view-or-take-a-survey\", \"bocce/mixins/interactions/video_commentary\", \"bocce/mixins/interactions/video_mixer\"], function (_exports, _embedParser, template, _environment, _audio_comments, _audio_markers_quiz, _bpm_tap_pad, _content_container, _content_framing, _codec_comparison, _cycle_, _fill_in_the_blanks_quiz, _drag_and_drop_quiz, _embedder, _flash_cards, _form_builder, _guitar, _hot_spot_quiz, _image_explorer, _javascript, _list_rollover, _mix_visualizer, _piano_quiz, _requirements, _reveal_content, _reveal_text_quiz, _rive, _simple_slideshow, _slideshow, _solfege_ladder, _sound_mixer, _text_line_and_text_select_hybrid, _timed_writing, _timeline, _timeline_knightlab, _viewOrTakeASurvey, _video_commentary, _video_mixer) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n\n // PRM: there's probably a better way to do this, comining the above imports\n // and the below hash, but I couldn't find it quickly.\n let interactions = {\n AudioComments: _audio_comments.default,\n AudioMarkersQuiz: _audio_markers_quiz.default,\n BpmTapPad: _bpm_tap_pad.default,\n ContentContainer: _content_container.default,\n ContentFraming: _content_framing.default,\n CodecComparison: _codec_comparison.default,\n Cycle5Explorer: _cycle_.default,\n DragAndDropQuiz: _drag_and_drop_quiz.default,\n FillInTheBlanksQuiz: _fill_in_the_blanks_quiz.default,\n Embedder: _embedder.default,\n FlashCards: _flash_cards.default,\n FormBuilder: _form_builder.default,\n Guitar: _guitar.default,\n HotSpotQuiz: _hot_spot_quiz.default,\n ImageExplorer: _image_explorer.default,\n Javascript: _javascript.default,\n ListRollover: _list_rollover.default,\n MixVisualizer: _mix_visualizer.default,\n PianoQuiz: _piano_quiz.default,\n Requirements: _requirements.default,\n RevealContent: _reveal_content.default,\n RevealTextQuiz: _reveal_text_quiz.default,\n Rive: _rive.default,\n SimpleSlideshow: _simple_slideshow.default,\n Slideshow: _slideshow.default,\n SolfegeLadder: _solfege_ladder.default,\n SoundMixer: _sound_mixer.default,\n TextLineAndTextSelectHybrid: _text_line_and_text_select_hybrid.default,\n TimedWriting: _timed_writing.default,\n Timeline: _timeline.default,\n TimelineKnightlab: _timeline_knightlab.default,\n TakeaSurvey: _viewOrTakeASurvey.default,\n ViewaSurvey: _viewOrTakeASurvey.default,\n VideoCommentary: _video_commentary.default,\n VideoMixer: _video_mixer.default\n };\n window.bm = window.bm || {};\n\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create(_embedParser.default, {\n session: Ember.inject.service(),\n boot_area: function ($doc_node, standalone_player, boot_interaction, mark_booted, skip_av, skip_parse) {\n // Interactions now need to return a promise to reflect when they're\n // done booting.\n let promises = [],\n that = this;\n\n // Tidal boot fn\n function bootTidal() {\n var scriptPath = 'https://embed.tidal.com/',\n embeds = document.querySelectorAll('.tidal-embed'),\n i,\n embed,\n related,\n token,\n custom,\n type,\n URLType,\n URLId;\n for (i = 0; i < embeds.length; i++) {\n embed = embeds[i];\n related = embed.getAttribute('data-related-id') || '';\n token = embed.getAttribute('data-token') || '';\n custom = embed.getAttribute('data-custom') || '';\n if (embed.getAttribute('data-initialized')) {\n continue;\n }\n embed.setAttribute('data-initialized', true);\n type = embed.getAttribute('data-type');\n if (type === 't') {\n URLType = 'tracks';\n } else if (type === 'p') {\n URLType = 'playlists';\n } else if (type === 'a') {\n URLType = 'albums';\n } else if (type === 'v') {\n URLType = 'videos';\n }\n URLId = embed.getAttribute('data-id');\n embed.innerHTML = ``;\n embed.id = 'tidal-embed-' + i;\n }\n }\n\n // Piano hover boot fn\n function bootPianoHover() {\n // Find all items of class piano-sample and attach an on click event to play them\n $doc_node.find('[class^=\"piano-note-\"]').click(function () {\n // Get piano-note-id from the data-piano-note-id attribute\n /* eslint-disable-next-line ember/no-jquery */\n var notes = Ember.$(this).attr('class').replace('piano-note-', '');\n notes = notes.split('-');\n if (!Array.isArray(notes)) {\n notes = [notes];\n }\n if (notes) {\n window.piano.playNotes(notes, 1000, true);\n }\n });\n }\n\n // Add other link parser commands here:\n if (!skip_parse) {\n // TODO: Make this work differently!\n // Replacing the HTML here breaks quiz grading, presumably due to breaking something\n // that Ember is expecting (or maybe we're doing it out of order and need to defer\n // until after rendering). Either way, even a $doc_node.html($doc_node.html()) call\n // here will cause quiz grades to appear as \"false\".\n if (!$doc_node.hasClass('Quiz')) {\n $doc_node.html(this.parseEmbed($doc_node.html(), 'tidal'));\n }\n addPdfPreview($doc_node);\n var embeds = document.querySelectorAll('.tidal-embed');\n var receiver = function (event) {\n if (event.data) {\n var message = event.data;\n if (typeof message === 'string') {\n try {\n message = JSON.parse(message);\n } catch (error) {\n return; // If invalid JSON it is not for us\n }\n }\n if (message && !isNaN(message.height) && !isNaN(message.pid)) {\n var el = document.querySelector('#tidal-embed-' + message.pid + ' iframe');\n if (el) {\n el.style.height = message.height + 'px';\n }\n } else if (message && !isNaN(message.activatePlayerId)) {\n for (var i = 0; i < embeds.length; ++i) {\n if (i === message.activatePlayerId) {\n continue;\n }\n embedPlayerSendCommand(i, 'pause');\n }\n }\n }\n };\n if (window.addEventListener) {\n window.addEventListener('message', receiver, false);\n } else if (window.attachEvent) {\n window.attachEvent('onmessage', receiver);\n }\n const embedPlayerSendCommand = function (pid, commandName) {\n var container = document.getElementById('tidal-embed-' + pid);\n if (container) {\n var playerIframe = container.getElementsByTagName('iframe')[0];\n if (playerIframe) {\n playerIframe.contentWindow.postMessage(JSON.stringify({\n 'commandName': commandName\n }), '*');\n }\n }\n };\n window.TIDAL = window.TIDAL || {};\n window.TIDAL.embedPlayerCommand = embedPlayerSendCommand;\n }\n bootTidal();\n bootPianoHover();\n if ($doc_node.parents('#about-grading, #grading-and-other-course-policies').length && !session.course.isGettingStarted && !session.course.isPublic) {\n addLateGradingPolicy($doc_node, session);\n }\n\n // We need to access the container to look up our various templates.\n template.set_container(Ember.getOwner(this));\n\n // Label SoundSlice, Wistia, Vimeo players to differentiate from YT\n $doc_node.find('.soundslice-player').closest('.video-component, .video_component').addClass('not-youtube');\n $doc_node.find('.wistia_embed').closest('.video-component, .video_component').addClass('not-youtube');\n $doc_node.find('.vimeo-player').closest('.video-component, .video_component').addClass('not-youtube');\n\n /*\n * PRM: This is the first thing we do so the popups will start with\n * raw HTML instead of the already processed HTML.\n */\n $doc_node.find('.popup-component, .popup_component').not('.booted').each(function (pop_ndx, pop_node) {\n /* eslint-disable-next-line ember/no-jquery */\n var $pop_node = Ember.$(pop_node),\n $anchor = $pop_node.find('>a'),\n $anchorHtml = $anchor.html(),\n $nodeContent;\n $pop_node.addClass('booted');\n $nodeContent = $pop_node.find('>div').removeAttr('style').addClass('popup-content');\n $anchor.replaceWith('
' + $anchorHtml + '
');\n $pop_node.find('.popup-label').on('click', function () {\n $nodeContent.children().clone(true, true).appendTo('.popup-container .content');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-container').addClass('active');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-container').attr('closeId', $pop_node.attr('id'));\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-backdrop').addClass('active');\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$('.side-panel').length > 0) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').addClass('on-modal');\n }\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').addClass('no-scroll');\n /* eslint-disable-next-line ember/no-jquery */\n that.boot_area(Ember.$('.popup-container .content'), true, true, false, false);\n });\n });\n $doc_node.find('.image-component, .image_component').not('.booted').each(function (pop_ndx, img_node) {\n /* eslint-disable-next-line ember/no-jquery */\n var $img_node = Ember.$(img_node),\n $img = $img_node.find('img'),\n imgWidth = $img.length > 0 ? $img.attr('width') : false;\n\n // Use $doc_node.width() as another check before adding the zoomable feature. In some cases, such as the\n // assignment modal the width is zero, so the test is otherwise always false.\n let dn_width = $doc_node.width();\n if (imgWidth && dn_width && imgWidth > dn_width) {\n $img_node.on('click', function () {\n $img_node.clone(false, false).appendTo('.popup-container .content');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-container').addClass('active');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-container').attr('closeId', $img_node.attr('id'));\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.popup-backdrop').addClass('active');\n /* eslint-disable-next-line ember/no-jquery */\n if (Ember.$('.side-panel').length > 0) {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.side-panel').addClass('on-modal');\n }\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.main-panel').addClass('no-scroll');\n });\n $img_node.addClass('zoomable');\n }\n $img_node.addClass('booted');\n });\n if (!skip_av) {\n $doc_node.find('.player.audio:not(.booted)').not('.popup-content .player.audio').each(function (ndx, node) {\n /* eslint-disable-next-line ember/no-jquery */\n var $node = Ember.$(node),\n /* eslint-disable-next-line ember/no-jquery */\n title = Ember.$(node.parentElement).find('.component_caption').text().replace(/\"/g, '"'),\n source = get_html_attribute($node, 'source');\n if (!$node.hasClass('booted')) {\n if (!source) {\n source = $node.find('.data-field.source').text();\n }\n\n // If an audio player is in an interaction, render it directly.\n if ($node.parents('.interaction-component, .interaction_component').length > 0 || !!standalone_player) {\n $node.html(``);\n } else {\n $node.html(``);\n }\n $node.addClass('booted');\n }\n });\n $doc_node.find('.player.audio_multi:not(.booted)').not('.popup-content .player.audio_multi').each(function (ndx, node) {\n if (!window.bm.counter) {\n window.bm.counter = 0;\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n let $nodeBase = Ember.$(node),\n $node = $nodeBase.find('.track'),\n attrs = ['track', 'source', 'title', 'artist', 'composer', 'album', 'label', 'year', 'image'],\n vals = {},\n uniqueId = $nodeBase.attr('id') + window.bm.counter,\n i,\n trackNodes,\n prev,\n next;\n window.bm.counter = parseInt(window.bm.counter) + 1;\n attrs.forEach(attr => {\n vals[attr] = $node.find('.' + attr).map(function () {\n return this.textContent;\n }).get();\n });\n trackNodes = '';\n for (i = 0; i < vals['source'].length; i++) {\n if (i > 0) {\n prev = 'prev=\"track-' + uniqueId + '-' + (i - 1) + '\"';\n } else {\n prev = '';\n }\n if (i + 1 < vals['source'].length) {\n next = 'next=\"track-' + uniqueId + '-' + (i + 1) + '\"';\n } else {\n next = '';\n }\n trackNodes += '';\n //trackNodes += '';\n trackNodes += '';\n trackNodes += '
';\n\n // For each track info type, insert the value if it exists\n trackNodes += '
');\n $nodeBase.addClass('booted');\n });\n\n // copy to clipboard function; makes 'copy to clipboard' functionality out of text style\n $doc_node.find('.click_to_copy').each(function (ndx, node) {\n /* eslint-disable-next-line ember/no-jquery */\n var $node = Ember.$(node);\n var text_to_copy = node.innerText;\n $node.html('
Copied to clipboard.
');\n var ctc_text = node.firstChild,\n ctc_button = node.children[1],\n ctc_toggletip = $node.find('#ctc_toggletip');\n ctc_text.append(text_to_copy);\n ctc_button.addEventListener('click', function () {\n var range = document.createRange();\n range.selectNode(ctc_text);\n window.getSelection().addRange(range);\n try {\n var successful = document.execCommand('copy');\n var msg = successful ? 'Copied to clipboard.' : 'Sorry, unable to copy.';\n ctc_toggletip.text(msg);\n // pop tooltip here\n ctc_toggletip.addClass('show');\n window.setTimeout(function () {\n ctc_toggletip.removeClass('show');\n }, 2500);\n } catch (err) {\n alert('Sorry, unable to copy.');\n }\n window.getSelection().removeAllRanges();\n });\n });\n $doc_node.find('.player.video:not(.booted), .kaltura_video').each((ndx, node) => {\n /* eslint-disable-next-line ember/no-jquery */\n var $node = Ember.$(node);\n var kaltura = $node[0].classList.value.split('kaltura_embed_');\n var src = get_html_attribute($node, 'source');\n var poster = get_html_attribute($node, 'poster');\n var track = get_html_attribute($node, 'caption');\n var account_name = get_html_attribute($node, 'account');\n var account = account_name && account_name === 'campus' ? 730212 : 2588802;\n var player_id = get_html_attribute($node, 'account') || 44413892;\n var audioMulti = '';\n var audioClass = '';\n var isMultiplayer = false;\n var node_id = node.id || $node.parents(\".component\")[0].id;\n var standaloneString = standalone_player ? 'standalone=\"true\"' : '';\n var uncompressedString = get_html_attribute($node, 'source') ? 'uncompressed=\"true\"' : '';\n\n // TODO -- If we find any other interactions that should not have their videos pre-booted, add them to the find method below\n /* eslint-disable-next-line ember/no-jquery */\n if ($node.parents('.interaction_component').find('.ImageExplorer').length > 0 && !standalone_player) {\n return;\n }\n\n // Mark all non-YT videos accordingly\n $node.closest('.video-component, .video_component').addClass('not-youtube');\n if (kaltura.length > 1) {\n var kaltura_classlist = $node[0].classList;\n kaltura = kaltura_classlist[1].split('kaltura_embed_')[1];\n src = kaltura;\n poster = 'https://cdnsecakmi.kaltura.com/p/2588802/thumbnail/entry_id/' + kaltura + '/width/1000';\n if (kaltura_classlist.length > 2) {\n let player_id_raw = kaltura_classlist[2];\n let account_name_raw = kaltura_classlist[3];\n player_id = player_id_raw ? player_id_raw.split('kaltura_player_id_')[1] || 44413892 : 44413892;\n account_name = account_name_raw ? account_name_raw.split('kaltura_account_')[1] : false;\n account = account_name && account_name === 'campus' ? 730212 : 2588802;\n if (player_id === '15224161') {\n audioClass = 'is-audio-player';\n } else if (player_id === '14255301') {\n audioMulti = 'multi=\"true\"';\n isMultiplayer = true;\n audioClass = 'is-multi-player';\n }\n }\n } else {\n kaltura = false;\n }\n if ($node.parents('.interaction_component, .interaction-component').find('.VideoMixer').length > 0) {\n $node.addClass('booted');\n $node.html('`);\n } else if (kaltura.length > 0) {\n if (!standalone_player && audioClass.length > 0) {\n $node.html(``);\n } else if (isMultiplayer) {\n $node.addClass('booted');\n $node.html(``);\n kWidget.embed({\n targetId: `kaltura-embed-${node_id}`,\n flashvars: {\n 'autoPlay': false,\n 'playlistAPI.kpl0Id': src\n },\n wid: '_' + account,\n uiconf_id: player_id\n });\n } else {\n $node.addClass('booted');\n $node.html(``);\n }\n } else {\n $node.addClass('booted');\n $node.html(``);\n }\n });\n }\n\n /*\n * Generalized interaction boot sequence. This looks for interaction divs\n * from the CMS. Each interaction block must have a child div with\n * class \"interaction-type\" and a data-value with the name of its class.\n * Beyond that, it may have any number of \"interaction-init\" and\n * \"interaction-asset\" child divs. For \"init\"s, data-field should be the name\n * of the configuration setting and data-value its value. For \"asset\"s,\n * data-field should be the asset array name, and data-value the path to the\n * asset to append to that array. You can have many \"asset\" divs for the\n * same asset array.\n */\n\n // Preboot Requirements and any future interactions that need to be pre-booted here\n $doc_node.find('.interaction-component, .interaction_component').not('.interaction_booted').find('.interaction_content.Requirements, .interaction-content.Requirements').each((int_ndx, int_node) => {\n let parentNode = Ember.$(int_node).closest('.interaction-component, .interaction_component').first();\n let newPromises = bootInteraction($doc_node, int_ndx, parentNode, true, true);\n promises.concat(newPromises);\n });\n $doc_node.find('.interaction-component, .interaction_component').not('.popup-content .interaction-component, .popup-content .interaction_component, .interaction_booted').sort((node_a, node_b) => {\n // Process deeper nodes first\n /* eslint-disable-next-line ember/no-jquery */\n let a = Ember.$(node_a).parents().length,\n /* eslint-disable-next-line ember/no-jquery */\n b = Ember.$(node_b).parents().length;\n if (a === b) {\n return 0;\n }\n if (a < b) {\n return 1;\n }\n return -1;\n }).each((int_ndx, int_node) => {\n let newPromises = bootInteraction($doc_node, int_ndx, int_node, boot_interaction, mark_booted, Ember.$.isMobile);\n promises.concat(newPromises);\n });\n return promises;\n }\n });\n /*\n * Canvas eats HTML data attributes for breakfast. Thids lets us use\n * raw HTML attributes in a similar way.\n */\n function get_html_attribute($parent, name) {\n return $parent.find('span.' + name).first().text();\n }\n function bootInteraction($doc_node, int_ndx, int_node, preboot, mark_booted, isMobile) {\n let $int_node = Ember.$(int_node);\n let promises = [];\n if (!preboot) {\n if (isMobile) {\n $int_node.addClass('mobile_interaction');\n /* eslint-disable-next-line ember/no-jquery */\n $int_node.prepend(Ember.$(''));\n $int_node.find('.int-prep').on('click', function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.interaction_content, .interaction-content').html('');\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.int-prep').removeClass('hidden');\n /* eslint-disable-next-line ember/no-jquery */\n let interactionRaw = Ember.$(this.parentElement).find(\".interaction_data\").html();\n let interactionID = this.parentElement.id;\n // use the boot_area function to boot the interaction\n bootInteraction($doc_node, int_ndx, int_node, true);\n Ember.$(`#${interactionID} .interaction_data`).html(interactionRaw);\n });\n } else {\n /* eslint-disable-next-line ember/no-jquery */\n $int_node.prepend(Ember.$('
Loading interaction...
'));\n }\n return;\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n let data = {\n assets: {},\n config: {}\n },\n type = $int_node.find('.interaction-type:first');\n $int_node.addClass('interaction_prebooted');\n if (type.text() === 'TimedAssignment') {\n $int_node.replaceWith('');\n return;\n }\n if (!$int_node.hasClass('mobile_interaction') || mark_booted) {\n $int_node.addClass('interaction_booted');\n }\n $int_node.find('.int-prep').addClass('hidden');\n if (!type.length) {\n /* eslint-disable-next-line ember/no-jquery */\n $int_node.replaceWith(Ember.$('
Unmigrated interaction: Old style initialization
'));\n return;\n }\n type = type.text();\n $int_node.find('.interaction-asset').each(function (ndx, ast) {\n /* eslint-disable-next-line ember/no-jquery */\n var $ast = Ember.$(ast),\n field = get_html_attribute($ast, 'field');\n data.assets[field] = [];\n $ast.find('span.value').each(function (val_ndx, val) {\n /* eslint-disable-next-line ember/no-jquery */\n data.assets[field].push(Ember.$(val).text());\n });\n });\n $int_node.find('.interaction-config').each(function (ndx, cfg) {\n /* eslint-disable-next-line ember/no-jquery */\n var $cfg = Ember.$(cfg),\n field = get_html_attribute($cfg, 'field'),\n val = get_html_attribute($cfg, 'value');\n data.config[field] = val;\n });\n $int_node.find('.interaction-init').each(function (ndx, val) {\n /* eslint-disable-next-line ember/no-jquery */\n var $val = Ember.$(val),\n field = get_html_attribute($val, 'field');\n data[field] = get_html_attribute($val, 'value');\n });\n if (Object.prototype.hasOwnProperty.call(interactions, type)) {\n try {\n let interaction = new interactions[type]($int_node, data),\n promise = interaction.init();\n if (promise) {\n promises.push(promise);\n }\n } catch (e) {\n /* eslint-disable-next-line ember/no-jquery */\n $int_node.replaceWith(Ember.$('
'));\n }\n return promises;\n }\n function addPdfPreview($doc_node) {\n //Wraps all pdf links in a wrapper div and adds a preview button next to it. Preview\n //button downloads the pdf and shows it inside of an iframe.\n $doc_node.find('a[href*=\".pdf?\"]').each(function (index, node) {\n let url = node.href;\n let linkWrapper = Ember.$('');\n\n //Clone the matched link so that it can later be deleted without affecting the wrapper.\n linkWrapper.append(Ember.$(this).clone());\n const previewButton = Ember.$(`\n {\n if (e.target.status == 200 && e.loaded === e.total) {\n let blob = e.target.response;\n url = window.URL.createObjectURL(blob);\n Ember.$(this).after(``);\n Ember.$(this).data('previewState', state.SHOWN);\n Ember.$(this).find('.preview-pdf-text').text('Hide Preview');\n }\n };\n xhr.send();\n } else if (previewState == state.SHOWN) {\n //PDF preview is being hidden\n linkWrapper.find('.syllabus-content-preview').hide();\n Ember.$(this).find('.preview-pdf-text').text('Show Preview');\n Ember.$(this).data('previewState', state.HIDDEN);\n } else if (previewState == state.HIDDEN) {\n //PDF has been shown and then hidden, and \n //is going to be shown again. It has already been downloaded,\n //no need to do it again. Simply show the preview.\n linkWrapper.find('.syllabus-content-preview').show();\n Ember.$(this).find('.preview-pdf-text').text('Hide Preview');\n Ember.$(this).data('previewState', state.SHOWN);\n }\n });\n linkWrapper.append(previewButton);\n Ember.$(this).after(linkWrapper);\n node.remove();\n });\n }\n function addLateGradingPolicy($doc_node, session) {\n if ($doc_node.find('.late-grading-policy-syllabus').length === 0) {\n //These selectors are areas of the canvas html after which the grading policy belongs, arranged in\n //order of preference. The loop executes for only the first selector that matches.\n for (const selector of ['.callout-grades-body', '.gradingTable']) {\n if ($doc_node.find(selector).length) {\n //Prepare the html. The
tags will be filled in with $().text() so that the\n //late grading policy is escaped.\n const lateGradingPolicyHtmlPlaceholder = `
Instructor Late Grading Policy
\n ${(session.lateGradingPolicy || _environment.default.APP.text.noLateGradingPolicy\n\n //Splitting by new line lets us iterate through all the lines in the policy, empty lines and all.\n ).split(/[\\r]?\\n/).map((value, i) => {\n //If value is defined, that means there is content on this line\n if (value) {\n return `
${value}
`;\n } else {\n //else, this is purely an empty line. Add an empty-line div.\n return '';\n }\n }).join('')}`;\n Ember.$(`${lateGradingPolicyHtmlPlaceholder}`).insertAfter($doc_node.find(selector));\n\n //Fill in the
tags with escaped text.\n (session.lateGradingPolicy || _environment.default.APP.text.noLateGradingPolicy).split(/[\\r]?\\n/).forEach((t, i) => {\n if (t) {\n Ember.$(`.body.late-grading-policy-line${i}`).text(t);\n }\n });\n break;\n }\n }\n }\n }\n});","define(\"bocce/mixins/calendar-events\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create({\n actions: {\n addCalendarEvent: function (itemName, itemDate_arg) {\n var url_chunk = 'https://www.google.com/calendar/render?action=TEMPLATE&text=' + itemName + '&location=Berklee+online&calendar-label=Discussion&dates=',\n itemDate = new Date(itemDate_arg.valueOf() + itemDate_arg.getTimezoneOffset() * 60000),\n yyyy = itemDate.getFullYear().toString(),\n MM = ('0' + (itemDate.getMonth() + 1).toString()).slice(-2),\n dd = ('0' + itemDate.getDate().toString()).slice(-2),\n hh = ('0' + itemDate.getHours()).slice(-2),\n mm = ('0' + itemDate.getMinutes()).slice(-2),\n ss = ('0' + itemDate.getSeconds()).slice(-2),\n formattedDate_init = yyyy + MM + dd + 'T' + hh + mm + ss + 'Z',\n formattedDate = formattedDate_init + '/' + formattedDate_init,\n url = url_chunk + formattedDate + '&details=' + encodeURIComponent('https://canvas.online-dev.berklee.edu/' + event.target.closest('a').getAttribute('href'));\n window.open(url, '_blank');\n }\n }\n });\n});","define(\"bocce/mixins/conversable\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create({\n viewConversationdashboard: function (conversation_id) {\n let conversationController = this.conversation;\n if (!conversationController) {\n console.error('conversationController not found');\n return;\n }\n return this.store.findRecord('conversation', conversation_id, {\n reload: true\n }).then(conversation => {\n conversationController.set('model', conversation);\n Ember.run.schedule('afterRender', this, () => {\n // Scroll .modal-content to bottom\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.modal-content').scrollTop(Ember.$('.modal-content')[0].scrollHeight);\n this.set('conversation.dashboard.messageListOn', false);\n\n // Uncheck #convo-new-release\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#convo-new-release').prop('checked', false);\n });\n });\n },\n newConversationdashboard: function (user_id_string) {\n if (!user_id_string) {\n return;\n }\n let user_ids = user_id_string.toString().split('-');\n let users = user_ids.map(id => {\n return this.store.findRecord('user', id);\n });\n users = Promise.all(users);\n users.then(users => {\n if (users) {\n let group_conversation = user_ids.length > 2 ? true : false;\n let conversation = this.store.createRecord('conversation', {\n recipients: user_ids,\n private: !group_conversation,\n conversationPartners: users,\n group_conversation\n });\n let conversationController = this.conversation;\n conversationController.set('model', conversation);\n this.set('conversation.dashboard.messageListOn', false);\n\n // Uncheck #convo-new-release\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('#convo-new-release').prop('checked', false);\n }\n });\n },\n actions: {\n createConversation: function (recipient_id) {\n if (this.target.currentRouteName === \"dashboard\") {\n this.newConversationdashboard(recipient_id);\n } else {\n this.transitionToRoute('classroom.lessons.conversation-new-with', recipient_id);\n }\n },\n viewConversation: function (conversation_id) {\n if (this.target.currentRouteName === \"dashboard\") {\n this.viewConversationdashboard(conversation_id);\n } else {\n this.transitionToRoute('classroom.lessons.conversation', conversation_id);\n }\n },\n toggleConversationStar: function (conversation_id) {\n this.store.findRecord('conversation', conversation_id).then(conversation => {\n let starred = !conversation.get('starred');\n conversation.set('starred', starred);\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$.ajax({\n type: 'put',\n url: `/interface/conversations/${conversation_id}`,\n data: JSON.stringify({\n conversation: {\n starred\n }\n }),\n success: () => {\n // debug('Success starring conversation');\n },\n error: error => {\n Ember.debug('Error starring conversation');\n Ember.debug(error);\n }\n });\n });\n }\n }\n });\n});","define(\"bocce/mixins/discussable\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create({\n activityMessage: Ember.computed('message', function () {\n var txt = this;\n return txt;\n }),\n postTime: Ember.computed('date', function () {\n var retVal = moment(this.date).tz('America/New_York').format('MMMM Do, h:mmA') + ' ET';\n return retVal;\n }),\n /* eslint-disable-next-line ember/require-computed-property-dependencies */\n postAuthor: Ember.computed('message', function () {\n var retVal = this.user;\n return retVal;\n }),\n // Dead code?\n // Track whether or not a model is set\n // Set up/break down modal accordingly\n /* eslint-disable-next-line ember/no-observers */\n toggleModal: Ember.observer('model', 'activeNewDiscussion', 'classroom.activeNewDiscussion', function () {\n var mod = this.model || {},\n /* eslint-disable-next-line ember/no-get */\n and = this.activeNewDiscussion || this.get('classroom.activeNewDiscussion');\n let setupCameraTag = () => {\n if (window.CameraTag) {\n window.CameraTag.setup();\n }\n };\n\n // TODO: EUGENE - Remove this for Winter 25. This is strictly to fix old-style Tidal embeds\n let convertTidalEmbed = src => {\n // Extract parameters from src URL\n const urlParams = new URLSearchParams(src.split('?')[1]);\n const type = urlParams.get('type');\n const id = urlParams.get('id');\n\n // Define mapping between type parameters and full words\n const typeMapping = {\n t: 'tracks',\n v: 'videos',\n p: 'playlists',\n a: 'albums'\n };\n\n // If type is not recognized or id is not available, return null\n if (!type || !id) {\n return null;\n }\n\n // If type is recognized, construct new URL\n if (typeMapping[type]) {\n const newURL = `https://embed.tidal.com/${typeMapping[type]}/${id}`;\n return newURL;\n } else {\n return null; // Return null if type is not recognized\n }\n };\n if (and) {\n if (and === 'discussion') {\n and = 'conversation';\n }\n this.set('discussion.inEditor', true);\n\n /* eslint-disable-next-line ember/no-incorrect-calls-with-inline-anonymous-functions */\n Ember.run.scheduleOnce('afterRender', this, function () {\n setupCameraTag();\n let tidalEmbeds = Ember.$('.floating-modal iframe[src*=\"embed.tidal.com/player\"]');\n tidalEmbeds.each((index, el) => {\n let src = Ember.$(el).attr('src');\n let newSrc = convertTidalEmbed(src);\n if (newSrc) {\n Ember.$(el).attr('src', newSrc);\n }\n });\n });\n } else if (mod.id) {\n // There is a \"read\" status for conversations but disabling this\n // for conversations for now. - JRW\n if (mod.constructor.modelName !== 'conversation') {\n this.mark_read(mod);\n setupCameraTag();\n\n /* eslint-disable-next-line ember/no-incorrect-calls-with-inline-anonymous-functions */\n Ember.run.scheduleOnce('afterRender', this, function () {\n /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.floating-modal video').attr('preload', 'none');\n let tidalEmbeds = Ember.$('.floating-modal iframe[src*=\"embed.tidal.com/player\"]');\n tidalEmbeds.each((index, el) => {\n let src = Ember.$(el).attr('src');\n let newSrc = convertTidalEmbed(src);\n if (newSrc) {\n Ember.$(el).attr('src', newSrc);\n }\n });\n });\n }\n } else {\n this.set('discussion.inEditor', false);\n this.set('discussion.isLesson', false);\n this.set('discussion.bodyInput', '');\n this.set('bodyInput', '');\n this.set('titleInput', '');\n }\n this.discussion.send('clearAllFiles');\n }),\n mark_read: function (model) {\n model.set('read', true);\n if (model.get('hasUnreadResponses')) {\n model.set('markReadThrough', model.get('lastResponseId'));\n }\n model.save();\n },\n actions: {\n treatAssignment: function (id) {\n var that = this;\n this.store.findRecord('assignment', id).then(function (assignment) {\n var submission;\n if (that.session.get('isInstructor') || that.session.get('isObserver')) {\n submission = assignment.get('submissions.content.currentState');\n if (submission.length > 0) {\n submission = submission[0].id;\n } else {\n submission = false;\n }\n if (submission) {\n that.send('viewModal', 'submission', submission);\n } else {\n that.send('viewModal', 'no-submissions', assignment.id);\n }\n } else {\n if (that.session.get('course.isReadOnly')) {\n submission = assignment.get('currentUserSubmission');\n if (submission) {\n that.send('viewModal', 'submission', submission);\n } else {\n that.send('viewModal', 'submission-new', assignment.id);\n }\n } else {\n that.store.nestResources('submission', [{\n section: that.session.section.id\n }, {\n assignment: assignment.id\n }]);\n that.store.findAll('submission', {\n reload: true\n }).then(submissions => {\n submission = submissions.find(s => {\n let mine = s.get('my_submission');\n let correct_assignment = s.get('assignment.id') === assignment.id;\n return mine && correct_assignment;\n });\n if (submission) {\n that.send('viewModal', 'submission', submission.id);\n } else {\n that.send('viewModal', 'submission-new', assignment.id);\n }\n }, () => {\n that.send('viewModal', 'submission-new', assignment.id);\n });\n }\n }\n });\n },\n subscribe: function () {\n /* eslint-disable-next-line ember/no-get */\n var model = this.get('discussion.model');\n model.set('subscribed', !model.get('subscribed'));\n model.save();\n }\n }\n });\n});","define(\"bocce/mixins/editable\", [\"exports\", \"bocce/mixins/embed-parser\", \"bocce/helpers/upload\", \"sanitize-html\", \"bocce/utilities/dialog\"], function (_exports, _embedParser, _upload, _sanitizeHtml, _dialog) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n /* eslint-disable-next-line ember/no-mixins */\n\n // html sanitizer\n\n function sanitize(string) {\n const sanitizeHtmlOptions = {\n // options for the sanitize-html package. see docs:\n // https://www.npmjs.com/package/sanitize-html#htmlparser2-options\n allowedTags: _sanitizeHtml.default.defaults.allowedTags.concat(['h2', 'u']),\n transformTags: {\n 'h1': 'h2',\n // Word's header is h1, but our header is h2\n 'a': function (tagName, attribs) {\n // filter out useless links because apparently the geniuses\n // at Microsoft decided to start including THOSE now\n if (!attribs['href'] || attribs['href'] === '') {\n return false;\n } else {\n return {\n tagName: tagName,\n attribs: {\n href: attribs.href\n }\n };\n }\n }\n }\n };\n return (0, _sanitizeHtml.default)(string, sanitizeHtmlOptions);\n }\n\n /* eslint-disable-next-line ember/no-new-mixins */\n var _default = _exports.default = Ember.Mixin.create(_embedParser.default, {\n gainsight: Ember.inject.service(),\n inEditor: false,\n bodyInput: '',\n replyText: '',\n replyAuthor: '',\n file_ids: [],\n encoding_videos: Ember.A([]),\n replyId: false,\n postable: Ember.computed('bodyInputHasContent', 'attachmentsUploading', 'model', function () {\n let bodyInput = this.bodyInputHasContent;\n return bodyInput && !this.attachmentsUploading;\n }),\n mobileUpload: Ember.computed(function () {\n /* eslint-disable-next-line ember/no-jquery */\n return Ember.$.isMobile;\n }),\n totalFiles: Ember.computed('files.@each.deleted', function () {\n var length = this.files.filterBy('deleted', false).filterBy('valid', true).length;\n if (length === 0) {\n length = false;\n }\n return length;\n }),\n encodingVideosToContainer: Ember.computed('encoding_videos.[]', function () {\n return this.encoding_videos.slice(0).map(obj => {\n return {\n ...obj,\n file: {\n type: \"video\"\n }\n };\n });\n }),\n videoEmbedString: Ember.computed('files.@each.deleted', function () {\n var embeds = this.files.filterBy('deleted', false).filterBy('valid', true),\n string = '',\n i;\n for (i = 0; i < embeds.length; i++) {\n if (embeds[i].file.isUrl) {\n string += '
';\n string += '';\n string += '
' + embeds[i].file.created_at_formatted + '
';\n string += '
';\n }\n }\n return string;\n }),\n localArchive: Ember.computed('files.[]', function () {\n var array, obj, faved, unfaved;\n if (!localStorage.localArchive || localStorage.localArchive.length === 0) {\n return [];\n }\n try {\n obj = JSON.parse(localStorage.localArchive);\n } catch (err) {\n obj = {};\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n array = Ember.$.map(obj, function (value) {\n return [value];\n });\n array.reverse();\n\n /* eslint-disable-next-line ember/no-jquery */\n faved = Ember.$.grep(array, function (e) {\n return e.isFaved === true;\n });\n /* eslint-disable-next-line ember/no-jquery */\n unfaved = Ember.$.grep(array, function (e) {\n return e.isFaved !== true;\n });\n array = faved.concat(unfaved);\n return array;\n }),\n localDocs: Ember.computed(function () {\n var array, obj, faved, unfaved;\n if (!localStorage.localDocs || localStorage.localDocs.length === 0) {\n return [];\n }\n try {\n obj = JSON.parse(localStorage.localDocs);\n } catch (err) {\n obj = {};\n }\n\n /* eslint-disable-next-line ember/no-jquery */\n array = Ember.$.map(obj, function (value) {\n return [value];\n });\n array.reverse();\n\n /* eslint-disable-next-line ember/no-jquery */\n array = Ember.$.grep(array, function (e) {\n return e.name !== 'Auto Save';\n });\n\n /* eslint-disable-next-line ember/no-jquery */\n faved = Ember.$.grep(array, function (e) {\n return e.isFaved === true;\n });\n /* eslint-disable-next-line ember/no-jquery */\n unfaved = Ember.$.grep(array, function (e) {\n return e.isFaved !== true;\n });\n array = faved.concat(unfaved);\n return array;\n }),\n currentModel: Ember.computed.reads('model'),\n working: Ember.computed('files.@each.uploaded', function () {\n return this.files.filterBy('uploaded', false).length > 0;\n }),\n /* eslint-disable-next-line ember/no-observers */\n destroyer: Ember.observer('model', function () {\n if (!this.model) {\n if (this.files) {\n this.send('clearAllFiles');\n }\n this.send('destroyEditor');\n }\n }),\n // Grabs up files and uploads them immediately\n // Shoves returned ID into the fileIDs array\n /* eslint-disable-next-line ember/no-observers */\n uploadFiles: Ember.observer('files.[]', function () {\n let files = this.files;\n if (!this.store) {\n return;\n }\n for (let f of files) {\n this.uploadOneFile(f);\n }\n }),\n uploadOneFile(f) {\n if (!f || f.uploading || f.uploaded) {\n return;\n }\n let ftype = f.file.type.split('/')[0];\n f.uploaded_name = f.uploaded_name || f.file.name;\n let videoUploadCheck = Promise.resolve('Continue uploading');\n if (ftype === 'video' && f.file.type !== 'video/mp4' && !f.ignoreDownloadOnlyPrompt) {\n videoUploadCheck = (0, _dialog.default)('This file will be available for download only. Continue uploading or upload via video tool?', ['Continue uploading', 'Use video tool']);\n } else {\n videoUploadCheck = Promise.resolve('Continue uploading');\n }\n videoUploadCheck.then(choice => {\n if (choice === 'Use video tool') {\n this.send('toggleCameraPanel', false);\n this.files.removeObject(f);\n } else if (choice === 'Continue uploading') {\n if (!f.uploading && f.valid) {\n Ember.debug('Uploading ' + f.file.name);\n Ember.set(f, 'uploading', true);\n if (f.file.isUrl) {\n // File is a CameraTag Embed\n Ember.set(f, 'uploading', false);\n Ember.set(f, 'uploaded', true);\n return;\n }\n (0, _upload.default)(f.file, this.session.get('user.id'), this.store, /* eslint-disable-next-line ember/no-jquery */\n Ember.$('.existing-conversation').hasClass('active') ? 'conversation attachments' : null, null, progress => {\n Ember.set(f, 'percent_uploaded', progress);\n }).then(att_id => {\n return this.store.findRecord('attachment', att_id).then(() => {\n if (f && this.files.findBy('file', f.file)) {\n let ids = this.file_ids;\n ids.push({\n id: att_id\n });\n this.set('file_ids', ids);\n Ember.set(f, 'uploading', false);\n Ember.set(f, 'uploaded', true);\n Ember.set(f, 'uploaded_id', att_id);\n }\n });\n }, function (err) {\n Ember.debug(err, 'Could not upload file');\n alert(`\"${f.file.name}\" failed to upload. Please retry.`);\n this.files.removeObject(f);\n }.bind(f));\n }\n }\n });\n },\n attachmentsUploading: Ember.computed('files.[]', 'files.@each.uploaded', 'encoding_videos.[]', function () {\n /**\n * Return true if anything is currently uploading.\n */\n\n const uploadingFiles = this.files ? !!this.files.findBy('uploaded', false) : false;\n\n //We add to encoding_videos before uploading to Kaltura for instance, and we remove from it after the upload is done. So,\n //if there is anything in encoding_videos, this means that something is uploading.\n const videosAreEncoding = Array.isArray(this.encoding_videos) && this.encoding_videos.length;\n return uploadingFiles || videosAreEncoding;\n }),\n urlize: function (str) {\n return str.replace(/\\s+/g, '-').replace(/[^a-zA-Z0-9-]+/g, '_').toLowerCase();\n },\n bodyInputHasContent: Ember.computed('bodyInput', function () {\n // NK note to future self: Needing to handle all this at this stage\n // is ugly, but it gets the job done. A friendlier interface will\n // be to abstract away the sanitization/cleanup code within an\n // rte-editor Component, so that the Controller can just interact\n // with the bodyInput value without needing to do too much cleanup.\n\n const bodyInput = this.bodyInput || '';\n // cut out HTML\n let cleaned = sanitize(bodyInput);\n // remove line breaks\n cleaned = cleaned.replace(/ /g, '');\n // Remove the comment block that the browser puts in.\n cleaned = cleaned.replace(//g, '');\n // Remove spaces\n cleaned = cleaned.replace(/\\s/g, '');\n return cleaned.length > 0;\n }),\n hasNonDeletedAttachments: Ember.computed('files.@each.deleted', function () {\n const files = this.files || [];\n const hasNonDeleted = Boolean(files.find(file => file.valid && !file.deleted));\n return hasNonDeleted;\n }),\n editorHasContent: Ember.computed('bodyInputHasContent', 'hasNonDeletedAttachments', function () {\n const hasContent = this.bodyInputHasContent;\n const hasAttachments = this.hasNonDeletedAttachments;\n return hasContent || hasAttachments;\n }),\n actions: {\n promptAsRecovery: function (save) {\n (0, _dialog.default)('Looks like you closed your tab (or the application crashed) while you were writing something. Would you like to recover it?', ['Yes', 'No']).then(choice => {\n if (choice === 'Yes') {\n this.set('assetsRecovered', true);\n this.send('useArchivedText', save, true);\n this.send('deleteArchivedText', save);\n } else {\n this.set('assetsRecovered', true);\n this.send('deleteArchivedText', save);\n }\n });\n },\n editPost: function (post_id) {\n let pid = post_id ? '#discussion-response-' + post_id + ' .discussion-reply .discussion-reply-body' : '.first-post-body',\n /* eslint-disable-next-line ember/no-jquery */\n $bod = Ember.$('.floating-modal.active .modal-content ' + pid),\n /* eslint-disable-next-line ember/no-jquery */\n title = Ember.$('.floating-modal.active .modal-content .subject .discussion-title').html() || '',\n div = document.createElement('div'),\n vidString = '',\n whitelist = 'a, br, b, u, h2, ul, ol, li, p',\n vid,\n replyText,\n replyId,\n replyUser,\n i,\n reply,\n body;\n div.innerHTML = $bod.html();\n vid = div.getElementsByClassName('video-container');\n this.set('response_video', '');\n\n // Remove video embeds and stow them away to be added back\n // When reply edit is posted\n if (vid.length > 0) {\n for (i = 0; i < vid.length; i++) {\n vidString += '