diff --git a/CITATION.cff b/CITATION.cff index 940751ab..376380b4 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -37,7 +37,7 @@ identifiers: value: 10.5281/zenodo.17977921 description: Edirom Online Frontend repository-code: https://github.com/Edirom/Edirom-Online-Frontend -commit: 65d6487474bb360ba17c6c645cd4e2c2f73bb516 +commit: b265b241e4fe202ba5189c909390e95aba795b2c abstract: >- Edirom Online Frontend is the frontend for the Edirom Online which is used for the presentation and @@ -45,4 +45,4 @@ abstract: >- particularly in the fields of musicology and philology. license: GPL-3.0 version: v1.2.0 -date-released: 2026-01-19 +date-released: 2026-02-05 diff --git a/app/controller/window/AnnotationView.js b/app/controller/window/AnnotationView.js index dfbc001e..b0b9c646 100644 --- a/app/controller/window/AnnotationView.js +++ b/app/controller/window/AnnotationView.js @@ -28,7 +28,9 @@ Ext.define('EdiromOnline.controller.window.AnnotationView', { this.control({ 'annotationView': { afterlayout : this.onAfterLayout, - showAnnotation: this.onShowAnnotation + showAnnotation: this.onShowAnnotation, + loadAnnotations: this.onLoadAnnotations, + loadParticipantSingleContent: this.onLoadParticipantSingleContent }, 'annotationView button[action=openAll]': { click: this.onOpenAllParticipants @@ -51,15 +53,10 @@ Ext.define('EdiromOnline.controller.window.AnnotationView', { onShowAnnotation: function(view, uri) { - var editionId = this.application.activeEdition; - var lang = getPreference('application_language'); - window.doAJAXRequest('data/xql/getAnnotationText.xql', 'GET', { - uri: uri, - lang: lang, - edition: EdiromOnline.getApplication().activeEdition + uri: uri }, Ext.bind(function(response){ view.setContent(response.responseText); @@ -69,9 +66,7 @@ Ext.define('EdiromOnline.controller.window.AnnotationView', { window.doAJAXRequest('data/xql/getAnnotationMeta.xql', 'GET', { - uri: uri, - lang: lang, - edition: EdiromOnline.getApplication().activeEdition + uri: uri }, Ext.bind(function(response){ @@ -82,9 +77,7 @@ Ext.define('EdiromOnline.controller.window.AnnotationView', { window.doAJAXRequest('data/xql/getAnnotationPreviews.xql', 'GET', { - uri: uri, - lang: lang, - edition: EdiromOnline.getApplication().activeEdition + uri: uri }, Ext.bind(function(response){ var data = Ext.JSON.decode(response.responseText); @@ -140,5 +133,228 @@ Ext.define('EdiromOnline.controller.window.AnnotationView', { }); btn.windows = null; + }, + + onLoadAnnotations: function(view) { + this.loadAnnotationsAndCreateComponents(view); + }, + + onLoadParticipantSingleContent: function(view) { + this.loadParticipantSingleContent(view); + }, + + loadAnnotationsAndCreateComponents: function(view) { + var me = this; + + // Show loading mask while loading data + view.setLoading('Loading annotations...'); + + window.doAJAXRequest( + 'data/xql/getAnnotations.xql', + 'GET', + { + uri: view.uri + }, + Ext.bind(function(response) { + var data = Ext.JSON.decode(response.responseText); + + // Store the loaded data + view.data = data; + view.annotations = data.annotations; + view.annotationsLoaded = true; + + if(typeof(debug) !== 'undefined' && debug !== null && debug) { + console.log('view: AnnotationView: ajax callback'); + console.log(view); + console.log(data); + console.log(data.annotations); + console.log(data.emptyFields); + } + + // Now create the list and store that depend on the loaded data + me.createListAndStore(view, data.annotations, me.getStoreFieldsDefinition(view), data.emptyFields); + + // Add the list to the card layout at index 0 + view.insert(0, view.list); + + // Set the list as the active item initially + view.getLayout().setActiveItem(view.list); + + // Hide loading mask + view.setLoading(false); + + // Fire the event to notify that annotations are loaded + view.fireEvent('annotationsLoaded', view, view.uri, data); + + console.log('Annotations loaded and components created'); + console.log(view); + }, me), + Ext.bind(function(response) { + // Error handling + view.setLoading(false); + Ext.Msg.alert('Error', 'Failed to load annotations'); + }, me) + ); + }, + + createStore: function(view) { + var me = this; + + view.listStore = Ext.create('Ext.data.Store', { + //model: 'EdiromOnline.model.Annotation', + fields: me.getStoreFieldsDefinition(view), + proxy: { + reader: { + type: 'json', + useSimpleAccessors: false + } + }, + autoLoad: false + }); + + view.listStore.getProxy().extraParams = { + uri: view.uri, + lang: getPreference('application_language'), + edition: EdiromOnline.getApplication().activeEdition + }; + + return view.listStore; + }, + + createListAndStore: function(view, annotations, storeFields, emptyFields) { + var me = this; + + if(typeof(debug) !== 'undefined' && debug !== null && debug) { + console.log('view: AnnotationView: createListAndStore'); + console.log(view.annotations); + console.log(storeFields); + console.log(emptyFields); + } + + // Create list Store + view.listStore = Ext.create('Ext.data.Store', { + fields: storeFields, + autoLoad: false, + data: annotations + }); + + // Create the annotation list + view.list = Ext.create('Ext.grid.Panel', { + store: view.listStore, + title: getLangString('view.window.AnnotationView_Title'), + bodyBorder: false, + border: '0 0 0 0', + cls: 'annotationList', + features: [{ + ftype: 'filters', + encode: false, + local: true, + filters: [] + }], + columns: view.getColumns(storeFields, emptyFields) + }); + + // Add event listener for double click + view.list.on('itemdblclick', view.onItemDblClicked, view); + + if(typeof(debug) !== 'undefined' && debug !== null && debug) { + console.log('view: AnnotationView: createList final'); + console.log(view.list); + } + }, + + getStoreFieldsDefinition: function(view) { + var me = this; + + if(typeof(debug) !== 'undefined' && debug !== null && debug) { + console.log('view: AnnotationView: getStoreFieldsDefinition:'); + console.log(view); + console.log(view.data); + console.log(view.annotations); + } + + // initialise fields variable, setting default fields + var fields = [ + 'id', + 'sigla', + // create an integer field with the name pos for sorting in document-order + { + name: 'pos', + type: 'int' + }//, + //{name: "bazga.annotation", mapping: 'properties["bazga.annotation"]'} + ]; + + // Only proceed if data is loaded + if (!view.data || !view.data.fields) { + return fields; // Return default fields if data not loaded yet + } + + // create fields config + // get the existing field names from the above fields array + var existingFieldNames = fields.map(field => + typeof field === 'string' ? field : field.name + ); + + // Iterate over data.fields and add missing ones to fields variable + view.data.fields.forEach(fieldName => { + if (!existingFieldNames.includes(fieldName)) { + var fieldDef = { + name: fieldName, + mapping: me.createFieldMapping(fieldName) + }; + fields.push(fieldDef); + } + }); + + if(typeof(debug) !== 'undefined' && debug !== null && debug) { + console.log('view: AnnotationView: getStoreFieldsDefinition: return fields'); + console.log(fields); + } + + return fields; + }, + + createFieldMapping: function(fieldName) { + // Handle fields with dots by using a custom mapping function + // ExtJS interprets dots as nested object access, so we need bracket notation + if (fieldName.includes('.')) { + return function(data) { + return data[fieldName]; + }; + } + return fieldName; + }, + + loadStore: function(view) { + var me = this; + if(view.listStore) { + view.listStore.load(); + } + }, + + loadParticipantSingleContent: function(view) { + var me = this; + + var contEl = view.el.getById(view.id + '_annotationParticipantsSingle'); + var previewTxtData = contEl.query('input.previewTxtData'); + + if (previewTxtData.length == 0) return; + + var txtData = previewTxtData[0].value; + var uri = txtData.match(/uri:(.*)__\$\$__/)[1]; + var id = txtData.match(/__\$\$__participantId:(.*)/)[1]; + + window.doAJAXRequest('data/xql/getReducedDocument.xql?uri=' + uri + '&selectionId=' + id + '&subtreeRoot=div&idPrefix=' + view.id + '_', + 'GET', + {}, + Ext.bind(function(response){ + var contEl = view.el.getById(view.id + '_annotationParticipantsSingle'); + var txtBox = new Ext.Element(contEl.query('div.txtBox')[0]); + txtBox.update(response.responseText); + + contEl.query('#' + view.id + '_' + id)[0].scrollIntoView(txtBox); + }, me) + ); } }); diff --git a/app/model/Annotation.js b/app/model/Annotation.js deleted file mode 100644 index 973995ef..00000000 --- a/app/model/Annotation.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Edirom Online - * Copyright (C) 2014 The Edirom Project - * http://www.edirom.de - * - * Edirom Online is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Edirom Online is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Edirom Online. If not, see . - */ -Ext.define('EdiromOnline.model.Annotation', { - - requires: [], - - extend: 'Ext.data.Model', - fields: ['id', - 'title', - 'categories', - 'priority', - 'sigla', - { - name: 'pos', - type: 'int' - } - ], - - proxy: { - type: 'ajax', - url: '@backend.url@data/xql/getAnnotations.xql', - reader: { - type: 'json', - root: 'annotations', - totalProperty: 'total' - } - }, - - statics: { - updateProxyUrl: function (backendURL) { - var model = Ext.ModelManager.getModel('EdiromOnline.model.Annotation'); - if (model) { - model.getProxy().url = backendURL + 'data/xql/getAnnotations.xql'; - } - } - } -}); diff --git a/app/view/window/AnnotationView.js b/app/view/window/AnnotationView.js index d2c12844..89a9aec5 100644 --- a/app/view/window/AnnotationView.js +++ b/app/view/window/AnnotationView.js @@ -18,14 +18,12 @@ */ Ext.define('EdiromOnline.view.window.AnnotationView', { extend: 'EdiromOnline.view.window.View', - - cls: 'annotView', - + requires: [ 'Ext.grid.Panel', /*'Ext.grid.PagingScroller',*/ 'Ext.ux.grid.FiltersFeature', - 'EdiromOnline.model.Annotation', + /*'EdiromOnline.model.Annotation',*/ 'EdiromOnline.model.AnnotationParticipant', 'EdiromOnline.view.utils.Lightbox', 'EdiromOnline.view.window.annotationLayouts.AnnotationLayout1', @@ -48,53 +46,38 @@ Ext.define('EdiromOnline.view.window.AnnotationView', { me.addEvents('showAnnotation'); + me.addEvents('annotationsLoaded'); + me.activeSingleAnnotation = ""; - me.list = Ext.create('Ext.grid.Panel', { - store: me.createStore(), - title: getLangString('view.window.AnnotationView_Title'), - bodyBorder: false, - border: '0 0 0 0', - cls: 'annotationList', - features: [{ - ftype: 'filters', - encode: false, - local: true, - filters: [] - }], - columns: [ - { - header: getLangString('view.window.AnnotationView_No'), - dataIndex: 'pos', - width: 35 - }, - { - header: getLangString('view.window.AnnotationView_TitleLabel'), - dataIndex: 'title', - flex: 4, - filter: true - }, - { - header: getLangString('view.window.AnnotationView_Categories'), - dataIndex: 'categories', - flex: 2, - filter: true - }, - { - header: getLangString('view.window.AnnotationView_Priority'), - dataIndex: 'priority', - flex: 1, - filter: true - }, - { - header: getLangString('view.window.AnnotationView_Sigla'), - dataIndex: 'sigla', - flex: 2, - filter: true - } - ] - }); + me.annotationsLoaded = false; + + // Initialize components that don't depend on loaded annotation data + me.initializeIndependentComponents(); + + me.callParent(); + + me.on('afterrender', me.createToolbarEntries, me, {single: true}); + me.on('afterrender', me.createMenuEntries, me, {single: true}); + + // IMPORTANT: Load annotations and create dependent components after render + me.on('afterrender', function() { + me.fireEvent('loadAnnotations', me); + }, me, {single: true}); + + me.window.on('loadInternalLink', me.loadInternalId, me); + + me.on('resize', me.calculateLimitingImageFactor, me, {buffer: 100}); + me.on('resize', me.resizePanels, me, {buffer: 100}); + + }, + + initializeIndependentComponents: function() { + var me = this; + /* + * Create the single annotation perspective + */ me.participantsList = Ext.create('Ext.grid.Panel', { store: Ext.create('Ext.data.Store', { model: 'EdiromOnline.model.AnnotationParticipant' @@ -182,25 +165,13 @@ Ext.define('EdiromOnline.view.window.AnnotationView', { ] }); + // Initially add only the single view toolbar, list will be added after data loads me.items = [ - me.list, me.singlePlusToolbar ]; - - me.callParent(); - - me.on('afterrender', me.createToolbarEntries, me, {single: true}); - me.on('afterrender', me.createMenuEntries, me, {single: true}); - me.on('show', me.loadStore, me, {single: true}); - - me.window.on('loadInternalLink', me.loadInternalId, me); - - me.on('resize', me.calculateLimitingImageFactor, me, {buffer: 100}); - me.on('resize', me.resizePanels, me, {buffer: 100}); - - me.list.on('itemdblclick', me.onItemDblClicked, me); }, + resizePanels: function() { var me = this; @@ -284,32 +255,109 @@ Ext.define('EdiromOnline.view.window.AnnotationView', { }); me.bottomBar.add(me.closeAllButton); - + }, - createStore: function() { + + + getColumns: function(storeFields, emptyFields) { var me = this; - me.listStore = Ext.create('Ext.data.Store', { - model: 'EdiromOnline.model.Annotation', - autoLoad: false + if(typeof(debug) !== 'undefined' && debug !== null && debug) { + console.log('view: AnnotationView: getColumns for:'); + console.log(storeFields); + } + + // default columns configuration + var columns = [ + { + header: getLangString('view.window.AnnotationView_AnnotationID'), + dataIndex: 'id', + flex: 1, + hidden: true + }, + { + header: getLangString('view.window.AnnotationView_No'), + dataIndex: 'pos', + cls: 'pos', + tdCls: 'pos', + width: 45 + }, + { + header: getLangString('view.window.AnnotationView_Sigla'), + dataIndex: 'sigla', + flex: 2, + filter: true + }, + { + header: getLangString('view.window.AnnotationView_TitleLabel'), + dataIndex: 'title', + flex: 4, + filter: true + } + ]; + + // save existing dataIndex entries as column names + const existingColumnNames = columns.map(column => + column.dataIndex); + + //iterate over storeFields to create missing grid columns + storeFields.forEach(field => { + if (!existingColumnNames.includes(typeof field === 'string' ? field : field.name)) { + // if existingColumnNames does not include the value of field or field.name + // create fieldObject + const fieldName = typeof field === 'string' ? field : field.name; + const fieldObject = { + header: getLangString('view.window.AnnotationView_' + fieldName), + dataIndex: fieldName, + renderer: me.createFieldRenderer(fieldName), + flex: 1, + filter: true, + hidden: emptyFields ? emptyFields.includes(field) : false + }; + // push fieldObject to columns array + columns.push(fieldObject); + } + else { + // find columns entry with dataIndex === field + const fieldName = typeof field === 'string' ? field : field.name; + const existingColumn = columns.find(column => column.dataIndex === fieldName); + + if (existingColumn) { + // if column.hidden === true leave as is + // if column.hidden === undefined set to emptyFields.includes(field) + if (existingColumn.hidden !== true) { + existingColumn.hidden = emptyFields ? emptyFields.includes(fieldName) : false; + } + } + } + }); - me.listStore.getProxy().extraParams = { - uri: me.uri, - lang: getPreference('application_language'), - edition: EdiromOnline.getApplication().activeEdition - }; + if(typeof(debug) !== 'undefined' && debug !== null && debug) { + console.log('view: AnnotationView: finished columns:'); + console.log(columns); + } - return me.listStore; + return columns; }, - loadStore: function() { - var me = this; - me.listStore.load(); + createFieldRenderer: function(fieldName) { + // For fields with dots, create a custom renderer that accesses the data correctly + if (fieldName.includes('.')) { + return function(value, metaData, record) { + // Access the field value directly from record data using bracket notation + var data = record.data || record.raw; + return data && data[fieldName] ? data[fieldName] : ''; + }; + } + // For regular fields, return undefined to use default rendering + return undefined; }, + + getWeightForInternalLink: function(uri, type, id) { var me = this; @@ -340,10 +388,11 @@ Ext.define('EdiromOnline.view.window.AnnotationView', { showList: function() { var me = this; - if(me.getLayout().getActiveItem() != me.list) + if(me.list && me.getLayout().getActiveItem() != me.list) me.getLayout().setActiveItem(me.list); - var selection = me.list.getSelectionModel().getSelection(); + if(me.list) { + var selection = me.list.getSelectionModel().getSelection(); if(selection.length == 0) { if(me.activeSingleAnnotation != "") { @@ -351,6 +400,7 @@ Ext.define('EdiromOnline.view.window.AnnotationView', { me.list.getSelectionModel().select(activeIndex); }else me.list.getSelectionModel().select(0); + } } }, @@ -674,30 +724,6 @@ Ext.define('EdiromOnline.view.window.AnnotationView', { me.calculateLimitingImageFactor(); }, - loadParticipantSingleContent: function() { - var me = this; - - var contEl = me.el.getById(me.id + '_annotationParticipantsSingle'); - var previewTxtData = contEl.query('input.previewTxtData'); - - if (previewTxtData.length == 0) return; - - var txtData = previewTxtData[0].value; - var uri = txtData.match(/uri:(.*)__\$\$__/)[1]; - var id = txtData.match(/__\$\$__participantId:(.*)/)[1]; - - window.doAJAXRequest('data/xql/getReducedDocument.xql?uri=' + uri + '&selectionId=' + id + '&subtreeRoot=div&idPrefix=' + me.id + '_', - 'GET', - {}, - Ext.bind(function(response){ - var contEl = this.el.getById(me.id + '_annotationParticipantsSingle'); - var txtBox = new Ext.Element(contEl.query('div.txtBox')[0]); - txtBox.update(response.responseText); - - contEl.query('#' + this.id + '_' + id)[0].scrollIntoView(txtBox); - }, me) - ); - }, previousParticipantSingle: function() { //TODO: console.log(arguments); @@ -730,7 +756,7 @@ Ext.define('EdiromOnline.view.window.AnnotationView', { } break; case 'single': { me.participantsPanel.getLayout().setActiveItem(me.participantsPanelSingle); - me.loadParticipantSingleContent(); + me.fireEvent('loadParticipantSingleContent', me); } break; case 'list': { diff --git a/bootstrap.js b/bootstrap.js index 4b47c11d..f2989535 100644 --- a/bootstrap.js +++ b/bootstrap.js @@ -58,7 +58,6 @@ Ext.ClassManager.addNameAlternateMappings({ "EdiromOnline.controller.window.text.FacsimileView": [], "EdiromOnline.controller.window.text.TextFacsimileSplitView": [], "EdiromOnline.controller.window.text.TextView": [], - "EdiromOnline.model.Annotation": [], "EdiromOnline.model.AnnotationParticipant": [], "EdiromOnline.model.Edition": [], "EdiromOnline.model.Work": [], @@ -971,7 +970,6 @@ Ext.ClassManager.addNameAliasMappings({ "EdiromOnline.controller.window.text.FacsimileView": [], "EdiromOnline.controller.window.text.TextFacsimileSplitView": [], "EdiromOnline.controller.window.text.TextView": [], - "EdiromOnline.model.Annotation": [], "EdiromOnline.model.AnnotationParticipant": [], "EdiromOnline.model.Edition": [], "EdiromOnline.model.Work": [], diff --git a/packages/eoTheme/sass/etc/annotation.scss b/packages/eoTheme/sass/etc/annotation.scss index 942e7c57..34d5f66d 100755 --- a/packages/eoTheme/sass/etc/annotation.scss +++ b/packages/eoTheme/sass/etc/annotation.scss @@ -6,6 +6,19 @@ $ui_baseFont_size: 14px; $content_baseFont: 'Caudex', Arial, sans-serif; $content_baseFont_size: 14px; + +.annotationView { + + /* Fix for Chrome: splitter in Annotation View */ + .x-splitter-horizontal { + background-image: -webkit-radial-gradient(50% 50%,ellipse cover,#999,white 50%); + } + + .x-splitter-vertical { + background-image: -webkit-radial-gradient(50% 50%,ellipse cover,#999,white 50%); + } +} + .annotationList { .x-panel-header-default { @include background(linear-gradient(top,#ffffff,#e5e5e5)); @@ -15,6 +28,10 @@ $content_baseFont_size: 14px; @include background(linear-gradient(top,lighten(#ffd538,10%), lighten(#f07822,10%))); } + .x-grid-cell.pos * { + text-align: right !important; + } + } @@ -149,6 +166,16 @@ $content_baseFont_size: 14px; max-width: 100%; } } + + .ref { + color: rgb(146, 64, 92); + } + + .ref:hover { + color: rgb(146, 64, 92); + cursor: pointer; + text-decoration: underline; + } } diff --git a/resources/css/todo.css b/resources/css/todo.css index e876f3fe..aa765671 100644 --- a/resources/css/todo.css +++ b/resources/css/todo.css @@ -124,20 +124,6 @@ span.musicalSymbol { overflow: auto; } -.annotView .contentBox { - padding-left: 20px; - padding-right: 20px; -} - -/* Fix for Chrome: splitter in Annotation View */ -.annotationView .x-splitter-horizontal { - background-image: -webkit-radial-gradient(50% 50%,ellipse cover,#999,white 50%); -} - -.annotationView .x-splitter-vertical { - background-image: -webkit-radial-gradient(50% 50%,ellipse cover,#999,white 50%); -} - /*text Metadata*/ .headerView .section { @@ -274,16 +260,6 @@ left: 10px; width: 100% !important; } -.annotView .ref { - color: rgb(146, 64, 92); -} - -.annotView .ref:hover { - color: rgb(146, 64, 92); - cursor: pointer; - text-decoration: underline; -} - /* SEARCH WINDOW */ .searchWindow .searchResultDoc {