metadata_presenter 3.0.15 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/metadata_presenter/answers_controller.rb +107 -5
- data/app/controllers/metadata_presenter/file_controller.rb +9 -0
- data/app/controllers/metadata_presenter/pages_controller.rb +1 -2
- data/app/helpers/metadata_presenter/application_helper.rb +56 -0
- data/app/models/metadata_presenter/component.rb +10 -1
- data/app/models/metadata_presenter/file_uploader.rb +4 -0
- data/app/models/metadata_presenter/multi_upload_answer.rb +33 -0
- data/app/models/metadata_presenter/page.rb +4 -0
- data/app/models/metadata_presenter/page_answers.rb +60 -1
- data/app/presenters/metadata_presenter/page_answers_presenter.rb +4 -0
- data/app/validators/metadata_presenter/accept_validator.rb +11 -0
- data/app/validators/metadata_presenter/max_files_validator.rb +9 -0
- data/app/validators/metadata_presenter/multiupload_validator.rb +24 -0
- data/app/validators/metadata_presenter/required_validator.rb +4 -0
- data/app/validators/metadata_presenter/validate_answers.rb +15 -6
- data/app/views/metadata_presenter/component/_multiupload.html.erb +51 -0
- data/config/initializers/supported_components.rb +1 -1
- data/config/locales/en.yml +12 -0
- data/config/routes.rb +1 -0
- data/default_metadata/component/multiupload.json +30 -0
- data/default_metadata/string/error.max_files.json +6 -0
- data/default_metadata/string/error.multiupload.json +6 -0
- data/default_metadata/validations/max_files.json +1 -0
- data/fixtures/version.json +73 -0
- data/lib/metadata_presenter/version.rb +1 -1
- data/schemas/component/multiupload.json +42 -0
- data/schemas/validations/validations.json +38 -0
- metadata +25 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8762e144cc7633a21b9a53fce23efe99d668c8cbb2dc3a705ddf31693134b0d4
|
4
|
+
data.tar.gz: f46ae9f949c32cf959371050fefbd76e0f260ea6fcf803c29600dc5199534784
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78fde7cfe785f26a75ea4d9e268f4d2b274783e43bc195768733f347c15f3f107734efc4ed308d484056c0ae21fc916e1cab011b642cc450531777602e589234
|
7
|
+
data.tar.gz: 3687a2de78313c5365c8807bfe34e88dd23739fa76302465ccbd594db7e64c0cf3d8c975a942b2058ca7d00e551310002d311884575b07c9f06126203e268a1d
|
@@ -4,30 +4,85 @@ module MetadataPresenter
|
|
4
4
|
|
5
5
|
def create
|
6
6
|
@previous_answers = reload_user_data.deep_dup
|
7
|
-
|
7
|
+
|
8
|
+
@page_answers = PageAnswers.new(page, incoming_answer, autocomplete_items(page.components))
|
8
9
|
|
9
10
|
if params[:save_for_later].present?
|
10
|
-
save_user_data
|
11
|
-
|
11
|
+
save_user_data unless upload? || multiupload?
|
12
|
+
|
12
13
|
redirect_to save_path(page_slug: params[:page_slug]) and return
|
13
14
|
end
|
14
15
|
|
15
16
|
upload_files if upload?
|
17
|
+
upload_multiupload_new_files if multiupload? && answers_params.present?
|
16
18
|
|
17
19
|
if @page_answers.validate_answers
|
18
20
|
save_user_data # method signature
|
21
|
+
|
22
|
+
# if adding another file in multi upload, redirect back to referrer
|
23
|
+
if about_to_render_multiupload?
|
24
|
+
redirect_back(fallback_location: root_path) and return
|
25
|
+
end
|
26
|
+
|
19
27
|
redirect_to_next_page
|
20
28
|
else
|
29
|
+
# can't render error in the same way for the multiupload component
|
30
|
+
if about_to_render_multiupload?
|
31
|
+
@user_data = @previous_answers
|
32
|
+
|
33
|
+
render template: @page.template, status: :unprocessable_entity and return
|
34
|
+
end
|
21
35
|
render_validation_error
|
22
36
|
end
|
23
37
|
end
|
24
38
|
|
39
|
+
def about_to_render_multiupload?
|
40
|
+
answers_params.present? && multiupload?
|
41
|
+
end
|
42
|
+
|
25
43
|
def update_count_matching_filenames(original_filename, user_data)
|
26
44
|
extname = File.extname(original_filename)
|
27
45
|
basename = File.basename(original_filename, extname)
|
28
46
|
filename_regex = /^#{Regexp.quote(basename)}(?>-\((\d)\))?#{Regexp.quote(extname)}/
|
29
47
|
|
30
|
-
user_data.select { |_k, v|
|
48
|
+
user_data.select { |_k, v|
|
49
|
+
if v.is_a?(Array)
|
50
|
+
v.any? { |e| e['original_filename'] =~ filename_regex }
|
51
|
+
else
|
52
|
+
v['original_filename'] =~ filename_regex
|
53
|
+
end
|
54
|
+
}.count
|
55
|
+
end
|
56
|
+
|
57
|
+
def upload_multiupload_new_files
|
58
|
+
user_data = load_user_data
|
59
|
+
@page_answers.page.multiupload_components.each do |component|
|
60
|
+
previous_answers = user_data[component.id]
|
61
|
+
incoming_filename = @page_answers.send(component.id)[component.id].last['original_filename']
|
62
|
+
|
63
|
+
if editor_preview?
|
64
|
+
@page_answers.uploaded_files.push(multiuploaded_file(previous_answers, component))
|
65
|
+
else
|
66
|
+
|
67
|
+
if incoming_filename.present?
|
68
|
+
# determine if duplicate filename from any other user answer
|
69
|
+
@page_answers.count = update_count_matching_filenames(incoming_filename, user_data)
|
70
|
+
end
|
71
|
+
|
72
|
+
if previous_answers.present? && previous_answers.any? { |answer| answer['original_filename'] == incoming_answer.incoming_answer.values.first.original_filename }
|
73
|
+
@page_answers.count = nil # ensure we don't also try to suffix this filename as we will reject it anyway
|
74
|
+
file = MetadataPresenter::UploadedFile.new(
|
75
|
+
file: @page_answers.send(component.id)[component.id].last,
|
76
|
+
component:
|
77
|
+
)
|
78
|
+
|
79
|
+
file.errors.add('invalid.multiupload')
|
80
|
+
@page_answers.uploaded_files.push(file)
|
81
|
+
else
|
82
|
+
@page_answers.uploaded_files.push(multiuploaded_file(previous_answers, component))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
31
86
|
end
|
32
87
|
|
33
88
|
def show_save_and_return
|
@@ -67,6 +122,16 @@ module MetadataPresenter
|
|
67
122
|
render template: page.template, status: :unprocessable_entity
|
68
123
|
end
|
69
124
|
|
125
|
+
def incoming_answer
|
126
|
+
if multiupload?
|
127
|
+
multiupload_answer = MultiUploadAnswer.new
|
128
|
+
multiupload_answer.key = Array(page.components).first.id
|
129
|
+
multiupload_answer.previous_answers = @previous_answers[Array(page.components).first.id]
|
130
|
+
multiupload_answer.incoming_answer = answers_params
|
131
|
+
end
|
132
|
+
multiupload_answer || answers_params
|
133
|
+
end
|
134
|
+
|
70
135
|
def answers_params
|
71
136
|
params.permit(:page_slug, :save_for_later)
|
72
137
|
params[:answers] ? params[:answers].permit! : {}
|
@@ -84,7 +149,6 @@ module MetadataPresenter
|
|
84
149
|
user_data = load_user_data
|
85
150
|
@page_answers.page.upload_components.each do |component|
|
86
151
|
answer = user_data[component.id]
|
87
|
-
|
88
152
|
original_filename = answer.nil? ? @page_answers.send(component.id)['original_filename'] : answer['original_filename']
|
89
153
|
|
90
154
|
if original_filename.present?
|
@@ -112,6 +176,40 @@ module MetadataPresenter
|
|
112
176
|
end
|
113
177
|
end
|
114
178
|
|
179
|
+
def multiuploaded_file(answer, component)
|
180
|
+
if answer.present?
|
181
|
+
if @page_answers.answers.is_a?(MetadataPresenter::MultiUploadAnswer)
|
182
|
+
if @page_answers.answers.incoming_answer.present?
|
183
|
+
FileUploader.new(
|
184
|
+
session:,
|
185
|
+
page_answers: @page_answers,
|
186
|
+
component:,
|
187
|
+
adapter: upload_adapter
|
188
|
+
).upload
|
189
|
+
else
|
190
|
+
MetadataPresenter::UploadedFile.new(
|
191
|
+
file: @page_answers.answers.previous_answers.last,
|
192
|
+
component:
|
193
|
+
)
|
194
|
+
end
|
195
|
+
else
|
196
|
+
FileUploader.new(
|
197
|
+
session:,
|
198
|
+
page_answers: @page_answers,
|
199
|
+
component:,
|
200
|
+
adapter: upload_adapter
|
201
|
+
).upload
|
202
|
+
end
|
203
|
+
else
|
204
|
+
FileUploader.new(
|
205
|
+
session:,
|
206
|
+
page_answers: @page_answers,
|
207
|
+
component:,
|
208
|
+
adapter: upload_adapter
|
209
|
+
).upload
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
115
213
|
def upload_adapter
|
116
214
|
super if defined?(super)
|
117
215
|
end
|
@@ -119,5 +217,9 @@ module MetadataPresenter
|
|
119
217
|
def upload?
|
120
218
|
Array(page.components).any?(&:upload?)
|
121
219
|
end
|
220
|
+
|
221
|
+
def multiupload?
|
222
|
+
Array(page.components).any?(&:multiupload?)
|
223
|
+
end
|
122
224
|
end
|
123
225
|
end
|
@@ -5,8 +5,17 @@ module MetadataPresenter
|
|
5
5
|
redirect_back(fallback_location: root_path)
|
6
6
|
end
|
7
7
|
|
8
|
+
def remove_multifile
|
9
|
+
remove_file_from_data(params[:component_id], params[:file_uuid])
|
10
|
+
redirect_back(fallback_location: root_path)
|
11
|
+
end
|
12
|
+
|
8
13
|
def remove_user_data(component_id)
|
9
14
|
super(component_id) if defined?(super)
|
10
15
|
end
|
16
|
+
|
17
|
+
def remove_file_from_data(component_id, file_id)
|
18
|
+
super(component_id, file_id) if defined?(super)
|
19
|
+
end
|
11
20
|
end
|
12
21
|
end
|
@@ -4,13 +4,12 @@ module MetadataPresenter
|
|
4
4
|
|
5
5
|
def show
|
6
6
|
@user_data = load_user_data # method signature
|
7
|
-
@page ||= service.find_page_by_url(request.env['PATH_INFO'])
|
8
7
|
|
8
|
+
@page ||= service.find_page_by_url(request.env['PATH_INFO'])
|
9
9
|
if @page
|
10
10
|
load_autocomplete_items
|
11
11
|
|
12
12
|
@page_answers = PageAnswers.new(@page, @user_data)
|
13
|
-
|
14
13
|
render template: @page.template
|
15
14
|
else
|
16
15
|
not_found
|
@@ -35,5 +35,61 @@ module MetadataPresenter
|
|
35
35
|
def default_page_title(type)
|
36
36
|
MetadataPresenter::DefaultMetadata[type.to_s]&.[]('heading')
|
37
37
|
end
|
38
|
+
|
39
|
+
def multiupload_files_remaining
|
40
|
+
component = page_multiupload_component
|
41
|
+
answers = @user_data.keys.include?(component.id) ? @user_data.find(component.id).first : []
|
42
|
+
max_files = component.validation['max_files'].to_i
|
43
|
+
|
44
|
+
if uploads_remaining.zero?
|
45
|
+
I18n.t('presenter.questions.multiupload.none')
|
46
|
+
elsif max_files == 1
|
47
|
+
I18n.t('presenter.questions.multiupload.single_upload')
|
48
|
+
elsif uploads_remaining == 1
|
49
|
+
if answers.present?
|
50
|
+
I18n.t('presenter.questions.multiupload.answered_singular')
|
51
|
+
else
|
52
|
+
I18n.t('presenter.questions.multiupload.singular')
|
53
|
+
end
|
54
|
+
elsif answers.present?
|
55
|
+
I18n.t('presenter.questions.multiupload.answered_plural', num: uploads_remaining)
|
56
|
+
else
|
57
|
+
I18n.t('presenter.questions.multiupload.plural', num: uploads_remaining)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def uploads_remaining
|
62
|
+
component = page_multiupload_component
|
63
|
+
max_files = component.validation['max_files'].to_i
|
64
|
+
answers = @user_data.keys.include?(component.id) ? @user_data[component.id] : []
|
65
|
+
return 0 if answers.is_a?(ActionDispatch::Http::UploadedFile)
|
66
|
+
|
67
|
+
max_files - answers.count
|
68
|
+
end
|
69
|
+
|
70
|
+
def uploads_count
|
71
|
+
component = page_multiupload_component
|
72
|
+
answers = @user_data.keys.include?(component.id) ? @user_data[component.id] : []
|
73
|
+
|
74
|
+
return 0 if answers.is_a?(ActionDispatch::Http::UploadedFile)
|
75
|
+
|
76
|
+
answers.count == 1 ? I18n.t('presenter.questions.multiupload.answered_count_singular') : I18n.t('presenter.questions.multiupload.answered_count_plural', num: answers.count)
|
77
|
+
end
|
78
|
+
|
79
|
+
def files_to_render
|
80
|
+
component = page_multiupload_component
|
81
|
+
|
82
|
+
error_file = @page_answers.uploaded_files.select { |file| file.errors.any? }.first
|
83
|
+
|
84
|
+
if error_file.present?
|
85
|
+
@page_answers.send(component.id)[component.id].compact.reject { |file| file[error_file.file['original_filename'] == 'original_filename'] }
|
86
|
+
else
|
87
|
+
@page_answers.send(component.id)[component.id].compact
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def page_multiupload_component
|
92
|
+
@page.components.select { |c| c.type == 'multiupload' }.first
|
93
|
+
end
|
38
94
|
end
|
39
95
|
end
|
@@ -3,7 +3,8 @@ class MetadataPresenter::Component < MetadataPresenter::Metadata
|
|
3
3
|
'date' => 'date',
|
4
4
|
'number' => 'number',
|
5
5
|
'text' => 'string',
|
6
|
-
'textarea' => 'string'
|
6
|
+
'textarea' => 'string',
|
7
|
+
'multiupload' => 'file'
|
7
8
|
}.freeze
|
8
9
|
|
9
10
|
# Used for max_length and max_word validations.
|
@@ -57,6 +58,10 @@ class MetadataPresenter::Component < MetadataPresenter::Metadata
|
|
57
58
|
type == 'upload'
|
58
59
|
end
|
59
60
|
|
61
|
+
def multiupload?
|
62
|
+
type == 'multiupload'
|
63
|
+
end
|
64
|
+
|
60
65
|
def find_item_by_uuid(uuid)
|
61
66
|
items.find { |item| item.uuid == uuid }
|
62
67
|
end
|
@@ -73,6 +78,10 @@ class MetadataPresenter::Component < MetadataPresenter::Metadata
|
|
73
78
|
VALIDATION_STRING_LENGTH_THRESHOLD
|
74
79
|
end
|
75
80
|
|
81
|
+
def max_files
|
82
|
+
metadata.max_files.presence || '0'
|
83
|
+
end
|
84
|
+
|
76
85
|
private
|
77
86
|
|
78
87
|
def validation_bundle_key
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MetadataPresenter
|
2
|
+
class MultiUploadAnswer
|
3
|
+
attr_accessor :previous_answers, :incoming_answer, :key
|
4
|
+
|
5
|
+
def to_h
|
6
|
+
{
|
7
|
+
key => previous_answers_value.present? ? previous_answers_value.reject(&:blank?) : []
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def previous_answers_value
|
12
|
+
return nil if previous_answers.nil? && incoming_answer.nil?
|
13
|
+
return [incoming_answer] if previous_answers.nil? && incoming_answer.present?
|
14
|
+
|
15
|
+
if previous_answers.is_a?(Array)
|
16
|
+
return previous_answers.reject(&:blank?) if incoming_answer.nil? || previous_answers.find { |answer|
|
17
|
+
answer['original_filename'] == incoming_answer['original_filename']
|
18
|
+
}.present?
|
19
|
+
|
20
|
+
previous_answers.reject(&:blank?).push(incoming_answer)
|
21
|
+
else
|
22
|
+
return [previous_answers] if incoming_answer.nil?
|
23
|
+
|
24
|
+
[previous_answers, incoming_answer]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_h(input)
|
29
|
+
self.key = input.keys[0]
|
30
|
+
self.previous_answers = input[key]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -25,11 +25,13 @@ module MetadataPresenter
|
|
25
25
|
|
26
26
|
def method_missing(method_name, *_args)
|
27
27
|
component = components.find { |c| c.id == method_name.to_s }
|
28
|
-
|
29
28
|
if component && component.type == 'date'
|
30
29
|
date_answer(component.id)
|
31
30
|
elsif component && component.type == 'upload'
|
32
31
|
upload_answer(component.id, count)
|
32
|
+
elsif component && component.type == 'multiupload'
|
33
|
+
answer_object = multiupload_answer(component.id, count)
|
34
|
+
answer_object.to_h if answer_object.present?
|
33
35
|
elsif component && component.type == 'checkboxes'
|
34
36
|
answers[method_name.to_s].to_a
|
35
37
|
else
|
@@ -53,6 +55,62 @@ module MetadataPresenter
|
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
58
|
+
def multiupload_answer(component_id, _count)
|
59
|
+
file_details = answers[component_id.to_s] unless answers.is_a?(MetadataPresenter::MultiUploadAnswer)
|
60
|
+
return nil if file_details.nil? && answers.nil?
|
61
|
+
|
62
|
+
if file_details.is_a?(Hash)
|
63
|
+
# when referencing a single previous answer but no incoming new answer
|
64
|
+
presentable = MetadataPresenter::MultiUploadAnswer.new
|
65
|
+
presentable.key = component_id.to_s
|
66
|
+
presentable.previous_answers = [file_details]
|
67
|
+
return presentable
|
68
|
+
end
|
69
|
+
|
70
|
+
if file_details.is_a?(Array)
|
71
|
+
# when referencing multiple previous answers but no incoming new answer
|
72
|
+
presentable = MetadataPresenter::MultiUploadAnswer.new
|
73
|
+
presentable.key = component_id.to_s
|
74
|
+
presentable.previous_answers = file_details.reject { |f| f['original_filename'].blank? }
|
75
|
+
return presentable
|
76
|
+
end
|
77
|
+
|
78
|
+
if answers.blank?
|
79
|
+
return nil
|
80
|
+
end
|
81
|
+
|
82
|
+
if answers.is_a?(Hash) # rendering only existing answers
|
83
|
+
return if answers[component_id].blank?
|
84
|
+
|
85
|
+
if answers[component_id].is_a?(Array)
|
86
|
+
answers[component_id].each { |answer| answer['original_filename'] = sanitize(filename(update_filename(answer['original_filename']))) }
|
87
|
+
end
|
88
|
+
|
89
|
+
answers[component_id] = answers[component_id].reject { |a| a['original_filename'].blank? }
|
90
|
+
return answers
|
91
|
+
end
|
92
|
+
|
93
|
+
# uploading a new answer, this method will be called during multiple render operations
|
94
|
+
if answers.incoming_answer.present? && answers.incoming_answer.is_a?(ActionController::Parameters)
|
95
|
+
answers.incoming_answer[component_id].original_filename = sanitize(filename(update_filename(answers.incoming_answer[component_id].original_filename)))
|
96
|
+
end
|
97
|
+
|
98
|
+
if answers.incoming_answer.present? && answers.incoming_answer.is_a?(Hash)
|
99
|
+
answers.incoming_answer['original_filename'] = sanitize(filename(update_filename(answers.incoming_answer['original_filename'])))
|
100
|
+
end
|
101
|
+
|
102
|
+
if answers.incoming_answer.present? && answers.incoming_answer[component_id].is_a?(ActionDispatch::Http::UploadedFile)
|
103
|
+
answers.incoming_answer = {
|
104
|
+
'original_filename' => sanitize(filename(update_filename(answers.incoming_answer[component_id].original_filename))),
|
105
|
+
'content_type' => answers.incoming_answer[component_id].content_type,
|
106
|
+
'tempfile' => answers.incoming_answer[component_id].tempfile.path.to_s,
|
107
|
+
'uuid' => SecureRandom.uuid
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
answers
|
112
|
+
end
|
113
|
+
|
56
114
|
def date_answer(component_id)
|
57
115
|
date = raw_date_answer(component_id)
|
58
116
|
|
@@ -81,6 +139,7 @@ module MetadataPresenter
|
|
81
139
|
basename = File.basename(filename, extname)
|
82
140
|
|
83
141
|
filename = "#{basename}-(#{count})#{extname}"
|
142
|
+
@count = nil # this is called multiple times for multiupload components so ensure we apply suffix to filename only once
|
84
143
|
end
|
85
144
|
|
86
145
|
filename
|
@@ -84,6 +84,10 @@ module MetadataPresenter
|
|
84
84
|
file_hash['original_filename']
|
85
85
|
end
|
86
86
|
|
87
|
+
def multiupload(multifile_hash)
|
88
|
+
multifile_hash[component.id].map { |i| i['original_filename'] }.join('<br>').html_safe
|
89
|
+
end
|
90
|
+
|
87
91
|
def autocomplete(value)
|
88
92
|
JSON.parse(value)['text']
|
89
93
|
end
|
@@ -3,5 +3,16 @@ module MetadataPresenter
|
|
3
3
|
def error_name
|
4
4
|
'accept'
|
5
5
|
end
|
6
|
+
|
7
|
+
def error_message_hash
|
8
|
+
if component.type == 'multiupload'
|
9
|
+
{
|
10
|
+
control: page_answers.send(component.id)[component.id].last['original_filename'],
|
11
|
+
schema_key.to_sym => component.validation[schema_key]
|
12
|
+
}
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
6
17
|
end
|
7
18
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MetadataPresenter
|
2
|
+
class MultiuploadValidator < BaseValidator
|
3
|
+
def invalid_answer?
|
4
|
+
user_answer.errors.any? { |error| error.attribute.to_s == error_name }
|
5
|
+
end
|
6
|
+
|
7
|
+
def user_answer
|
8
|
+
page_answers.uploaded_files.find do |uploaded_file|
|
9
|
+
component.id == uploaded_file.component.id
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def error_message_hash
|
14
|
+
{
|
15
|
+
control: page_answers.send(component.id)[component.id].last['original_filename'],
|
16
|
+
schema_key.to_sym => component.validation[schema_key]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def error_name
|
21
|
+
'invalid.multiupload'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -21,12 +21,21 @@ module MetadataPresenter
|
|
21
21
|
def validators
|
22
22
|
components.map { |component|
|
23
23
|
component_validations(component).map do |key|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
if key == 'max_files'
|
25
|
+
'MetadataPresenter::MaxFilesValidator'.constantize.new(
|
26
|
+
**{
|
27
|
+
page_answers:,
|
28
|
+
component:
|
29
|
+
}.merge(autocomplete_param(key))
|
30
|
+
)
|
31
|
+
else
|
32
|
+
"MetadataPresenter::#{key.classify}Validator".constantize.new(
|
33
|
+
**{
|
34
|
+
page_answers:,
|
35
|
+
component:
|
36
|
+
}.merge(autocomplete_param(key))
|
37
|
+
)
|
38
|
+
end
|
30
39
|
end
|
31
40
|
}.compact.flatten
|
32
41
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<legend class="govuk-heading-xl"><%= input_title %></legend>
|
2
|
+
|
3
|
+
<% if answered?(component.id) && @page_answers.send(component.id)[component.id].compact.count.positive? %>
|
4
|
+
<label id="uploaded-file-summary-list-label"><p class="govuk-heading-s"><%= uploads_count %></p></label>
|
5
|
+
|
6
|
+
<dl id="uploaded-file-summary-list" class="fb-block fb-block-answers govuk-summary-list" aria-labelled-by="uploaded-file-summary-list-label">
|
7
|
+
<% files_to_render.each do |previous_file| %>
|
8
|
+
<div class="govuk-summary-list__row">
|
9
|
+
<dt class="govuk-summary-list__value">
|
10
|
+
<%= previous_file['original_filename'] %>
|
11
|
+
</dt>
|
12
|
+
<dd class="govuk-summary-list__actions">
|
13
|
+
<%= link_to "#{t('presenter.questions.multiupload.remove_file')}<span class=\"govuk-visually-hidden\">#{previous_file['original_filename']}</span>".html_safe, remove_multifile_path(component.id, previous_file['uuid']), class: 'govuk-link' %>
|
14
|
+
</dd>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
17
|
+
</dl>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<% if uploads_remaining.positive? || editable? %>
|
21
|
+
<div data-multiupload-element="upload-another-file" <%= answered?(component.id) && @page_answers.send(component.id)[component.id].compact.count.positive? ? 'hidden' : '' %>>
|
22
|
+
<%= f.govuk_file_field component.id.to_sym,
|
23
|
+
hint: {
|
24
|
+
data: { "fb-default-text" => default_text('option_hint') },
|
25
|
+
text: component.hint.present? ? component.hint : ''
|
26
|
+
},
|
27
|
+
accept: component.validation['accept'],
|
28
|
+
disabled: editable?,
|
29
|
+
label: -> do %>
|
30
|
+
<% if answered?(component.id) && @page_answers.send(component.id)[component.id].compact.count.positive? && !editable? %>
|
31
|
+
<h3 class="govuk-heading-s govuk-!-margin-top-8"><%= t('presenter.questions.multiupload.add_another') %></h3>
|
32
|
+
<% else %>
|
33
|
+
<h3 class="govuk-visually-hidden"><%= t('presenter.questions.multiupload.add_another') %></h3>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
38
|
+
|
39
|
+
<% if editable? %>
|
40
|
+
<% if editor_preview? && answered?(component.id) %>
|
41
|
+
<p class="govuk-!-margin-bottom-8"><%= t('presenter.questions.multiupload.none') %></p>
|
42
|
+
<% else %>
|
43
|
+
<p class="govuk-!-margin-bottom-8"><%= component.validation['max_files'].to_i > 1 ? t('presenter.questions.multiupload.plural', num: component.validation['max_files']) : t('presenter.questions.multiupload.single_upload') %></p>
|
44
|
+
<% end %>
|
45
|
+
<% else %>
|
46
|
+
<p class="govuk-!-margin-bottom-8"><%= multiupload_files_remaining %></p>
|
47
|
+
<% end %>
|
48
|
+
|
49
|
+
<% if answered?(component.id) && uploads_remaining.positive? && @page_answers.send(component.id)[component.id].compact.count.positive? %>
|
50
|
+
<button class="govuk-button govuk-!-margin-bottom-8 govuk-button--secondary" data-multiupload-element="add-another-file"><%= t('presenter.questions.multiupload.add_another') %></button>
|
51
|
+
<% end %>
|
@@ -21,7 +21,7 @@ Rails.application.config.supported_components =
|
|
21
21
|
content: %w(content)
|
22
22
|
},
|
23
23
|
singlequestion: {
|
24
|
-
input: %w(text textarea number date radios checkboxes email upload autocomplete),
|
24
|
+
input: %w(text textarea number date radios checkboxes email upload multiupload autocomplete),
|
25
25
|
content: %w()
|
26
26
|
}
|
27
27
|
})
|
data/config/locales/en.yml
CHANGED
@@ -41,6 +41,18 @@ en:
|
|
41
41
|
payment_enabled: You still need to pay
|
42
42
|
continue_to_pay_button: Continue to pay
|
43
43
|
application_complete: 'Application complete'
|
44
|
+
questions:
|
45
|
+
multiupload:
|
46
|
+
remove_file: 'Delete'
|
47
|
+
add_another: 'Add another file'
|
48
|
+
single_upload: 'Maximum file size 7MB'
|
49
|
+
answered_singular: 'You can add 1 more file (maximum 7MB per file)'
|
50
|
+
answered_plural: 'You can add %{num} more files (maximum 7MB per file)'
|
51
|
+
answered_count_singular: 'You have added 1 file'
|
52
|
+
answered_count_plural: 'You have added %{num} files'
|
53
|
+
singular: 'You can add 1 more file (maximum 7MB)'
|
54
|
+
plural: 'You can add up to %{num} files (maximum 7MB per file)'
|
55
|
+
none: "You can't add any more files. Remove a file if you need to add a different one."
|
44
56
|
save_and_return:
|
45
57
|
save: 'Save for later'
|
46
58
|
show:
|
data/config/routes.rb
CHANGED
@@ -7,6 +7,7 @@ MetadataPresenter::Engine.routes.draw do
|
|
7
7
|
# We are not adding rails ujs to the editor app so we need to make it
|
8
8
|
# as get verb.
|
9
9
|
get '/reserved/file/:component_id', to: 'file#destroy', as: :remove_file
|
10
|
+
get '/reserved/file/:component_id/:file_uuid', to: 'file#remove_multifile', as: :remove_multifile
|
10
11
|
|
11
12
|
get 'session/expired', to: 'session#expired'
|
12
13
|
get 'session/complete', to: 'session#complete'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"_id": "component.multiupload",
|
3
|
+
"_type": "multiupload",
|
4
|
+
"errors": {},
|
5
|
+
"legend": "Question",
|
6
|
+
"hint": "",
|
7
|
+
"name": "component-name",
|
8
|
+
"validation": {
|
9
|
+
"required": true,
|
10
|
+
"max_size": "7340032",
|
11
|
+
"virus_scan": true,
|
12
|
+
"max_files": "1",
|
13
|
+
"multiupload": true,
|
14
|
+
"accept": [
|
15
|
+
"text/csv",
|
16
|
+
"text/plain",
|
17
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
18
|
+
"application/msword",
|
19
|
+
"application/vnd.oasis.opendocument.spreadsheet",
|
20
|
+
"application/vnd.oasis.opendocument.text",
|
21
|
+
"application/pdf",
|
22
|
+
"application/rtf",
|
23
|
+
"application/csv",
|
24
|
+
"image/jpeg",
|
25
|
+
"image/png",
|
26
|
+
"application/vnd.ms-excel",
|
27
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
28
|
+
]
|
29
|
+
}
|
30
|
+
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
{
|
2
|
+
"_id": "error.multiupload",
|
3
|
+
"_type": "string.error",
|
4
|
+
"description": "Cannot upload files with duplicate filenames",
|
5
|
+
"value": "The selected file cannot have the same name as a file you have already selected. Please check you aren't uploading the same file again."
|
6
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "max_files": "10" }
|
data/fixtures/version.json
CHANGED
@@ -69,6 +69,12 @@
|
|
69
69
|
}
|
70
70
|
},
|
71
71
|
"2ef7d11e-0307-49e9-9fe2-345dc528dd66": {
|
72
|
+
"_type": "flow.page",
|
73
|
+
"next": {
|
74
|
+
"default": "2ef7d11e-0307-49e9-9fe2-345dc528dd67"
|
75
|
+
}
|
76
|
+
},
|
77
|
+
"2ef7d11e-0307-49e9-9fe2-345dc528dd67": {
|
72
78
|
"_type": "flow.page",
|
73
79
|
"next": {
|
74
80
|
"default": "c7755991-436b-4495-afa6-803db58cefbc"
|
@@ -574,6 +580,73 @@
|
|
574
580
|
}
|
575
581
|
]
|
576
582
|
},
|
583
|
+
{
|
584
|
+
"_id": "page.dog-picture-2",
|
585
|
+
"url": "dog-picture-2",
|
586
|
+
"_type": "page.singlequestion",
|
587
|
+
"_uuid": "2ef7d11e-0307-49e9-9fe2-345dc528dd67",
|
588
|
+
"heading": "Multiupload",
|
589
|
+
"components": [
|
590
|
+
{
|
591
|
+
"_id": "dog-picture_upload_2",
|
592
|
+
"name": "dog-picture_upload_2",
|
593
|
+
"_type": "multiupload",
|
594
|
+
"_uuid": "f056a76e-ec3f-47ae-b625-1bba92220ad2",
|
595
|
+
"hint": "",
|
596
|
+
"legend": "Upload your best dog photos",
|
597
|
+
"validation": {
|
598
|
+
"required": true,
|
599
|
+
"max_files": "3",
|
600
|
+
"multiupload": true,
|
601
|
+
"accept": [
|
602
|
+
"audio/*",
|
603
|
+
"image/bmp",
|
604
|
+
"text/csv",
|
605
|
+
"application/vnd.ms-excel",
|
606
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
607
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
608
|
+
"application/vnd.ms-excel.sheet.macroEnabled.12",
|
609
|
+
"application/vnd.ms-excel.template.macroEnabled.12",
|
610
|
+
"application/vnd.ms-excel.addin.macroEnabled.12",
|
611
|
+
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
612
|
+
"image/gif",
|
613
|
+
"image/*",
|
614
|
+
"application/x-iwork-pages-sffpages",
|
615
|
+
"image/jpeg",
|
616
|
+
"application/pdf",
|
617
|
+
"text/plain",
|
618
|
+
"image/png",
|
619
|
+
"application/vnd.ms-powerpoint",
|
620
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
621
|
+
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
622
|
+
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
623
|
+
"application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
624
|
+
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
625
|
+
"application/vnd.ms-powerpoint.template.macroEnabled.12",
|
626
|
+
"application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
627
|
+
"text/rtf",
|
628
|
+
"excel",
|
629
|
+
"csv",
|
630
|
+
"image/svg+xml",
|
631
|
+
"pdf",
|
632
|
+
"word",
|
633
|
+
"rtf",
|
634
|
+
"plaintext",
|
635
|
+
"image/tiff",
|
636
|
+
"video/*",
|
637
|
+
"application/msword",
|
638
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
639
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
640
|
+
"application/vnd.ms-word.document.macroEnabled.12",
|
641
|
+
"application/vnd.ms-word.template.macroEnabled.12",
|
642
|
+
"application/csv"
|
643
|
+
],
|
644
|
+
"max_size": 7340032,
|
645
|
+
"virus_scan": true
|
646
|
+
}
|
647
|
+
}
|
648
|
+
]
|
649
|
+
},
|
577
650
|
{
|
578
651
|
"_id": "page.countries",
|
579
652
|
"url": "countries",
|
@@ -0,0 +1,42 @@
|
|
1
|
+
{
|
2
|
+
"$id": "http://gov.uk/schema/v1.0.0/multiupload",
|
3
|
+
"_name": "component.multiupload",
|
4
|
+
"title": "Multiupload",
|
5
|
+
"description": "Let users select and upload one or more files",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"_type": {
|
9
|
+
"const": "multiupload"
|
10
|
+
},
|
11
|
+
"max_files": {
|
12
|
+
"title": "Maximum number of files",
|
13
|
+
"description": "Maximum number of files a user can upload",
|
14
|
+
"type": "number"
|
15
|
+
},
|
16
|
+
"min_files": {
|
17
|
+
"title": "Minimum number of files",
|
18
|
+
"description": "Minimum number of files a user can upload - 1 if required, 0 if not required",
|
19
|
+
"type": "number"
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"allOf": [
|
23
|
+
{
|
24
|
+
"$ref": "definition.field"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"$ref": "definition.width_class.input"
|
28
|
+
},
|
29
|
+
{
|
30
|
+
"$ref": "validations#/definitions/errors_accept"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"$ref": "validations#/definitions/errors_max_size"
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"$ref": "validations#/definitions/errors_virus_scan"
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"$ref": "validations#/definitions/file_bundle"
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
@@ -118,6 +118,20 @@
|
|
118
118
|
}
|
119
119
|
]
|
120
120
|
},
|
121
|
+
"max_files": {
|
122
|
+
"title": "Maximum files",
|
123
|
+
"description": "The maximum number of fiels a user can upload",
|
124
|
+
"type": "number",
|
125
|
+
"minimum": 0
|
126
|
+
},
|
127
|
+
"errors_max_files": {
|
128
|
+
"title": "Error messages for 'Maximum files'",
|
129
|
+
"allOf": [
|
130
|
+
{
|
131
|
+
"$ref": "#/definitions/error_strings"
|
132
|
+
}
|
133
|
+
]
|
134
|
+
},
|
121
135
|
"pattern": {
|
122
136
|
"title": "Pattern to match string against",
|
123
137
|
"description": "A regular expression for validating users’ answers",
|
@@ -450,6 +464,9 @@
|
|
450
464
|
},
|
451
465
|
"exclusive_minimum": {
|
452
466
|
"$ref": "#/definitions/exclusive_minimum"
|
467
|
+
},
|
468
|
+
"max_files": {
|
469
|
+
"$ref": "#/definitions/max_files"
|
453
470
|
}
|
454
471
|
}
|
455
472
|
},
|
@@ -469,6 +486,9 @@
|
|
469
486
|
},
|
470
487
|
"exclusive_minimum": {
|
471
488
|
"$ref": "#/definitions/errors_exclusive_minimum"
|
489
|
+
},
|
490
|
+
"max_files": {
|
491
|
+
"$ref": "#/definitions/max_files"
|
472
492
|
}
|
473
493
|
}
|
474
494
|
}
|
@@ -509,6 +529,24 @@
|
|
509
529
|
}
|
510
530
|
}
|
511
531
|
}
|
532
|
+
},
|
533
|
+
"file_bundle": {
|
534
|
+
"properties": {
|
535
|
+
"validation": {
|
536
|
+
"properties": {
|
537
|
+
"max_files": {
|
538
|
+
"$ref": "#/definitions/max_files"
|
539
|
+
}
|
540
|
+
}
|
541
|
+
},
|
542
|
+
"errors": {
|
543
|
+
"properties": {
|
544
|
+
"max_files": {
|
545
|
+
"$ref": "#/definitions/max_files"
|
546
|
+
}
|
547
|
+
}
|
548
|
+
}
|
549
|
+
}
|
512
550
|
}
|
513
551
|
},
|
514
552
|
"category": [
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metadata_presenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MoJ Forms
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: govuk_design_system_formbuilder
|
@@ -178,6 +178,20 @@ dependencies:
|
|
178
178
|
- - ">="
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: hashie
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
181
195
|
- !ruby/object:Gem::Dependency
|
182
196
|
name: rspec-rails
|
183
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -332,6 +346,7 @@ files:
|
|
332
346
|
- app/models/metadata_presenter/meta.rb
|
333
347
|
- app/models/metadata_presenter/meta_item.rb
|
334
348
|
- app/models/metadata_presenter/metadata.rb
|
349
|
+
- app/models/metadata_presenter/multi_upload_answer.rb
|
335
350
|
- app/models/metadata_presenter/next_page.rb
|
336
351
|
- app/models/metadata_presenter/offline_upload_adapter.rb
|
337
352
|
- app/models/metadata_presenter/page.rb
|
@@ -362,6 +377,7 @@ files:
|
|
362
377
|
- app/validators/metadata_presenter/date_before_validator.rb
|
363
378
|
- app/validators/metadata_presenter/date_validator.rb
|
364
379
|
- app/validators/metadata_presenter/email_validator.rb
|
380
|
+
- app/validators/metadata_presenter/max_files_validator.rb
|
365
381
|
- app/validators/metadata_presenter/max_length_validator.rb
|
366
382
|
- app/validators/metadata_presenter/max_size_validator.rb
|
367
383
|
- app/validators/metadata_presenter/max_word_validator.rb
|
@@ -369,6 +385,7 @@ files:
|
|
369
385
|
- app/validators/metadata_presenter/min_length_validator.rb
|
370
386
|
- app/validators/metadata_presenter/min_word_validator.rb
|
371
387
|
- app/validators/metadata_presenter/minimum_validator.rb
|
388
|
+
- app/validators/metadata_presenter/multiupload_validator.rb
|
372
389
|
- app/validators/metadata_presenter/number_validator.rb
|
373
390
|
- app/validators/metadata_presenter/required_validator.rb
|
374
391
|
- app/validators/metadata_presenter/upload_validator.rb
|
@@ -400,6 +417,7 @@ files:
|
|
400
417
|
- app/views/metadata_presenter/component/_content.html.erb
|
401
418
|
- app/views/metadata_presenter/component/_date.html.erb
|
402
419
|
- app/views/metadata_presenter/component/_email.html.erb
|
420
|
+
- app/views/metadata_presenter/component/_multiupload.html.erb
|
403
421
|
- app/views/metadata_presenter/component/_number.html.erb
|
404
422
|
- app/views/metadata_presenter/component/_radios.html.erb
|
405
423
|
- app/views/metadata_presenter/component/_text.html.erb
|
@@ -445,6 +463,7 @@ files:
|
|
445
463
|
- default_metadata/component/content.json
|
446
464
|
- default_metadata/component/date.json
|
447
465
|
- default_metadata/component/email.json
|
466
|
+
- default_metadata/component/multiupload.json
|
448
467
|
- default_metadata/component/number.json
|
449
468
|
- default_metadata/component/radios.json
|
450
469
|
- default_metadata/component/text.json
|
@@ -473,6 +492,7 @@ files:
|
|
473
492
|
- default_metadata/string/error.date_after.json
|
474
493
|
- default_metadata/string/error.date_before.json
|
475
494
|
- default_metadata/string/error.email.json
|
495
|
+
- default_metadata/string/error.max_files.json
|
476
496
|
- default_metadata/string/error.max_length.json
|
477
497
|
- default_metadata/string/error.max_size.json
|
478
498
|
- default_metadata/string/error.max_word.json
|
@@ -480,11 +500,13 @@ files:
|
|
480
500
|
- default_metadata/string/error.min_length.json
|
481
501
|
- default_metadata/string/error.min_word.json
|
482
502
|
- default_metadata/string/error.minimum.json
|
503
|
+
- default_metadata/string/error.multiupload.json
|
483
504
|
- default_metadata/string/error.number.json
|
484
505
|
- default_metadata/string/error.required.json
|
485
506
|
- default_metadata/string/error.virus_scan.json
|
486
507
|
- default_metadata/validations/date_after.json
|
487
508
|
- default_metadata/validations/date_before.json
|
509
|
+
- default_metadata/validations/max_files.json
|
488
510
|
- default_metadata/validations/max_length.json
|
489
511
|
- default_metadata/validations/max_word.json
|
490
512
|
- default_metadata/validations/maximum.json
|
@@ -531,6 +553,7 @@ files:
|
|
531
553
|
- schemas/component/content.json
|
532
554
|
- schemas/component/date.json
|
533
555
|
- schemas/component/email.json
|
556
|
+
- schemas/component/multiupload.json
|
534
557
|
- schemas/component/number.json
|
535
558
|
- schemas/component/radios.json
|
536
559
|
- schemas/component/text.json
|