Merge pull request #4441 from aignatov-bio/ai-sci-7210-add-code-validation-to-label-templates

Add validation to label template code [SCI-7210]
This commit is contained in:
aignatov-bio 2022-09-20 11:59:59 +02:00 committed by GitHub
commit e524355207
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 21 deletions

View file

@ -29,6 +29,13 @@
} }
} }
.label-preview__error {
background-color: $brand-danger-light;
color: $brand-danger-hover;
margin-top: .5em;
padding: 1.5em 2em;
}
.label-preview__controls { .label-preview__controls {
height: 0; height: 0;
overflow: hidden; overflow: hidden;

View file

@ -55,6 +55,24 @@
.label-textarea-container { .label-textarea-container {
height: calc(100% - 6em); height: calc(100% - 6em);
.label-textarea {
height: 100%;
margin-top: .5em;
padding: .5em;
width: 100%;
}
&.error {
.label-textarea {
border: 1px solid $brand-danger;
height: calc(100% - 2em);
}
.error-message {
color: $brand-danger;
}
}
} }
.label-edit-header { .label-edit-header {
@ -115,13 +133,6 @@
} }
} }
.label-textarea {
height: 100%;
margin-top: .5em;
padding: .5em;
width: 100%;
}
.inser-field-dropdown { .inser-field-dropdown {
.open-dropdown-button:not(.collapsed) { .open-dropdown-button:not(.collapsed) {
.fas { .fas {

View file

@ -51,6 +51,10 @@
<div v-if="base64Image" class="label-preview__image"> <div v-if="base64Image" class="label-preview__image">
<img :src="`data:image/png;base64,${base64Image}`" /> <img :src="`data:image/png;base64,${base64Image}`" />
</div> </div>
<div class="label-preview__error" v-html="i18n.t('label_templates.label_preview.error_html')"
v-else-if="base64Image != null && base64Image.length === 0">
</div>
</div> </div>
</template> </template>
@ -113,6 +117,9 @@
} }
this.setDefaults(); this.setDefaults();
},
zpl() {
this.refreshPreview();
} }
}, },
methods: { methods: {
@ -123,6 +130,8 @@
!this.height && (this.height = this.unit === 'in' ? 1 : 25.4); !this.height && (this.height = this.unit === 'in' ? 1 : 25.4);
}, },
refreshPreview() { refreshPreview() {
if (this.zpl.length === 0) return;
$.ajax({ $.ajax({
url: this.previewUrl, url: this.previewUrl,
type: 'GET', type: 'GET',
@ -134,7 +143,17 @@
}, },
success: (result) => { success: (result) => {
this.base64Image = result.base64_preview; this.base64Image = result.base64_preview;
if (this.base64Image.length > 0) {
this.$emit('preview:valid');
} else {
this.$emit('preview:invalid');
} }
},
error: (result) => {
this.base64Image = '';
this.$emit('preview:invalid');
}
}); });
}, },
updateUnit(unit) { updateUnit(unit) {

View file

@ -55,23 +55,26 @@
/> />
</div> </div>
<template v-if="editingContent"> <template v-if="editingContent">
<div class="label-textarea-container"> <div class="label-textarea-container" :class="{'error': hasError }">
<textarea <textarea
ref="contentInput" ref="contentInput"
v-model="newContent" v-model="newContent"
class="label-textarea" class="label-textarea"
@blur="updateContent" @blur="saveCursorPosition"
></textarea> ></textarea>
<div class="error-message">
{{ codeErrorMessage }}
</div>
</div> </div>
<div class="button-container"> <div class="button-container">
<div class="btn btn-secondary refresh-preview"> <div class="btn btn-secondary refresh-preview" @click="generatePreview(true)">
<i class="fas fa-sync"></i> <i class="fas fa-sync"></i>
{{ i18n.t('label_templates.show.buttons.refresh') }} {{ i18n.t('label_templates.show.buttons.refresh') }}
</div> </div>
<div class="btn btn-secondary" @mousedown="disableContentEdit"> <div class="btn btn-secondary" @mousedown="disableContentEdit">
{{ i18n.t('general.cancel') }} {{ i18n.t('general.cancel') }}
</div> </div>
<div class="btn btn-primary save-template" @click="updateContent"> <div class="btn btn-primary save-template" :disabled="hasError && previewValid" @click="generatePreview(false)">
<i class="fas fa-save"></i> <i class="fas fa-save"></i>
{{ i18n.t('label_templates.show.buttons.save') }} {{ i18n.t('label_templates.show.buttons.save') }}
</div> </div>
@ -83,7 +86,7 @@
</div> </div>
</div> </div>
<div class="label-preview-container"> <div class="label-preview-container">
<LabelPreview :zpl='labelTemplate.attributes.content' :previewUrl="previewUrl" /> <LabelPreview :zpl='previewContent' :previewUrl="previewUrl" @preview:valid="updateContent" @preview:invalid="invalidPreview" />
</div> </div>
</div> </div>
</div> </div>
@ -112,14 +115,32 @@
editingDescription: false, editingDescription: false,
editingContent: false, editingContent: false,
newContent: '', newContent: '',
previewContent: '',
previewValid: false,
skipSave: false,
codeErrorMessage: '',
cursorPos: 0 cursorPos: 0
} }
}, },
watch: {
newContent() {
this.showErrors();
},
previewValid() {
this.showErrors();
}
},
computed: {
hasError() {
return this.codeErrorMessage.length > 0
}
},
components: {InlineEdit, InsertFieldDropdown, LabelPreview}, components: {InlineEdit, InsertFieldDropdown, LabelPreview},
created() { created() {
$.get(this.labelTemplateUrl, (result) => { $.get(this.labelTemplateUrl, (result) => {
this.labelTemplate = result.data this.labelTemplate = result.data
this.newContent = this.labelTemplate.attributes.content this.newContent = this.labelTemplate.attributes.content
this.previewContent = this.labelTemplate.attributes.content
}) })
}, },
methods: { methods: {
@ -134,6 +155,7 @@
disableContentEdit() { disableContentEdit() {
this.editingContent = false; this.editingContent = false;
this.newContent = this.labelTemplate.attributes.content; this.newContent = this.labelTemplate.attributes.content;
this.previewContent = this.labelTemplate.attributes.content;
}, },
updateName(newName) { updateName(newName) {
$.ajax({ $.ajax({
@ -156,7 +178,18 @@
}); });
}, },
updateContent() { updateContent() {
this.cursorPos = $(this.$refs.contentInput).prop('selectionStart'); this.previewValid = true;
if (!this.editingContent) return;
if (this.skipSave) {
this.skipSave = false;
return;
}
this.$nextTick(() => {
if (this.hasError) return;
$.ajax({ $.ajax({
url: this.labelTemplate.attributes.urls.update, url: this.labelTemplate.attributes.urls.update,
type: 'PATCH', type: 'PATCH',
@ -166,6 +199,23 @@
this.editingContent = false; this.editingContent = false;
} }
}); });
});
},
generatePreview(skipSave = false) {
this.skipSave = skipSave;
if (!skipSave && this.previewContent === this.newContent && this.previewValid) {
this.updateContent();
} else {
this.previewContent = this.newContent;
}
},
invalidPreview() {
this.previewValid = false;
this.skipSave = false;
},
saveCursorPosition() {
this.cursorPos = $(this.$refs.contentInput).prop('selectionStart');
}, },
insertField(field) { insertField(field) {
this.enableContentEdit(); this.enableContentEdit();
@ -173,6 +223,21 @@
let textAfter = this.newContent.substring(this.cursorPos, this.newContent.length); let textAfter = this.newContent.substring(this.cursorPos, this.newContent.length);
this.newContent = textBefore + field + textAfter; this.newContent = textBefore + field + textAfter;
this.cursorPos = this.cursorPos + field.length; this.cursorPos = this.cursorPos + field.length;
},
showErrors() {
if (this.editingContent) {
if (this.newContent.length === 0) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.empty')
} else if (this.newContent.length > 10000) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.too_long')
} else if (!this.previewValid) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.invalid')
} else {
this.codeErrorMessage = ''
}
} else {
this.codeErrorMessage = ''
}
} }
} }
} }

