diff --git a/app/adapters/page.js b/app/adapters/page.js index 7e1e34e95f9b4c877b7c29e3dbbcfe5a388378f5..686a2be58d7e7abbe8e7811ca247afb1b396feb9 100644 --- a/app/adapters/page.js +++ b/app/adapters/page.js @@ -26,17 +26,26 @@ export default class PageAdapter extends ApplicationAdapter { async loadImage(page, accept='image/jpeg') { /* - * Requests binary image/jpeg from backend and sets `page.url` attribute - * to the `URL` pointing to newly retrieved binary image. + * Requests page's image from backend + + It can request image/jpeg or image/svg+xml. In case it asks + for image/svg+xml media type and svg image is not available, + server will return jpeg instead. + Server will return 404 only in case neither svg nor jpeg images + are available. */ let response, image_blob, image_object_url; response = await this.getImage(page.id, accept); - image_blob = await response.blob(); - image_object_url = URL.createObjectURL(image_blob); - page.url = image_object_url; + if (response.headers.get('content-type') == 'image/svg+xml') { + page.svg_image = await response.text(); + } else { + image_blob = await response.blob(); + image_object_url = URL.createObjectURL(image_blob); + page.url = image_object_url; + } return page; } diff --git a/app/components/viewer/index.hbs b/app/components/viewer/index.hbs index 779ea06dce569350c4120b7df28145c10ba817e9..2b58a4e961f20b166b5d2535b37673ae676fb510 100644 --- a/app/components/viewer/index.hbs +++ b/app/components/viewer/index.hbs @@ -1,10 +1,10 @@ <div class="panel viewer col m-2 p-2"> <div class="d-flex justify-content-between"> <Viewer::ActionButtons - @document_versions={{@document_versions}} - @isLocked={{@isLocked}} - @ocrStatus={{@ocrStatus}} - @onRunOCR={{@onRunOCR}} /> + @document_versions={{this.document_versions}} + @isLocked={{this.isLocked}} + @ocrStatus={{this.ocrStatus}} + @onRunOCR={{this.onRunOCR}} /> </div> <Breadcrumb @@ -13,7 +13,7 @@ @hint={{@hint}} /> <div class="d-flex"> - <Viewer::Thumbnails @pages={{@pages}} /> - <Viewer::Pages @pages={{@pages}} /> + <Viewer::Thumbnails @pages={{this.pages}} /> + <Viewer::Pages @pages={{this.pages}} /> </div> </div> diff --git a/app/components/viewer/index.js b/app/components/viewer/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6da9cf5d85b0c9c5d1a5f43f46ea6dfe7479b015 --- /dev/null +++ b/app/components/viewer/index.js @@ -0,0 +1,168 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { A } from '@ember/array'; + + +export default class ViewerComponent extends Component { + /* + Document Viewer Component + + Arguments: + @doc - `models.document` instance - the backend model document currently + shown to the user by this component + @pages - an array of `models.page` instances. Besides, each page + instance has either `url` or `svg_image` attribute. + */ + + @service websockets; + @service store; + @service requests; + + @tracked ocr_status = null; + @tracked is_locked = false; + + @tracked _document_versions = A([]); + @tracked __document_versions__; + + @tracked _pages = A([]); + @tracked __pages__; + + constructor(owner, args) { + super(owner, args); + + this.websockets.addHandler(this.messageHandler, this); + } + + messageHandler(message) { + // console.log(message); + if (message.document_id != this.args.doc.id) { + // this websocket message was not addressed to currently + // viewed document. Just skip it. + return; + } + + // message addressed to currently viewed document + switch (message.type) { + case 'ocrdocumenttask.taskreceived': + this.ocr_status = 'received'; + break; + case 'ocrdocumenttask.taskstarted': + this.ocr_status = 'started'; + break; + case 'ocrdocumenttask.tasksucceeded': + this.ocr_status = 'succeeded'; + this.is_locked = false; + this.update_document(); + break; + case 'ocrdocumenttask.taskfailed': + this.ocr_status = 'failed'; + break; + } // end of switch + } + + update_document() { + /* + Pulls latest document version + document version's pages + from server side. + */ + let last_version, + page_adapter, + that = this; + + page_adapter = this.store.adapterFor('page'); + + this.store.findRecord( + 'document', + this.args.doc.id, + { reload: true } + ).then((doc) => { + last_version = doc.last_version; + that._document_versions.push(last_version); + that.__document_versions__ = last_version; + + page_adapter.loadImages(last_version.pages, 'image/svg+xml').then( + (pages) => { + that._pages = pages; + that.__pages__ = pages; + }); + }); + } + + @action + onRunOCR() { + this.is_locked = true; + this.requests.runOCR({ + doc_id: this.args.doc.id, + lang: 'deu' + }); + } + + get versions() { + /* Just a shortcut */ + return this.args.doc.versions; + } + + get document_versions() { + /** + Returns document versions received via this.args.doc.versions + additional + versions created via OCRing. + + The point here is to update versions dropdown with newly + created versions (when user clicks run OCR). + */ + if (this.__document_versions__) { + // workaround for tracking changes in array + } + + if (this._document_versions.length > 0) { + // include newly OCRed versions as well + return this.versions.concat( + this._document_versions + ); + } + + return this.versions; + } + + get pages() { + + if (this.__pages__) { + // workaround for tracking changes in array + } + + if (this._pages.length > 0) { + // If newer version of the pages is available + // (e.g. document was OCRed) then just use it + return this._pages; + } + + // Initial version of the pages + return this.args.pages; + } + + + get ocrStatus() { + /* + ocr status as received from: + + 1. websockets' messages for current document model + 2. model itself + + The notifications received via websockets's messages (1.) have priority + over current document model (2.) + + Initially `this.ocr_status` will be `null` which will result in displaying + the actual ocr status from `this.args.doc.ocr_status`. If user chooses + to run OCR, the controller `this.ocr_status` will be updated in realtime + to non-null value (via websockets) and accordingly `this.ocr_status` will + take over in terms of priority. + */ + return this.ocr_status || this.args.doc.ocr_status; + } + + get isLocked() { + return this.is_locked; + } +} \ No newline at end of file diff --git a/app/components/viewer/page.hbs b/app/components/viewer/page.hbs index a45c717ec2e3e36447240a966b7b91021bcbef57..6c432f6e412dc877a07421cf36617667578ce4e7 100644 --- a/app/components/viewer/page.hbs +++ b/app/components/viewer/page.hbs @@ -1,5 +1,9 @@ <div class="page d-flex flex-column align-items-center"> - <img src="{{@page.url}}" /> + {{#if @page.svg_image}} + {{{@page.svg_image}}} + {{else}} + <img src="{{@page.url}}" /> + {{/if}} <div class="number fs-3 m-3"> {{@page.number}} </div> diff --git a/app/components/viewer/thumbnail.hbs b/app/components/viewer/thumbnail.hbs index accedddff942de211be11958d65fb30cbc5812dd..293513a05d4a69d68ca29bb472c6c396aff81b34 100644 --- a/app/components/viewer/thumbnail.hbs +++ b/app/components/viewer/thumbnail.hbs @@ -1,4 +1,10 @@ <div class="thumbnail d-flex flex-column align-items-center"> - <img src="{{@page.url}}" /> - {{@page.number}} + {{#if @page.svg_image}} + {{{@page.svg_image}}} + {{else}} + <img src="{{@page.url}}" /> + {{/if}} + <div class="number fs-3 m-3"> + {{@page.number}} + </div> </div> \ No newline at end of file diff --git a/app/controllers/authenticated/document.js b/app/controllers/authenticated/document.js index 00571d1ce9097ee55595ef23e1e0b38625e8c8b6..ea57177d4275d1732f22b8b70225e674a8efcd37 100644 --- a/app/controllers/authenticated/document.js +++ b/app/controllers/authenticated/document.js @@ -2,90 +2,17 @@ import Controller from '@ember/controller'; import { action } from '@ember/object'; import { tracked } from "@glimmer/tracking"; import { inject as service } from '@ember/service'; -import { A } from '@ember/array'; export default class ViewerController extends Controller { @service currentUser; - @service requests; - @service websockets; @tracked extranode_id = null; @tracked extradoc_id = null; - @tracked is_locked = false; - @tracked ocr_status = null; - - @tracked _document_versions = A([]); - @tracked __document_versions__; - - @tracked _pages = A([]); - @tracked __pages__; queryParams = ['extranode_id', 'extradoc_id'] - - constructor(owner, args) { - super(owner, args); - - this.websockets.addHandler(this.messageHandler, this); - } - - update_document() { - /* - Pulls latest document version + document version's pages - from server side. - */ - let last_version, - page_adapter, - that = this; - - page_adapter = this.store.adapterFor('page'); - - this.store.findRecord( - 'document', - this.model.doc.id, - { reload: true } - ).then((doc) => { - last_version = doc.last_version; - that._document_versions.push(last_version); - that.__document_versions__ = last_version; - - page_adapter.loadImages(last_version.pages, 'image/svg+xml').then( - (pages) => { - that._pages = pages; - that.__pages__ = pages; - }); - }); - } - - messageHandler(message) { - // console.log(message); - if (message.document_id != this.model.doc.id) { - // this websocket message was not addressed to currently - // viewed document. Just skip it. - return; - } - - // message addressed to currently viewed document - switch (message.type) { - case 'ocrdocumenttask.taskreceived': - this.ocr_status = 'received'; - break; - case 'ocrdocumenttask.taskstarted': - this.ocr_status = 'started'; - break; - case 'ocrdocumenttask.tasksucceeded': - this.ocr_status = 'succeeded'; - this.is_locked = false; - this.update_document(); - break; - case 'ocrdocumenttask.taskfailed': - this.ocr_status = 'failed'; - break; - } // end of switch - } - @action async onPanelToggle() { let home_folder; @@ -98,74 +25,4 @@ export default class ViewerController extends Controller { } } - @action - onRunOCR() { - this.is_locked = true; - this.requests.runOCR({ - doc_id: this.model.doc.id, - lang: 'deu' - }); - } - - get document_versions() { - /** - Returns document versions received via this.model.document_versions + additional - versions created via OCRing. - - The point here is to update versions dropdown with newly - created versions (when user clicks run OCR). - */ - if (this.__document_versions__) { - // workaround for tracking changes in array - } - - if (this._document_versions.length > 0) { - // include newly OCRed versions as well - return this.model.document_versions.concat( - this._document_versions - ); - } - - return this.model.document_versions; - } - - get pages() { - - if (this.__pages__) { - // workaround for tracking changes in array - } - - if (this._pages.length > 0) { - // If newer version of the pages is available - // (e.g. document was OCRed) then just use it - return this._pages; - } - - // Initial version of the pages - return this.model.pages; - } - - get isLocked() { - return this.is_locked; - } - - get ocrStatus() { - /* - ocr status as received from: - - 1. websockets' messages for current document model - 2. model itself - - The notifications received via websockets's messages (1.) have priority - over current document model (2.) - - Initially `this.ocr_status` will be `null` which will result in displaying - the actual ocr status from `this.model.doc.ocr_status`. If user chooses - to run OCR, the controller `this.ocr_status` will be updated in realtime - to non-null value (via websockets) and accordingly `this.ocr_status` will - take over in terms of priority. - */ - return this.ocr_status || this.model.doc.ocr_status; - } - } diff --git a/app/routes/authenticated/document.js b/app/routes/authenticated/document.js index 98c56ba6d313f2403933f33ef53d6e1b1c7120a9..22bf95ff97755cdca530069a44b603b9e2f1b174 100644 --- a/app/routes/authenticated/document.js +++ b/app/routes/authenticated/document.js @@ -21,10 +21,11 @@ export default class DocumentRoute extends Route { page_adapter, extranode, doc, + extra_doc, last_version, - last_version2, + extra_last_version, pages_with_url, - pages_with_url2; + extra_pages_with_url; page_adapter = this.store.adapterFor('page'); @@ -36,22 +37,30 @@ export default class DocumentRoute extends Route { last_version = doc.last_version; - pages_with_url = await page_adapter.loadImages(last_version.pages); + pages_with_url = await page_adapter.loadImages(last_version.pages, 'image/svg+xml'); if (params.extradoc_id) { - doc = await this.store.findRecord( + extra_doc = await this.store.findRecord( 'document', params.extradoc_id, { reload: true } ); - last_version2 = doc.last_version - pages_with_url2 = await page_adapter.loadImages(last_version2.pages); + extra_last_version = extra_doc.last_version + extra_pages_with_url = await page_adapter.loadImages( + extra_last_version.pages + ); return { - 'document_version': last_version, + 'doc': doc, + 'document_versions': doc.versions, + 'last_document_version': last_version, 'pages': pages_with_url, - 'extra_document_version': last_version2, - 'extra_pages': pages_with_url2, + 'extra': { + 'doc': extra_doc, + 'document_versions': extra_doc.versions, + 'last_document_version': extra_last_version, + 'pages': extra_pages_with_url, + } }; } diff --git a/app/styles/document_version.scss b/app/styles/document_version.scss index e73fd64f983b2606f6240321c3f04c07f035a37b..dd37d4bb002317831766c0348651fa3d42be1373 100644 --- a/app/styles/document_version.scss +++ b/app/styles/document_version.scss @@ -1,5 +1,7 @@ .thumbnails { + max-width: 8rem; + .thumbnail { margin: 1rem; img { diff --git a/app/templates/authenticated/viewer.hbs b/app/templates/authenticated/viewer.hbs index 3a2f3d64af4947c6d0d27be89af6d95a775c1e9f..5436ccbae7eb267d070c9f3f8397925777e9a8a5 100644 --- a/app/templates/authenticated/viewer.hbs +++ b/app/templates/authenticated/viewer.hbs @@ -1,23 +1,18 @@ <div class="panels d-flex row"> <Viewer - @last_document_version={{@model.last_document_version}} - @document_versions={{this.document_versions}} @doc={{@model.doc}} - @pages={{this.pages}} + @pages={{@model.pages}} @extranode={{this.extranode.current_node}} @onPanelToggle={{this.onPanelToggle}} @dualPanelMode={{this.dualpanel_mode}} - @onRunOCR={{this.onRunOCR}} - @isLocked={{this.isLocked}} - @ocrStatus={{this.ocrStatus}} @hint="left" /> - {{#if this.dualpanel_mode}} - {{#if this.extra_document_version}} + {{#if @model.extra}} + {{#if @model.extra.doc}} <Viewer - @document_version={{this.extra_document_version}} - @pages={{this.extra_pages}} - @extranode={{@model.document_version}} + @doc={{@model.extra.doc}} + @pages={{@model.extra.pages}} + @extranode={{this.extranode.current_node}} @onPanelToggle={{this.onPanelToggle}} @dualPanelMode={{this.dualpanel_mode}} @hint="right" />