lato 3.19.5 → 3.19.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfe56f72d90e03ddb366a46c67ba94bf49635d0b5c8edaabfab6dd84aee1c2bd
4
- data.tar.gz: 30ae4d087532a4888978b1d328e2bdac6083e4b2b18121bcb57766d11502f1b5
3
+ metadata.gz: 9ddcd8ca090462fcc1ce80afc270605f0f777d879c6408a0a62a3abfd4f45245
4
+ data.tar.gz: 30241f1b60707214faf19fbda81bf98244c87a94298a0b975c5c982f49f68db9
5
5
  SHA512:
6
- metadata.gz: 6d1ab66f2e2c4e8d431b2ab0b56a882501d836590d62b41712a235f1b5e7d86b70abb06efc34fa71ba92cd4e1d05605b14da89d78207a5a6f934c72fb9178ef0
7
- data.tar.gz: 4a23b51116acf7ae10c1af06f184d5004969918541ac114b9b00d2f0dc2e797c914af9df7e59203d80a96633ab7dc1e863b5bc3c10853c9b4c37f02b63047778
6
+ metadata.gz: f1ad908430ec114c1c8ef4793bc018c0533c2742fc3f8e24f1dbd4cb7ebca3cd6437062b1cd75bd4207945d13e31d709b699c9dca9ccb36c73cbbd2c8f778f40
7
+ data.tar.gz: 62410dee533b4f52c0c0cec2e1a43a66dca86eec5b30de4181a582ba6fda55b40fd655b25571c3ab9b3a3d6d15e917ba78f3ef4a76b59fac883b6809de9e7ddc
@@ -0,0 +1,210 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["canvas", "fileInput", "uploadInput", "preview", "drawPanel", "uploadPanel", "drawTab", "uploadTab", "currentTab", "currentPanel", "clearRow"]
5
+ static values = {
6
+ existingUrl: String,
7
+ defaultTab: String
8
+ }
9
+
10
+ connect() {
11
+ this.isDrawing = false
12
+ this.hasDrawing = false
13
+ this.activeTab = this.defaultTabValue || 'draw'
14
+ this._submitting = false
15
+
16
+ if (this.hasCanvasTarget) {
17
+ this._setupCanvas()
18
+ }
19
+
20
+ // Nascondi il pulsante clear se si parte sulla tab current
21
+ this._updateClearRow()
22
+
23
+ this._form = this.element.closest('form')
24
+ if (this._form) {
25
+ this._boundSubmit = this.onFormSubmit.bind(this)
26
+ this._form.addEventListener('submit', this._boundSubmit)
27
+ }
28
+ }
29
+
30
+ disconnect() {
31
+ if (this._form && this._boundSubmit) {
32
+ this._form.removeEventListener('submit', this._boundSubmit)
33
+ }
34
+ }
35
+
36
+ _setupCanvas() {
37
+ const canvas = this.canvasTarget
38
+ // Use fixed internal resolution; CSS handles display size
39
+ canvas.width = 800
40
+ canvas.height = 200
41
+
42
+ const ctx = canvas.getContext('2d')
43
+ ctx.strokeStyle = '#1a1a1a'
44
+ ctx.lineWidth = 2.5
45
+ ctx.lineCap = 'round'
46
+ ctx.lineJoin = 'round'
47
+ this._ctx = ctx
48
+ }
49
+
50
+ _getPos(clientX, clientY) {
51
+ const canvas = this.canvasTarget
52
+ const rect = canvas.getBoundingClientRect()
53
+ const scaleX = canvas.width / rect.width
54
+ const scaleY = canvas.height / rect.height
55
+ return {
56
+ x: (clientX - rect.left) * scaleX,
57
+ y: (clientY - rect.top) * scaleY
58
+ }
59
+ }
60
+
61
+ onMouseDown(e) {
62
+ if (this.activeTab !== 'draw') return
63
+ this.isDrawing = true
64
+ const pos = this._getPos(e.clientX, e.clientY)
65
+ this._ctx.beginPath()
66
+ this._ctx.moveTo(pos.x, pos.y)
67
+ }
68
+
69
+ onMouseMove(e) {
70
+ if (!this.isDrawing) return
71
+ e.preventDefault()
72
+ const pos = this._getPos(e.clientX, e.clientY)
73
+ this._ctx.lineTo(pos.x, pos.y)
74
+ this._ctx.stroke()
75
+ this.hasDrawing = true
76
+ }
77
+
78
+ onMouseUp() {
79
+ this.isDrawing = false
80
+ }
81
+
82
+ onMouseLeave() {
83
+ this.isDrawing = false
84
+ }
85
+
86
+ onTouchStart(e) {
87
+ if (this.activeTab !== 'draw') return
88
+ e.preventDefault()
89
+ this.isDrawing = true
90
+ const touch = e.touches[0]
91
+ const pos = this._getPos(touch.clientX, touch.clientY)
92
+ this._ctx.beginPath()
93
+ this._ctx.moveTo(pos.x, pos.y)
94
+ }
95
+
96
+ onTouchMove(e) {
97
+ if (!this.isDrawing) return
98
+ e.preventDefault()
99
+ const touch = e.touches[0]
100
+ const pos = this._getPos(touch.clientX, touch.clientY)
101
+ this._ctx.lineTo(pos.x, pos.y)
102
+ this._ctx.stroke()
103
+ this.hasDrawing = true
104
+ }
105
+
106
+ onTouchEnd() {
107
+ this.isDrawing = false
108
+ }
109
+
110
+ switchToCurrentTab(e) {
111
+ e.preventDefault()
112
+ this.activeTab = 'current'
113
+ if (this.hasCurrentPanelTarget) this.currentPanelTarget.classList.remove('d-none')
114
+ this.drawPanelTarget.classList.add('d-none')
115
+ this.uploadPanelTarget.classList.add('d-none')
116
+ if (this.hasCurrentTabTarget) this.currentTabTarget.classList.add('active')
117
+ this.drawTabTarget.classList.remove('active')
118
+ this.uploadTabTarget.classList.remove('active')
119
+ // Nessun nuovo file: mantieni la firma esistente
120
+ this.fileInputTarget.value = ''
121
+ this._updateClearRow()
122
+ }
123
+
124
+ switchToDrawTab(e) {
125
+ e.preventDefault()
126
+ this.activeTab = 'draw'
127
+ if (this.hasCurrentPanelTarget) this.currentPanelTarget.classList.add('d-none')
128
+ this.drawPanelTarget.classList.remove('d-none')
129
+ this.uploadPanelTarget.classList.add('d-none')
130
+ if (this.hasCurrentTabTarget) this.currentTabTarget.classList.remove('active')
131
+ this.drawTabTarget.classList.add('active')
132
+ this.uploadTabTarget.classList.remove('active')
133
+ this._updateClearRow()
134
+ }
135
+
136
+ switchToUploadTab(e) {
137
+ e.preventDefault()
138
+ this.activeTab = 'upload'
139
+ if (this.hasCurrentPanelTarget) this.currentPanelTarget.classList.add('d-none')
140
+ this.drawPanelTarget.classList.add('d-none')
141
+ this.uploadPanelTarget.classList.remove('d-none')
142
+ if (this.hasCurrentTabTarget) this.currentTabTarget.classList.remove('active')
143
+ this.drawTabTarget.classList.remove('active')
144
+ this.uploadTabTarget.classList.add('active')
145
+ this._updateClearRow()
146
+ }
147
+
148
+ _updateClearRow() {
149
+ if (!this.hasClearRowTarget) return
150
+ if (this.activeTab === 'current') {
151
+ this.clearRowTarget.classList.add('d-none')
152
+ } else {
153
+ this.clearRowTarget.classList.remove('d-none')
154
+ }
155
+ }
156
+
157
+ clearSignature(e) {
158
+ e.preventDefault()
159
+ if (this.activeTab === 'draw') {
160
+ this._ctx.clearRect(0, 0, this.canvasTarget.width, this.canvasTarget.height)
161
+ this.hasDrawing = false
162
+ } else {
163
+ this.uploadInputTarget.value = ''
164
+ this.previewTarget.src = ''
165
+ this.previewTarget.classList.add('d-none')
166
+ }
167
+ // Reset the hidden file input so the existing attachment is preserved (no new file)
168
+ this.fileInputTarget.value = ''
169
+ }
170
+
171
+ onUploadChange() {
172
+ const file = this.uploadInputTarget.files[0]
173
+ if (!file) return
174
+
175
+ const reader = new FileReader()
176
+ reader.onload = (ev) => {
177
+ this.previewTarget.src = ev.target.result
178
+ this.previewTarget.classList.remove('d-none')
179
+ }
180
+ reader.readAsDataURL(file)
181
+
182
+ // Copy the selected file to the real (hidden) input
183
+ const dt = new DataTransfer()
184
+ dt.items.add(file)
185
+ this.fileInputTarget.files = dt.files
186
+ }
187
+
188
+ onFormSubmit(e) {
189
+ // Se siamo sulla tab current, non toccare il file input (mantieni firma esistente)
190
+ if (this.activeTab === 'current') return
191
+ // Se siamo sulla tab upload, il file input è già popolato
192
+ if (this.activeTab === 'upload') return
193
+ // Tab draw: intercetta solo se l'utente ha disegnato qualcosa
194
+ if (this._submitting) return
195
+ if (!this.hasDrawing) return
196
+
197
+ e.preventDefault()
198
+ this._submitting = true
199
+
200
+ this.canvasTarget.toBlob((blob) => {
201
+ const file = new File([blob], 'signature.png', { type: 'image/png' })
202
+ const dt = new DataTransfer()
203
+ dt.items.add(file)
204
+ this.fileInputTarget.files = dt.files
205
+ this.hasDrawing = false
206
+ this._submitting = false
207
+ this._form.requestSubmit()
208
+ }, 'image/png')
209
+ }
210
+ }
@@ -244,6 +244,10 @@ module Lato
244
244
  render 'lato/components/form_item_input_list', form: form, key: key, partial: partial, partial_params: partial_params