View file

@ -872,6 +872,10 @@ en:
content_title: '%{format} template code' content_title: '%{format} template code'
preview_title: 'Template preview' preview_title: 'Template preview'
view_content_tooltip: 'Click to edit template code' view_content_tooltip: 'Click to edit template code'
code_errors:
empty: 'ZPL template code cannot be empty'
too_long: 'ZPL template code has exceeded the maximum of 10.000 characters'
invalid: 'ZPL template code invalid'
insert_dropdown: insert_dropdown:
button: 'Insert field code' button: 'Insert field code'
common_fields: 'Common custom fields' common_fields: 'Common custom fields'
@ -896,6 +900,7 @@ en:
refresh_preview: 'Refresh preview' refresh_preview: 'Refresh preview'
mm: 'Millimeters (mm) 1 cm = 10 mm' mm: 'Millimeters (mm) 1 cm = 10 mm'
in: 'Inches (in)' in: 'Inches (in)'
error_html: 'Label cannot be previewed:<br>the ZPL code might be invalid, or the preview generator is down. Please double-check your code, save it and reload the page.'
promo: promo:
head_title: 'Label templates' head_title: 'Label templates'
promo_title: 'This feature is disabled by default in open source SciNote' promo_title: 'This feature is disabled by default in open source SciNote'