mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-11 23:54:43 +08:00
Merge pull request #4311 from aignatov-bio/ai-sci-7030-label-template-view-edit-screen
Add general layout for template view screen [SCI-7030]
This commit is contained in:
commit
e5448f3d1f
18 changed files with 390 additions and 33 deletions
|
@ -23,7 +23,11 @@
|
|||
}
|
||||
|
||||
function renderNameHTML(data, type, row) {
|
||||
return `${data.icon_url}<a href='${row.recordInfoUrl}' class='record-info-link'>${data.name}</a>`;
|
||||
return `${data.icon_url}<a
|
||||
href='${row.DT_RowAttr['data-edit-url']}'
|
||||
class='record-info-link'
|
||||
onclick='window.open(this.href, "_self")'
|
||||
>${data.name}</a>`;
|
||||
}
|
||||
|
||||
function addAttributesToRow(row, data) {
|
||||
|
@ -43,11 +47,9 @@
|
|||
});
|
||||
}
|
||||
|
||||
function initEditButton() {
|
||||
$('#editLabelTemplate').on('click', function() {
|
||||
if (rowsSelected.length === 1) {
|
||||
window.open(editUrl, '_blank');
|
||||
}
|
||||
function initCreateButton() {
|
||||
$('#newLabelTemplate').on('click', function() {
|
||||
$.post(this.dataset.url);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -249,7 +251,7 @@
|
|||
|
||||
let toolBar = $($('#labelTemplatesToolbar').html());
|
||||
$('.label-buttons-container').html(toolBar);
|
||||
initEditButton();
|
||||
initCreateButton();
|
||||
initSetDefaultButton();
|
||||
initDuplicateButton();
|
||||
initDeleteModal();
|
||||
|
|
89
app/assets/stylesheets/label_templates/show.scss
Normal file
89
app/assets/stylesheets/label_templates/show.scss
Normal file
|
@ -0,0 +1,89 @@
|
|||
// scss-lint:disable SelectorDepth NestingDepth ImportantRule
|
||||
|
||||
.label-templates-show {
|
||||
.title-row {
|
||||
height: unset !important;
|
||||
|
||||
.label-template-icon {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: auto;
|
||||
margin-right: 5px;
|
||||
margin-top: 13px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.sci-inline-edit {
|
||||
@include font-h1;
|
||||
flex-grow: 1;
|
||||
max-width: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
|
||||
.template-descripiton {
|
||||
@include font-button;
|
||||
margin: 1em 0;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.label-template-container {
|
||||
display: flex;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.label-edit-container {
|
||||
flex-basis: 60%;
|
||||
padding-right: 1em;
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
|
||||
.refresh-preview {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.save-template {
|
||||
margin-left: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label-preview-container {
|
||||
flex-basis: 40%;
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.label-view-container {
|
||||
cursor: pointer;
|
||||
padding: .5em;
|
||||
position: relative;
|
||||
white-space: pre;
|
||||
|
||||
.fa-pen {
|
||||
display: none;
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-concrete;
|
||||
|
||||
.fa-pen {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label-textarea {
|
||||
min-height: 240px;
|
||||
padding: .5em;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -36,7 +36,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
textarea,
|
||||
input {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
min-height: 1em;
|
||||
|
|
|
@ -5,9 +5,9 @@ class LabelTemplatesController < ApplicationController
|
|||
|
||||
before_action :check_feature_enabled
|
||||
before_action :check_view_permissions, only: %i(index datatable)
|
||||
before_action :check_manage_permissions, only: %i(new duplicate set_default delete)
|
||||
before_action :check_manage_permissions, only: %i(create duplicate set_default delete update)
|
||||
before_action :load_label_templates, only: %i(index datatable)
|
||||
before_action :load_label_template, only: %i(edit set_default)
|
||||
before_action :load_label_template, only: %i(show set_default update)
|
||||
|
||||
layout 'fluid'
|
||||
|
||||
|
@ -25,9 +25,33 @@ class LabelTemplatesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def new; end
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json { render json: @label_template, serializer: LabelTemplateSerializer, user: current_user }
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def edit; end
|
||||
def create
|
||||
label_template = LabelTemplate.create!(
|
||||
team_id: current_team.id,
|
||||
name: I18n.t('label_templates.new_label_template'),
|
||||
language_type: :zpl,
|
||||
format: 'ZPL',
|
||||
size: '1" x 0.5" / 25.4mm x 12.7mm',
|
||||
content: Extends::DEFAULT_LABEL_TEMPLATE[:zpl]
|
||||
)
|
||||
|
||||
redirect_to label_template_path(label_template, new_label: true)
|
||||
end
|
||||
|
||||
def update
|
||||
if @label_template.update(label_template_params)
|
||||
render json: @label_template, serializer: LabelTemplateSerializer, user: current_user
|
||||
else
|
||||
render json: { error: @label_template.errors.messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def duplicate
|
||||
ActiveRecord::Base.transaction do
|
||||
|
@ -89,4 +113,8 @@ class LabelTemplatesController < ApplicationController
|
|||
def load_label_template
|
||||
@label_template = LabelTemplate.where(team_id: current_team.id).find(params[:id])
|
||||
end
|
||||
|
||||
def label_template_params
|
||||
params.require(:label_template).permit(:name, :description, :content)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,7 +44,7 @@ class LabelTemplateDatatable < CustomDatatable
|
|||
'8' => I18n.l(record.created_at, format: :full),
|
||||
'recordInfoUrl' => '',
|
||||
'DT_RowAttr': {
|
||||
'data-edit-url': edit_label_template_path(record),
|
||||
'data-edit-url': label_template_path(record),
|
||||
'data-set-default-url': set_default_label_template_path(record)
|
||||
},
|
||||
'manage_permission' => @manage_template
|
||||
|
|
25
app/javascript/packs/vue/label_template.js
Normal file
25
app/javascript/packs/vue/label_template.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import TurbolinksAdapter from 'vue-turbolinks';
|
||||
import Vue from 'vue/dist/vue.esm';
|
||||
import LabelTemplateContainer from '../../vue/label_template/container.vue';
|
||||
|
||||
Vue.use(TurbolinksAdapter);
|
||||
Vue.prototype.i18n = window.I18n;
|
||||
|
||||
window.initLabelTemplateComponent = () => {
|
||||
|
||||
new Vue({
|
||||
el: '#labelTemplateContainer',
|
||||
components: {
|
||||
'label-template-container': LabelTemplateContainer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
labelTemplateUrl: $('#labelTemplateContainer').data('label-template-url'),
|
||||
labelTemplatesUrl: $('#labelTemplateContainer').data('label-templates-url'),
|
||||
newLabel: $('#labelTemplateContainer').data('new-label')
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
initLabelTemplateComponent();
|
152
app/javascript/vue/label_template/container.vue
Normal file
152
app/javascript/vue/label_template/container.vue
Normal file
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="content-pane flexible label-templates-show">
|
||||
<div class="content-header">
|
||||
<div id="breadcrumbsWrapper">
|
||||
<div class="breadcrumbs-container">
|
||||
<a :href="labelTemplatesUrl" class="breadcrumbs-link">
|
||||
{{ i18n.t('label_templates.show.breadcrumb_index') }}
|
||||
</a>
|
||||
<span class="delimiter">/</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="labelTemplate.id" class="title-row">
|
||||
<img :src="labelTemplate.attributes.icon_url" class="label-template-icon"/>
|
||||
<InlineEdit
|
||||
:value="labelTemplate.attributes.name"
|
||||
:characterLimit="255"
|
||||
:allowBlank="false"
|
||||
:attributeName="i18n.t('label_templates.show.name_error_prefix')"
|
||||
:autofocus="editingName"
|
||||
:editOnload="newLabel"
|
||||
@editingEnabled="editingName = true"
|
||||
@editingDisabled="editingName = false"
|
||||
@update="updateName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content-label-templates-show">
|
||||
<div v-if="labelTemplate.id" class="template-descripiton">
|
||||
<div class="title">
|
||||
{{ i18n.t('label_templates.show.description_title') }}
|
||||
</div>
|
||||
<InlineEdit
|
||||
:value="labelTemplate.attributes.description"
|
||||
:characterLimit="255"
|
||||
:allowBlank="true"
|
||||
:attributeName="i18n.t('label_templates.show.description_error_prefix')"
|
||||
:placeholder="i18n.t('label_templates.show.description_placeholder')"
|
||||
:autofocus="editingDescription"
|
||||
@editingEnabled="editingDescription = true"
|
||||
@editingDisabled="editingDescription = false"
|
||||
@update="updateDescription"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="labelTemplate.id" class="label-template-container">
|
||||
<div class="label-edit-container">
|
||||
<div class="title">
|
||||
{{ i18n.t('label_templates.show.content_title', { format: labelTemplate.attributes.language_type.toUpperCase() }) }}
|
||||
</div>
|
||||
<template v-if="editingContent">
|
||||
<div class="label-textarea-container">
|
||||
<textarea
|
||||
ref="contentInput"
|
||||
v-model="newContent"
|
||||
class="label-textarea"
|
||||
@blur="updateContent"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<div class="btn btn-secondary refresh-preview">
|
||||
<i class="fas fa-sync"></i>
|
||||
{{ i18n.t('label_templates.show.buttons.refresh') }}
|
||||
</div>
|
||||
<div class="btn btn-secondary" @click="editingContent = false">
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</div>
|
||||
<div class="btn btn-primary save-template" @click="updateContent">
|
||||
<i class="fas fa-save"></i>
|
||||
{{ i18n.t('label_templates.show.buttons.save') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="label-view-container" :title="i18n.t('label_templates.show.view_content_tooltip')" @click="editingContent = true">{{ labelTemplate.attributes.content}}
|
||||
<i class="fas fa-pen"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label-preview-container">
|
||||
<div class="title">
|
||||
{{ i18n.t('label_templates.show.preview_title') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import InlineEdit from 'vue/shared/inline_edit.vue'
|
||||
|
||||
export default {
|
||||
name: 'LabelTemplateContainer',
|
||||
props: {
|
||||
labelTemplateUrl: String,
|
||||
labelTemplatesUrl: String,
|
||||
newLabel: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
labelTemplate: {
|
||||
attributes: {}
|
||||
},
|
||||
editingName: false,
|
||||
editingDescription: false,
|
||||
editingContent: false,
|
||||
newContent: ''
|
||||
}
|
||||
},
|
||||
components: {InlineEdit},
|
||||
created() {
|
||||
console.log(this.newLabel)
|
||||
$.get(this.labelTemplateUrl, (result) => {
|
||||
this.labelTemplate = result.data
|
||||
this.newContent = this.labelTemplate.attributes.content
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
updateName(newName) {
|
||||
$.ajax({
|
||||
url: this.labelTemplate.attributes.urls.update,
|
||||
type: 'PATCH',
|
||||
data: {label_template: {name: newName}},
|
||||
success: (result) => {
|
||||
this.labelTemplate.attributes.name = result.data.attributes.name
|
||||
}
|
||||
});
|
||||
},
|
||||
updateDescription(newDescription) {
|
||||
$.ajax({
|
||||
url: this.labelTemplate.attributes.urls.update,
|
||||
type: 'PATCH',
|
||||
data: {label_template: {description: newDescription}},
|
||||
success: (result) => {
|
||||
this.labelTemplate.attributes.description = result.data.attributes.description
|
||||
}
|
||||
});
|
||||
},
|
||||
updateContent() {
|
||||
$.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
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -21,6 +21,13 @@ class LabelTemplate < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def icon_url
|
||||
case language_type
|
||||
when 'zpl'
|
||||
'/images/label_template_icons/zpl.svg'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_template
|
||||
|
|
13
app/serializers/label_template_serializer.rb
Normal file
13
app/serializers/label_template_serializer.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class LabelTemplateSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :name, :description, :language_type, :icon_url, :urls, :content
|
||||
|
||||
def urls
|
||||
{
|
||||
update: label_template_path(object)
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,14 +1,10 @@
|
|||
<template id="labelTemplatesToolbar">
|
||||
<% if can_manage_label_templates?(current_team) %>
|
||||
<a href="<%= new_label_template_path %>" title="<%= t('label_templates.index.toolbar.new') %>"
|
||||
class="btn btn-primary auto-shrink-button" id="newLabelTemplate" target="_blank">
|
||||
<button data-url="<%= label_templates_path %>" title="<%= t('label_templates.index.toolbar.new') %>"
|
||||
class="btn btn-primary auto-shrink-button" id="newLabelTemplate">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span class="button-text"><%= t('label_templates.index.toolbar.new') %></span>
|
||||
</a>
|
||||
<div class="btn btn-light hidden selected-actions auto-shrink-button" id="editLabelTemplate" title="<%= t('label_templates.index.toolbar.edit') %>">
|
||||
<i class="fas fa-pen"></i>
|
||||
<span class="button-text"><%= t('label_templates.index.toolbar.edit') %></span>
|
||||
</div>
|
||||
</button>
|
||||
<div class="btn btn-light hidden selected-actions auto-shrink-button"
|
||||
title="<%= t('label_templates.index.toolbar.duplicate') %>"
|
||||
data-url="<%= duplicate_label_templates_path %>"
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<% provide(:sidebar_title, t('sidebar.templates.sidebar_title')) %>
|
||||
<% provide(:container_class, "no-second-nav-container") %>
|
||||
<%= content_for :sidebar do %>
|
||||
<%= render partial: "/shared/sidebar/templates_sidebar", locals: {active: :label} %>
|
||||
<% end %>
|
|
@ -25,7 +25,7 @@
|
|||
<div class="label-templates-datatable">
|
||||
<table id="label-templates-table"
|
||||
class="table"
|
||||
data-source="<%= label_templates_datatable_path(format: :json) %>">
|
||||
data-source="<%= datatable_label_templates_path(format: :json) %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="select-all">
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<% provide(:sidebar_title, t('sidebar.templates.sidebar_title')) %>
|
||||
<% provide(:container_class, "no-second-nav-container") %>
|
||||
<%= content_for :sidebar do %>
|
||||
<%= render partial: "/shared/sidebar/templates_sidebar", locals: {active: :label} %>
|
||||
<% end %>
|
19
app/views/label_templates/show.html.erb
Normal file
19
app/views/label_templates/show.html.erb
Normal file
|
@ -0,0 +1,19 @@
|
|||
<% provide(:sidebar_title, t('sidebar.templates.sidebar_title')) %>
|
||||
<% provide(:container_class, "no-second-nav-container") %>
|
||||
<%= content_for :sidebar do %>
|
||||
<%= render partial: "/shared/sidebar/templates_sidebar", locals: {active: :label} %>
|
||||
<% end %>
|
||||
|
||||
<div id="labelTemplateContainer"
|
||||
data-label-template-url="<%= label_template_path(params[:id], format: :json) %>"
|
||||
data-label-templates-url="<%= label_templates_path() %>"
|
||||
data-new-label="<%= params[:new_label] || false %>"
|
||||
>
|
||||
<label-template-container
|
||||
:label-template-url="labelTemplateUrl"
|
||||
:label-templates-url="labelTemplatesUrl"
|
||||
:new-label="newLabel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<%= javascript_pack_tag 'vue/label_template' %>
|
|
@ -490,6 +490,24 @@ class Extends
|
|||
)
|
||||
|
||||
STI_PRELOAD_CLASSES = %w(LinkedRepository BmtRepository)
|
||||
|
||||
DEFAULT_LABEL_TEMPLATE = {
|
||||
zpl:
|
||||
<<~HEREDOC
|
||||
^XA
|
||||
^MTT
|
||||
^MUD,300,300
|
||||
^PR2
|
||||
^MD30
|
||||
^LH20,20
|
||||
^PW310
|
||||
^CF0,23
|
||||
^FO0,0^FD{{item_id}}^FS
|
||||
^FO0,20^BQN,2,4^FDMA,{{item_id}}^FS
|
||||
^FO95,30^FB180,4,0,L^FD{{item_name}}^FS^FS
|
||||
^XZ
|
||||
HEREDOC
|
||||
}
|
||||
end
|
||||
|
||||
# rubocop:enable Style/MutableConstant
|
||||
|
|
|
@ -815,6 +815,7 @@ en:
|
|||
can_add_user_to_project: "Can not add user to the project."
|
||||
|
||||
label_templates:
|
||||
new_label_template: 'New label'
|
||||
index:
|
||||
head_title: 'Label templates'
|
||||
search_templates: 'Filter templates'
|
||||
|
@ -837,6 +838,18 @@ en:
|
|||
delete_modal:
|
||||
title: 'Delete Label template(s)'
|
||||
description: 'Are you sure you want to delete the selected Label template(s)?'
|
||||
show:
|
||||
breadcrumb_index: 'Label templates'
|
||||
name_error_prefix: 'Label template name'
|
||||
description_error_prefix: 'Label template description'
|
||||
description_title: 'Template description'
|
||||
description_placeholder: 'Enter the template description (optional)'
|
||||
content_title: '%{format} template code'
|
||||
preview_title: 'Template preview'
|
||||
view_content_tooltip: 'Click to edit template code'
|
||||
buttons:
|
||||
refresh: 'Refresh preview'
|
||||
save: 'Save template code'
|
||||
|
||||
promo:
|
||||
head_title: 'Label templates'
|
||||
|
|
|
@ -45,16 +45,16 @@ Rails.application.routes.draw do
|
|||
to: 'users/settings/account/addons#index',
|
||||
as: 'addons'
|
||||
|
||||
resources :label_templates, only: %i(index new edit) do
|
||||
resources :label_templates, only: %i(index show update create) do
|
||||
member do
|
||||
post :set_default
|
||||
end
|
||||
collection do
|
||||
post :duplicate
|
||||
post :delete
|
||||
get :datatable
|
||||
end
|
||||
end
|
||||
get 'label_templates/datatable', to: 'label_templates#datatable'
|
||||
|
||||
resources :label_printers, except: :show, path: 'users/settings/account/addons/label_printers' do
|
||||
post :create_fluics, on: :collection
|
||||
|
|
4
public/images/label_template_icons/zpl.svg
Normal file
4
public/images/label_template_icons/zpl.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2315 4.00001L13.9001 8.00001L11.2312 12H1V12H0V12C0 12.2652 0.105357 12.5196 0.292893 12.7071C0.48043 12.8947 0.734784 13 1 13H11.2312C11.3957 13.0006 11.5577 12.9604 11.7028 12.8829C11.8479 12.8055 11.9715 12.6932 12.0625 12.5563L14.9187 8.27501C14.9741 8.19398 15.0037 8.09814 15.0037 8.00001C15.0037 7.90188 14.9741 7.80603 14.9187 7.72501L12.0625 3.44376C11.9715 3.30678 11.8479 3.19454 11.7028 3.11709C11.5577 3.03965 11.3957 2.99941 11.2312 3.00001H1C0.734784 3.00001 0.48043 3.10536 0.292893 3.2929C0.105358 3.48043 1.7995e-06 3.73479 0 4H1V4.00001L11.2315 4.00001Z" fill="#A0A0A8"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.35297e-06 5.5C2.35297e-06 5.22386 0.22386 5 0.500002 5H2.5C2.66591 5 2.82102 5.0823 2.91404 5.21969C3.00705 5.35707 3.02586 5.53165 2.96424 5.6857L1.23852 10H2.5C2.77614 10 3 10.2239 3 10.5C3 10.7761 2.77614 11 2.5 11H0.500002C0.33409 11 0.178984 10.9177 0.0859687 10.7803C-0.00704625 10.6429 -0.0258541 10.4683 0.035764 10.3143L1.76149 6H0.500002C0.22386 6 2.35297e-06 5.77614 2.35297e-06 5.5ZM10.5 11C10.775 11 11 10.775 11 10.5C11 10.225 10.775 10 10.5 10H9V5.5C9 5.225 8.775 5 8.5 5C8.225 5 8 5.225 8 5.5V10.5C8 10.775 8.225 11 8.5 11H10.5ZM7 7C7 6.03437 6.21563 5 5.25 5H4.5C4.22375 5 4 5.225 4 5.5V10.5C4 10.775 4.22375 11 4.5 11C4.77625 11 5 10.775 5 10.5V9H5.25C6.21563 9 7 7.96563 7 7ZM5.25 6C5.66563 6 6 6.58438 6 7C6 7.41563 5.66563 8 5.25 8H5V6H5.25Z" fill="#404048"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
Loading…
Add table
Reference in a new issue