mirror of
https://github.com/filamentphp/filament.git
synced 2026-10-29 18:19:53 +08:00
wip
This commit is contained in:
+35
-27
@@ -34,35 +34,43 @@ const defaultOptions = {
|
||||
'.woff2': 'file',
|
||||
'.data': 'base64',
|
||||
},
|
||||
plugins: [{
|
||||
name: 'watchPlugin',
|
||||
setup: function (build) {
|
||||
build.onStart(() => {
|
||||
console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
|
||||
})
|
||||
plugins: [
|
||||
{
|
||||
name: 'watchPlugin',
|
||||
setup: function (build) {
|
||||
build.onStart(() => {
|
||||
console.log(
|
||||
`Build started at ${new Date(
|
||||
Date.now(),
|
||||
).toLocaleTimeString()}: ${
|
||||
build.initialOptions.outfile
|
||||
}`,
|
||||
)
|
||||
})
|
||||
|
||||
build.onEnd((result) => {
|
||||
if (result.errors.length > 0) {
|
||||
console.log(
|
||||
`Build failed at ${new Date(
|
||||
Date.now(),
|
||||
).toLocaleTimeString()}: ${
|
||||
build.initialOptions.outfile
|
||||
}`,
|
||||
result.errors,
|
||||
)
|
||||
} else {
|
||||
console.log(
|
||||
`Build finished at ${new Date(
|
||||
Date.now(),
|
||||
).toLocaleTimeString()}: ${
|
||||
build.initialOptions.outfile
|
||||
}`,
|
||||
)
|
||||
}
|
||||
})
|
||||
build.onEnd((result) => {
|
||||
if (result.errors.length > 0) {
|
||||
console.log(
|
||||
`Build failed at ${new Date(
|
||||
Date.now(),
|
||||
).toLocaleTimeString()}: ${
|
||||
build.initialOptions.outfile
|
||||
}`,
|
||||
result.errors,
|
||||
)
|
||||
} else {
|
||||
console.log(
|
||||
`Build finished at ${new Date(
|
||||
Date.now(),
|
||||
).toLocaleTimeString()}: ${
|
||||
build.initialOptions.outfile
|
||||
}`,
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
}
|
||||
|
||||
const corePackages = ['forms', 'notifications', 'panels', 'support', 'tables']
|
||||
|
||||
Generated
+26
-9
@@ -23,6 +23,7 @@
|
||||
"chart.js": "^3.6.0",
|
||||
"choices.js": "^10.1.0",
|
||||
"codemirror": "^5.65.8",
|
||||
"cropperjs": "^1.5.13",
|
||||
"dayjs": "^1.10.6",
|
||||
"easymde": "^2.18.0",
|
||||
"esbuild": "^0.17.18",
|
||||
@@ -31,6 +32,7 @@
|
||||
"filepond-plugin-file-validate-size": "^2.2.4",
|
||||
"filepond-plugin-file-validate-type": "^1.2.6",
|
||||
"filepond-plugin-image-crop": "^2.0.6",
|
||||
"filepond-plugin-image-edit": "^1.6.3",
|
||||
"filepond-plugin-image-exif-orientation": "^1.0.11",
|
||||
"filepond-plugin-image-preview": "^4.6.7",
|
||||
"filepond-plugin-image-resize": "^2.0.10",
|
||||
@@ -3758,9 +3760,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001515",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz",
|
||||
"integrity": "sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==",
|
||||
"version": "1.0.30001516",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz",
|
||||
"integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4211,6 +4213,12 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cropperjs": {
|
||||
"version": "1.5.13",
|
||||
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.13.tgz",
|
||||
"integrity": "sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
@@ -4784,9 +4792,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.460",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.460.tgz",
|
||||
"integrity": "sha512-kKiHnbrHME7z8E6AYaw0ehyxY5+hdaRmeUbjBO22LZMdqTYCO29EvF0T1cQ3pJ1RN5fyMcHl1Lmcsdt9WWJpJQ==",
|
||||
"version": "1.4.461",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.461.tgz",
|
||||
"integrity": "sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
@@ -5421,6 +5429,15 @@
|
||||
"filepond": ">=3.x <5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/filepond-plugin-image-edit": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/filepond-plugin-image-edit/-/filepond-plugin-image-edit-1.6.3.tgz",
|
||||
"integrity": "sha512-5q3RDaVlfvyI346ckF1DfKw4uN5rfAmUCv7HCG30jBZcGmepg8hFyjVM71uZXYeb4AgLhpCkSPP8Immwig8bzw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"filepond": ">=3.7.2 <5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/filepond-plugin-image-exif-orientation": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/filepond-plugin-image-exif-orientation/-/filepond-plugin-image-exif-orientation-1.0.11.tgz",
|
||||
@@ -10105,9 +10122,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.32.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
|
||||
"integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==",
|
||||
"version": "3.33.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.33.0.tgz",
|
||||
"integrity": "sha512-ARGC7vbufOHfpvyGcZZXFaXCMZ9A4fffOGC5ucOW7+WHDGlAe8LJdf3Jts1sWhDeiI1RSWrKy5Hodl+JWGdW2A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"chart.js": "^3.6.0",
|
||||
"choices.js": "^10.1.0",
|
||||
"codemirror": "^5.65.8",
|
||||
"cropperjs": "^1.5.13",
|
||||
"dayjs": "^1.10.6",
|
||||
"easymde": "^2.18.0",
|
||||
"esbuild": "^0.17.18",
|
||||
@@ -36,6 +37,7 @@
|
||||
"filepond-plugin-file-validate-size": "^2.2.4",
|
||||
"filepond-plugin-file-validate-type": "^1.2.6",
|
||||
"filepond-plugin-image-crop": "^2.0.6",
|
||||
"filepond-plugin-image-edit": "^1.6.3",
|
||||
"filepond-plugin-image-exif-orientation": "^1.0.11",
|
||||
"filepond-plugin-image-preview": "^4.6.7",
|
||||
"filepond-plugin-image-resize": "^2.0.10",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+19
-1
File diff suppressed because one or more lines are too long
@@ -24,12 +24,12 @@ window.FilePond = FilePond
|
||||
|
||||
export default function fileUploadFormComponent({
|
||||
acceptedFileTypes,
|
||||
cropperEmptyFillColor,
|
||||
cropperMode,
|
||||
cropperViewportHeight,
|
||||
cropperViewportWidth,
|
||||
imageCropperEmptyFillColor,
|
||||
imageCropperMode,
|
||||
imageCropperViewportHeight,
|
||||
imageCropperViewportWidth,
|
||||
deleteUploadedFileUsing,
|
||||
disabled,
|
||||
isDisabled,
|
||||
getUploadedFilesUsing,
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
@@ -38,7 +38,7 @@ export default function fileUploadFormComponent({
|
||||
imageResizeTargetWidth,
|
||||
imageResizeUpscale,
|
||||
isAvatar,
|
||||
isCroppable,
|
||||
hasCroppableImages,
|
||||
isDownloadable,
|
||||
isOpenable,
|
||||
isPreviewable,
|
||||
@@ -74,7 +74,7 @@ export default function fileUploadFormComponent({
|
||||
|
||||
uploadedFileIndex: {},
|
||||
|
||||
showCropper: false,
|
||||
isCropperOpen: false,
|
||||
|
||||
editingFile: {},
|
||||
|
||||
@@ -174,12 +174,12 @@ export default function fileUploadFormComponent({
|
||||
load()
|
||||
},
|
||||
},
|
||||
allowImageEdit: isCroppable,
|
||||
allowImageEdit: hasCroppableImages,
|
||||
imageEditEditor: {
|
||||
open: (file) => this.loadCropper(file),
|
||||
onconfirm: () => {},
|
||||
oncancel: () => this.cancelCropper(),
|
||||
onclose: () => this.cancelCropper(),
|
||||
oncancel: () => this.closeCropper(),
|
||||
onclose: () => this.closeCropper(),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -413,46 +413,59 @@ export default function fileUploadFormComponent({
|
||||
},
|
||||
|
||||
initCropper: function () {
|
||||
if (disabled) return
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isCroppable) return
|
||||
if (!hasCroppableImages) {
|
||||
return
|
||||
}
|
||||
|
||||
this.cropper = new Cropper(this.$refs.cropper, {
|
||||
aspectRatio: cropperViewportWidth / cropperViewportHeight,
|
||||
aspectRatio:
|
||||
imageCropperViewportWidth / imageCropperViewportHeight,
|
||||
autoCropArea: 1,
|
||||
center: true,
|
||||
crop: (event) => {
|
||||
this.$refs.inputX.value = Math.round(event.detail.x)
|
||||
this.$refs.inputY.value = Math.round(event.detail.y)
|
||||
this.$refs.inputHeight.value = Math.round(
|
||||
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.inputWidth.value = Math.round(event.detail.width)
|
||||
this.$refs.inputRotate.value = event.detail.rotate
|
||||
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: cropperMode,
|
||||
viewMode: imageCropperMode,
|
||||
wheelZoomRatio: 0.02,
|
||||
})
|
||||
},
|
||||
|
||||
cancelCropper: function () {
|
||||
closeCropper: function () {
|
||||
this.editingFile = {}
|
||||
this.showCropper = false
|
||||
|
||||
this.isCropperOpen = false
|
||||
|
||||
this.cropper.destroy()
|
||||
this.cropper = null
|
||||
},
|
||||
|
||||
loadCropper: function (file) {
|
||||
if (disabled) return
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isCroppable) return
|
||||
if (!hasCroppableImages) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!file) return
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
|
||||
this.editingFile = file
|
||||
|
||||
@@ -460,7 +473,7 @@ export default function fileUploadFormComponent({
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
this.showCropper = true
|
||||
this.isCropperOpen = true
|
||||
|
||||
setTimeout(() => this.cropper.replace(event.target.result), 200)
|
||||
}
|
||||
@@ -469,13 +482,17 @@ export default function fileUploadFormComponent({
|
||||
},
|
||||
|
||||
saveCropper: function () {
|
||||
if (disabled) return
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isCroppable) return
|
||||
if (!hasCroppableImages) {
|
||||
return
|
||||
}
|
||||
|
||||
this.cropper
|
||||
.getCroppedCanvas({
|
||||
fillColor: cropperEmptyFillColor,
|
||||
fillColor: imageCropperEmptyFillColor,
|
||||
height: imageResizeTargetHeight,
|
||||
imageSmoothingEnabled: true,
|
||||
imageSmoothingQuality: 'high',
|
||||
@@ -493,6 +510,8 @@ export default function fileUploadFormComponent({
|
||||
)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.shouldUpdateState = false
|
||||
|
||||
this.pond
|
||||
.addFile(
|
||||
new File(
|
||||
@@ -509,7 +528,7 @@ export default function fileUploadFormComponent({
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
this.cancelCropper()
|
||||
this.closeCropper()
|
||||
})
|
||||
})
|
||||
}, this.editingFile.type)
|
||||
|
||||
@@ -96,14 +96,6 @@ return [
|
||||
'label' => 'Flip image vertical',
|
||||
],
|
||||
|
||||
'free' => [
|
||||
'label' => 'Free',
|
||||
],
|
||||
|
||||
'height' => [
|
||||
'label' => 'Height',
|
||||
],
|
||||
|
||||
'move_down' => [
|
||||
'label' => 'Move image down',
|
||||
],
|
||||
@@ -116,10 +108,6 @@ return [
|
||||
'label' => 'Move image to right',
|
||||
],
|
||||
|
||||
'move_top_left' => [
|
||||
'label' => 'Move image to top/left corner of the container',
|
||||
],
|
||||
|
||||
'move_up' => [
|
||||
'label' => 'Move image up',
|
||||
],
|
||||
@@ -128,14 +116,6 @@ return [
|
||||
'label' => 'Reset',
|
||||
],
|
||||
|
||||
'rotate' => [
|
||||
'label' => 'Rotate',
|
||||
],
|
||||
|
||||
'rotate_180' => [
|
||||
'label' => 'Rotate image 180°',
|
||||
],
|
||||
|
||||
'rotate_left' => [
|
||||
'label' => 'Rotate image to left',
|
||||
],
|
||||
@@ -144,12 +124,12 @@ return [
|
||||
'label' => 'Rotate image to right',
|
||||
],
|
||||
|
||||
'save' => [
|
||||
'label' => 'Save',
|
||||
'set_aspect_ratio' => [
|
||||
'label' => 'Set aspect ratio to :ratio',
|
||||
],
|
||||
|
||||
'width' => [
|
||||
'label' => 'Width',
|
||||
'save' => [
|
||||
'label' => 'Save',
|
||||
],
|
||||
|
||||
'zoom_100' => [
|
||||
@@ -166,6 +146,45 @@ return [
|
||||
|
||||
],
|
||||
|
||||
'fields' => [
|
||||
|
||||
'height' => [
|
||||
'label' => 'Height',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
'rotation' => [
|
||||
'label' => 'Rotation',
|
||||
'unit' => 'deg',
|
||||
],
|
||||
|
||||
'width' => [
|
||||
'label' => 'Width',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
'x_position' => [
|
||||
'label' => 'X',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
'y_position' => [
|
||||
'label' => 'Y',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'aspect_ratios' => [
|
||||
|
||||
'label' => 'Aspect ratios',
|
||||
|
||||
'no_fixed' => [
|
||||
'label' => 'Free',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -96,14 +96,6 @@ return [
|
||||
'label' => 'Vänd bilden vertikalt',
|
||||
],
|
||||
|
||||
'free' => [
|
||||
'label' => 'Fritt',
|
||||
],
|
||||
|
||||
'height' => [
|
||||
'label' => 'Höjd',
|
||||
],
|
||||
|
||||
'move_down' => [
|
||||
'label' => 'Flytta bilden nedåt',
|
||||
],
|
||||
@@ -116,10 +108,6 @@ return [
|
||||
'label' => 'Flytta bilden åt höger',
|
||||
],
|
||||
|
||||
'move_top_left' => [
|
||||
'label' => 'Flytta bilden till vänstra hörnet',
|
||||
],
|
||||
|
||||
'move_up' => [
|
||||
'label' => 'Flytta bilden uppåt',
|
||||
],
|
||||
@@ -128,14 +116,6 @@ return [
|
||||
'label' => 'Reset',
|
||||
],
|
||||
|
||||
'rotate' => [
|
||||
'label' => 'Rotera',
|
||||
],
|
||||
|
||||
'rotate_180' => [
|
||||
'label' => 'Rotera bilden 180°',
|
||||
],
|
||||
|
||||
'rotate_left' => [
|
||||
'label' => 'Rotera bilden åt vänster',
|
||||
],
|
||||
@@ -148,10 +128,6 @@ return [
|
||||
'label' => 'Spara',
|
||||
],
|
||||
|
||||
'width' => [
|
||||
'label' => 'Bredd',
|
||||
],
|
||||
|
||||
'zoom_100' => [
|
||||
'label' => 'Zooma bilden till 100%',
|
||||
],
|
||||
@@ -166,6 +142,32 @@ return [
|
||||
|
||||
],
|
||||
|
||||
'fields' => [
|
||||
|
||||
'height' => [
|
||||
'label' => 'Höjd',
|
||||
],
|
||||
|
||||
'rotation' => [
|
||||
'label' => 'Rotera',
|
||||
],
|
||||
|
||||
'width' => [
|
||||
'label' => 'Bredd',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'aspect_ratios' => [
|
||||
|
||||
'label' => 'Aspect ratios',
|
||||
|
||||
'no_fixed' => [
|
||||
'label' => 'Fritt',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -10,64 +10,69 @@
|
||||
$isAvatar = $isAvatar();
|
||||
$statePath = $getStatePath();
|
||||
$isDisabled = $isDisabled();
|
||||
$isCroppable = $isCroppable();
|
||||
$hasCroppableImages = $hasCroppableImages();
|
||||
@endphp
|
||||
|
||||
<div
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('file-upload', 'filament/forms') }}"
|
||||
x-data="fileUploadFormComponent({
|
||||
acceptedFileTypes: @js($getAcceptedFileTypes()),
|
||||
deleteUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.deleteUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
getUploadedFilesUsing: async () => {
|
||||
return await $wire.getFormUploadedFiles(@js($statePath))
|
||||
},
|
||||
imageCropAspectRatio: @js($imageCropAspectRatio),
|
||||
imagePreviewHeight: @js($getImagePreviewHeight()),
|
||||
imageResizeMode: @js($getImageResizeMode()),
|
||||
imageResizeTargetHeight: @js($imageResizeTargetHeight),
|
||||
imageResizeTargetWidth: @js($imageResizeTargetWidth),
|
||||
imageResizeUpscale: @js($getImageResizeUpscale()),
|
||||
isAvatar: {{ $isAvatar ? 'true' : 'false' }},
|
||||
isCroppable: @js($isCroppable),
|
||||
isDownloadable: @js($isDownloadable()),
|
||||
isOpenable: @js($isOpenable()),
|
||||
isPreviewable: @js($isPreviewable()),
|
||||
isReorderable: @js($isReorderable()),
|
||||
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
|
||||
locale: @js(app()->getLocale()),
|
||||
panelAspectRatio: @js($getPanelAspectRatio()),
|
||||
panelLayout: @js($getPanelLayout()),
|
||||
placeholder: @js($getPlaceholder()),
|
||||
maxSize: {{ ($size = $getMaxSize()) ? "'{$size} KB'" : 'null' }},
|
||||
minSize: {{ ($size = $getMinSize()) ? "'{$size} KB'" : 'null' }},
|
||||
removeUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.removeFormUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
|
||||
reorderUploadedFilesUsing: async (files) => {
|
||||
return await $wire.reorderFormUploadedFiles(@js($statePath), files)
|
||||
},
|
||||
shouldAppendFiles: @js($shouldAppendFiles()),
|
||||
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
|
||||
shouldTransformImage: @js($imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
uploadButtonPosition: @js($getUploadButtonPosition()),
|
||||
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
|
||||
uploadUsing: (fileKey, file, success, error, progress) => {
|
||||
$wire.upload(
|
||||
`{{ $statePath }}.${fileKey}`,
|
||||
file,
|
||||
() => {
|
||||
success(fileKey)
|
||||
acceptedFileTypes: @js($getAcceptedFileTypes()),
|
||||
imageCropperEmptyFillColor: '{{ $getImageCropperEmptyFillColor() }}',
|
||||
imageCropperMode: {{ $getImageCropperMode() }},
|
||||
imageCropperViewportHeight: @js($getImageCropperViewportHeight()),
|
||||
imageCropperViewportWidth: @js($getImageCropperViewportWidth()),
|
||||
deleteUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.deleteUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
error,
|
||||
progress,
|
||||
)
|
||||
},
|
||||
})"
|
||||
getUploadedFilesUsing: async () => {
|
||||
return await $wire.getFormUploadedFiles(@js($statePath))
|
||||
},
|
||||
hasCroppableImages: @js($hasCroppableImages),
|
||||
imageCropAspectRatio: @js($imageCropAspectRatio),
|
||||
imagePreviewHeight: @js($getImagePreviewHeight()),
|
||||
imageResizeMode: @js($getImageResizeMode()),
|
||||
imageResizeTargetHeight: @js($imageResizeTargetHeight),
|
||||
imageResizeTargetWidth: @js($imageResizeTargetWidth),
|
||||
imageResizeUpscale: @js($getImageResizeUpscale()),
|
||||
isAvatar: @js($isAvatar),
|
||||
isDisabled: @js($isDisabled),
|
||||
isDownloadable: @js($isDownloadable()),
|
||||
isOpenable: @js($isOpenable()),
|
||||
isPreviewable: @js($isPreviewable()),
|
||||
isReorderable: @js($isReorderable()),
|
||||
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
|
||||
locale: @js(app()->getLocale()),
|
||||
panelAspectRatio: @js($getPanelAspectRatio()),
|
||||
panelLayout: @js($getPanelLayout()),
|
||||
placeholder: @js($getPlaceholder()),
|
||||
maxSize: {{ ($size = $getMaxSize()) ? "'{$size} KB'" : 'null' }},
|
||||
minSize: {{ ($size = $getMinSize()) ? "'{$size} KB'" : 'null' }},
|
||||
removeUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.removeFormUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
|
||||
reorderUploadedFilesUsing: async (files) => {
|
||||
return await $wire.reorderFormUploadedFiles(@js($statePath), files)
|
||||
},
|
||||
shouldAppendFiles: @js($shouldAppendFiles()),
|
||||
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
|
||||
shouldTransformImage: @js($imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
uploadButtonPosition: @js($getUploadButtonPosition()),
|
||||
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
|
||||
uploadUsing: (fileKey, file, success, error, progress) => {
|
||||
$wire.upload(
|
||||
`{{ $statePath }}.${fileKey}`,
|
||||
file,
|
||||
() => {
|
||||
success(fileKey)
|
||||
},
|
||||
error,
|
||||
progress,
|
||||
)
|
||||
},
|
||||
})"
|
||||
wire:ignore
|
||||
x-ignore
|
||||
{{
|
||||
@@ -109,151 +114,163 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if ($isCroppable && (! $isDisabled))
|
||||
|
||||
@if ($hasCroppableImages && (! $isDisabled))
|
||||
<div
|
||||
x-show="showCropper"
|
||||
x-show="isCropperOpen"
|
||||
x-cloak
|
||||
x-on:click.stop
|
||||
x-trap.noscroll="showCropper"
|
||||
x-on:keydown.escape.window="cancelCropper"
|
||||
class="fixed inset-0 h-screen w-screen p-2 sm:p-10 md:p-20 z-50 isolate"
|
||||
x-trap.noscroll="isCropperOpen"
|
||||
x-on:keydown.escape.window="closeCropper"
|
||||
class="fixed inset-0 isolate z-50 h-screen w-screen p-2 sm:p-10 md:p-20"
|
||||
>
|
||||
<div
|
||||
x-on:click="cancelCropper"
|
||||
aria-hidden="true"
|
||||
class="filament-modal-close-overlay fixed inset-0 w-full h-full bg-black/50 cursor-pointer"
|
||||
style="will-change: transform;"
|
||||
class="fixed inset-0 h-full w-full cursor-pointer bg-black/50"
|
||||
style="will-change: transform"
|
||||
></div>
|
||||
<div class="w-full h-full flex justify-center items-center isolate z-10">
|
||||
<div class="mx-auto h-full w-full flex flex-col lg:flex-row overflow-hidden bg-white dark:bg-gray-800 dark:ring-gray-50/10 ring-1 ring-gray-900/10 rounded-xl">
|
||||
<div class="flex-1 w-full lg:h-full overflow-auto p-4">
|
||||
|
||||
<div
|
||||
class="isolate z-10 flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<div
|
||||
class="mx-auto flex h-full w-full flex-col overflow-hidden rounded-xl bg-white ring-1 ring-gray-900/10 dark:bg-gray-800 dark:ring-gray-50/10 lg:flex-row"
|
||||
>
|
||||
<div class="w-full flex-1 overflow-auto p-4 lg:h-full">
|
||||
<div class="h-full w-full">
|
||||
<img x-ref="cropper" src="" class="h-full w-auto"/>
|
||||
<img x-ref="cropper" class="h-full w-auto" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full h-96 lg:h-full lg:max-w-xs overflow-auto bg-gray-50 dark:bg-gray-900/30 flex flex-col shadow-top lg:shadow-none z-[1]">
|
||||
<div
|
||||
class="shadow-top z-[1] flex h-96 w-full flex-col overflow-auto bg-gray-50 dark:bg-gray-900/30 lg:h-full lg:max-w-xs lg:shadow-none"
|
||||
>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="flex flex-col h-full overflow-y-auto">
|
||||
<div
|
||||
class="flex h-full flex-col overflow-y-auto"
|
||||
>
|
||||
<div class="flex-1 overflow-auto">
|
||||
<div class="space-y-3 p-4">
|
||||
|
||||
<div class="w-full space-y-2">
|
||||
<div class="space-y-6 p-4">
|
||||
<div class="w-full space-y-3">
|
||||
@foreach ([
|
||||
[
|
||||
'label' => 'X',
|
||||
'name' => 'inputX',
|
||||
'placeholder' => 'x',
|
||||
'unit' => 'px',
|
||||
'onEnter' => 'cropper.setData({...cropper.getData(true), x: +$el.value})',
|
||||
|
||||
'label' => __('filament-forms::components.file_upload.cropper.fields.x_position.label'),
|
||||
'ref' => 'xPositionInput',
|
||||
'unit' => __('filament-forms::components.file_upload.cropper.fields.x_position.unit'),
|
||||
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), x: +$el.value})',
|
||||
],
|
||||
[
|
||||
'label' => 'Y',
|
||||
'name' => 'inputY',
|
||||
'placeholder' => 'y',
|
||||
'unit' => 'px',
|
||||
'onEnter' => 'cropper.setData({...cropper.getData(true), y: +$el.value})',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.fields.y_position.label'),
|
||||
'ref' => 'yPositionInput',
|
||||
'unit' => __('filament-forms::components.file_upload.cropper.fields.y_position.unit'),
|
||||
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), y: +$el.value})',
|
||||
],
|
||||
[
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.width.label'),
|
||||
'name' => 'inputWidth',
|
||||
'placeholder' => 'width',
|
||||
'unit' => 'px',
|
||||
'onEnter' => 'cropper.setData({...cropper.getData(true), width: +$el.value})',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.fields.width.label'),
|
||||
'ref' => 'widthInput',
|
||||
'unit' => __('filament-forms::components.file_upload.cropper.fields.width.unit'),
|
||||
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), width: +$el.value})',
|
||||
],
|
||||
[
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.height.label'),
|
||||
'name' => 'inputHeight',
|
||||
'placeholder' => 'height',
|
||||
'unit' => 'px',
|
||||
'onEnter' => 'cropper.setData({...cropper.getData(true), height: +$el.value})',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.fields.height.label'),
|
||||
'ref' => 'heightInput',
|
||||
'unit' => __('filament-forms::components.file_upload.cropper.fields.height.unit'),
|
||||
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), height: +$el.value})',
|
||||
],
|
||||
[
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.rotate.label'),
|
||||
'name' => 'inputRotate',
|
||||
'placeholder' => 'rotate',
|
||||
'unit' => 'deg',
|
||||
'onEnter' => 'cropper.rotateTo(+$el.value)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.fields.rotation.label'),
|
||||
'ref' => 'rotationInput',
|
||||
'unit' => __('filament-forms::components.file_upload.cropper.fields.rotation.unit'),
|
||||
'alpineSaveHandler' => 'cropper.rotateTo(+$el.value)',
|
||||
],
|
||||
] as $input)
|
||||
<label class="flex items-center w-full text-sm border border-gray-300 dark:border-gray-700 rounded-lg shadow-sm bg-gray-100 dark:bg-gray-800">
|
||||
<span class="w-20 flex-shrink-0 self-stretch flex items-center justify-center px-2 border-e border-gray-300 dark:border-gray-700">{{ $input['label'] }}</span>
|
||||
<label
|
||||
class="flex w-full items-center rounded-lg border border-gray-300 bg-gray-100 text-sm shadow-sm dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<span
|
||||
class="flex w-20 flex-shrink-0 items-center justify-center self-stretch border-e border-gray-300 px-2 dark:border-gray-700"
|
||||
>
|
||||
{{ $input['label'] }}
|
||||
</span>
|
||||
|
||||
<input
|
||||
@class([
|
||||
"text-sm block w-full transition duration-75 border-none focus:border-primary-500 focus:ring-1 focus:ring-inset focus:ring-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500"
|
||||
'block w-full border-none text-sm transition duration-75 focus:border-primary-500 focus:ring-1 focus:ring-inset focus:ring-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500',
|
||||
])
|
||||
x-on:keyup.enter.stop.prevent="{{ $input['onEnter'] }}"
|
||||
x-on:blur="{{ $input['onEnter'] }}"
|
||||
x-ref="{{ $input['name'] }}"
|
||||
name="@js($statePath . $input['name'])"
|
||||
placeholder="{{ $input['placeholder'] }}"
|
||||
x-on:keyup.enter.stop.prevent="{{ $input['alpineSaveHandler'] }}"
|
||||
x-on:blur="{{ $input['alpineSaveHandler'] }}"
|
||||
x-ref="{{ $input['ref'] }}"
|
||||
x-on:keydown.enter.prevent
|
||||
type="text"
|
||||
/>
|
||||
<span class="w-16 self-stretch flex items-center justify-center px-2 border-s border-gray-300 dark:border-gray-700">{{ $input['unit'] }}</span>
|
||||
</label>
|
||||
|
||||
<span
|
||||
class="flex w-16 items-center justify-center self-stretch border-s border-gray-300 px-2 dark:border-gray-700"
|
||||
>
|
||||
{{ $input['unit'] }}
|
||||
</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@foreach ($getCropperActions() as $buttonGroups)
|
||||
<div class="flex items-center w-full isolate rounded-lg shadow">
|
||||
@foreach ($buttonGroups as $button)
|
||||
<x-filament::button
|
||||
type="button"
|
||||
x-tooltip.raw="{{ $button['tooltip'] }}"
|
||||
x-on:click.stop.prevent="{{ $button['click'] }}"
|
||||
color="gray"
|
||||
@class([
|
||||
'!rounded-none !shadow-none flex-1 focus:z-10 active:z-10',
|
||||
'!rounded-l-lg' => $loop->first,
|
||||
'!rounded-r-lg' => $loop->last,
|
||||
'-ml-px' => ! $loop->first,
|
||||
])
|
||||
>
|
||||
{!! $button['icon'] !!}
|
||||
</x-filament::button>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="space-y-3">
|
||||
@foreach ($getImageCropperActions(iconSizeClasses: 'h-5 w-5 mx-auto') as $groupedActions)
|
||||
<x-filament::button.group
|
||||
class="w-full"
|
||||
>
|
||||
@foreach ($groupedActions as $action)
|
||||
<x-filament::button
|
||||
x-tooltip.raw="{{ $action['label'] }}"
|
||||
x-on:click.stop.prevent="{{ $action['alpineClickHandler'] }}"
|
||||
color="gray"
|
||||
grouped
|
||||
>
|
||||
{!! $action['iconHtml'] !!}
|
||||
|
||||
@foreach (collect($getCropperAspectRatios())->chunk(5) as $chunk)
|
||||
<div class="flex items-center w-full isolate rounded-lg shadow">
|
||||
@foreach ($chunk as $label => $ratio)
|
||||
<x-filament::button
|
||||
type="button"
|
||||
x-tooltip.raw="Set aspect ratio: {{ $label }}"
|
||||
x-on:click.stop.prevent="currentRatio = '{{ $label }}'; cropper.setAspectRatio({{ $ratio }})"
|
||||
color="gray"
|
||||
x-bind:class="{'!bg-gray-50 dark:!bg-gray-700': currentRatio === '{{ $label }}'}"
|
||||
@class([
|
||||
'!rounded-none !shadow-none flex-1 focus:z-10 active:z-10',
|
||||
'!rounded-l-lg !rounded-r-none' => $loop->first,
|
||||
'!rounded-r-lg !rounded-l-none' => $loop->last,
|
||||
'-ml-px' => ! $loop->first,
|
||||
])
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
{{ $action['label'] }}
|
||||
</span>
|
||||
</x-filament::button>
|
||||
@endforeach
|
||||
</x-filament::button.group>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if (count($aspectRatios = $getImageCropperAspectRatiosForJs()))
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
class="text-xs text-gray-950 dark:text-white"
|
||||
>
|
||||
{{ __('filament-forms::components.file_upload.cropper.aspect_ratios.label') }}
|
||||
</div>
|
||||
|
||||
@foreach (collect($aspectRatios)->chunk(5) as $ratiosChunk)
|
||||
<x-filament::button.group
|
||||
class="w-full"
|
||||
>
|
||||
{{ $label }}
|
||||
</x-filament::button>
|
||||
@foreach ($ratiosChunk as $label => $ratio)
|
||||
<x-filament::button
|
||||
:x-tooltip.raw="__('filament-forms::components.file_upload.cropper.actions.set_aspect_ratio.label', ['ratio' => $label])"
|
||||
x-on:click.stop.prevent="currentRatio = '{{ $label }}'; cropper.setAspectRatio({{ $ratio }})"
|
||||
color="gray"
|
||||
x-bind:class="{'!bg-gray-50 dark:!bg-gray-700': currentRatio === '{{ $label }}'}"
|
||||
grouped
|
||||
>
|
||||
{{ $label }}
|
||||
</x-filament::button>
|
||||
@endforeach
|
||||
</x-filament::button.group>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 py-3 px-4 border-t border-gray-200 dark:border-gray-800 bg-gray-200 dark:bg-black/10">
|
||||
<div
|
||||
class="flex items-center gap-3 px-4 py-3"
|
||||
>
|
||||
<x-filament::button
|
||||
size="sm"
|
||||
color="warning"
|
||||
x-on:click.stop.prevent="cropper.reset()"
|
||||
>
|
||||
{{ __('filament-forms::components.file_upload.cropper.actions.reset.label') }}
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button
|
||||
size="sm"
|
||||
color="gray"
|
||||
x-on:click.prevent="pond.imageEditEditor.oncancel"
|
||||
>
|
||||
@@ -261,10 +278,16 @@
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button
|
||||
size="sm"
|
||||
color="warning"
|
||||
x-on:click.stop.prevent="cropper.reset()"
|
||||
class="ml-auto"
|
||||
>
|
||||
{{ __('filament-forms::components.file_upload.cropper.actions.reset.label') }}
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button
|
||||
color="success"
|
||||
x-on:click.prevent="saveCropper"
|
||||
class="ml-auto"
|
||||
>
|
||||
{{ __('filament-forms::components.file_upload.cropper.actions.save.label') }}
|
||||
</x-filament::button>
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace Filament\Forms\Components;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Support\Concerns\HasAlignment;
|
||||
use Filament\Support\Concerns\HasExtraAlpineAttributes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class FileUpload extends BaseFileUpload
|
||||
@@ -49,17 +51,20 @@ class FileUpload extends BaseFileUpload
|
||||
|
||||
protected string | Closure $uploadProgressIndicatorPosition = 'right';
|
||||
|
||||
protected bool | Closure $isCroppable = false;
|
||||
protected bool | Closure $hasCroppableImages = false;
|
||||
|
||||
protected ?int $cropperViewportWidth = null;
|
||||
protected int | Closure | null $imageCropperViewportWidth = null;
|
||||
|
||||
protected ?int $cropperViewportHeight = null;
|
||||
protected int | Closure | null $imageCropperViewportHeight = null;
|
||||
|
||||
protected int $cropperMode = 1;
|
||||
protected int $imageCropperMode = 1;
|
||||
|
||||
protected string $cropperEmptyFillColor = 'transparent';
|
||||
protected string | Closure $imageCropperEmptyFillColor = 'transparent';
|
||||
|
||||
protected array | Closure $cropperAspectRatios = [];
|
||||
/**
|
||||
* @var array<?string> | Closure
|
||||
*/
|
||||
protected array | Closure $imageCropperAspectRatios = [];
|
||||
|
||||
public function appendFiles(bool | Closure $condition = true): static
|
||||
{
|
||||
@@ -81,7 +86,6 @@ class FileUpload extends BaseFileUpload
|
||||
$this->removeUploadedFileButtonPosition('center bottom');
|
||||
$this->uploadButtonPosition('center bottom');
|
||||
$this->uploadProgressIndicatorPosition('center bottom');
|
||||
$this->circularCropper();
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -281,81 +285,83 @@ class FileUpload extends BaseFileUpload
|
||||
return (bool) $this->evaluate($this->shouldOrientImagesFromExif);
|
||||
}
|
||||
|
||||
public function croppable(bool | Closure $condition = true): static
|
||||
public function croppableImages(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isCroppable = $condition;
|
||||
$this->hasCroppableImages = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cropperViewportWidth(int $width): static
|
||||
public function imageCropperViewportWidth(int | Closure | null $width): static
|
||||
{
|
||||
$this->cropperViewportWidth = $width;
|
||||
$this->imageCropperViewportWidth = $width;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cropperViewportHeight(int $height): static
|
||||
public function imageCropperViewportHeight(int | Closure | null $height): static
|
||||
{
|
||||
$this->cropperViewportHeight = $height;
|
||||
$this->imageCropperViewportHeight = $height;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// https://github.com/fengyuanchen/cropperjs#viewmode
|
||||
public function cropperMode(int $mode): static
|
||||
public function imageCropperMode(int $mode): static
|
||||
{
|
||||
//setting data via inputs won't work with viewMode 0 src: https://github.com/fengyuanchen/cropperjs#setdatadata, see "note"
|
||||
if (in_array($mode, [1, 2, 3])) {
|
||||
$this->cropperMode = $mode;
|
||||
if (! in_array($mode, [1, 2, 3])) {
|
||||
throw new Exception("The file upload cropper mode must be either 1, 2 or 3. [{$mode}] given, which is unsupported. See https://github.com/fengyuanchen/cropperjs#viewmode for more information on the available modes. Mode 0 is not supported, as it does not allow configuration via manual inputs.");
|
||||
}
|
||||
|
||||
$this->imageCropperMode = $mode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// https://github.com/fengyuanchen/cropperjs#getcroppedcanvasoptions
|
||||
public function cropperEmptyFillColor(string $color): static
|
||||
public function imageCropperEmptyFillColor(string | Closure $color): static
|
||||
{
|
||||
$this->cropperEmptyFillColor = $color;
|
||||
$this->imageCropperEmptyFillColor = $color;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cropperAspectRatios(array $ratios): static
|
||||
/**
|
||||
* @param array<?string> | Closure $ratios
|
||||
*/
|
||||
public function imageCropperAspectRatios(array | Closure $ratios): static
|
||||
{
|
||||
$this->cropperAspectRatios = $ratios;
|
||||
$this->imageCropperAspectRatios = $ratios;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCropperViewportHeight(): ?int
|
||||
public function getImageCropperViewportHeight(): ?int
|
||||
{
|
||||
if (($targetHeight = (int) $this->getImageResizeTargetHeight()) > 1) {
|
||||
return (int) round($targetHeight * $this->getParentTargetSizes($targetHeight), 0);
|
||||
return (int) round($targetHeight * $this->getParentTargetSizes($targetHeight), precision: 0);
|
||||
}
|
||||
|
||||
if (is_string($ratio = $this->getImageCropAspectRatio())) {
|
||||
if (filled($ratio = $this->getImageCropAspectRatio())) {
|
||||
[$numerator, $denominator] = explode(':', $ratio);
|
||||
|
||||
return $denominator;
|
||||
return (int) $denominator;
|
||||
}
|
||||
|
||||
return $this->cropperViewportHeight;
|
||||
return $this->evaluate($this->imageCropperViewportHeight);
|
||||
}
|
||||
|
||||
public function getCropperViewportWidth(): ?int
|
||||
public function getImageCropperViewportWidth(): ?int
|
||||
{
|
||||
if (($targetWidth = (int) $this->getImageResizeTargetWidth()) > 1) {
|
||||
return (int) round($targetWidth * $this->getParentTargetSizes($targetWidth), 0);
|
||||
return (int) round($targetWidth * $this->getParentTargetSizes($targetWidth), precision: 0);
|
||||
}
|
||||
|
||||
if (is_string($ratio = $this->getImageCropAspectRatio())) {
|
||||
if (filled($ratio = $this->getImageCropAspectRatio())) {
|
||||
[$numerator, $denominator] = explode(':', $ratio);
|
||||
|
||||
return $numerator;
|
||||
return (int) $numerator;
|
||||
}
|
||||
|
||||
return $this->cropperViewportWidth;
|
||||
return $this->evaluate($this->imageCropperViewportWidth);
|
||||
}
|
||||
|
||||
protected function getParentTargetSizes(int $withOrHeight): float
|
||||
@@ -363,143 +369,147 @@ class FileUpload extends BaseFileUpload
|
||||
return $withOrHeight > 1 ? 360 / (int) $this->getImageResizeTargetWidth() : 1;
|
||||
}
|
||||
|
||||
public function getCropperShape(): string
|
||||
public function getImageCropperMode(): int
|
||||
{
|
||||
return $this->isAvatar ? 'circle' : $this->cropperShape;
|
||||
return $this->imageCropperMode;
|
||||
}
|
||||
|
||||
public function getCropperMode(): int
|
||||
public function getImageCropperEmptyFillColor(): string
|
||||
{
|
||||
return $this->isAvatar ? 1 : $this->cropperMode;
|
||||
return $this->evaluate($this->imageCropperEmptyFillColor);
|
||||
}
|
||||
|
||||
public function getCropperEmptyFillColor(): string
|
||||
public function hasCroppableImages(): bool
|
||||
{
|
||||
return $this->cropperEmptyFillColor;
|
||||
return (bool) $this->evaluate($this->hasCroppableImages);
|
||||
}
|
||||
|
||||
public function isCroppable(): bool
|
||||
/**
|
||||
* @return array<string, float | string>
|
||||
*/
|
||||
public function getImageCropperAspectRatiosForJs(): array
|
||||
{
|
||||
return (bool) $this->evaluate($this->isCroppable);
|
||||
return collect($this->evaluate($this->imageCropperAspectRatios) ?? [])
|
||||
->when(
|
||||
filled($imageCropAspectRatio = $this->getImageCropAspectRatio()),
|
||||
fn (Collection $ratios): Collection => $ratios->push($imageCropAspectRatio),
|
||||
)
|
||||
->unique()
|
||||
->mapWithKeys(fn (?string $ratio): array => [
|
||||
$ratio ?? __('filament-forms::components.file_upload.cropper.aspect_ratios.no_fixed.label') => $this->normalizeImageCroppingRatioForJs($ratio),
|
||||
])
|
||||
->filter(fn (float | string | false $ratio): bool => $ratio !== false)
|
||||
->when(
|
||||
fn (Collection $ratios): bool => $ratios->count() < 2,
|
||||
fn (Collection $ratios): Collection => $ratios->take(0),
|
||||
)
|
||||
->all();
|
||||
}
|
||||
|
||||
public function getCropperAspectRatios(): array
|
||||
{
|
||||
$ratios = $this->evaluate($this->cropperAspectRatios);
|
||||
|
||||
$ratios[] = $this->getImageCropAspectRatio();
|
||||
|
||||
if (is_array($ratios)) {
|
||||
$compiled = [];
|
||||
|
||||
foreach (array_unique($ratios) as $ratio) {
|
||||
if ($validRatio = $this->makeRatio($ratio)) {
|
||||
$compiled[$ratio ?? __('filament-forms::components.file_upload.cropper.actions.free.label')] = $validRatio;
|
||||
}
|
||||
}
|
||||
|
||||
//don't output any aspect ratio buttons if there is only one, nothing to toggle...
|
||||
return count($compiled) > 1 ? $compiled : [];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function makeRatio(null | string | int $ratio): float | int | bool | string
|
||||
protected function normalizeImageCroppingRatioForJs(?string $ratio): float | string | false
|
||||
{
|
||||
if ($ratio === null) {
|
||||
return 'NaN';
|
||||
}
|
||||
|
||||
$parts = explode(':', $ratio);
|
||||
$ratioParts = explode(':', $ratio);
|
||||
|
||||
if (count($parts) !== 2) {
|
||||
if (count($ratioParts) !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$numerator, $denominator] = $parts;
|
||||
[$numerator, $denominator] = $ratioParts;
|
||||
|
||||
if ($denominator == 0 || ! is_numeric($numerator) || ! is_numeric($denominator)) {
|
||||
if (! $denominator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! is_numeric($numerator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! is_numeric($denominator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $numerator / $denominator;
|
||||
}
|
||||
|
||||
public function getCropperActions(): array
|
||||
/**
|
||||
* @return array<array<array<string, mixed>>>
|
||||
*/
|
||||
public function getImageCropperActions(string $iconSizeClasses): array
|
||||
{
|
||||
$size = 'h-5 w-5 mx-auto';
|
||||
|
||||
return [
|
||||
'zoom' => [
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.drag_move.label'),
|
||||
'icon' => new HtmlString('<svg class="' . $size . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M13 6v5h5V7.75L22.25 12L18 16.25V13h-5v5h3.25L12 22.25L7.75 18H11v-5H6v3.25L1.75 12L6 7.75V11h5V6H7.75L12 1.75L16.25 6H13Z"/></svg>'),
|
||||
'click' => "cropper.setDragMode('move')",
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.drag_move.label'),
|
||||
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M13 6v5h5V7.75L22.25 12L18 16.25V13h-5v5h3.25L12 22.25L7.75 18H11v-5H6v3.25L1.75 12L6 7.75V11h5V6H7.75L12 1.75L16.25 6H13Z"/></svg>'),
|
||||
'alpineClickHandler' => "cropper.setDragMode('move')",
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.drag_crop.label'),
|
||||
'icon' => new HtmlString('<svg class="' . $size . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M17 23v-4H7q-.825 0-1.412-.587Q5 17.825 5 17V7H1V5h4V1h2v16h16v2h-4v4Zm0-8V7H9V5h8q.825 0 1.413.588Q19 6.175 19 7v8Z"/></svg>'),
|
||||
'click' => "cropper.setDragMode('crop')",
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.drag_crop.label'),
|
||||
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M17 23v-4H7q-.825 0-1.412-.587Q5 17.825 5 17V7H1V5h4V1h2v16h16v2h-4v4Zm0-8V7H9V5h8q.825 0 1.413.588Q19 6.175 19 7v8Z"/></svg>'),
|
||||
'alpineClickHandler' => "cropper.setDragMode('crop')",
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.zoom_in.label'),
|
||||
'icon' => svg('heroicon-o-magnifying-glass-plus', $size)->toHtml(),
|
||||
'click' => 'cropper.zoom(0.1)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.zoom_in.label'),
|
||||
'iconHtml' => svg('heroicon-o-magnifying-glass-plus', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.zoom(0.1)',
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.zoom_out.label'),
|
||||
'icon' => svg('heroicon-o-magnifying-glass-minus', $size)->toHtml(),
|
||||
'click' => 'cropper.zoom(-0.1)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.zoom_out.label'),
|
||||
'iconHtml' => svg('heroicon-o-magnifying-glass-minus', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.zoom(-0.1)',
|
||||
],
|
||||
[
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.zoom_100.label'),
|
||||
'iconHtml' => svg('heroicon-o-arrows-pointing-out', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.zoomTo(1)',
|
||||
],
|
||||
// [
|
||||
// 'tooltip' => __('filament-forms::components.file_upload.cropper.actions.zoom_100.label'),
|
||||
// 'icon' => svg('heroicon-o-arrows-pointing-out', $size)->toHtml(),
|
||||
// 'click' => 'cropper.zoomTo(1)',
|
||||
// ],
|
||||
],
|
||||
'move' => [
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_left.label'),
|
||||
'icon' => svg('heroicon-o-arrow-left-circle', $size)->toHtml(),
|
||||
'click' => 'cropper.move(-10, 0)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.move_left.label'),
|
||||
'iconHtml' => svg('heroicon-o-arrow-left-circle', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.move(-10, 0)',
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_right.label'),
|
||||
'icon' => svg('heroicon-o-arrow-right-circle', $size)->toHtml(),
|
||||
'click' => 'cropper.move(10, 0)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.move_right.label'),
|
||||
'iconHtml' => svg('heroicon-o-arrow-right-circle', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.move(10, 0)',
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_up.label'),
|
||||
'icon' => svg('heroicon-o-arrow-up-circle', $size)->toHtml(),
|
||||
'click' => 'cropper.move(0, -10)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.move_up.label'),
|
||||
'iconHtml' => svg('heroicon-o-arrow-up-circle', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.move(0, -10)',
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_down.label'),
|
||||
'icon' => svg('heroicon-o-arrow-down-circle', $size)->toHtml(),
|
||||
'click' => 'cropper.move(0, 10)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.move_down.label'),
|
||||
'iconHtml' => svg('heroicon-o-arrow-down-circle', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.move(0, 10)',
|
||||
],
|
||||
],
|
||||
'rotate_flip' => [
|
||||
'transform' => [
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.rotate_left.label'),
|
||||
'icon' => svg('heroicon-o-arrow-uturn-left', $size)->toHtml(),
|
||||
'click' => 'cropper.rotate(-90)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.rotate_left.label'),
|
||||
'iconHtml' => svg('heroicon-o-arrow-uturn-left', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.rotate(-90)',
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.rotate_right.label'),
|
||||
'icon' => svg('heroicon-o-arrow-uturn-right', $size)->toHtml(),
|
||||
'click' => 'cropper.rotate(90)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.rotate_right.label'),
|
||||
'iconHtml' => svg('heroicon-o-arrow-uturn-right', $iconSizeClasses)->toHtml(),
|
||||
'alpineClickHandler' => 'cropper.rotate(90)',
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.flip_horizontal.label'),
|
||||
'icon' => new HtmlString('<svg class="' . $size . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m17 3l-5 5l-5-5h10m0 18l-5-5l-5 5h10M4 12H2m8 0H8m8 0h-2m8 0h-2"/></svg>'),
|
||||
'click' => 'cropper.scaleX(-cropper.getData().scaleX || -1)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.flip_horizontal.label'),
|
||||
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m17 3l-5 5l-5-5h10m0 18l-5-5l-5 5h10M4 12H2m8 0H8m8 0h-2m8 0h-2"/></svg>'),
|
||||
'alpineClickHandler' => 'cropper.scaleX(-cropper.getData().scaleX || -1)',
|
||||
],
|
||||
[
|
||||
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.flip_vertical.label'),
|
||||
'icon' => new HtmlString('<svg class="' . $size . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m3 7l5 5l-5 5V7m18 0l-5 5l5 5V7m-9 13v2m0-8v2m0-8v2m0-8v2"/></svg>'),
|
||||
'click' => 'cropper.scaleY(-cropper.getData().scaleY || -1)',
|
||||
'label' => __('filament-forms::components.file_upload.cropper.actions.flip_vertical.label'),
|
||||
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m3 7l5 5l-5 5V7m18 0l-5 5l5 5V7m-9 13v2m0-8v2m0-8v2m0-8v2"/></svg>'),
|
||||
'alpineClickHandler' => 'cropper.scaleY(-cropper.getData().scaleY || -1)',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
Vendored
+20
-2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
<div
|
||||
{{
|
||||
$attributes->class([
|
||||
'flex items-center divide-x divide-gray-950/10 overflow-hidden rounded-lg shadow ring-1 ring-gray-950/10 dark:divide-white/20 dark:ring-white/20',
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
+22
-13
@@ -2,6 +2,7 @@
|
||||
'color' => 'primary',
|
||||
'disabled' => false,
|
||||
'form' => null,
|
||||
'grouped' => false,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'iconPosition' => 'before',
|
||||
@@ -21,7 +22,9 @@
|
||||
@php
|
||||
$buttonClasses = \Illuminate\Support\Arr::toCssClasses([
|
||||
...[
|
||||
"fi-btn fi-btn-size-{$size} relative grid-flow-col items-center justify-center rounded-lg font-medium outline-none transition duration-75 focus:ring-2 disabled:pointer-events-none disabled:opacity-70",
|
||||
"fi-btn fi-btn-size-{$size} relative grid-flow-col items-center justify-center font-medium outline-none transition duration-75 focus:ring-2 disabled:pointer-events-none disabled:opacity-70",
|
||||
'flex-1' => $grouped,
|
||||
'rounded-lg' => ! $grouped,
|
||||
is_string($color) ? "fi-btn-color-{$color}" : null,
|
||||
match ($size) {
|
||||
'xs' => 'gap-1 px-2 py-1.5 text-xs',
|
||||
@@ -41,19 +44,25 @@
|
||||
},
|
||||
],
|
||||
...(
|
||||
$outlined
|
||||
? [
|
||||
'fi-btn-outlined ring-1 ',
|
||||
match ($color) {
|
||||
'gray' => 'ring-gray-300 text-gray-950 hover:bg-gray-400/10 focus:bg-gray-400/10 focus:ring-gray-400/40 dark:ring-gray-700 dark:text-white',
|
||||
default => 'ring-custom-600 text-custom-600 hover:bg-custom-400/10 focus:bg-custom-400/10 focus:ring-custom-500/50 dark:ring-custom-400 dark:text-custom-400 dark:focus:ring-custom-400/70',
|
||||
},
|
||||
]
|
||||
: [
|
||||
'shadow',
|
||||
$outlined ?
|
||||
[
|
||||
'fi-btn-outlined ring-1',
|
||||
match ($color) {
|
||||
'gray' => 'ring-gray-950/10 ring-1 bg-white text-gray-950 hover:bg-gray-50 focus:bg-gray-50 dark:ring-white/20 dark:bg-white/10 dark:text-white dark:hover:bg-white/20 dark:hover:ring-white/30 dark:focus:bg-white/20 dark:focus:ring-white/30',
|
||||
default => 'bg-custom-600 text-white hover:bg-custom-500 focus:bg-custom-500 focus:ring-custom-400/50 dark:bg-custom-500 dark:hover:bg-custom-400 dark:focus:bg-custom-400',
|
||||
'gray' => 'ring-gray-300 text-gray-950 hover:bg-gray-400/10 focus:bg-gray-400/10 focus:ring-gray-400/40 dark:ring-gray-700 dark:text-white',
|
||||
default => 'ring-custom-600 text-custom-600 hover:bg-custom-400/10 focus:bg-custom-400/10 focus:ring-custom-500/50 dark:ring-custom-400 dark:text-custom-400 dark:focus:ring-custom-400/70',
|
||||
},
|
||||
] :
|
||||
[
|
||||
'shadow' => ! $grouped,
|
||||
...match ($color) {
|
||||
'gray' => [
|
||||
'bg-white text-gray-950 hover:bg-gray-50 focus:bg-gray-50 dark:bg-white/10 dark:text-white dark:hover:bg-white/20 dark:focus:bg-white/20',
|
||||
'ring-gray-950/10 ring-1 dark:ring-white/20 dark:hover:ring-white/30 dark:focus:ring-white/30' => ! $grouped,
|
||||
],
|
||||
default => [
|
||||
'bg-custom-600 text-white hover:bg-custom-500 focus:bg-custom-500 dark:bg-custom-500 dark:hover:bg-custom-400 dark:focus:bg-custom-400',
|
||||
'focus:ring-custom-400/50' => ! $grouped,
|
||||
],
|
||||
},
|
||||
]
|
||||
),
|
||||
Reference in New Issue
Block a user