From 31a8d05f4f4e403e141f5f676473dae5329dd717 Mon Sep 17 00:00:00 2001 From: Abhishek-Punhani Date: Wed, 10 Jun 2026 00:28:46 +0530 Subject: [PATCH] feat: Initial Scaffold for QTI editor components and add a development page Signed-off-by: Abhishek-Punhani --- .../frontend/channelEdit/constants.js | 1 + .../channelEdit/pages/QTIDemoPage.vue | 72 ++++++ .../frontend/channelEdit/router.js | 6 + .../components/QTIItemEditor/index.vue | 231 ++++++++++++++++++ .../shared/views/QTIEditor/constants.js | 28 +++ .../frontend/shared/views/QTIEditor/index.vue | 214 ++++++++++++++++ .../views/QTIEditor/qtiEditorStrings.js | 68 ++++++ 7 files changed, 620 insertions(+) create mode 100644 contentcuration/contentcuration/frontend/channelEdit/pages/QTIDemoPage.vue create mode 100644 contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue create mode 100644 contentcuration/contentcuration/frontend/shared/views/QTIEditor/constants.js create mode 100644 contentcuration/contentcuration/frontend/shared/views/QTIEditor/index.vue create mode 100644 contentcuration/contentcuration/frontend/shared/views/QTIEditor/qtiEditorStrings.js diff --git a/contentcuration/contentcuration/frontend/channelEdit/constants.js b/contentcuration/contentcuration/frontend/channelEdit/constants.js index 8932058ecf..2658a5cd19 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/constants.js +++ b/contentcuration/contentcuration/frontend/channelEdit/constants.js @@ -18,6 +18,7 @@ export const RouteNames = { TRASH: 'TRASH', ADD_PREVIOUS_STEPS: 'ADD_PREVIOUS_STEPS', ADD_NEXT_STEPS: 'ADD_NEXT_STEPS', + QTI_DEMO: 'QTI_DEMO', }; export const viewModes = { diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/QTIDemoPage.vue b/contentcuration/contentcuration/frontend/channelEdit/pages/QTIDemoPage.vue new file mode 100644 index 0000000000..bb11ec769f --- /dev/null +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/QTIDemoPage.vue @@ -0,0 +1,72 @@ + + + + diff --git a/contentcuration/contentcuration/frontend/channelEdit/router.js b/contentcuration/contentcuration/frontend/channelEdit/router.js index 69615e4d7b..7414d8e712 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/router.js +++ b/contentcuration/contentcuration/frontend/channelEdit/router.js @@ -9,6 +9,7 @@ import TrashModal from './views/trash/TrashModal'; import SearchOrBrowseWindow from './views/ImportFromChannels/SearchOrBrowseWindow'; import ReviewSelectionsPage from './views/ImportFromChannels/ReviewSelectionsPage'; import EditModal from './components/edit/EditModal'; +import QTIDemoPage from './pages/QTIDemoPage'; import ChannelDetailsModal from 'shared/views/channel/ChannelDetailsModal'; import ChannelModal from 'shared/views/channel/ChannelModal'; import { RouteNames as ChannelRouteNames } from 'frontend/channelList/constants'; @@ -244,6 +245,11 @@ const router = new VueRouter({ }); }, }, + { + name: RouteNames.QTI_DEMO, + path: '/qti-demo', + component: QTIDemoPage, + }, { name: RouteNames.TREE_VIEW, path: '/:nodeId/:detailNodeId?', diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue new file mode 100644 index 0000000000..0088de8d0d --- /dev/null +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue @@ -0,0 +1,231 @@ + + + + + + + diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/constants.js b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/constants.js new file mode 100644 index 0000000000..76e313f8c9 --- /dev/null +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/constants.js @@ -0,0 +1,28 @@ +export const Cardinality = Object.freeze({ + SINGLE: 'single', + MULTIPLE: 'multiple', + ORDERED: 'ordered', + RECORD: 'record', +}); + +export const BaseType = Object.freeze({ + IDENTIFIER: 'identifier', + BOOLEAN: 'boolean', + INTEGER: 'integer', + FLOAT: 'float', + STRING: 'string', + POINT: 'point', + PAIR: 'pair', + DIRECTED_PAIR: 'directedPair', + DURATION: 'duration', + FILE: 'file', + URI: 'uri', +}); + +export const QtiInteraction = Object.freeze({ + CHOICE: 'choiceInteraction', + ORDER: 'orderInteraction', + MATCH: 'matchInteraction', + TEXT_ENTRY: 'textEntryInteraction', + EXTENDED_TEXT: 'extendedTextInteraction', +}); diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/index.vue b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/index.vue new file mode 100644 index 0000000000..1e39672c0e --- /dev/null +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/index.vue @@ -0,0 +1,214 @@ + + + + + + + diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/qtiEditorStrings.js b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/qtiEditorStrings.js new file mode 100644 index 0000000000..a134e7b9a3 --- /dev/null +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/qtiEditorStrings.js @@ -0,0 +1,68 @@ +import { createTranslator } from 'shared/i18n'; + +const NAMESPACE = 'QTIEditorStrings'; + +const MESSAGES = { + noQuestionsPlaceholder: { + message: 'No questions yet', + context: 'Shown when the question list is empty', + }, + newQuestionBtnLabel: { + message: 'New question', + context: 'Button that adds a new question to the list', + }, + questionNumberLabel: { + message: 'Question {number} of {total}', + context: 'Card header when card is open, e.g. "Question 2 of 5"', + }, + questionNumberAndTypeLabel: { + message: 'Question {number} of {total} \u2014 {type}', + context: 'Card header when card is closed, e.g. "Question 1 of 3 \u2014 Choice"', + }, + toolbarItemLabel: { + message: 'question', + context: 'Noun used by the toolbar for accessible action labels', + }, + closeBtnLabel: { + message: 'Close', + context: 'Button that collapses the open question card', + }, + questionContentPlaceholder: { + message: 'Question content editor coming soon', + context: 'Placeholder inside an open card until interaction editors are built', + }, + showAnswers: { + message: 'Show answers', + context: 'Checkbox label to toggle displaying answers/previews', + }, + interactionTypeChoice: { + message: 'Choice', + context: 'Display name for choiceInteraction', + }, + interactionTypeOrder: { + message: 'Order', + context: 'Display name for orderInteraction', + }, + interactionTypeMatch: { + message: 'Match', + context: 'Display name for matchInteraction', + }, + interactionTypeTextEntry: { + message: 'Text entry', + context: 'Display name for textEntryInteraction', + }, + interactionTypeExtendedText: { + message: 'Extended text', + context: 'Display name for extendedTextInteraction', + }, + interactionTypeUnknown: { + message: 'Unknown type', + context: 'Fallback when an item has an unrecognised interaction type', + }, +}; + +export const qtiEditorStrings = createTranslator(NAMESPACE, MESSAGES); + +export function useQTIStr(key, args) { + return qtiEditorStrings.$tr(key, args); +}