245
245
  end
246
246
 
247
+ def lato_form_item_input_signature(form, key, options = {})
248
+ render 'lato/components/form_item_input_signature', form: form, key: key, options: options
249
+ end
250
+
247
251
  def lato_form_submit(form, label, options = {})
248
252
  options[:class] ||= []
249
253
  options[:class].push('btn')
@@ -0,0 +1,112 @@
1
+ <%
2
+
3
+ form ||= nil
4
+ key ||= nil
5
+ options ||= {}
6
+
7
+ existing_attachment = form.object.respond_to?(key) ? form.object.send(key) : nil
8
+ existing_url = (existing_attachment.respond_to?(:attached?) && existing_attachment.attached?) ? url_for(existing_attachment) : nil
9
+
10
+ has_error = form.object && form.object.errors[key] && form.object.errors[key].any?
11
+ container_classes = ['lato-input-signature', 'border', 'rounded', 'p-3']
12
+ container_classes << 'border-danger' if has_error
13
+
14
+ # Se esiste una firma, la tab attiva di default è 'current', altrimenti 'draw'
15
+ default_tab = existing_url ? 'current' : 'draw'
16
+
17
+ %>
18
+
19
+ <div class="<%= container_classes.join(' ') %>"
20
+ data-controller="lato-input-signature"
21
+ data-lato-input-signature-default-tab-value="<%= default_tab %>"
22
+ <% if existing_url %>data-lato-input-signature-existing-url-value="<%= existing_url %>"<% end %>>
23
+
24
+ <%# Hidden file input – submitted with the form %>
25
+ <%= form.file_field key, class: 'd-none', data: { 'lato_input_signature_target': 'fileInput' }, accept: 'image/png,image/jpeg' %>
26
+
27
+ <%# Tab navigation %>
28
+ <ul class="nav nav-tabs mb-2" style="font-size: 0.8rem;">
29
+ <% if existing_url %>
30
+ <li class="nav-item">
31
+ <a href="#" class="nav-link py-1 px-2 <%= 'active' if default_tab == 'current' %>"
32
+ data-lato-input-signature-target="currentTab"
33
+ data-action="click->lato-input-signature#switchToCurrentTab">
34
+ <i class="bi bi-image"></i> <%= I18n.t('lato.signature_current') %>
35
+ </a>
36
+ </li>
37
+ <% end %>
38
+ <li class="nav-item">
39
+ <a href="#" class="nav-link py-1 px-2 <%= 'active' if default_tab == 'draw' %>"
40
+ data-lato-input-signature-target="drawTab"
41
+ data-action="click->lato-input-signature#switchToDrawTab">
42
+ <i class="bi bi-pen"></i> <%= I18n.t('lato.signature_draw') %>
43
+ </a>
44
+ </li>
45
+ <li class="nav-item">
46
+ <a href="#" class="nav-link py-1 px-2"
47
+ data-lato-input-signature-target="uploadTab"
48
+ data-action="click->lato-input-signature#switchToUploadTab">
49
+ <i class="bi bi-upload"></i> <%= I18n.t('lato.signature_upload') %>
50
+ </a>
51
+ </li>
52
+ </ul>
53
+
54
+ <%# Current signature panel (solo se esiste) %>
55
+ <% if existing_url %>
56
+ <div data-lato-input-signature-target="currentPanel">
57
+ <img src="<%= existing_url %>"
58
+ class="border rounded"
59
+ style="max-height: 120px; max-width: 100%; object-fit: contain; display: block;"
60
+ alt="<%= I18n.t('lato.signature_current') %>">
61
+ </div>
62
+ <% end %>
63
+
64
+ <%# Draw panel %>
65
+ <div class="<%= existing_url ? 'd-none' : '' %>" data-lato-input-signature-target="drawPanel">
66
+ <canvas class="border rounded w-100 bg-white"
67
+ style="height: 140px; cursor: crosshair; touch-action: none; display: block;"
68
+ data-lato-input-signature-target="canvas"
69
+ data-action="mousedown->lato-input-signature#onMouseDown
70
+ mousemove->lato-input-signature#onMouseMove
71
+ mouseup->lato-input-signature#onMouseUp
72
+ mouseleave->lato-input-signature#onMouseLeave
73
+ touchstart->lato-input-signature#onTouchStart
74
+ touchmove->lato-input-signature#onTouchMove
75
+ touchend->lato-input-signature#onTouchEnd">
76
+ </canvas>
77
+ <div class="text-muted mt-1" style="font-size: 0.75rem;">
78
+ <i class="bi bi-info-circle"></i> <%= I18n.t('lato.signature_draw_help') %>
79
+ </div>
80
+ </div>
81
+
82
+ <%# Upload panel %>
83
+ <div class="d-none" data-lato-input-signature-target="uploadPanel">
84
+ <input type="file"
85
+ accept="image/png,image/jpeg"
86
+ class="form-control form-control-sm"
87
+ data-lato-input-signature-target="uploadInput"
88
+ data-action="change->lato-input-signature#onUploadChange">
89
+ <img class="mt-2 border rounded d-none"
90
+ style="max-height: 120px; max-width: 100%; object-fit: contain;"
91
+ data-lato-input-signature-target="preview"
92
+ alt="<%= I18n.t('lato.signature_preview_alt') %>">
93
+ </div>
94
+
95
+ <%# Validation error %>
96
+ <% if has_error %>
97
+ <div class="invalid-feedback d-block mt-1">
98
+ <%= form.object.errors[key].first %>
99
+ </div>
100
+ <% end %>
101
+
102
+ <%# Clear button (solo nelle tab di editing) %>
103
+ <div class="d-flex justify-content-end mt-2" data-lato-input-signature-target="clearRow">
104
+ <button type="button"
105
+ class="btn btn-outline-secondary btn-sm"
106
+ style="font-size: 0.75rem;"
107
+ data-action="click->lato-input-signature#clearSignature">
108
+ <i class="bi bi-eraser"></i> <%= I18n.t('lato.signature_clear') %>
109
+ </button>
110
+ </div>
111
+
112
+ </div>
@@ -1,3 +1,6 @@
1
+ <link rel="canonical" href="<%= request.original_url %>">
2
+
3
+ <!-- PWA Support -->
1
4
  <meta name="application-name" content="<%= Lato.config.application_title %>">
