tessa 2.0 → 6.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -4
- 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}")
|
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
|