Merge pull request #4819 from scinote-eln/features/label-template-logo

Features/label template logo
This commit is contained in:
Alex Kriuchykhin 2023-01-13 11:58:01 +01:00 committed by GitHub
commit d4b0f0da9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 155 additions and 25 deletions

View file

@ -97,6 +97,7 @@ gem 'devise-async',
git: 'https://github.com/mhfs/devise-async.git', git: 'https://github.com/mhfs/devise-async.git',
branch: 'devise-4.x' branch: 'devise-4.x'
gem 'image_processing', '~> 1.12' gem 'image_processing', '~> 1.12'
gem 'img2zpl', git: 'https://github.com/scinote-eln/img2zpl'
gem 'rufus-scheduler', '~> 3.5' gem 'rufus-scheduler', '~> 3.5'
gem 'discard', '~> 1.0' gem 'discard', '~> 1.0'

View file

@ -39,6 +39,13 @@ GIT
devise-async (0.10.2) devise-async (0.10.2)
devise (>= 4.0) devise (>= 4.0)
GIT
remote: https://github.com/scinote-eln/img2zpl
revision: 23d61cfc3e90ac4caa62dd08546fa0d7590a5140
specs:
img2zpl (1.0.1)
mini_magick (~> 4.9)
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:

View file

@ -139,7 +139,16 @@
} }
} }
.inser-field-dropdown { .insert-field-dropdown {
.dimensions-container {
align-items: center;
display: flex;
img {
margin-top: 27px;
}
}
.open-dropdown-button:not(.collapsed) { .open-dropdown-button:not(.collapsed) {
.fas { .fas {
@include rotate(-180deg); @include rotate(-180deg);
@ -171,7 +180,12 @@
display: flex; display: flex;
padding: 10px 10px 10px 24px; padding: 10px 10px 10px 24px;
.fas { .fas:not(.fa-plus-square) {
margin-left: -1.25em;
margin-right: .25em;
}
.fa-plus-square {
@include font-main; @include font-main;
display: none; display: none;
margin-left: auto; margin-left: auto;
@ -180,7 +194,7 @@
&:hover { &:hover {
background-color: $color-concrete; background-color: $color-concrete;
.fas { .fa-plus-square {
display: inline-block; display: inline-block;
} }
} }

View file

@ -0,0 +1,72 @@
<template>
<div ref="modal" @keydown.esc="cancel" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.title') }}
</h4>
</div>
<div class="modal-body">
<p>{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.description') }}</p>
<div class="dimensions-container">
<div class="sci-input-container">
<label>{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.width', {unit: unit}) }}</label>
<input type="number" min="0" v-model="width" class="sci-input-field" @change="updateHeight">
</div>
<img src="/images/icon_small/link.svg"/>
<div class="sci-input-container">
<label>{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.height', {unit: unit}) }}</label>
<input type="number" min="0" v-model="height" class="sci-input-field" @change="updateWidth">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" @click="confirm">{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.insert') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'logoInsertModal',
props: {
unit: { type: String, required: true },
dimension: { type: Array, required: true }
},
data() {
return {
width: 0,
height: 0,
ratio: 1
}
},
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
this.width = this.dimension[0]
this.height = this.dimension[1]
this.ratio = this.dimension[0] / this.dimension[1]
},
methods: {
updateHeight() {
this.height = Math.round(this.width * 10 / this.ratio) / 10
},
updateWidth() {
this.width = Math.round(this.height * this.ratio * 10) / 10
},
confirm() {
this.$emit('insert:tag', {tag: `{{LOGO, ${this.unit}, ${this.width}, ${this.height}}}`});
$(this.$refs.modal).modal('hide');
},
cancel() {
$(this.$refs.modal).modal('hide');
}
}
}
</script>

View file

@ -59,7 +59,7 @@
<InsertFieldDropdown <InsertFieldDropdown
v-if="editingContent" v-if="editingContent"
:labelTemplate="labelTemplate" :labelTemplate="labelTemplate"
@insertField="insertField" @insertTag="insertTag"
/> />
</div> </div>
<template v-if="editingContent"> <template v-if="editingContent">
@ -293,12 +293,12 @@
saveCursorPosition() { saveCursorPosition() {
this.cursorPos = $(this.$refs.contentInput).prop('selectionStart'); this.cursorPos = $(this.$refs.contentInput).prop('selectionStart');
}, },
insertField(field) { insertTag(tag) {
this.enableContentEdit(); this.enableContentEdit();
let textBefore = this.newContent.substring(0, this.cursorPos); let textBefore = this.newContent.substring(0, this.cursorPos);
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 + tag + textAfter;
this.cursorPos = this.cursorPos + field.length; this.cursorPos = this.cursorPos + tag.length;
this.$nextTick(() => { this.$nextTick(() => {
$(this.$refs.contentInput).prop('selectionStart', this.cursorPos); $(this.$refs.contentInput).prop('selectionStart', this.cursorPos);

View file

@ -1,5 +1,5 @@
<template> <template>
<div ref="dropdown" class="inser-field-dropdown dropdown"> <div ref="dropdown" class="insert-field-dropdown dropdown">
<a class="open-dropdown-button collapsed" role="button" data-toggle="dropdown" id="fieldsContainer" aria-expanded="false"> <a class="open-dropdown-button collapsed" role="button" data-toggle="dropdown" id="fieldsContainer" aria-expanded="false">
{{ i18n.t('label_templates.show.insert_dropdown.button') }} {{ i18n.t('label_templates.show.insert_dropdown.button') }}
<i class="fas fa-chevron-down"></i> <i class="fas fa-chevron-down"></i>
@ -22,7 +22,7 @@
:data-template="tooltipTemplate" :data-template="tooltipTemplate"
class="field-element" class="field-element"
:title="i18n.t('label_templates.show.insert_dropdown.field_code', {code: field.tag})" :title="i18n.t('label_templates.show.insert_dropdown.field_code', {code: field.tag})"
@click="$emit('insertField', field.tag)" @click="insertTag(field)"
> >
{{ field.key }} {{ field.key }}
<i class="fas fa-plus-square"></i> <i class="fas fa-plus-square"></i>
@ -36,8 +36,9 @@
:data-template="tooltipTemplate" :data-template="tooltipTemplate"
class="field-element" class="field-element"
:title="i18n.t('label_templates.show.insert_dropdown.field_code', {code: field.tag})" :title="i18n.t('label_templates.show.insert_dropdown.field_code', {code: field.tag})"
@click="$emit('insertField', field.tag)" @click="insertTag(field)"
> >
<i v-if="field.icon" :class="field.icon"></i>
{{ field.key }} {{ field.key }}
<i class="fas fa-plus-square"></i> <i class="fas fa-plus-square"></i>
</div> </div>
@ -51,7 +52,7 @@
:data-template="tooltipTemplate" :data-template="tooltipTemplate"
class="field-element" class="field-element"
:title="i18n.t('label_templates.show.insert_dropdown.field_code', {code: field.tag})" :title="i18n.t('label_templates.show.insert_dropdown.field_code', {code: field.tag})"
@click="$emit('insertField', field.tag)" @click="insertTag(field)"
> >
{{ field.key }} {{ field.key }}
<i class="fas fa-plus-square"></i> <i class="fas fa-plus-square"></i>
@ -62,10 +63,17 @@
</div> </div>
</div> </div>
</div> </div>
<LogoInsertModal v-if="openLogoModal"
:unit="labelTemplate.attributes.unit"
:dimension="logoDimension"
@insert:tag="insertTag"
@cancel="openLogoModal = false"/>
</div> </div>
</template> </template>
<script> <script>
import LogoInsertModal from './components/logo_insert_modal.vue'
export default { export default {
name: 'InsertFieldDropdown', name: 'InsertFieldDropdown',
props: { props: {
@ -81,9 +89,12 @@
common: [], common: [],
repositories: [] repositories: []
}, },
openLogoModal: false,
logoDimension: null,
searchValue: '' searchValue: ''
} }
}, },
components: {LogoInsertModal},
computed: { computed: {
tooltipTemplate() { tooltipTemplate() {
return `<div class="tooltip" role="tooltip"> return `<div class="tooltip" role="tooltip">
@ -135,6 +146,14 @@
}); });
}, },
methods: { methods: {
insertTag(field) {
if (field.id == 'logo') {
this.logoDimension = field.dimension
this.openLogoModal = true
return
}
this.$emit('insertTag', field.tag)
},
filterArray(array, key) { filterArray(array, key) {
return array.filter(field => field[key].toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1) return array.filter(field => field[key].toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1)
} }

View file

@ -1,12 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
module LabelTemplates module LabelTemplates
class ColumnNotFoundError < StandardError; end
class LogoNotFoundError < StandardError; end
class LogoParamsError < StandardError; end
class RepositoryRowService class RepositoryRowService
class UnsupportedKeyError < StandardError; end
class ColumnNotFoundError < StandardError; end
class LogoNotFoundError < StandardError; end
MAX_PRINTABLE_ITEM_NAME_LENGTH = 64 MAX_PRINTABLE_ITEM_NAME_LENGTH = 64
@ -21,7 +19,9 @@ module LabelTemplates
keys = @label_template.content.scan(/(?<=\{\{).*?(?=\}\})/).uniq keys = @label_template.content.scan(/(?<=\{\{).*?(?=\}\})/).uniq
label = keys.reduce(@label_template.content.dup) do |rendered_content, key| label = keys.reduce(@label_template.content.dup) do |rendered_content, key|
rendered_content.gsub!(/\{\{#{key}\}\}/, fetch_value(key)) rendered_content.gsub!(/\{\{#{key}\}\}/, fetch_value(key))
rescue ColumnNotFoundError, LogoNotFoundError => e rescue LabelTemplates::ColumnNotFoundError,
LabelTemplates::LogoNotFoundError,
LabelTemplates::LogoParamsError => e
errors.push(e) errors.push(e)
rendered_content rendered_content
end end
@ -49,7 +49,7 @@ module LabelTemplates
when /^c_(.*)/ when /^c_(.*)/
name = Regexp.last_match(1) name = Regexp.last_match(1)
unless @repository_columns.include?(name) unless @repository_columns.include?(name)
raise ColumnNotFoundError, I18n.t('label_templates.repository_row.errors.column_not_found') raise LabelTemplates::ColumnNotFoundError, I18n.t('label_templates.repository_row.errors.column_not_found')
end end
fetch_custom_column_value(name) fetch_custom_column_value(name)
@ -61,15 +61,15 @@ module LabelTemplates
@repository_row.created_by.full_name @repository_row.created_by.full_name
when 'ADDED_ON' when 'ADDED_ON'
I18n.l(@repository_row.created_at, format: :full) I18n.l(@repository_row.created_at, format: :full)
when 'LOGO' when /^LOGO/
logo logo(key)
else else
raise ColumnNotFoundError, I18n.t('label_templates.repository_row.errors.column_not_found') raise LabelTemplates::ColumnNotFoundError, I18n.t('label_templates.repository_row.errors.column_not_found')
end end
end end
def logo def logo(_key)
raise LogoNotFoundError, I18n.t('label_templates.repository_row.errors.logo_not_supported') raise LabelTemplates::LogoNotFoundError, I18n.t('label_templates.repository_row.errors.logo_not_supported')
end end
end end
end end

View file

@ -16,13 +16,17 @@ module LabelTemplates
def tags def tags
{ {
default: DEFAULT_COLUMNS, default: DEFAULT_COLUMNS,
common: repository_tags.pluck(:tags).reduce(:&), common: common_columns + repository_tags.pluck(:tags).reduce(:&),
repositories: repository_tags repositories: repository_tags
} }
end end
private private
def common_columns
@common_columns ||= []
end
def repository_tags def repository_tags
@repository_tags ||= @repository_tags ||=
Repository.includes(:repository_columns).active.where(team: @team).map do |repository| Repository.includes(:repository_columns).active.where(team: @team).map do |repository|

View file

@ -897,6 +897,12 @@ en:
nothing_found: 'Nothing found…' nothing_found: 'Nothing found…'
field_code: 'Field code: %{code}' field_code: 'Field code: %{code}'
search_placeholder: 'Type to search…' search_placeholder: 'Type to search…'
logo_modal:
title: 'Insert logo into the label'
description: 'Please specify width or height, another dimension will adjust proportionally'
width: 'Width (%{unit})'
height: 'Height (%{unit})'
insert: 'Insert'
buttons: buttons:
refresh: 'Refresh preview' refresh: 'Refresh preview'
save: 'Save template code' save: 'Save template code'

View file

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 11H6V12H5V11Z" fill="#A0A0A8"/>
<path d="M10 11H14V12H10V11Z" fill="#A0A0A8"/>
<path d="M13 10H14.5C15.3284 10 16 10.6716 16 11.5C16 12.3284 15.3284 13 14.5 13H13V14H14.5C15.8807 14 17 12.8807 17 11.5C17 10.1193 15.8807 9 14.5 9H13V10Z" fill="#A0A0A8"/>
<path d="M11 10V9H9.5C8.11929 9 7 10.1193 7 11.5C7 12.8807 8.11929 14 9.5 14H11V13H9.5C8.67157 13 8 12.3284 8 11.5C8 10.6716 8.67157 10 9.5 10H11Z" fill="#A0A0A8"/>
<path d="M18 11H19V12H18V11Z" fill="#A0A0A8"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B