2
5
  <meta name="theme-color" content="<%= Lato.config.application_brand_color %>">
3
6
  <meta name="full-screen" content="yes">
@@ -6,8 +9,26 @@
6
9
  <meta name="mobile-web-app-capable" content="yes">
7
10
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
8
11
  <meta name="apple-mobile-web-app-title" content="<%= Lato.config.application_title %>">
9
-
10
12
  <link rel="icon" href="/favicon.ico">
11
13
  <link rel="manifest" href="/manifest.json">
12
14
 
13
- <!-- Free for head tags -->
15
+ <!-- Standard Metadata -->
16
+ <%# <meta name="description" content=""> %>
17
+ <%# <meta name="keywords" content=""> %>
18
+ <%# <meta name="author" content=""> %>
19
+
20
+ <!-- OG Tags -->
21
+ <meta property="og:title" content="<%= @layout_page_title || Lato.config.application_title %>">
22
+ <meta property="og:site_name" content="<%= Lato.config.application_title %>">
23
+ <meta property="og:type" content="website">
24
+ <meta property="og:url" content="<%= request.original_url %>">
25
+ <%# <meta property="og:description" content=""> %>
26
+ <%# <meta property="og:image" content=""> %>
27
+
28
+ <!-- Twitter Card Tags -->
29
+ <meta name="twitter:card" content="summary_large_image">
30
+ <meta name="twitter:title" content="<%= @layout_page_title || Lato.config.application_title %>">
31
+ <%# <meta name="twitter:description" content=""> %>
32
+ <%# <meta name="twitter:image" content=""> %>
33
+
34
+ <%# Free for head tags %>
@@ -93,6 +93,14 @@ en:
93
93
  dropzone_drag_and_drop_or_click: Drag and drop files here or click to upload
