Add validation to label template code [SCI-7210]

This commit is contained in:
Anton 2022-09-19 11:56:43 +02:00
parent e1ce81280e
commit 17bf490fef
5 changed files with 128 additions and 21 deletions
app
assets/stylesheets/label_templates
javascript/vue/label_template
config/locales

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 {
height: 0;
overflow: hidden;

View file

@ -55,6 +55,24 @@
.label-textarea-container {
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 {
@ -115,13 +133,6 @@
}
}
.label-textarea {
height: 100%;
margin-top: .5em;
padding: .5em;
width: 100%;
}
.inser-field-dropdown {
.open-dropdown-button:not(.collapsed) {
.fas {

View file

@ -51,6 +51,10 @@
<div v-if="base64Image" class="label-preview__image">
<img :src="`data:image/png;base64,${base64Image}`" />
</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>
</template>
@ -113,6 +117,9 @@
}
this.setDefaults();
},
zpl() {
this.refreshPreview();
}
},
methods: {
@ -123,6 +130,8 @@
!this.height && (this.height = this.unit === 'in' ? 1 : 25.4);
},
refreshPreview() {
if (this.zpl.length === 0) return;
$.ajax({
url: this.previewUrl,
type: 'GET',
@ -134,7 +143,17 @@
},
success: (result) => {
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) {

View file

@ -55,23 +55,26 @@
/>
</div>
<template v-if="editingContent">
<div class="label-textarea-container">
<div class="label-textarea-container" :class="{'error': hasError }">
<textarea
ref="contentInput"
v-model="newContent"
class="label-textarea"
@blur="updateContent"
@blur="saveCursorPosition"
></textarea>
<div class="error-message">
{{ codeErrorMessage }}
</div>
</div>
<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>
{{ i18n.t('label_templates.show.buttons.refresh') }}
</div>
<div class="btn btn-secondary" @mousedown="disableContentEdit">
{{ i18n.t('general.cancel') }}
</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>
{{ i18n.t('label_templates.show.buttons.save') }}
</div>
@ -83,7 +86,7 @@
</div>
</div>
<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>
@ -112,14 +115,32 @@
editingDescription: false,
editingContent: false,
newContent: '',
previewContent: '',
previewValid: false,
skipSave: false,
codeErrorMessage: '',
cursorPos: 0
}
},
watch: {
newContent() {
this.showErrors();
},
previewValid() {
this.showErrors();
}
},
computed: {
hasError() {
return this.codeErrorMessage.length > 0
}
},
components: {InlineEdit, InsertFieldDropdown, LabelPreview},
created() {
$.get(this.labelTemplateUrl, (result) => {
this.labelTemplate = result.data
this.newContent = this.labelTemplate.attributes.content
this.previewContent = this.labelTemplate.attributes.content
})
},
methods: {
@ -134,6 +155,7 @@
disableContentEdit() {
this.editingContent = false;
this.newContent = this.labelTemplate.attributes.content;
this.previewContent = this.labelTemplate.attributes.content;
},
updateName(newName) {
$.ajax({
@ -156,23 +178,66 @@
});
},
updateContent() {
this.cursorPos = $(this.$refs.contentInput).prop('selectionStart');
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {label_template: {content: this.newContent}},
success: (result) => {
this.labelTemplate.attributes.content = result.data.attributes.content;
this.editingContent = false;
}
this.previewValid = true;
if (!this.editingContent) return;
if (this.skipSave) {
this.skipSave = false;
return;
}
this.$nextTick(() => {
if (this.hasError) return;
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {label_template: {content: this.newContent}},
success: (result) => {
this.labelTemplate.attributes.content = result.data.attributes.content;
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) {
this.enableContentEdit();
let textBefore = this.newContent.substring(0, this.cursorPos);
let textAfter = this.newContent.substring(this.cursorPos, this.newContent.length);
this.newContent = textBefore + field + textAfter;
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'
preview_title: 'Template preview'
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:
button: 'Insert field code'
common_fields: 'Common custom fields'
@ -896,6 +900,7 @@ en:
refresh_preview: 'Refresh preview'
mm: 'Millimeters (mm) 1 cm = 10 mm'
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:
head_title: 'Label templates'
promo_title: 'This feature is disabled by default in open source SciNote'