tessa 2.0 → 6.0.0.rc2
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 +4 -4
- data/README.md +11 -18
- data/app/assets/javascripts/tessa.esm.js +212 -173
- data/app/assets/javascripts/tessa.js +206 -167
- data/app/javascript/activestorage/file_checksum.ts +76 -0
- data/app/javascript/tessa/index.ts +264 -0
- data/app/javascript/tessa/types.ts +34 -0
- data/config/routes.rb +0 -1
- data/lib/tessa/simple_form/asset_input.rb +24 -25
- data/lib/tessa/version.rb +1 -1
- data/lib/tessa.rb +0 -80
- data/package.json +7 -2
- data/rollup.config.js +5 -5
- data/spec/dummy/app/models/single_asset_model.rb +1 -1
- data/spec/dummy/app/models/single_asset_model_form.rb +3 -5
- data/spec/dummy/bin/rails +2 -2
- data/spec/dummy/bin/rake +2 -2
- data/spec/dummy/bin/setup +14 -6
- data/spec/dummy/bin/yarn +9 -3
- data/spec/dummy/config/application.rb +11 -9
- data/spec/dummy/config/boot.rb +1 -1
- data/spec/dummy/config/environment.rb +1 -1
- data/spec/dummy/config/environments/development.rb +34 -5
- data/spec/dummy/config/environments/production.rb +49 -10
- data/spec/dummy/config/environments/test.rb +28 -12
- data/spec/dummy/config/initializers/backtrace_silencers.rb +4 -3
- data/spec/dummy/config/initializers/content_security_policy.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +3 -1
- data/spec/dummy/config/initializers/new_framework_defaults_6_1.rb +67 -0
- data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +5 -0
- data/spec/dummy/config/locales/en.yml +1 -1
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/dummy/config/storage.yml +31 -0
- data/spec/dummy/config.ru +2 -1
- data/spec/dummy/db/migrate/20230406194400_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
- data/spec/dummy/db/migrate/20230406194401_create_active_storage_variant_records.active_storage.rb +27 -0
- data/spec/dummy/db/schema.rb +15 -7
- data/tessa.gemspec +4 -5
- data/tsconfig.json +10 -0
- data/yarn.lock +74 -7
- metadata +36 -74
- data/app/javascript/activestorage/file_checksum.js +0 -53
- data/app/javascript/tessa/index.js.coffee +0 -183
- data/lib/tasks/tessa.rake +0 -18
- data/lib/tessa/active_storage/asset_wrapper.rb +0 -32
- data/lib/tessa/asset/failure.rb +0 -37
- data/lib/tessa/asset.rb +0 -47
- data/lib/tessa/asset_change.rb +0 -49
- data/lib/tessa/asset_change_set.rb +0 -49
- data/lib/tessa/config.rb +0 -16
- data/lib/tessa/controller_helpers.rb +0 -16
- data/lib/tessa/fake_connection.rb +0 -29
- data/lib/tessa/jobs/migrate_assets_job.rb +0 -222
- data/lib/tessa/model/dynamic_extensions.rb +0 -145
- data/lib/tessa/model/field.rb +0 -77
- data/lib/tessa/model.rb +0 -94
- data/lib/tessa/rack_upload_proxy.rb +0 -41
- data/lib/tessa/response_factory.rb +0 -15
- data/lib/tessa/upload/uploads_file.rb +0 -18
- data/lib/tessa/upload.rb +0 -31
- data/lib/tessa/view_helpers.rb +0 -23
- data/spec/dummy/app/models/multiple_asset_model.rb +0 -8
- data/spec/support/remote_call_macro.rb +0 -40
- data/spec/tessa/asset/failure_spec.rb +0 -48
- data/spec/tessa/asset_change_set_spec.rb +0 -198
- data/spec/tessa/asset_change_spec.rb +0 -86
- data/spec/tessa/asset_spec.rb +0 -196
- data/spec/tessa/config_spec.rb +0 -70
- data/spec/tessa/controller_helpers_spec.rb +0 -55
- data/spec/tessa/jobs/migrate_assets_job_spec.rb +0 -247
- data/spec/tessa/model_field_spec.rb +0 -72
- data/spec/tessa/model_spec.rb +0 -325
- data/spec/tessa/rack_upload_proxy_spec.rb +0 -83
- data/spec/tessa/upload/uploads_file_spec.rb +0 -72
- data/spec/tessa/upload_spec.rb +0 -125
- data/spec/tessa_spec.rb +0 -23
@@ -0,0 +1,264 @@
|
|
1
|
+
import type Dropzone from 'dropzone'
|
2
|
+
import {FileChecksum} from '../activestorage/file_checksum'
|
3
|
+
import type { ActiveStorageDirectUploadParams, ActiveStorageDirectUploadResponse } from './types'
|
4
|
+
|
5
|
+
declare global {
|
6
|
+
interface Window {
|
7
|
+
WCC: any,
|
8
|
+
jQuery: typeof jQuery,
|
9
|
+
Dropzone: typeof Dropzone,
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
window.WCC ||= {}
|
14
|
+
const $ = window.jQuery
|
15
|
+
|
16
|
+
window.Dropzone.autoDiscover = false
|
17
|
+
|
18
|
+
interface WCCDropzoneFile extends Dropzone.DropzoneFile {
|
19
|
+
uploadURL: string,
|
20
|
+
uploadMethod: string,
|
21
|
+
uploadHeaders: Record<string, string> | undefined,
|
22
|
+
signedID: string,
|
23
|
+
}
|
24
|
+
|
25
|
+
interface WCCDropzoneOptions extends Dropzone.DropzoneOptions {
|
26
|
+
directUploadURL: string,
|
27
|
+
}
|
28
|
+
|
29
|
+
const BaseDropzone = window.Dropzone
|
30
|
+
class WCCDropzone extends BaseDropzone {
|
31
|
+
readonly options!: WCCDropzoneOptions
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Performs a direct upload to the signed upload URL created in "accept".
|
35
|
+
* On complete, calls the Dropzone "success" callback.
|
36
|
+
*/
|
37
|
+
uploadFile(file: WCCDropzoneFile): void {
|
38
|
+
const xhr = new XMLHttpRequest()
|
39
|
+
file.xhr = xhr
|
40
|
+
|
41
|
+
xhr.open(file.uploadMethod, file.uploadURL, true)
|
42
|
+
let response: any = null
|
43
|
+
|
44
|
+
const handleError = () => {
|
45
|
+
(this as any)._errorProcessing([file], response || this.options.dictResponseError?.replace("{{statusCode}}", xhr.status.toString()), xhr)
|
46
|
+
}
|
47
|
+
|
48
|
+
const updateProgress = (e?: any) => {
|
49
|
+
let progress: number
|
50
|
+
if (e) {
|
51
|
+
progress = 100 * e.loaded / e.total
|
52
|
+
|
53
|
+
file.upload = {
|
54
|
+
...file.upload,
|
55
|
+
progress: progress,
|
56
|
+
total: e.total,
|
57
|
+
bytesSent: e.loaded
|
58
|
+
} as Dropzone.DropzoneFileUpload
|
59
|
+
} else {
|
60
|
+
// Called when the file finished uploading
|
61
|
+
progress = 100
|
62
|
+
let allFilesFinished = false
|
63
|
+
if (file.upload!.progress == 100 && file.upload!.bytesSent == file.upload!.total) {
|
64
|
+
allFilesFinished = true
|
65
|
+
}
|
66
|
+
file.upload!.progress = progress
|
67
|
+
file.upload!.bytesSent = file.upload?.total!
|
68
|
+
|
69
|
+
// Nothing to do, all files already at 100%
|
70
|
+
if (allFilesFinished) { return }
|
71
|
+
}
|
72
|
+
|
73
|
+
this.emit("uploadprogress", file, progress, file.upload!.bytesSent)
|
74
|
+
}
|
75
|
+
|
76
|
+
xhr.onload = (e) => {
|
77
|
+
if (file.status == WCCDropzone.CANCELED) { return }
|
78
|
+
if (xhr.readyState != 4) {return }
|
79
|
+
|
80
|
+
response = xhr.responseText
|
81
|
+
|
82
|
+
if (xhr.getResponseHeader("content-type") &&
|
83
|
+
xhr.getResponseHeader("content-type")!.indexOf("application/json") >= 0) {
|
84
|
+
try {
|
85
|
+
response = JSON.parse(response)
|
86
|
+
} catch(e) {
|
87
|
+
response = "Invalid JSON response from server."
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
updateProgress()
|
92
|
+
|
93
|
+
if (xhr.status < 200 || xhr.status >= 300)
|
94
|
+
handleError()
|
95
|
+
else {
|
96
|
+
(this as any)._finished([file], response, e)
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
xhr.onerror = () => {
|
101
|
+
if (file.status == WCCDropzone.CANCELED) { return }
|
102
|
+
handleError()
|
103
|
+
}
|
104
|
+
|
105
|
+
// Some browsers do not have the .upload property
|
106
|
+
let progressObj = xhr.upload ?? xhr
|
107
|
+
progressObj.onprogress = updateProgress
|
108
|
+
|
109
|
+
let headers = {
|
110
|
+
"Accept": "application/json",
|
111
|
+
"Cache-Control": "no-cache",
|
112
|
+
"X-Requested-With": "XMLHttpRequest",
|
113
|
+
}
|
114
|
+
|
115
|
+
if (this.options.headers) { Object.assign(headers, this.options.headers) }
|
116
|
+
if (file.uploadHeaders) { Object.assign(headers, file.uploadHeaders) }
|
117
|
+
|
118
|
+
for (let [headerName, headerValue] of Object.entries(headers)) {
|
119
|
+
xhr.setRequestHeader(headerName, headerValue)
|
120
|
+
}
|
121
|
+
|
122
|
+
this.emit("sending", file, xhr)
|
123
|
+
|
124
|
+
xhr.send(file)
|
125
|
+
}
|
126
|
+
|
127
|
+
uploadFiles(files: WCCDropzoneFile[]): void {
|
128
|
+
for (const file of files) {
|
129
|
+
this.uploadFile(file as WCCDropzoneFile)
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
const uploadPendingWarning = "File uploads have not yet completed. If you submit the form now they will not be saved. Are you sure you want to continue?"
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Called on page load to initialize the Dropzone
|
138
|
+
*/
|
139
|
+
function tessaInit() {
|
140
|
+
$('.tessa-upload').each(function(i: number, item: HTMLElement) {
|
141
|
+
const $item = $(item)
|
142
|
+
|
143
|
+
const directUploadURL = $item.data('direct-upload-url') || '/rails/active_storage/direct_uploads'
|
144
|
+
|
145
|
+
const options: WCCDropzoneOptions = {
|
146
|
+
maxFiles: 1,
|
147
|
+
addRemoveLinks: true,
|
148
|
+
url: 'UNUSED',
|
149
|
+
dictDefaultMessage: 'Drop files or click to upload.',
|
150
|
+
accept: createAcceptFn({ directUploadURL }),
|
151
|
+
...$item.data("dropzone-options")
|
152
|
+
}
|
153
|
+
|
154
|
+
if ($item.hasClass("multiple")) { options.maxFiles = undefined }
|
155
|
+
|
156
|
+
const dropzone = new WCCDropzone(item, options)
|
157
|
+
|
158
|
+
/**
|
159
|
+
* On load, if an asset already exists, add it's thumbnail to the dropzone.
|
160
|
+
*/
|
161
|
+
$item.find('input[type="hidden"]').each(function(i: number, input: HTMLElement) {
|
162
|
+
const $input = $(input)
|
163
|
+
const mockFile = $input.data("meta")
|
164
|
+
if (!mockFile) { return }
|
165
|
+
|
166
|
+
mockFile.accepted = true
|
167
|
+
dropzone.options.addedfile?.call(dropzone, mockFile)
|
168
|
+
dropzone.options.thumbnail?.call(dropzone, mockFile, mockFile.url)
|
169
|
+
dropzone.emit("complete", mockFile)
|
170
|
+
dropzone.files.push(mockFile)
|
171
|
+
})
|
172
|
+
|
173
|
+
const inputName = $item.data('input-name') || $item.find('input[type="hidden"]').attr('name');
|
174
|
+
|
175
|
+
/**
|
176
|
+
* On successful dropzone upload, create the hidden input with the signed ID.
|
177
|
+
* On the server side, ActiveStorage can then use the signed ID to create an attachment to the blob.
|
178
|
+
*/
|
179
|
+
dropzone.on('success', (file: WCCDropzoneFile) => {
|
180
|
+
$(`input[name="${inputName}"]`).val(file.signedID)
|
181
|
+
})
|
182
|
+
|
183
|
+
/**
|
184
|
+
* On dropzone file removal, delete the hidden input. This removes the attachment from the record.
|
185
|
+
*/
|
186
|
+
dropzone.on('removedfile', (file: WCCDropzoneFile) => {
|
187
|
+
$item.find(`input[name="${inputName}"]`).val('')
|
188
|
+
})
|
189
|
+
})
|
190
|
+
|
191
|
+
$('form:has(.tessa-upload)').each((i, form) => {
|
192
|
+
const $form = $(form)
|
193
|
+
$form.on('submit', (event: any) => {
|
194
|
+
let safeToSubmit = true
|
195
|
+
$form.find('.tessa-upload').each((j, dropzoneElement) => {
|
196
|
+
(dropzoneElement.dropzone.files as WCCDropzoneFile[]).forEach((file) => {
|
197
|
+
if (file.status && file.status != WCCDropzone.SUCCESS) {
|
198
|
+
safeToSubmit = false
|
199
|
+
}
|
200
|
+
})
|
201
|
+
})
|
202
|
+
if (!safeToSubmit && !confirm(uploadPendingWarning)) {
|
203
|
+
// prevent form submission
|
204
|
+
return false
|
205
|
+
}
|
206
|
+
})
|
207
|
+
})
|
208
|
+
}
|
209
|
+
|
210
|
+
interface AcceptOptions {
|
211
|
+
directUploadURL: string
|
212
|
+
}
|
213
|
+
|
214
|
+
/**
|
215
|
+
* Accepts a file upload from Dropzone, and creates an ActiveStorage blob via the Tessa::RackUploadProxy.
|
216
|
+
*
|
217
|
+
* Upon successfully creating the blob, retrieves a signed upload URL for direct upload. The
|
218
|
+
* signed URL is attached to the File object which is then passed to "uploadFile".
|
219
|
+
*
|
220
|
+
* @param file Binary file data uploaded by the user
|
221
|
+
* @param done Callback when file is accepted or rejected by the Tessa::RackUploadProxy
|
222
|
+
*/
|
223
|
+
function createAcceptFn({ directUploadURL }: AcceptOptions) {
|
224
|
+
return function(file: WCCDropzoneFile, done: (error?: string | Error) => void) {
|
225
|
+
const postData: ActiveStorageDirectUploadParams = {
|
226
|
+
blob: {
|
227
|
+
filename: file.name,
|
228
|
+
byte_size: file.size,
|
229
|
+
content_type: file.type,
|
230
|
+
checksum: ''
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
FileChecksum.create(file, (error, checksum) => {
|
235
|
+
if (error) {
|
236
|
+
return done(error)
|
237
|
+
}
|
238
|
+
if (!checksum) {
|
239
|
+
return done(`Failed to generate checksum for file '${file.name}'`)
|
240
|
+
}
|
241
|
+
|
242
|
+
postData.blob['checksum'] = checksum
|
243
|
+
|
244
|
+
$.ajax(directUploadURL, {
|
245
|
+
type: 'POST',
|
246
|
+
data: postData,
|
247
|
+
success: (response: ActiveStorageDirectUploadResponse) => {
|
248
|
+
file.uploadURL = response.direct_upload.url
|
249
|
+
file.uploadMethod = 'PUT' // ActiveStorage is always PUT
|
250
|
+
file.uploadHeaders = response.direct_upload.headers
|
251
|
+
file.signedID = response.signed_id
|
252
|
+
done()
|
253
|
+
},
|
254
|
+
error: (response: any) => {
|
255
|
+
console.error(response)
|
256
|
+
done("Failed to initiate the upload process!")
|
257
|
+
}
|
258
|
+
})
|
259
|
+
|
260
|
+
})
|
261
|
+
}
|
262
|
+
}
|
263
|
+
|
264
|
+
$(tessaInit)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
export interface ActiveStorageDirectUploadParams {
|
3
|
+
blob: {
|
4
|
+
filename: string,
|
5
|
+
byte_size: number,
|
6
|
+
checksum: string,
|
7
|
+
content_type: string
|
8
|
+
metadata?: Record<string, string>
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
type Stringable = { toString(): string }
|
13
|
+
|
14
|
+
export interface ActiveStorageDirectUploadResponse {
|
15
|
+
"id": Stringable,
|
16
|
+
"key": string,
|
17
|
+
"filename": string,
|
18
|
+
"content_type": string,
|
19
|
+
"metadata"?: {
|
20
|
+
"identified"?: boolean
|
21
|
+
},
|
22
|
+
"byte_size": number,
|
23
|
+
"checksum": string,
|
24
|
+
"created_at": string,
|
25
|
+
"service_name": string,
|
26
|
+
"signed_id": string,
|
27
|
+
"attachable_sgid": string,
|
28
|
+
"direct_upload": {
|
29
|
+
"url": string,
|
30
|
+
"headers"?: {
|
31
|
+
"Content-Type"?: string
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
data/config/routes.rb
CHANGED
@@ -1,44 +1,43 @@
|
|
1
1
|
module Tessa
|
2
2
|
class AssetInput < SimpleForm::Inputs::Base
|
3
3
|
def input(wrapper_options=nil)
|
4
|
-
|
5
|
-
field = object.class.tessa_fields[attribute_name]
|
4
|
+
raise StandardError, "AssetInput with multiple: true not yet supported" if options[:multiple]
|
6
5
|
|
7
6
|
template.content_tag(
|
8
7
|
:div,
|
9
|
-
hidden_fields_for(
|
10
|
-
"class" => "tessa-upload dropzone #{"multiple" if
|
11
|
-
"data-asset-field-prefix" => tessa_field_prefix,
|
8
|
+
hidden_fields_for(attribute_name),
|
9
|
+
"class" => "tessa-upload dropzone #{"multiple" if options[:multiple]}",
|
12
10
|
"data-dropzone-options" => (options[:dropzone] || {}).to_json,
|
13
|
-
"data-
|
11
|
+
"data-input-name" => "#{object_name}[#{attribute_name}]",
|
12
|
+
"data-direct-upload-url" => Rails.application.routes.url_helpers.rails_direct_uploads_path,
|
14
13
|
)
|
15
14
|
end
|
16
15
|
|
17
16
|
private
|
18
17
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
18
|
+
def hidden_fields_for(attribute_name)
|
19
|
+
asset = object.public_send(attribute_name)
|
20
|
+
unless asset&.key.present?
|
21
|
+
return @builder.hidden_field("#{attribute_name}", value: nil)
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
"id" => "tessa_asset_action_#{asset.id}"
|
30
|
-
)
|
31
|
-
end.join.html_safe
|
24
|
+
@builder.hidden_field("#{attribute_name}",
|
25
|
+
value: asset.key,
|
26
|
+
data: {
|
27
|
+
# These get read by the JS to populate the preview in Dropzone
|
28
|
+
meta: meta_for_asset(asset)
|
29
|
+
})
|
32
30
|
end
|
33
31
|
|
34
32
|
def meta_for_asset(asset)
|
35
33
|
{
|
36
|
-
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
34
|
+
# this allows us to find the hidden HTML input to remove it if we remove the asset
|
35
|
+
"signedID" => asset.key,
|
36
|
+
"name" => asset.filename,
|
37
|
+
"size" => asset.byte_size,
|
38
|
+
"mimeType" => asset.content_type,
|
39
|
+
"url" => asset.service_url(disposition: :inline, expires_in: 1.hour),
|
41
40
|
}.to_json
|
42
|
-
end
|
41
|
+
end
|
43
42
|
end
|
44
|
-
end
|
43
|
+
end
|
data/lib/tessa/version.rb
CHANGED
data/lib/tessa.rb
CHANGED
@@ -1,90 +1,10 @@
|
|
1
1
|
require "tessa/version"
|
2
2
|
|
3
|
-
require "virtus"
|
4
|
-
require "json"
|
5
|
-
|
6
|
-
require "tessa/fake_connection"
|
7
|
-
require "tessa/config"
|
8
|
-
require "tessa/response_factory"
|
9
|
-
require "tessa/asset"
|
10
|
-
require "tessa/asset_change"
|
11
|
-
require "tessa/asset_change_set"
|
12
|
-
require "tessa/controller_helpers"
|
13
|
-
require "tessa/model"
|
14
|
-
require "tessa/rack_upload_proxy"
|
15
|
-
require "tessa/upload"
|
16
|
-
require "tessa/view_helpers"
|
17
|
-
|
18
|
-
if defined?(ActiveJob)
|
19
|
-
require "tessa/jobs/migrate_assets_job"
|
20
|
-
end
|
21
|
-
|
22
3
|
if defined?(SimpleForm)
|
23
4
|
require "tessa/simple_form"
|
24
5
|
end
|
25
6
|
|
26
7
|
module Tessa
|
27
|
-
class << self
|
28
|
-
def config
|
29
|
-
@config ||= Config.new
|
30
|
-
end
|
31
|
-
|
32
|
-
def setup
|
33
|
-
yield config
|
34
|
-
end
|
35
|
-
|
36
|
-
def find_assets(ids)
|
37
|
-
return find_all_assets(ids) if ids.is_a?(Array)
|
38
|
-
|
39
|
-
return find_asset(ids)
|
40
|
-
end
|
41
|
-
|
42
|
-
def model_registry
|
43
|
-
@model_registry ||= []
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def find_asset(id)
|
49
|
-
return nil unless id
|
50
|
-
|
51
|
-
if blob = ::ActiveStorage::Blob.find_by(key: id)
|
52
|
-
return Tessa::ActiveStorage::AssetWrapper.new(blob)
|
53
|
-
end
|
54
|
-
|
55
|
-
Tessa::Asset.find(id)
|
56
|
-
rescue Tessa::RequestFailed => err
|
57
|
-
Tessa::Asset::Failure.factory(id: id, response: err.response)
|
58
|
-
end
|
59
|
-
|
60
|
-
def find_all_assets(ids)
|
61
|
-
return [] if ids.empty?
|
62
|
-
|
63
|
-
blobs = ::ActiveStorage::Blob.where(key: ids).to_a
|
64
|
-
.map { |a| Tessa::ActiveStorage::AssetWrapper.new(a) }
|
65
|
-
ids = ids - blobs.map(&:key)
|
66
|
-
assets =
|
67
|
-
begin
|
68
|
-
Tessa::Asset.find(ids) if ids.any?
|
69
|
-
rescue Tessa::RequestFailed => err
|
70
|
-
ids.map do |id|
|
71
|
-
Tessa::Asset::Failure.factory(id: id, response: err.response)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
[*blobs, *assets]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
class RequestFailed < StandardError
|
80
|
-
attr_reader :response
|
81
|
-
|
82
|
-
def initialize(message=nil, response=nil)
|
83
|
-
super(message)
|
84
|
-
@response = response
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
8
|
end
|
89
9
|
|
90
10
|
if defined?(Rails::Railtie)
|
data/package.json
CHANGED
@@ -27,12 +27,17 @@
|
|
27
27
|
"devDependencies": {
|
28
28
|
"@rollup/plugin-commonjs": "^19.0.1",
|
29
29
|
"@rollup/plugin-node-resolve": "^11.0.1",
|
30
|
-
"
|
30
|
+
"@rollup/plugin-typescript": "^11.1.0",
|
31
|
+
"@types/dropzone": "^5.7.4",
|
32
|
+
"@types/jquery": "^3.5.16",
|
33
|
+
"@types/spark-md5": "^3.0.2",
|
31
34
|
"eslint": "^4.3.0",
|
32
35
|
"eslint-plugin-import": "^2.23.4",
|
33
36
|
"rollup": "^2.35.1",
|
34
37
|
"rollup-plugin-coffee-script": "^2.0.0",
|
35
|
-
"rollup-plugin-terser": "^7.0.2"
|
38
|
+
"rollup-plugin-terser": "^7.0.2",
|
39
|
+
"tslib": "^2.5.0",
|
40
|
+
"typescript": "^5.0.4"
|
36
41
|
},
|
37
42
|
"scripts": {
|
38
43
|
"prebuild": "yarn lint",
|
data/rollup.config.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import resolve from "@rollup/plugin-node-resolve"
|
2
2
|
import commonjs from "@rollup/plugin-commonjs"
|
3
3
|
import { terser } from "rollup-plugin-terser"
|
4
|
-
import
|
4
|
+
import typescript from "@rollup/plugin-typescript"
|
5
5
|
|
6
6
|
const terserOptions = {
|
7
7
|
mangle: false,
|
@@ -14,7 +14,7 @@ const terserOptions = {
|
|
14
14
|
|
15
15
|
export default [
|
16
16
|
{
|
17
|
-
input: "app/javascript/tessa/index.
|
17
|
+
input: "app/javascript/tessa/index.ts",
|
18
18
|
output: {
|
19
19
|
file: "app/assets/javascripts/tessa.js",
|
20
20
|
format: "umd",
|
@@ -22,21 +22,21 @@ export default [
|
|
22
22
|
},
|
23
23
|
plugins: [
|
24
24
|
resolve(),
|
25
|
-
|
25
|
+
typescript(),
|
26
26
|
commonjs(),
|
27
27
|
terser(terserOptions)
|
28
28
|
]
|
29
29
|
},
|
30
30
|
|
31
31
|
{
|
32
|
-
input: "app/javascript/tessa/index.
|
32
|
+
input: "app/javascript/tessa/index.ts",
|
33
33
|
output: {
|
34
34
|
file: "app/assets/javascripts/tessa.esm.js",
|
35
35
|
format: "es"
|
36
36
|
},
|
37
37
|
plugins: [
|
38
38
|
resolve(),
|
39
|
-
|
39
|
+
typescript(),
|
40
40
|
commonjs(),
|
41
41
|
terser(terserOptions)
|
42
42
|
]
|
@@ -5,21 +5,19 @@ class SingleAssetModelForm
|
|
5
5
|
|
6
6
|
ATTRIBUTES = %w[
|
7
7
|
title
|
8
|
-
|
8
|
+
avatar
|
9
9
|
]
|
10
10
|
|
11
11
|
attr_accessor :single_asset_model
|
12
12
|
attr_accessor *ATTRIBUTES
|
13
13
|
|
14
|
-
asset :avatar
|
15
|
-
|
16
14
|
def self.from_single_asset_model(model, attrs = {})
|
17
15
|
new(
|
18
16
|
model.attributes
|
19
17
|
.slice(*ATTRIBUTES)
|
20
18
|
.merge(attrs)
|
21
|
-
.merge(single_asset_model: model)
|
19
|
+
.merge(single_asset_model: model)
|
22
20
|
)
|
23
21
|
end
|
24
22
|
|
25
|
-
end
|
23
|
+
end
|
data/spec/dummy/bin/rails
CHANGED
data/spec/dummy/bin/rake
CHANGED
data/spec/dummy/bin/setup
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require
|
3
|
-
include FileUtils
|
2
|
+
require "fileutils"
|
4
3
|
|
5
4
|
# path to your application root.
|
6
5
|
APP_ROOT = File.expand_path('..', __dir__)
|
@@ -9,16 +8,25 @@ def system!(*args)
|
|
9
8
|
system(*args) || abort("\n== Command #{args} failed ==")
|
10
9
|
end
|
11
10
|
|
12
|
-
chdir APP_ROOT do
|
13
|
-
# This script is a
|
11
|
+
FileUtils.chdir APP_ROOT do
|
12
|
+
# This script is a way to set up or update your development environment automatically.
|
13
|
+
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
|
14
14
|
# Add necessary setup steps to this file.
|
15
15
|
|
16
16
|
puts '== Installing dependencies =='
|
17
17
|
system! 'gem install bundler --conservative'
|
18
18
|
system('bundle check') || system!('bundle install')
|
19
19
|
|
20
|
-
# Install JavaScript dependencies
|
21
|
-
|
20
|
+
# Install JavaScript dependencies
|
21
|
+
system! 'bin/yarn'
|
22
|
+
|
23
|
+
# puts "\n== Copying sample files =="
|
24
|
+
# unless File.exist?('config/database.yml')
|
25
|
+
# FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
|
26
|
+
# end
|
27
|
+
|
28
|
+
puts "\n== Preparing database =="
|
29
|
+
system! 'bin/rails db:prepare'
|
22
30
|
|
23
31
|
puts "\n== Removing old logs and tempfiles =="
|
24
32
|
system! 'bin/rails log:clear tmp:clear'
|
data/spec/dummy/bin/yarn
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
APP_ROOT = File.expand_path('..', __dir__)
|
3
3
|
Dir.chdir(APP_ROOT) do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
|
5
|
+
select { |dir| File.expand_path(dir) != __dir__ }.
|
6
|
+
product(["yarn", "yarn.cmd", "yarn.ps1"]).
|
7
|
+
map { |dir, file| File.expand_path(file, dir) }.
|
8
|
+
find { |file| File.executable?(file) }
|
9
|
+
|
10
|
+
if yarn
|
11
|
+
exec yarn, *ARGV
|
12
|
+
else
|
7
13
|
$stderr.puts "Yarn executable was not detected in the system."
|
8
14
|
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
|
9
15
|
exit 1
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "boot"
|
2
2
|
|
3
3
|
require "rails"
|
4
4
|
# Pick the frameworks you want:
|
@@ -8,10 +8,12 @@ require "active_record/railtie"
|
|
8
8
|
require "active_storage/engine"
|
9
9
|
require "action_controller/railtie"
|
10
10
|
require "action_mailer/railtie"
|
11
|
+
require "action_mailbox/engine"
|
12
|
+
require "action_text/engine"
|
11
13
|
require "action_view/railtie"
|
12
14
|
# require "action_cable/engine"
|
13
15
|
require "sprockets/railtie"
|
14
|
-
|
16
|
+
require "rails/test_unit/railtie"
|
15
17
|
|
16
18
|
# Require the gems listed in Gemfile, including any gems
|
17
19
|
# you've limited to :test, :development, or :production.
|
@@ -22,12 +24,12 @@ module Dummy
|
|
22
24
|
# Initialize configuration defaults for originally generated Rails version.
|
23
25
|
config.load_defaults 5.2
|
24
26
|
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
|
30
|
-
#
|
31
|
-
config.
|
27
|
+
# Configuration for the application, engines, and railties goes here.
|
28
|
+
#
|
29
|
+
# These settings can be overridden in specific environments using the files
|
30
|
+
# in config/environments, which are processed later.
|
31
|
+
#
|
32
|
+
# config.time_zone = "Central Time (US & Canada)"
|
33
|
+
# config.eager_load_paths << Rails.root.join("extras")
|
32
34
|
end
|
33
35
|
end
|