94
94
  add_item: Add item
95
95
  remove_item: Remove item
96
+ signature_draw: Draw
97
+ signature_upload: Upload image
98
+ signature_draw_help: Draw your signature in the space above
99
+ signature_clear: Clear
100
+ signature_current: Current signature
101
+ signature_preview_alt: Signature preview
102
+ signature_edit: Edit
103
+ signature_cancel_edit: Cancel edit
96
104
 
97
105
  invitation_mailer:
98
106
  invite_mail_subject: You received an invitation
@@ -93,6 +93,14 @@ fr:
93
93
  operation_failed_title: Échec de l'opération
94
94
  operation_failed_subtitle: Une erreur s'est produite lors de l'opération
95
95
  dropzone_drag_and_drop_or_click: Faites glisser et déposez les fichiers ici ou cliquez pour télécharger
96
+ signature_draw: Dessiner
97
+ signature_upload: Télécharger une image
98
+ signature_draw_help: Dessinez votre signature dans l'espace ci-dessus
99
+ signature_clear: Effacer
100
+ signature_current: Signature actuelle
101
+ signature_preview_alt: Aperçu de la signature
102
+ signature_edit: Modifier
103
+ signature_cancel_edit: Annuler la modification
96
104
  add_item: Ajouter un élément
97
105
  remove_item: Supprimer un élément
