837 lines
26 KiB
JavaScript
837 lines
26 KiB
JavaScript
import * as FilePond from 'filepond'
|
|
import Cropper from 'cropperjs'
|
|
import mime from 'mime'
|
|
import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size'
|
|
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
|
|
import FilePondPluginImageCrop from 'filepond-plugin-image-crop'
|
|
import FilePondPluginImageEdit from 'filepond-plugin-image-edit'
|
|
import FilePondPluginImageExifOrientation from 'filepond-plugin-image-exif-orientation'
|
|
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
|
|
import FilePondPluginImageResize from 'filepond-plugin-image-resize'
|
|
import FilePondPluginImageTransform from 'filepond-plugin-image-transform'
|
|
import FilePondPluginMediaPreview from 'filepond-plugin-media-preview'
|
|
|
|
FilePond.registerPlugin(FilePondPluginFileValidateSize)
|
|
FilePond.registerPlugin(FilePondPluginFileValidateType)
|
|
FilePond.registerPlugin(FilePondPluginImageCrop)
|
|
FilePond.registerPlugin(FilePondPluginImageEdit)
|
|
FilePond.registerPlugin(FilePondPluginImageExifOrientation)
|
|
FilePond.registerPlugin(FilePondPluginImagePreview)
|
|
FilePond.registerPlugin(FilePondPluginImageResize)
|
|
FilePond.registerPlugin(FilePondPluginImageTransform)
|
|
FilePond.registerPlugin(FilePondPluginMediaPreview)
|
|
|
|
window.FilePond = FilePond
|
|
|
|
export default function fileUploadFormComponent({
|
|
acceptedFileTypes,
|
|
imageEditorEmptyFillColor,
|
|
imageEditorMode,
|
|
imageEditorViewportHeight,
|
|
imageEditorViewportWidth,
|
|
deleteUploadedFileUsing,
|
|
isDeletable,
|
|
isDisabled,
|
|
getUploadedFilesUsing,
|
|
imageCropAspectRatio,
|
|
imagePreviewHeight,
|
|
imageResizeMode,
|
|
imageResizeTargetHeight,
|
|
imageResizeTargetWidth,
|
|
imageResizeUpscale,
|
|
isAvatar,
|
|
hasImageEditor,
|
|
hasCircleCropper,
|
|
canEditSvgs,
|
|
isSvgEditingConfirmed,
|
|
confirmSvgEditingMessage,
|
|
disabledSvgEditingMessage,
|
|
isDownloadable,
|
|
isMultiple,
|
|
isOpenable,
|
|
isPasteable,
|
|
isPreviewable,
|
|
isReorderable,
|
|
itemPanelAspectRatio,
|
|
loadingIndicatorPosition,
|
|
locale,
|
|
maxFiles,
|
|
maxSize,
|
|
minSize,
|
|
maxParallelUploads,
|
|
mimeTypeMap,
|
|
panelAspectRatio,
|
|
panelLayout,
|
|
placeholder,
|
|
removeUploadedFileButtonPosition,
|
|
removeUploadedFileUsing,
|
|
reorderUploadedFilesUsing,
|
|
shouldAppendFiles,
|
|
shouldOrientImageFromExif,
|
|
shouldTransformImage,
|
|
state,
|
|
uploadButtonPosition,
|
|
uploadingMessage,
|
|
uploadProgressIndicatorPosition,
|
|
uploadUsing,
|
|
}) {
|
|
return {
|
|
fileKeyIndex: {},
|
|
|
|
pond: null,
|
|
|
|
shouldUpdateState: true,
|
|
|
|
state,
|
|
|
|
lastState: null,
|
|
|
|
error: null,
|
|
|
|
uploadedFileIndex: {},
|
|
|
|
isEditorOpen: false,
|
|
|
|
editingFile: {},
|
|
|
|
currentRatio: '',
|
|
|
|
editor: {},
|
|
|
|
init: async function () {
|
|
FilePond.setOptions(locales[locale] ?? locales['en'])
|
|
|
|
this.pond = FilePond.create(this.$refs.input, {
|
|
acceptedFileTypes,
|
|
allowImageExifOrientation: shouldOrientImageFromExif,
|
|
allowPaste: isPasteable,
|
|
allowRemove: isDeletable,
|
|
allowReorder: isReorderable,
|
|
allowImagePreview: isPreviewable,
|
|
allowVideoPreview: isPreviewable,
|
|
allowAudioPreview: isPreviewable,
|
|
allowImageTransform: shouldTransformImage,
|
|
credits: false,
|
|
files: await this.getFiles(),
|
|
imageCropAspectRatio,
|
|
imagePreviewHeight,
|
|
imageResizeTargetHeight,
|
|
imageResizeTargetWidth,
|
|
imageResizeMode,
|
|
imageResizeUpscale,
|
|
imageTransformOutputStripImageHead: false,
|
|
itemInsertLocation: shouldAppendFiles ? 'after' : 'before',
|
|
...(placeholder && { labelIdle: placeholder }),
|
|
maxFiles,
|
|
maxFileSize: maxSize,
|
|
minFileSize: minSize,
|
|
...(maxParallelUploads && { maxParallelUploads }),
|
|
styleButtonProcessItemPosition: uploadButtonPosition,
|
|
styleButtonRemoveItemPosition: removeUploadedFileButtonPosition,
|
|
styleItemPanelAspectRatio: itemPanelAspectRatio,
|
|
styleLoadIndicatorPosition: loadingIndicatorPosition,
|
|
stylePanelAspectRatio: panelAspectRatio,
|
|
stylePanelLayout: panelLayout,
|
|
styleProgressIndicatorPosition: uploadProgressIndicatorPosition,
|
|
server: {
|
|
load: async (source, load) => {
|
|
let response = await fetch(source, {
|
|
cache: 'no-store',
|
|
})
|
|
let blob = await response.blob()
|
|
|
|
load(blob)
|
|
},
|
|
process: (
|
|
fieldName,
|
|
file,
|
|
metadata,
|
|
load,
|
|
error,
|
|
progress,
|
|
) => {
|
|
this.shouldUpdateState = false
|
|
|
|
let fileKey = (
|
|
[1e7] +
|
|
-1e3 +
|
|
-4e3 +
|
|
-8e3 +
|
|
-1e11
|
|
).replace(/[018]/g, (c) =>
|
|
(
|
|
c ^
|
|
(crypto.getRandomValues(new Uint8Array(1))[0] &
|
|
(15 >> (c / 4)))
|
|
).toString(16),
|
|
)
|
|
|
|
uploadUsing(
|
|
fileKey,
|
|
file,
|
|
(fileKey) => {
|
|
this.shouldUpdateState = true
|
|
|
|
load(fileKey)
|
|
},
|
|
error,
|
|
progress,
|
|
)
|
|
},
|
|
remove: async (source, load) => {
|
|
let fileKey = this.uploadedFileIndex[source] ?? null
|
|
|
|
if (!fileKey) {
|
|
return
|
|
}
|
|
|
|
await deleteUploadedFileUsing(fileKey)
|
|
|
|
load()
|
|
},
|
|
revert: async (uniqueFileId, load) => {
|
|
await removeUploadedFileUsing(uniqueFileId)
|
|
|
|
load()
|
|
},
|
|
},
|
|
allowImageEdit: hasImageEditor,
|
|
imageEditEditor: {
|
|
open: (file) => this.loadEditor(file),
|
|
onconfirm: () => {},
|
|
oncancel: () => this.closeEditor(),
|
|
onclose: () => this.closeEditor(),
|
|
},
|
|
fileValidateTypeDetectType: (source, detectedType) => {
|
|
return new Promise((resolve, reject) => {
|
|
const extension = source.name
|
|
.split('.')
|
|
.pop()
|
|
.toLowerCase()
|
|
const mimeType =
|
|
mimeTypeMap[extension] ||
|
|
detectedType ||
|
|
mime.getType(extension)
|
|
|
|
mimeType ? resolve(mimeType) : reject()
|
|
})
|
|
},
|
|
})
|
|
|
|
this.$watch('state', async () => {
|
|
if (!this.pond) {
|
|
return
|
|
}
|
|
|
|
if (!this.shouldUpdateState) {
|
|
return
|
|
}
|
|
|
|
if (this.state === undefined) {
|
|
return
|
|
}
|
|
|
|
// We don't want to overwrite the files that are already in the input, if they haven't been saved yet.
|
|
if (
|
|
this.state !== null &&
|
|
Object.values(this.state).filter((file) =>
|
|
file.startsWith('livewire-file:'),
|
|
).length
|
|
) {
|
|
this.lastState = null
|
|
|
|
return
|
|
}
|
|
|
|
// Don't do anything if the state hasn't changed
|
|
if (JSON.stringify(this.state) === this.lastState) {
|
|
return
|
|
}
|
|
|
|
this.lastState = JSON.stringify(this.state)
|
|
|
|
this.pond.files = await this.getFiles()
|
|
})
|
|
|
|
this.pond.on('reorderfiles', async (files) => {
|
|
const orderedFileKeys = files
|
|
.map((file) =>
|
|
file.source instanceof File
|
|
? file.serverId
|
|
: (this.uploadedFileIndex[file.source] ?? null),
|
|
) // file.serverId is null for a file that is not yet uploaded
|
|
.filter((fileKey) => fileKey)
|
|
|
|
await reorderUploadedFilesUsing(
|
|
shouldAppendFiles
|
|
? orderedFileKeys
|
|
: orderedFileKeys.reverse(),
|
|
)
|
|
})
|
|
|
|
this.pond.on('initfile', async (fileItem) => {
|
|
if (!isDownloadable) {
|
|
return
|
|
}
|
|
|
|
if (isAvatar) {
|
|
return
|
|
}
|
|
|
|
this.insertDownloadLink(fileItem)
|
|
})
|
|
|
|
this.pond.on('initfile', async (fileItem) => {
|
|
if (!isOpenable) {
|
|
return
|
|
}
|
|
|
|
if (isAvatar) {
|
|
return
|
|
}
|
|
|
|
this.insertOpenLink(fileItem)
|
|
})
|
|
|
|
this.pond.on('addfilestart', async (file) => {
|
|
if (file.status !== FilePond.FileStatus.PROCESSING_QUEUED) {
|
|
return
|
|
}
|
|
|
|
this.dispatchFormEvent('form-processing-started', {
|
|
message: uploadingMessage,
|
|
})
|
|
})
|
|
|
|
const handleFileProcessing = async () => {
|
|
if (
|
|
this.pond
|
|
.getFiles()
|
|
.filter(
|
|
(file) =>
|
|
file.status ===
|
|
FilePond.FileStatus.PROCESSING ||
|
|
file.status ===
|
|
FilePond.FileStatus.PROCESSING_QUEUED,
|
|
).length
|
|
) {
|
|
return
|
|
}
|
|
|
|
this.dispatchFormEvent('form-processing-finished')
|
|
}
|
|
|
|
this.pond.on('processfile', handleFileProcessing)
|
|
|
|
this.pond.on('processfileabort', handleFileProcessing)
|
|
|
|
this.pond.on('processfilerevert', handleFileProcessing)
|
|
|
|
if (panelLayout === 'compact circle') {
|
|
// The compact circle layout does not have enough space to render an error message inside the input.
|
|
// As such, we need to display the error message outside of the input, using the `error` Alpine.js
|
|
// property that is output as a message in the field's view.
|
|
|
|
this.pond.on('error', (error) => {
|
|
// FilePond has a weird English translation for the error message when a file of an unexpected
|
|
// type is uploaded, for example: `File of invalid type: Expects or image/*`. This is a
|
|
// hacky workaround to fix the message to be `File of invalid type: Expects image/*`.
|
|
this.error = `${error.main}: ${error.sub}`.replace(
|
|
'Expects or',
|
|
'Expects',
|
|
)
|
|
})
|
|
|
|
this.pond.on('removefile', () => (this.error = null))
|
|
}
|
|
},
|
|
|
|
destroy: function () {
|
|
this.destroyEditor()
|
|
|
|
FilePond.destroy(this.$refs.input)
|
|
this.pond = null
|
|
},
|
|
|
|
dispatchFormEvent: function (name, detail = {}) {
|
|
this.$el.closest('form')?.dispatchEvent(
|
|
new CustomEvent(name, {
|
|
composed: true,
|
|
cancelable: true,
|
|
detail,
|
|
}),
|
|
)
|
|
},
|
|
|
|
getUploadedFiles: async function () {
|
|
const uploadedFiles = await getUploadedFilesUsing()
|
|
|
|
this.fileKeyIndex = uploadedFiles ?? {}
|
|
|
|
this.uploadedFileIndex = Object.entries(this.fileKeyIndex)
|
|
.filter(([key, value]) => value?.url)
|
|
.reduce((obj, [key, value]) => {
|
|
obj[value.url] = key
|
|
|
|
return obj
|
|
}, {})
|
|
},
|
|
|
|
getFiles: async function () {
|
|
await this.getUploadedFiles()
|
|
|
|
let files = []
|
|
|
|
for (const uploadedFile of Object.values(this.fileKeyIndex)) {
|
|
if (!uploadedFile) {
|
|
continue
|
|
}
|
|
|
|
files.push({
|
|
source: uploadedFile.url,
|
|
options: {
|
|
type: 'local',
|
|
...(!uploadedFile.type ||
|
|
(isPreviewable &&
|
|
(/^audio/.test(uploadedFile.type) ||
|
|
/^image/.test(uploadedFile.type) ||
|
|
/^video/.test(uploadedFile.type)))
|
|
? {}
|
|
: {
|
|
file: {
|
|
name: uploadedFile.name,
|
|
size: uploadedFile.size,
|
|
type: uploadedFile.type,
|
|
},
|
|
}),
|
|
},
|
|
})
|
|
}
|
|
|
|
return shouldAppendFiles ? files : files.reverse()
|
|
},
|
|
|
|
insertDownloadLink: function (file) {
|
|
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
|
return
|
|
}
|
|
|
|
const anchor = this.getDownloadLink(file)
|
|
|
|
if (!anchor) {
|
|
return
|
|
}
|
|
|
|
document
|
|
.getElementById(`filepond--item-${file.id}`)
|
|
.querySelector('.filepond--file-info-main')
|
|
.prepend(anchor)
|
|
},
|
|
|
|
insertOpenLink: function (file) {
|
|
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
|
return
|
|
}
|
|
|
|
const anchor = this.getOpenLink(file)
|
|
|
|
if (!anchor) {
|
|
return
|
|
}
|
|
|
|
document
|
|
.getElementById(`filepond--item-${file.id}`)
|
|
.querySelector('.filepond--file-info-main')
|
|
.prepend(anchor)
|
|
},
|
|
|
|
getDownloadLink: function (file) {
|
|
let fileSource = file.source
|
|
|
|
if (!fileSource) {
|
|
return
|
|
}
|
|
|
|
const anchor = document.createElement('a')
|
|
anchor.className = 'filepond--download-icon'
|
|
anchor.href = fileSource
|
|
anchor.download = file.file.name
|
|
|
|
return anchor
|
|
},
|
|
|
|
getOpenLink: function (file) {
|
|
let fileSource = file.source
|
|
|
|
if (!fileSource) {
|
|
return
|
|
}
|
|
|
|
const anchor = document.createElement('a')
|
|
anchor.className = 'filepond--open-icon'
|
|
anchor.href = fileSource
|
|
anchor.target = '_blank'
|
|
|
|
return anchor
|
|
},
|
|
|
|
initEditor: function () {
|
|
if (isDisabled) {
|
|
return
|
|
}
|
|
|
|
if (!hasImageEditor) {
|
|
return
|
|
}
|
|
|
|
this.editor = new Cropper(this.$refs.editor, {
|
|
aspectRatio:
|
|
imageEditorViewportWidth / imageEditorViewportHeight,
|
|
autoCropArea: 1,
|
|
center: true,
|
|
crop: (event) => {
|
|
this.$refs.xPositionInput.value = Math.round(event.detail.x)
|
|
this.$refs.yPositionInput.value = Math.round(event.detail.y)
|
|
this.$refs.heightInput.value = Math.round(
|
|
event.detail.height,
|
|
)
|
|
this.$refs.widthInput.value = Math.round(event.detail.width)
|
|
this.$refs.rotationInput.value = event.detail.rotate
|
|
},
|
|
cropBoxResizable: true,
|
|
guides: true,
|
|
highlight: true,
|
|
responsive: true,
|
|
toggleDragModeOnDblclick: true,
|
|
viewMode: imageEditorMode,
|
|
wheelZoomRatio: 0.02,
|
|
})
|
|
},
|
|
|
|
closeEditor: function () {
|
|
this.editingFile = {}
|
|
|
|
this.isEditorOpen = false
|
|
|
|
this.destroyEditor()
|
|
},
|
|
|
|
fixImageDimensions: function (file, callback) {
|
|
if (file.type !== 'image/svg+xml') {
|
|
return callback(file)
|
|
}
|
|
|
|
const svgReader = new FileReader()
|
|
|
|
svgReader.onload = (event) => {
|
|
const svgElement = new DOMParser()
|
|
.parseFromString(event.target.result, 'image/svg+xml')
|
|
?.querySelector('svg')
|
|
|
|
if (!svgElement) {
|
|
return callback(file)
|
|
}
|
|
|
|
const viewBoxAttribute = ['viewBox', 'ViewBox', 'viewbox'].find(
|
|
(attribute) => svgElement.hasAttribute(attribute),
|
|
)
|
|
|
|
if (!viewBoxAttribute) {
|
|
return callback(file)
|
|
}
|
|
|
|
const viewBox = svgElement
|
|
.getAttribute(viewBoxAttribute)
|
|
.split(' ')
|
|
|
|
if (!viewBox || viewBox.length !== 4) {
|
|
return callback(file)
|
|
}
|
|
|
|
svgElement.setAttribute('width', parseFloat(viewBox[2]) + 'pt')
|
|
svgElement.setAttribute('height', parseFloat(viewBox[3]) + 'pt')
|
|
|
|
return callback(
|
|
new File(
|
|
[
|
|
new Blob(
|
|
[
|
|
new XMLSerializer().serializeToString(
|
|
svgElement,
|
|
),
|
|
],
|
|
{ type: 'image/svg+xml' },
|
|
),
|
|
],
|
|
file.name,
|
|
{
|
|
type: 'image/svg+xml',
|
|
_relativePath: '',
|
|
},
|
|
),
|
|
)
|
|
}
|
|
|
|
svgReader.readAsText(file)
|
|
},
|
|
|
|
loadEditor: function (file) {
|
|
if (isDisabled) {
|
|
return
|
|
}
|
|
|
|
if (!hasImageEditor) {
|
|
return
|
|
}
|
|
|
|
if (!file) {
|
|
return
|
|
}
|
|
|
|
const isFileSvg = file.type === 'image/svg+xml'
|
|
|
|
if (!canEditSvgs && isFileSvg) {
|
|
alert(disabledSvgEditingMessage)
|
|
|
|
return
|
|
}
|
|
|
|
if (
|
|
isSvgEditingConfirmed &&
|
|
isFileSvg &&
|
|
!confirm(confirmSvgEditingMessage)
|
|
) {
|
|
return
|
|
}
|
|
|
|
this.fixImageDimensions(file, (editingFile) => {
|
|
this.editingFile = editingFile
|
|
|
|
this.initEditor()
|
|
|
|
const reader = new FileReader()
|
|
|
|
reader.onload = (event) => {
|
|
this.isEditorOpen = true
|
|
|
|
setTimeout(
|
|
() => this.editor.replace(event.target.result),
|
|
200,
|
|
)
|
|
}
|
|
|
|
reader.readAsDataURL(file)
|
|
})
|
|
},
|
|
|
|
getRoundedCanvas: function (sourceCanvas) {
|
|
let width = sourceCanvas.width
|
|
let height = sourceCanvas.height
|
|
|
|
let canvas = document.createElement('canvas')
|
|
canvas.width = width
|
|
canvas.height = height
|
|
|
|
let context = canvas.getContext('2d')
|
|
context.imageSmoothingEnabled = true
|
|
context.drawImage(sourceCanvas, 0, 0, width, height)
|
|
context.globalCompositeOperation = 'destination-in'
|
|
context.beginPath()
|
|
context.ellipse(
|
|
width / 2,
|
|
height / 2,
|
|
width / 2,
|
|
height / 2,
|
|
0,
|
|
0,
|
|
2 * Math.PI,
|
|
)
|
|
context.fill()
|
|
|
|
return canvas
|
|
},
|
|
|
|
saveEditor: function () {
|
|
if (isDisabled) {
|
|
return
|
|
}
|
|
|
|
if (!hasImageEditor) {
|
|
return
|
|
}
|
|
|
|
let croppedCanvas = this.editor.getCroppedCanvas({
|
|
fillColor: imageEditorEmptyFillColor ?? 'transparent',
|
|
height: imageResizeTargetHeight,
|
|
imageSmoothingEnabled: true,
|
|
imageSmoothingQuality: 'high',
|
|
width: imageResizeTargetWidth,
|
|
})
|
|
|
|
if (hasCircleCropper) {
|
|
croppedCanvas = this.getRoundedCanvas(croppedCanvas)
|
|
}
|
|
|
|
croppedCanvas.toBlob(
|
|
(croppedImage) => {
|
|
if (isMultiple) {
|
|
this.pond.removeFile(
|
|
this.pond
|
|
.getFiles()
|
|
.find(
|
|
(uploadedFile) =>
|
|
uploadedFile.filename ===
|
|
this.editingFile.name,
|
|
)?.id,
|
|
{ revert: true },
|
|
)
|
|
}
|
|
|
|
this.$nextTick(() => {
|
|
this.shouldUpdateState = false
|
|
|
|
let editingFileName = this.editingFile.name.slice(
|
|
0,
|
|
this.editingFile.name.lastIndexOf('.'),
|
|
)
|
|
let editingFileExtension = this.editingFile.name
|
|
.split('.')
|
|
.pop()
|
|
|
|
if (editingFileExtension === 'svg') {
|
|
editingFileExtension = 'png'
|
|
}
|
|
|
|
const fileNameVersionRegex = /-v(\d+)/
|
|
|
|
if (fileNameVersionRegex.test(editingFileName)) {
|
|
editingFileName = editingFileName.replace(
|
|
fileNameVersionRegex,
|
|
(match, number) => {
|
|
const newNumber = Number(number) + 1
|
|
|
|
return `-v${newNumber}`
|
|
},
|
|
)
|
|
} else {
|
|
editingFileName += '-v1'
|
|
}
|
|
|
|
this.pond
|
|
.addFile(
|
|
new File(
|
|
[croppedImage],
|
|
`${editingFileName}.${editingFileExtension}`,
|
|
{
|
|
type:
|
|
this.editingFile.type ===
|
|
'image/svg+xml' ||
|
|
hasCircleCropper
|
|
? 'image/png'
|
|
: this.editingFile.type,
|
|
lastModified: new Date().getTime(),
|
|
},
|
|
),
|
|
)
|
|
.then(() => {
|
|
this.closeEditor()
|
|
})
|
|
.catch(() => {
|
|
this.closeEditor()
|
|
})
|
|
})
|
|
},
|
|
hasCircleCropper ? 'image/png' : this.editingFile.type,
|
|
)
|
|
},
|
|
|
|
destroyEditor: function () {
|
|
if (this.editor && typeof this.editor.destroy === 'function') {
|
|
this.editor.destroy()
|
|
}
|
|
|
|
this.editor = null
|
|
},
|
|
}
|
|
}
|
|
|
|
import am from 'filepond/locale/am-et'
|
|
import ar from 'filepond/locale/ar-ar'
|
|
import az from 'filepond/locale/az-az'
|
|
import ca from 'filepond/locale/ca-ca'
|
|
import ckb from 'filepond/locale/ku-ckb'
|
|
import cs from 'filepond/locale/cs-cz'
|
|
import da from 'filepond/locale/da-dk'
|
|
import de from 'filepond/locale/de-de'
|
|
import el from 'filepond/locale/el-el'
|
|
import en from 'filepond/locale/en-en'
|
|
import es from 'filepond/locale/es-es'
|
|
import fa from 'filepond/locale/fa_ir'
|
|
import fi from 'filepond/locale/fi-fi'
|
|
import fr from 'filepond/locale/fr-fr'
|
|
import he from 'filepond/locale/he-he'
|
|
import hr from 'filepond/locale/hr-hr'
|
|
import hu from 'filepond/locale/hu-hu'
|
|
import id from 'filepond/locale/id-id'
|
|
import it from 'filepond/locale/it-it'
|
|
import ja from 'filepond/locale/ja-ja'
|
|
import km from 'filepond/locale/km-km'
|
|
import ko from 'filepond/locale/ko-kr'
|
|
import lt from 'filepond/locale/lt-lt'
|
|
import lv from 'filepond/locale/lv-lv'
|
|
import nl from 'filepond/locale/nl-nl'
|
|
import no from 'filepond/locale/no_nb'
|
|
import pl from 'filepond/locale/pl-pl'
|
|
import pt_BR from 'filepond/locale/pt-br'
|
|
import pt_PT from 'filepond/locale/pt-br'
|
|
import ro from 'filepond/locale/ro-ro'
|
|
import ru from 'filepond/locale/ru-ru'
|
|
import sk from 'filepond/locale/sk-sk'
|
|
import sv from 'filepond/locale/sv_se'
|
|
import tr from 'filepond/locale/tr-tr'
|
|
import uk from 'filepond/locale/uk-ua'
|
|
import vi from 'filepond/locale/vi-vi'
|
|
import zh_CN from 'filepond/locale/zh-cn'
|
|
import zh_TW from 'filepond/locale/zh-tw'
|
|
|
|
const locales = {
|
|
am,
|
|
ar,
|
|
az,
|
|
ca,
|
|
ckb,
|
|
cs,
|
|
da,
|
|
de,
|
|
el,
|
|
en,
|
|
es,
|
|
fa,
|
|
fi,
|
|
fr,
|
|
he,
|
|
hr,
|
|
hu,
|
|
id,
|
|
it,
|
|
ja,
|
|
km,
|
|
ko,
|
|
lt,
|
|
lv,
|
|
nl,
|
|
no,
|
|
pl,
|
|
pt_BR,
|
|
pt_PT,
|
|
ro,
|
|
ru,
|
|
sk,
|
|
sv,
|
|
tr,
|
|
uk,
|
|
vi,
|
|
zh_CN,
|
|
zh_TW,
|
|
}
|