1import { Controller } from "@hotwired/stimulus" 2import Uppy from '@uppy/core' 3import Dashboard from '@uppy/dashboard'; 4import AwsS3 from '@uppy/aws-s3'; 5 6// Connects to data-controller="admin--pictures--upload" 7export default class extends Controller { 8 static targets = [ 9 'fields', 10 'submitBtn', 11 'pictureReference', 12 'pictureCredit', 13 'pictureTribe', 14 'pictureCountry', 15 'pictureNote', 16 'pictureCopyright', 17 'pictureExternalUse', 18 'defaultReference', 19 'defaultCredit', 20 'defaultTribe', 21 'defaultCountry', 22 'defaultNote', 23 'defaultCopyright', 24 'defaultExternalUse' 25 ] 26 // Create unique identifier for each set of picture fields - currently just an increment. 27 // Could just generate uuid within pictures#upload_form_fields. 28 static values = { fieldNumber: Number } 29 30 connect () { 31 this.uppyNotCreated = true; 32 } 33 34 canInitUppy () { 35 let fieldsFilled = [ 36 this.defaultReferenceTarget.value, 37 this.defaultCreditTarget.value 38 ].every(val => { return val !== ''; }) 39 if (fieldsFilled && this['uppyNotCreated']) { this.initUppy(); } 40 } 41 42 initUppy () { 43 this.uppyNotCreated = false; 44 const uppy = new Uppy({ 45 autoProceed: true, 46 restrictions: { 47 maxNumberOfFiles: 30, 48 minNumberOfFiles: 1, 49 allowedFileTypes: ["image/*"] 50 } 51 }) 52 .use(Dashboard, { inline: true, height: 600, width: '100%', target: '.uppy-dashboard-elem' }) 53 .use(AwsS3, { 54 shouldUseMultipart: (file) => file.size > 100 * 2 ** 20, 55 endpoint: '/', 56 }) 57 58 uppy.on('upload-success', (file, response) => { 59 const fileId = decodeURIComponent(response.uploadURL).match(/cache\/(.+)/)[1]; 60 // construct uploaded file data in the format that Shrine expects 61 const uploadedFileData = { 62 id: fileId, 63 storage: 'cache', 64 metadata: { 65 size: file.size, 66 filename: file.name, 67 mime_type: file.type, 68 } 69 } 70 71 const fileData = JSON.stringify(uploadedFileData); 72 this.createNestedFormFields(file, fileData); 73 if (this.submitBtnTarget.disabled === true) { this.submitBtnTarget.disabled = false; } 74 }) 75 } 76 77 // This logic can likely be replace by creating a js triggered turbo_frame 78 async createNestedFormFields (file, fileData) { 79 const csrfToken = document.head.querySelector("meta[name=csrf-token]")?.content; 80 const fieldNumber = this.fieldNumberValue; 81 this.fieldNumberValue += 1; 82 await fetch(`/admin/pictures/upload_form_fields?filename=${file.name}&field_number=${fieldNumber}`, 83 { 84 headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/x-www-form-urlencoded' }, 85 method: 'GET' 86 } 87 ).then(response => { 88 return response.text(); 89 }) 90 .then(htmlString => { 91 this.fieldsTarget.innerHTML += htmlString; 92 return; 93 }) 94 95 const pictureFields = document.querySelector(`[data-form-fields-file="${file.name}"]`) 96 // Add required shrine file metadata to hidden input 97 pictureFields.querySelector(`[data-form-fields-file-data]`).value = fileData; 98 // Show preview image within correct form 99 pictureFields.querySelector('img').src = file.preview; 100 } 101 102 pictureReferenceTargetConnected(element) { 103 element.value = this.defaultReferenceTarget.value; 104 } 105 106 pictureNoteTargetConnected(element) { 107 element.value = this.defaultNoteTarget.value; 108 } 109 110 pictureTribeTargetConnected(element) { 111 element.value = this.defaultTribeTarget.value; 112 } 113 114 pictureCountryTargetConnected(element) { 115 element.value = this.defaultCountryTarget.value; 116 } 117 118 pictureCopyrightTargetConnected(element) { 119 element.checked = this.defaultCopyrightTarget.checked; 120 } 121 122 pictureExternalUseTargetConnected(element) { 123 element.checked = this.defaultExternalUseTarget.checked; 124 } 125 126 pictureCreditTargetConnected(element) { 127 element.value = this.defaultCreditTarget.value; 128 } 129}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.