98
106
 
@@ -95,6 +95,14 @@ it:
95
95
  dropzone_drag_and_drop_or_click: Trascina qui i file o clicca per caricare
96
96
  add_item: Aggiungi elemento
97
97
  remove_item: Rimuovi elemento
98
+ signature_draw: Disegna
99
+ signature_upload: Carica immagine
100
+ signature_draw_help: Disegna la tua firma nello spazio sopra
101
+ signature_clear: Cancella
102
+ signature_current: Firma attuale
103
+ signature_preview_alt: Anteprima firma
104
+ signature_edit: Modifica
105
+ signature_cancel_edit: Annulla modifica
98
106
 
99
107
  invitation_mailer:
100
108
  invite_mail_subject: Hai ricevuto un invito
@@ -93,6 +93,14 @@ ro:
93
93
  operation_failed_title: Operație eșuată
94
94
  operation_failed_subtitle: A apărut o eroare în timpul operației
95
95
  dropzone_drag_and_drop_or_click: Trage și plasează fișiere aici sau fă clic pentru a le încărca
96
+ signature_draw: Desenează
97
+ signature_upload: Încarcă imagine
98
+ signature_draw_help: Desenează semnătura ta în spațiul de mai sus
99
+ signature_clear: Șterge
100
+ signature_current: Semnătura actuală
101
+ signature_preview_alt: Previzualizare semnătură
102
+ signature_edit: Editează
103
+ signature_cancel_edit: Anulează modificarea
96
104
  add_item: Adaugă un element
