diff --git a/app/apps/core/static/js/edit/components/base_panel.js b/app/apps/core/static/js/edit/components/base_panel.js index 5aca4f5b68469f3a9dfbf532822b0e8b271281c4..cfc1ee8d1ee10ea52bed2e79360ef62adc9c51c7 100644 --- a/app/apps/core/static/js/edit/components/base_panel.js +++ b/app/apps/core/static/js/edit/components/base_panel.js @@ -6,8 +6,10 @@ const BasePanel = Vue.extend({ }; }, watch: { - 'ratio': function(n, o) { - if (this.part.loaded) this.refresh(); + 'part.loaded': function(n, o) { + if (this.part.loaded) { + this.refresh(); + } } }, methods: { diff --git a/app/apps/core/static/js/edit/components/diplo_line.js b/app/apps/core/static/js/edit/components/diplo_line.js index 0139e660e6b60a6ec49c3b43b06999fd68d8526a..64f167a70ea14b8c41265b99c19bc58e63e541c2 100644 --- a/app/apps/core/static/js/edit/components/diplo_line.js +++ b/app/apps/core/static/js/edit/components/diplo_line.js @@ -17,6 +17,7 @@ var diploLine = LineBase.extend({ mounted() { Vue.nextTick(function() { this.$parent.appendLine(); + if (this.line.currentTrans) this.setElContent(this.line.currentTrans.content); }.bind(this)); }, beforeDestroy() { diff --git a/app/apps/core/static/js/edit/components/diplo_panel.js b/app/apps/core/static/js/edit/components/diplo_panel.js index a2bbb04901bdc480bb6e691723c851efb040ec4d..cd64124d1991608f1976a4693d342652d3e47b6f 100644 --- a/app/apps/core/static/js/edit/components/diplo_panel.js +++ b/app/apps/core/static/js/edit/components/diplo_panel.js @@ -43,6 +43,7 @@ var DiploPanel = BasePanel.extend({ this.editor = this.$el.querySelector('#diplomatic-lines'); this.sortModeBtn = this.$el.querySelector('#sortMode'); this.saveNotif = this.$el.querySelector('.tools #save-notif'); + this.refresh(); }, methods: { empty() { @@ -151,15 +152,18 @@ var DiploPanel = BasePanel.extend({ this.bulkUpdate(); this.bulkCreate(); }, - setHeight() { - this.$el.querySelector('.content-container').style.minHeight = Math.round(this.part.image.size[1] * this.ratio) + 'px'; - }, focusNextLine(sel, line) { if (line.nextSibling) { let range = document.createRange(); range.setStart(line.nextSibling, 0); range.collapse(false); sel.removeAllRanges(); + + if (line.nextSibling.offsetTop > + this.editor.parentNode.scrollTop + this.editor.parentNode.clientHeight) { + line.nextSibling.scrollIntoView(false); + } + sel.addRange(range); } }, @@ -168,6 +172,12 @@ var DiploPanel = BasePanel.extend({ let range = document.createRange(); range.setStart(line.previousSibling, 0); sel.removeAllRanges(); + + if (line.previousSibling.offsetTop - this.editor.parentNode.offsetTop < + this.editor.parentNode.scrollTop) { + line.previousSibling.scrollIntoView(true); + } + sel.addRange(range); } }, @@ -285,12 +295,11 @@ var DiploPanel = BasePanel.extend({ elt.version_updated_at = lt.version_updated_at; } }, + setHeight() { + this.$el.querySelector('.content-container').style.minHeight = Math.round(this.part.image.size[1] * this.ratio) + 'px'; + }, updateView() { - /* - update the size of the panel - */ this.setHeight(); } - }, - + } }); diff --git a/app/apps/core/static/js/edit/components/visu_line.js b/app/apps/core/static/js/edit/components/visu_line.js index 9bb6456cbc00e4cd71a154e41b16e442bdc8a190..ad2e1482d963ae00106a395b68ca15be1a5af379 100644 --- a/app/apps/core/static/js/edit/components/visu_line.js +++ b/app/apps/core/static/js/edit/components/visu_line.js @@ -29,7 +29,6 @@ const visuLine = LineBase.extend({ this.textElement.style.fontSize = lineHeight * (1/2) + 'px'; return 10+'px'; }, - computeTextLength() { if (!this.line.currentTrans) return; content = this.line.currentTrans.content; @@ -61,7 +60,6 @@ const visuLine = LineBase.extend({ textPathId() { return this.line ? 'textPath'+this.line.pk : ''; }, - maskStrokeColor() { if (this.line.currentTrans && this.line.currentTrans.content) { return 'none'; @@ -73,7 +71,6 @@ const visuLine = LineBase.extend({ if (this.line == null || !this.line.mask) return ''; return this.line.mask.map(pt => Math.round(pt[0]*this.ratio)+','+Math.round(pt[1]*this.ratio)).join(' '); }, - fakeBaseline() { // create a fake path based on the mask, var min = this.line.mask.reduce((minPt, curPt) => (curPt[0] < minPt[0]) ? curPt : minPt); diff --git a/app/apps/core/static/js/edit/components/visu_panel.js b/app/apps/core/static/js/edit/components/visu_panel.js index 01871e6719239515791d61e1cceceb544bfaecc5..16e2394ce5b3217def92c53bebb414302e24b934 100644 --- a/app/apps/core/static/js/edit/components/visu_panel.js +++ b/app/apps/core/static/js/edit/components/visu_panel.js @@ -30,14 +30,17 @@ const VisuPanel = BasePanel.extend({ this.editLine = this.part.lines[index - 1]; } }, + resetLines() { + if (this.part.lines.length) { + this.$refs.visulines.forEach(function(line) { + line.reset(); + }); + } + }, updateView() { this.$el.querySelector('svg').style.height = Math.round(this.part.image.size[1] * this.ratio) + 'px'; Vue.nextTick(function() { - if (this.part.lines.length) { - this.$refs.visulines.forEach(function(line) { - line.reset(); - }); - } + this.resetLines(); }.bind(this)); } } diff --git a/app/apps/imports/forms.py b/app/apps/imports/forms.py index 2989588a00942341f0b0eed1fbd52753ad44a7f3..a9f69100ef844e4ec9e34d44e1d25583a419012c 100644 --- a/app/apps/imports/forms.py +++ b/app/apps/imports/forms.py @@ -44,7 +44,6 @@ class ImportForm(BootstrapFormMixin, forms.Form): def clean_iiif_uri(self): uri = self.cleaned_data.get('iiif_uri') - if uri: try: resp = requests.get(uri) @@ -83,7 +82,7 @@ class ImportForm(BootstrapFormMixin, forms.Form): cleaned_data = super().clean() if (not cleaned_data['resume_import'] and not cleaned_data.get('upload_file') - and not cleaned_data['iiif_uri']): + and not cleaned_data.get('iiif_uri')): raise forms.ValidationError(_("Choose one type of import.")) return cleaned_data diff --git a/app/apps/imports/parsers.py b/app/apps/imports/parsers.py index 8f5eb6ced4945c53548e6938101d00222287a49f..987f80dd02cdcccd765dc38b47f4f99d55214d21 100644 --- a/app/apps/imports/parsers.py +++ b/app/apps/imports/parsers.py @@ -70,7 +70,8 @@ class PdfParser(ParserDocument): def validate(self): try: self.doc = pyvips.Image.new_from_buffer(self.file.read(), "", - dpi=300, n=-1, access="sequential") + dpi=300, n=-1, + access="sequential") except pyvips.error.Error as e: logger.exception(e) raise ParseError(_("Invalid pdf file.")) @@ -83,21 +84,22 @@ class PdfParser(ParserDocument): return 0 def parse(self, start_at=0, override=False, user=None): - self.doc = pyvips.Image.new_from_buffer(self.file.read(), "", - dpi=300, n=-1, access="sequential") + buff = self.file.read() + doc = pyvips.Image.new_from_buffer(buff, "", + dpi=300, n=-1, + access="sequential") + n_pages = doc.get('n-pages') try: - self.doc.flatten(background=255) - n_pages = self.doc.get('n-pages') - page_width = self.doc.width - page_height = self.doc.height / n_pages - - for i in range(0, n_pages): - page = self.doc.crop(0, i * page_height, page_width, page_height) + for page_nb in range(start_at, n_pages): + page = pyvips.Image.new_from_buffer(buff, "", dpi=300, + access="sequential", + page=page_nb) part = DocumentPart(document=self.document) - part.image.save('%s_page_%d.png' % (self.file.name, i+1), + part.image.save('%s_page_%d.png' % (self.file.name, page_nb+1), ContentFile(page.write_to_buffer('.png'))) part.save() yield part + page_nb = page_nb + 1 except pyvips.error.Error as e: msg = _("Parse error in {filename}: {error}, skipping it.").format( filename=self.file.name, error=e.args[0] @@ -606,19 +608,19 @@ class IIIFManifestParser(ParserDocument): def manifest(self): try: return json.loads(self.file.read()) - except (json.JSONDecodeError) as e: - raise ParseError(e) + except (json.JSONDecodeError, UnicodeDecodeError) as e: + raise ParseError(_("Couldn't decode invalid manifest ({error})").format(error=e)) @cached_property def canvases(self): try: return self.manifest["sequences"][0]["canvases"] - except (KeyError, IndexError) as e: - raise ParseError(e) + except (KeyError, IndexError): + return 0 def validate(self): if len(self.canvases) < 1: - raise ParseError(_("Empty manifesto.")) + raise ParseError(_("Empty or invalid manifest, no images found.")) @property def total(self): @@ -633,13 +635,13 @@ class IIIFManifestParser(ParserDocument): DocumentMetadata.objects.get_or_create( document=self.document, key=md, value=metadata["value"][:512] ) - except KeyError as e: + except KeyError: pass - try: - for i, canvas in enumerate(self.canvases): - if i < start_at: - continue + for i, canvas in enumerate(self.canvases): + if i < start_at: + continue + try: resource = canvas["images"][0]["resource"] url = resource["@id"] uri_template = "{image}/{region}/{size}/{rotation}/{quality}.{format}" @@ -664,8 +666,10 @@ class IIIFManifestParser(ParserDocument): part.save() yield part time.sleep(0.1) # avoid being throttled - except (KeyError, IndexError) as e: - raise ParseError(e) + except (KeyError, IndexError) as e: + if self.report: + self.report.append(_('Error while fetching {filename}: {error}').format( + filename=name, error=e)) class TranskribusPageXmlParser(PagexmlParser): diff --git a/app/apps/reporting/models.py b/app/apps/reporting/models.py index f19f7c56fd67ce61337b3a96b7a9826a1fd51b2e..bf62987f56aa600b43801bf5ea4d80a8e1610c12 100644 --- a/app/apps/reporting/models.py +++ b/app/apps/reporting/models.py @@ -56,7 +56,7 @@ class TaskReport(models.Model): self.save() self.user.notify(_('%(task_label)s error!') % {'task_label': self.label}, - level='error', + level='danger', links=[{'text': 'Report', 'src': self.uri}]) def end(self, extra_links=None): diff --git a/app/escriptorium/static/css/escriptorium.css b/app/escriptorium/static/css/escriptorium.css index 8c4d07e1387d8163cc2b391d9577e1fbd30d3803..2b060198e5fce09ff82f59fcf1d50cf55c9b3ac5 100644 --- a/app/escriptorium/static/css/escriptorium.css +++ b/app/escriptorium/static/css/escriptorium.css @@ -444,12 +444,12 @@ i.panel-icon { } .panel .content-container .js-wheelzoom-container { - min-height: calc(100vh - 190px); + min-height: calc(100vh - 200px); } .panel#diplo-panel .content-container { overflow-y: scroll; - max-height: calc(100vh - 190px); + max-height: calc(100vh - 200px); } .panel .panel-img { @@ -648,4 +648,4 @@ i.panel-icon { } #reset-onboarding{ margin-top: 5%; -} \ No newline at end of file +} diff --git a/app/escriptorium/templates/core/document_part_edit.html b/app/escriptorium/templates/core/document_part_edit.html index f4aa6b5e57030fe228d5a46702b2a6f6b1ea42f8..f4f3802b92648e2ecd9c92c8e1185ffe159384b3 100644 --- a/app/escriptorium/templates/core/document_part_edit.html +++ b/app/escriptorium/templates/core/document_part_edit.html @@ -544,47 +544,48 @@ </TranscriptionModal> </div> </VisuPanel> - </keep-alive> - <keep-alive> - <DiploPanel id="diplo-panel" - v-bind:part="part" - v-if="show.diplomatic && part.loaded" - ref="diploPanel" - inline-template> - <div class="col panel"> - <div class="tools"> - <i title="{% trans 'Text Panel' %}" class="panel-icon fas fa-list-ol"></i> - <i id="save-notif" title="{% trans "There is content waiting to be saved (don't leave the page)" %}" class="notice fas fa-save hide"></i> - <button id="sortMode" - title="{% trans "Toggle sorting mode." %}" - class="btn btn-sm ml-3 btn-info fas fa-sort" - @click="toggleSort" - autocomplete="off"></button> - </div> - <div class="content-container {{ document.read_direction }}"> - - <diploline v-for="line in part.lines" - v-bind:line="line" - v-bind:ratio="ratio" - v-bind:key="'DL' + line.pk"> - </diploline> - - <div id="diplomatic-lines" - contenteditable="true" - autocomplete="off" - @keydown="onKeyPress" - @keyup="constrainLineNumber" - @input="changed" - @focusin="startEdit" - @focusout="stopEdit" - @paste="onPaste" - @mousemove="showOverlay" - @mouseleave="hideOverlay"> - </div> - </div> - </div> - </DiploPanel> - </keep-alive> + </keep-alive> + + <keep-alive> + <DiploPanel id="diplo-panel" + v-bind:part="part" + v-if="show.diplomatic && part.loaded" + ref="diploPanel" + inline-template> + <div class="col panel"> + <div class="tools"> + <i title="{% trans 'Text Panel' %}" class="panel-icon fas fa-list-ol"></i> + <i id="save-notif" title="{% trans "There is content waiting to be saved (don't leave the page)" %}" class="notice fas fa-save hide"></i> + <button id="sortMode" + title="{% trans "Toggle sorting mode." %}" + class="btn btn-sm ml-3 btn-info fas fa-sort" + @click="toggleSort" + autocomplete="off"></button> + </div> + <div class="content-container {{ document.read_direction }}"> + + <diploline v-for="line in part.lines" + v-bind:line="line" + v-bind:ratio="ratio" + v-bind:key="'DL' + line.pk"> + </diploline> + + <div id="diplomatic-lines" + contenteditable="true" + autocomplete="off" + @keydown="onKeyPress" + @keyup="constrainLineNumber" + @input="changed" + @focusin="startEdit" + @focusout="stopEdit" + @paste="onPaste" + @mousemove="showOverlay" + @mouseleave="hideOverlay"> + </div> + </div> + </div> + </DiploPanel> + </keep-alive> <div class="col-sides"> {% if document.read_direction == 'rtl' %} diff --git a/app/requirements.txt b/app/requirements.txt index 0961da92e174937e9d351cf021bcabc270fd667a..0c77e4a9742166bf645b8aa8e644a482e7557828 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -12,7 +12,7 @@ django-redis==4.10.0 psycopg2-binary==2.7.6 django-ordered-model==3.1.1 easy-thumbnails==2.5 -git+https://github.com/mittagessen/kraken.git@3.0b16#egg=kraken +git+https://github.com/mittagessen/kraken.git@3.0b17#egg=kraken django-cleanup==3.0.1 djangorestframework==3.9.2 drf-nested-routers==0.91