From 7f7ade601b291d6875c2a11967a638f36011adac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20G=C3=A4rtner?= <andreas.gaertner@mni.thm.de> Date: Sun, 2 Oct 2016 20:26:43 +0200 Subject: [PATCH] Complete overhaul of question import/export buttons and handlers Modal panels have been replaced with Ext.MessageBox instances. Import and Export to arsnova.click have been added. The upload input is invisible and is triggered by button clicks. Messages and titles for modal popups have been replaced for flashcard variant. --- src/main/webapp/app/internationalization.js | 68 +++--- .../app/view/speaker/AudienceQuestionPanel.js | 201 ++++++++++++------ .../app/view/speaker/QuestionDetailsPanel.js | 12 +- .../webapp/resources/sass/app/_general.scss | 46 ++++ 4 files changed, 229 insertions(+), 98 deletions(-) diff --git a/src/main/webapp/app/internationalization.js b/src/main/webapp/app/internationalization.js index d44c394a8..0d2422873 100755 --- a/src/main/webapp/app/internationalization.js +++ b/src/main/webapp/app/internationalization.js @@ -373,6 +373,8 @@ ARE_YOU_SURE: "Sind Sie sicher?", DELETE_QUESTION_TITLE: "Frage löschen", DELETE_QUESTIONS_TITLE: "Fragen löschen", + DELETE_FLASHCARD_TITLE: "Lernkarte löschen", + DELETE_FLASHCARDS_TITLE: "Lernkarten löschen", DELETE_SESSION_TITLE: "Session löschen", DELETE_SESSION_NOTICE: "Es werden alle Fragen und Antworten der Session gelöscht", CATEGORY: "Thema", @@ -404,12 +406,16 @@ STATISTICS: "Statistiken", ALL_CORRECT: "Komplett richtig", ALL_WRONG: "Komplett falsch", - LEARNING_STATUS: "Lernstand des Kurses", + LEARNING_STATUS: "Lernstand des Kurses", DELETE_ALL: "Alle löschen", DELETE_ANSWERS_REQUEST: "Antworten löschen?", DELETE_ALL_ANSWERS_REQUEST: "Alle Antworten löschen?", + DELETE_VIEWS_REQUEST: "Ansichten löschen?", + DELETE_ALL_VIEWS_REQUEST: "Alle Ansichten löschen?", ALL_QUESTIONS_REMAIN: "Alle Fragen bleiben erhalten", + ALL_FLASHCARDS_REMAIN: "Alle Lernkarten bleiben erhalten", QUESTION_REMAINS: "Die Frage bleibt erhalten", + FLASHCARD_REMAINS: "Die Lernkarte bleibt erhalten", INCOMPLETE_INPUTS: "Ihre Eingaben sind unvollständig", DELETE_ALL_ANSWERS_INFO: "Es werden auch alle bisher gegebenen Antworten gelöscht", DELETE_ROUND_ANSWERS_COMPLETED: "Die Antworten der aktuellen Runde wurden gelöscht", @@ -793,18 +799,21 @@ IMP_ERROR_SAVE: "Session konnte nicht gespeichert werden", IMP_ERROR_IMAGE: "Session konnte nicht gespeichert werden. Bild überschreitet maximale Größe.", - /* CSV export*/ - QUESTIONS_CSV_EXPORT_BUTTON: "Fragen<br>exportieren", - QUESTIONS_CSV_EXPORT_MSBOX_TITLE: "Inhalte exportieren", - QUESTIONS_CSV_EXPORT_MSBOX_INFO: "Die Inhalte werden als CSV-Datei exportiert. Fragen vom Typ \"Hot Spots\" werden übersprungen. Für den gesamten Export der Session steht der Export auf der Sessionübersichtseite zur Verfügung. <br>Fragen exportieren?", - - /* CSV import*/ - QUESTIONS_CSV_IMPORT_BUTTON: "Fragen<br>importieren", - QUESTIONS_CSV_IMPORT_MSBOX_TITLE: "Inhalte importieren", - QUESTIONS_CSV_IMPORT_ERR_IN_ROW: "in Zeile", - QUESTIONS_CSV_IMPORT_TYPE_ERROR: "Ungültiger Fragentyp", - QUESTIONS_CSV_IMPORT_ABSTENTION_ERROR: "Fehler im Feld 'abstention'", - QUESTIONS_CSV_IMPORT_INVALID_FORMAT: "Ungültiges Dateiformat" + /* content export*/ + QUESTIONS_EXPORT_BUTTON: "Fragen<br>exportieren", + QUESTIONS_EXPORT_MSBOX_TITLE: "Inhalte exportieren", + QUESTIONS_EXPORT_MSBOX_INFO: "Die Inhalte können wahlweise als CSV-Datei oder für den Import in ARSnova-Cards als JSON-Datei exportiert werden. Fragen vom Typ \"Hot Spots\" werden übersprungen. Für den gesamten Export der Session steht der Export auf der Sessionübersichtseite zur Verfügung.", + ARSNOVA_CARDS: "ARSnova.cards", + CSV_FILE: "CSV-Datei", + + /* content import*/ + QUESTIONS_IMPORT_BUTTON: "Fragen<br>importieren", + QUESTIONS_IMPORT_MSBOX_TITLE: "Inhalte importieren", + QUESTIONS_IMPORT_ERR_IN_ROW: "in Zeile", + QUESTIONS_IMPORT_TYPE_ERROR: "Ungültiger Fragentyp", + QUESTIONS_IMPORT_ABSTENTION_ERROR: "Fehler im Feld 'abstention'", + QUESTIONS_IMPORT_INVALID_FORMAT: "Ungültiges Dateiformat", + FLASHCARDS_CHOOSE_SUBJECT: "Legen Sie ein Thema für das zu importierende Lernkarten-Set fest" }; switch (variation) { @@ -1218,6 +1227,8 @@ ARE_YOU_SURE: "Are you sure?", DELETE_QUESTION_TITLE: "Delete question", DELETE_QUESTIONS_TITLE: "Delete questions", + DELETE_FLASHCARD_TITLE: "Delete flashcard", + DELETE_FLASHCARDS_TITLE: "Delete flashcards", DELETE_SESSION_TITLE: "Delete session", DELETE_SESSION_NOTICE: "All questions and answers of this session will be deleted.", CATEGORY: "Subject", @@ -1253,12 +1264,16 @@ DELETE_ALL: "Delete all", DELETE_ANSWERS_REQUEST: "Delete answers?", DELETE_ALL_ANSWERS_REQUEST: "Delete all answers?", + DELETE_VIEWS_REQUEST: "Delete views?", + DELETE_ALL_VIEWS_REQUEST: "Delete all views?", DELETE_ROUND_ANSWERS_COMPLETED: "All answers of the actual round have been deleted.", RELEASE_LIVE_VOTING: "Release voting", CLOSE_LIVE_VOTING: "Freeze voting", QUESTION_REMAINS: "The question itself stays unaffected.", + FLASHCARD_REMAINS: "The flashcard itself stays unaffected.", INCOMPLETE_INPUTS: "Your inputs are incomplete.", ALL_QUESTIONS_REMAIN: "The questions themselves stay unaffected.", + ALL_FLASHCARDS_REMAIN: "The flashcards themselves stay unaffected", DELETE_ALL_ANSWERS_INFO: "This will also delete all previously given answers.", CHANGE_RELEASE: "Changing the release...", TYPE: 'Type', @@ -1644,18 +1659,21 @@ IMP_ERROR_SAVE: "Could not save session to database.", IMP_ERROR_IMAGE: "Could not save session, image exceeds maximal size.", - /* CSV export */ - QUESTIONS_CSV_EXPORT_BUTTON: "Export<br>questions", - QUESTIONS_CSV_EXPORT_MSBOX_TITLE: "Export content", - QUESTIONS_CSV_EXPORT_MSBOX_INFO: "The questions will be exported as a CSV file. \"Hot Spots\" questions won't be exported. For exporting the whole session please use the export function on the session overview.<br>Export questions?", - - /* CSV import */ - QUESTIONS_CSV_IMPORT_BUTTON: "Import<br>questions", - QUESTIONS_CSV_IMPORT_MSBOX_TITLE: "Import content", - QUESTIONS_CSV_IMPORT_ERR_IN_ROW: "at line", - QUESTIONS_CSV_IMPORT_TYPE_ERROR: "Invalid question type", - QUESTIONS_CSV_IMPORT_ABSTENTION_ERROR: "Error in field 'abstention'", - QUESTIONS_CSV_IMPORT_INVALID_FORMAT: "Invalid file format" + /* content export */ + QUESTIONS_EXPORT_BUTTON: "Export<br>content", + QUESTIONS_EXPORT_MSBOX_TITLE: "Export content", + QUESTIONS_EXPORT_MSBOX_INFO: "The content can be exported as a CSV file or preformatted for ARSnova.cards as a JSON file. \"Hot Spots\" questions won't be exported. For exporting the whole session please use the export function on the session overview.", + ARSNOVA_CARDS: "ARSnova.cards", + CSV_FILE: "CSV file", + + /* content import */ + QUESTIONS_IMPORT_BUTTON: "Import<br>content", + QUESTIONS_IMPORT_MSBOX_TITLE: "Import content", + QUESTIONS_IMPORT_ERR_IN_ROW: "at line", + QUESTIONS_IMPORT_TYPE_ERROR: "Invalid question type", + QUESTIONS_IMPORT_ABSTENTION_ERROR: "Error in field 'abstention'", + QUESTIONS_IMPORT_INVALID_FORMAT: "Invalid file format", + FLASHCARDS_CHOOSE_SUBJECT: "Enter a subject for the imported flashcard-set" }; switch (variation) { diff --git a/src/main/webapp/app/view/speaker/AudienceQuestionPanel.js b/src/main/webapp/app/view/speaker/AudienceQuestionPanel.js index 7d5afaece..8f9f8e126 100644 --- a/src/main/webapp/app/view/speaker/AudienceQuestionPanel.js +++ b/src/main/webapp/app/view/speaker/AudienceQuestionPanel.js @@ -50,9 +50,7 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { questions: null, newQuestionButton: null, - questionStore: null, - reader: new FileReader(), updateAnswerCount: { name: 'refresh the number of answers inside the badges', @@ -71,16 +69,6 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { var actionButtonCls = screenWidth < 410 ? 'smallerActionButton' : 'actionButton'; this.screenWidth = screenWidth; - this.reader.onload = function (event) { - ARSnova.app.getController('QuestionImport').importCsvFile(self.reader.result); - var field = self.loadFilePanel.query('filefield')[0]; - // field.setValue currently has no effect - Sencha bug? - field.setValue(null); - // Workaround: Directly reset value on DOM element - field.element.query('input')[0].value = null; - field.enable(); - }; - this.questionStore = Ext.create('Ext.data.JsonStore', { model: 'ARSnova.model.Question', sorters: { @@ -210,7 +198,7 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { }); this.questionsImport = Ext.create('ARSnova.view.MatrixButton', { - text: Messages.QUESTIONS_CSV_IMPORT_BUTTON, + text: Messages.QUESTIONS_IMPORT_BUTTON, buttonConfig: 'icon', imageCls: 'icon-cloud-upload', cls: 'actionButton', @@ -224,49 +212,113 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { centered: true }); - this.loadFilePanel = Ext.create('Ext.Panel', { - modal: true, - centered: true, - ui: 'light', - items: [ - { - xtype: 'toolbar', - docked: 'top', - title: Messages.QUESTIONS_CSV_IMPORT_MSBOX_TITLE, - ui: 'light', - items: [{ - xtype: 'spacer' - }, { - xtype: 'button', - ui: 'plain', - iconCls: 'delete', - iconMask: true, - text: '', - action: 'hideModal' + this.uploadField = Ext.create('Ext.ux.Fileup', { + xtype: 'fileupload', + autoUpload: true, + loadAsDataUrl: true, + cls: 'importFileField', + flex: 0, + listeners: { + loadsuccess: function (data) { + if (!Ext.os.is.iOS) { + // remove prefix and decode + var str = data.substring(data.indexOf("base64,") + 7); + data = decodeURIComponent(window.escape(atob(str))); + + if (self.getVariant() === 'flashcard') { + self.loadFilePanel.hide(); + if (this.importCsv) { + ARSnova.app.getController('FlashcardImport').importCsvFile(data); + } else if (this.importFlashcards) { + ARSnova.app.getController('FlashcardImport').importJsonFile(data); + } + } else { + ARSnova.app.getController('QuestionImport').importCsvFile(data); } - ] + + this.importCsv = false; + this.importFlashcards = false; + } }, - { - xtype: 'filefield', - accept: 'text/csv', - listeners: { - change: function (element, newValue, oldValue) { - // Workaround: The change event is triggered twice in Chrome - if (element.isDisabled()) { - return; - } - element.disable(); + loadfailure: function (message) {} + } + }); - var path = element.getValue(); - var fileType = path.substring(path.lastIndexOf('.')); - if (fileType === '.csv') { - var file = element.bodyElement.dom.firstElementChild.firstElementChild.files[0]; - self.reader.readAsText(file); - } - } + this.loadFilePanel = Ext.create('Ext.MessageBox', { + hideOnMaskTap: true, + cls: 'importExportFilePanel', + title: Messages.QUESTIONS_IMPORT_MSBOX_TITLE, + items: [{ + xtype: 'button', + iconCls: 'icon-close', + cls: 'closeButton', + handler: function () { this.getParent().hide(); } + }, { + xtype: 'container', + layout: 'hbox', + defaults: { + xtype: 'button', + cls: 'overlayButton', + ui: 'action', + scope: this, + flex: 1 + }, + items: [this.uploadField, { + text: Messages.CSV_FILE, + handler: function () { + this.uploadField.importCsv = true; + this.uploadField.fileElement.dom.accept = 'text/csv'; + this.uploadField.fileElement.dom.click(); } - } - ] + }, { + text: Messages.ARSNOVA_CARDS, + itemId: 'flashcardImportButton', + handler: function () { + this.uploadField.importFlashcards = true; + this.uploadField.fileElement.dom.accept = 'application/json'; + this.uploadField.fileElement.dom.click(); + } + }] + }] + }); + + this.exportFilePanel = Ext.create('Ext.MessageBox', { + hideOnMaskTap: true, + cls: 'importExportFilePanel', + title: Messages.QUESTIONS_EXPORT_MSBOX_TITLE, + items: [{ + xtype: 'button', + iconCls: 'icon-close', + cls: 'closeButton', + handler: function () { this.getParent().hide(); } + }, { + html: Messages.QUESTIONS_EXPORT_MSBOX_INFO, + cls: 'x-msgbox-text' + }, { + xtype: 'container', + layout: 'hbox', + defaults: { + xtype: 'button', + ui: 'action', + scope: this, + flex: 1 + }, + items: [{ + text: Messages.CSV_FILE, + handler: function () { + ARSnova.app.getController('QuestionExport') + .exportQuestions(this.getController()); + this.exportFilePanel.hide(); + } + }, { + text: Messages.ARSNOVA_CARDS, + handler: function () { + ARSnova.app.getController('FlashcardExport') + .exportFlashcards(this.getController()); + this.exportFilePanel.hide(); + } + }] + }] }); this.actionButtonPanel = Ext.create('Ext.Panel', { @@ -319,10 +371,17 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { cls: actionButtonCls, scope: this, handler: function () { - var me = this; - Ext.Msg.confirm(Messages.DELETE_ALL_ANSWERS_REQUEST, Messages.ALL_QUESTIONS_REMAIN, function (answer) { + var title = Messages.DELETE_ALL_ANSWERS_REQUEST; + var message = Messages.ALL_QUESTIONS_REMAIN; + + if (this.getVariant() === 'flashcard') { + title = Messages.DELETE_ALL_VIEWS_REQUEST; + message = Messages.ALL_FLASHCARDS_REMAIN; + } + + Ext.Msg.confirm(title, message, function (answer) { if (answer === 'yes') { - me.getController().deleteAllQuestionsAnswers({ + this.getController().deleteAllQuestionsAnswers({ success: Ext.bind(this.handleDeleteAnswers, this), failure: Ext.emptyFn }); @@ -340,8 +399,15 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { scope: this, handler: function () { var msg = Messages.ARE_YOU_SURE; - msg += "<br>" + Messages.DELETE_ALL_ANSWERS_INFO; - Ext.Msg.confirm(Messages.DELETE_QUESTIONS_TITLE, msg, function (answer) { + var title = Messages.DELETE_QUESTIONS_TITLE; + + if (this.getVariant() === 'flashcard') { + title = Messages.DELETE_FLASHCARDS_TITLE; + } else { + msg += "<br>" + Messages.DELETE_ALL_ANSWERS_INFO; + } + + Ext.Msg.confirm(title, msg, function (answer) { if (answer === 'yes') { this.getController().destroyAll(sessionStorage.getItem("keyword"), { success: Ext.bind(this.onActivate, this), @@ -357,23 +423,14 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { this.exportCsvQuestionsButton = Ext.create('ARSnova.view.MatrixButton', { hidden: true, buttonConfig: 'icon', - text: Messages.QUESTIONS_CSV_EXPORT_BUTTON, + text: Messages.QUESTIONS_EXPORT_BUTTON, imageCls: 'icon-cloud-download', cls: 'actionButton', scope: this, handler: function () { - var msg = Messages.QUESTIONS_CSV_EXPORT_MSBOX_INFO; - - Ext.Msg.confirm(Messages.QUESTIONS_CSV_EXPORT_MSBOX_TITLE, msg, function (answer) { - if (answer === 'yes') { - this.getController().getQuestions(sessionStorage.getItem('keyword'), { - success: function (response) { - var questions = Ext.decode(response.responseText); - ARSnova.app.getController('QuestionExport').parseJsonToCsv(questions); - } - }); - } - }, this); + var msg = Messages.QUESTIONS_EXPORT_MSBOX_INFO; + Ext.Viewport.add(this.exportFilePanel); + this.exportFilePanel.show(); } }); @@ -427,6 +484,7 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { return; } ARSnova.app.taskManager.start(this.updateAnswerCount); + this.flashcardImportButton = Ext.ComponentQuery.query('#flashcardImportButton')[0]; this.applyUIChanges(); this.questionStore.removeAll(); this.getQuestions(); @@ -686,6 +744,7 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { questionListText = Messages.CONTENT_MANAGEMENT; deleteAnswersText = Messages.DELETE_FLASHCARD_VIEWS; deleteQuestionsText = Messages.DELETE_ALL_FLASHCARDS; + this.flashcardImportButton.show(); this.questionStatusButton.setFlashcardsWording(); @@ -701,6 +760,8 @@ Ext.define('ARSnova.view.speaker.AudienceQuestionPanel', { questions: "", answers: Messages.FLASHCARD_VIEWS }; + } else { + this.flashcardImportButton.hide(); } this.toolbar.setTitle(toolbarTitle); diff --git a/src/main/webapp/app/view/speaker/QuestionDetailsPanel.js b/src/main/webapp/app/view/speaker/QuestionDetailsPanel.js index 30464927d..4fdcc0300 100644 --- a/src/main/webapp/app/view/speaker/QuestionDetailsPanel.js +++ b/src/main/webapp/app/view/speaker/QuestionDetailsPanel.js @@ -704,7 +704,9 @@ Ext.define('ARSnova.view.speaker.QuestionDetailsPanel', { imageCls: 'icon-close warningIconColor', scope: this, handler: function () { - Ext.Msg.confirm(Messages.DELETE_ANSWERS_REQUEST, Messages.QUESTION_REMAINS, function (answer) { + var title = this.isFlashcard ? Messages.DELETE_VIEWS_REQUEST : Messages.DELETE_ANSWERS_REQUEST; + var message = this.isFlashcard ? Messages.FLASHCARD_REMAINS : Messages.QUESTION_REMAINS; + Ext.Msg.confirm(title, message, function (answer) { if (answer === 'yes') { var panel = ARSnova.app.mainTabPanel.tabPanel.speakerTabPanel.questionDetailsPanel; ARSnova.app.questionModel.deleteAnswers(panel.questionObj._id, { @@ -742,10 +744,14 @@ Ext.define('ARSnova.view.speaker.QuestionDetailsPanel', { scope: this, handler: function () { var msg = Messages.ARE_YOU_SURE; - if (this.questionObj.active) { + var title = this.isFlashcard ? Messages.DELETE_FLASHCARD_TITLE : + Messages.DELETE_QUESTION_TITLE; + + if (this.questionObj.active && !this.isFlashcard) { msg += "<br>" + Messages.DELETE_ALL_ANSWERS_INFO; } - Ext.Msg.confirm(Messages.DELETE_QUESTION_TITLE, msg, function (answer) { + + Ext.Msg.confirm(title, msg, function (answer) { if (answer === 'yes') { var sTP = ARSnova.app.mainTabPanel.tabPanel.speakerTabPanel; ARSnova.app.questionModel.destroy(sTP.questionDetailsPanel.questionObj, { diff --git a/src/main/webapp/resources/sass/app/_general.scss b/src/main/webapp/resources/sass/app/_general.scss index cc1b87e31..8155b5e20 100644 --- a/src/main/webapp/resources/sass/app/_general.scss +++ b/src/main/webapp/resources/sass/app/_general.scss @@ -680,3 +680,49 @@ code { font-size: 0.75em; opacity: 0.75; } + +.importFileField { + opacity: 0; + width: 0px; + height: 0px; + padding: 0px; + margin: 0 !important; +} + +.importSubjectPrompt { + input { + border-radius: 8px; + text-align: center; + } + + .x-field-input { + padding-right: 0px; + } + + .x-container.x-field-text { + margin: 2px 5px 5px 5px; + } +} + +.importExportFilePanel { + .x-dock-body { + overflow: visible; + } + + .x-button.closeButton { + margin: 0; + height: 30px; + font-size: 0.6em; + padding: 0 0.4em; + border: none; + color: $label-font-color; + background: transparent; + position: absolute; + right: 2px; + top: -28px; + } + + .x-button { + margin: 8px; + } +} -- GitLab