97
105
  remove_item: Elimină un element
98
106
 
data/lib/lato/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Lato
2
- VERSION = "3.19.5"
2
+ VERSION = "3.19.6"
3
3
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lato
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.19.5
4
+ version: 3.19.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-01-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -184,6 +183,7 @@ files:
184
183
  - app/assets/javascripts/lato/controllers/lato_input_autocomplete_controller.js
185
184
  - app/assets/javascripts/lato/controllers/lato_input_dropzone_controller.js
186
185
  - app/assets/javascripts/lato/controllers/lato_input_list_controller.js
186
+ - app/assets/javascripts/lato/controllers/lato_input_signature_controller.js
187
187
  - app/assets/javascripts/lato/controllers/lato_network_controller.js
188
188
  - app/assets/javascripts/lato/controllers/lato_operation_controller.js
189
189
  - app/assets/javascripts/lato/controllers/lato_tooltip_controller.js
@@ -247,6 +247,7 @@ files:
247
247
  - app/views/lato/authentication/webauthn.html.erb
248
248
  - app/views/lato/components/_form_item_input_file_dropzone.html.erb
249
249
  - app/views/lato/components/_form_item_input_list.html.erb
250
+ - app/views/lato/components/_form_item_input_signature.html.erb
250
251
  - app/views/lato/components/_index.html.erb
251
252
  - app/views/lato/components/_navbar_nav_item.html.erb
252
253
  - app/views/lato/components/_navbar_nav_locales_item.html.erb
@@ -310,7 +311,6 @@ licenses:
310
311
  metadata:
311
312
  homepage_uri: https://github.com/Lato-org/lato
312
313
  source_code_uri: https://github.com/Lato-org/lato
313
- post_install_message:
314
314
  rdoc_options: []
315
315
  require_paths:
316
316
  - lib
@@ -325,8 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
325
325
  - !ruby/object:Gem::Version
326
326
  version: '0'
327
327
  requirements: []
328
- rubygems_version: 3.4.1
329
- signing_key:
328
+ rubygems_version: 4.0.9
330
329
  specification_version: 4
331
330
  summary: Basic engine for all Lato projects
332
331
  test_files: []