affiliations
This commit is contained in:
1 parent
4f3f9c9aad
commit
0b5b3dd3ee
177 files changed
+9660
-8531
No files matched your search
+187
-39
@@ -5,9 +5,19 @@
|
||||
$shape = $attributes->get('shape', 'circle');
|
||||
$folder = $attributes->get('folder', 'uploads');
|
||||
$filename = $attributes->get('filename', 'cropped_' . time());
|
||||
$uploadUrl = $attributes->get('uploadUrl', route('profile.upload-picture'));
|
||||
$uploadUrl = $attributes->get('uploadUrl', '');
|
||||
$currentImage = $attributes->get('currentImage', '');
|
||||
$buttonText = $attributes->get('buttonText', 'Change Photo');
|
||||
$buttonClass = $attributes->get('buttonClass', 'btn btn-success px-4 fw-bold shadow-sm');
|
||||
$mode = $attributes->get('mode', 'ajax'); // 'ajax' or 'form'
|
||||
$inputName = $attributes->get('inputName', 'image'); // For form mode - the hidden input name
|
||||
$previewWidth = $attributes->get('previewWidth', $width);
|
||||
$previewHeight = $attributes->get('previewHeight', $height);
|
||||
$showPreview = $attributes->get('showPreview', $mode === 'form'); // Show preview by default in form mode
|
||||
@endphp
|
||||
|
||||
<x-toast-notification />
|
||||
|
||||
@once
|
||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/cropme.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
@@ -32,12 +42,78 @@
|
||||
}
|
||||
.form-range::-webkit-slider-thumb { background: #198754; }
|
||||
.form-range::-moz-range-thumb { background: #198754; }
|
||||
.cropper-preview-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.cropper-preview-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f0f0f0;
|
||||
border: 2px dashed #dee2e6;
|
||||
color: #6c757d;
|
||||
}
|
||||
.cropper-preview-image {
|
||||
object-fit: cover;
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
.cropper-remove-btn {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
.cropper-preview-container.has-image .cropper-remove-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@endonce
|
||||
|
||||
<button type="button" class="btn btn-success px-4 fw-bold shadow-sm" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
|
||||
Change Photo
|
||||
@if($mode === 'form')
|
||||
{{-- Form mode: Show preview and hidden input --}}
|
||||
<div class="cropper-form-wrapper text-center" id="wrapper_{{ $id }}">
|
||||
<div class="cropper-preview-container mb-2" id="previewContainer_{{ $id }}">
|
||||
@if($currentImage)
|
||||
<img src="{{ $currentImage }}"
|
||||
id="preview_{{ $id }}"
|
||||
class="cropper-preview-image"
|
||||
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: {{ $shape === 'circle' ? '50%' : '8px' }};">
|
||||
<button type="button" class="cropper-remove-btn" id="removeBtn_{{ $id }}" onclick="removeImage_{{ $id }}()">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
@else
|
||||
<div id="preview_{{ $id }}"
|
||||
class="cropper-preview-placeholder"
|
||||
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: {{ $shape === 'circle' ? '50%' : '8px' }};">
|
||||
<i class="bi bi-image" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<input type="hidden" name="{{ $inputName }}" id="hiddenInput_{{ $id }}" value="">
|
||||
<input type="hidden" name="{{ $inputName }}_folder" value="{{ $folder }}">
|
||||
<input type="hidden" name="{{ $inputName }}_filename" value="{{ $filename }}">
|
||||
<button type="button" class="{{ $buttonClass }}" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
|
||||
<i class="bi bi-camera me-2"></i>{{ $buttonText }}
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
{{-- AJAX mode: Just the button --}}
|
||||
<button type="button" class="{{ $buttonClass }}" data-bs-toggle="modal" data-bs-target="#cropperModal_{{ $id }}">
|
||||
<i class="bi bi-camera me-2"></i>{{ $buttonText }}
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@push('modals')
|
||||
<div class="modal fade" id="cropperModal_{{ $id }}" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
|
||||
@@ -64,7 +140,11 @@
|
||||
|
||||
<div class="d-grid gap-2 mt-2">
|
||||
<button type="button" class="btn btn-success btn-lg fw-bold py-3" id="save_{{ $id }}">
|
||||
Crop & Save Image
|
||||
@if($mode === 'form')
|
||||
Crop & Apply
|
||||
@else
|
||||
Crop & Save Image
|
||||
@endif
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,6 +158,8 @@ $(function() {
|
||||
const el_{{ $id }} = document.getElementById("box_{{ $id }}");
|
||||
const zoomMin_{{ $id }} = 0.01;
|
||||
const zoomMax_{{ $id }} = 3;
|
||||
const currentImage_{{ $id }} = '{{ $currentImage }}';
|
||||
const mode_{{ $id }} = '{{ $mode }}';
|
||||
|
||||
function applyTransform_{{ $id }}(instance) {
|
||||
if (!instance.properties.image) return;
|
||||
@@ -86,29 +168,40 @@ $(function() {
|
||||
p.image.style.transform = t;
|
||||
}
|
||||
|
||||
function initCropper_{{ $id }}(imageUrl) {
|
||||
if (cropper_{{ $id }}) cropper_{{ $id }}.destroy();
|
||||
|
||||
cropper_{{ $id }} = new Cropme(el_{{ $id }}, {
|
||||
container: { width: '100%', height: 400 },
|
||||
viewport: {
|
||||
width: {{ $width }},
|
||||
height: {{ $height }},
|
||||
type: '{{ $shape }}',
|
||||
border: { enable: true, width: 2, color: '#fff' }
|
||||
},
|
||||
transformOrigin: 'viewport',
|
||||
zoom: { min: zoomMin_{{ $id }}, max: zoomMax_{{ $id }}, enable: true, mouseWheel: true, slider: false },
|
||||
rotation: { enable: true, slider: false }
|
||||
});
|
||||
|
||||
cropper_{{ $id }}.bind({ url: imageUrl }).then(() => {
|
||||
$('#zoom_{{ $id }}').val(0);
|
||||
$('#rot_{{ $id }}').val(0);
|
||||
});
|
||||
}
|
||||
|
||||
// Load current image when modal opens
|
||||
$('#cropperModal_{{ $id }}').on('shown.bs.modal', function() {
|
||||
if (currentImage_{{ $id }} && !cropper_{{ $id }}) {
|
||||
initCropper_{{ $id }}(currentImage_{{ $id }});
|
||||
}
|
||||
});
|
||||
|
||||
$('#input_{{ $id }}').on('change', function() {
|
||||
if (this.files && this.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
if (cropper_{{ $id }}) cropper_{{ $id }}.destroy();
|
||||
|
||||
cropper_{{ $id }} = new Cropme(el_{{ $id }}, {
|
||||
container: { width: '100%', height: 400 },
|
||||
viewport: {
|
||||
width: {{ $width }},
|
||||
height: {{ $height }},
|
||||
type: '{{ $shape }}',
|
||||
border: { enable: true, width: 2, color: '#fff' }
|
||||
},
|
||||
transformOrigin: 'viewport',
|
||||
zoom: { min: zoomMin_{{ $id }}, max: zoomMax_{{ $id }}, enable: true, mouseWheel: true, slider: false },
|
||||
rotation: { enable: true, slider: false }
|
||||
});
|
||||
|
||||
cropper_{{ $id }}.bind({ url: event.target.result }).then(() => {
|
||||
$('#zoom_{{ $id }}').val(0);
|
||||
$('#rot_{{ $id }}').val(0);
|
||||
});
|
||||
initCropper_{{ $id }}(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(this.files[0]);
|
||||
}
|
||||
@@ -131,26 +224,81 @@ $(function() {
|
||||
$('#save_{{ $id }}').on('click', function() {
|
||||
if (!cropper_{{ $id }}) return;
|
||||
const btn = $(this);
|
||||
btn.prop('disabled', true).text('Uploading...');
|
||||
|
||||
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
|
||||
$.post("{{ $uploadUrl }}", {
|
||||
_token: "{{ csrf_token() }}",
|
||||
image: base64,
|
||||
folder: '{{ $folder }}',
|
||||
filename: '{{ $filename }}'
|
||||
}).done((res) => {
|
||||
alert('Saved successfully!');
|
||||
if (mode_{{ $id }} === 'form') {
|
||||
// Form mode: Store base64 in hidden input and update preview
|
||||
btn.prop('disabled', true).text('Processing...');
|
||||
|
||||
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
|
||||
// Store in hidden input
|
||||
$('#hiddenInput_{{ $id }}').val(base64);
|
||||
|
||||
// Update preview
|
||||
const previewContainer = $('#previewContainer_{{ $id }}');
|
||||
const borderRadius = '{{ $shape }}' === 'circle' ? '50%' : '8px';
|
||||
|
||||
previewContainer.html(`
|
||||
<img src="${base64}"
|
||||
id="preview_{{ $id }}"
|
||||
class="cropper-preview-image"
|
||||
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: ${borderRadius};">
|
||||
<button type="button" class="cropper-remove-btn" id="removeBtn_{{ $id }}" onclick="removeImage_{{ $id }}()">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`);
|
||||
previewContainer.addClass('has-image');
|
||||
|
||||
// Close modal
|
||||
$('#cropperModal_{{ $id }}').modal('hide');
|
||||
// Reload page to show new image
|
||||
location.reload();
|
||||
}).fail((err) => {
|
||||
alert('Upload failed: ' + (err.responseJSON?.message || 'Unknown error'));
|
||||
}).always(() => {
|
||||
btn.prop('disabled', false).text('Crop & Save Image');
|
||||
btn.prop('disabled', false).text('Crop & Apply');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// AJAX mode: Upload to server
|
||||
btn.prop('disabled', true).text('Uploading...');
|
||||
|
||||
cropper_{{ $id }}.crop({ type: 'base64' }).then(base64 => {
|
||||
$.post("{{ $uploadUrl }}", {
|
||||
_token: "{{ csrf_token() }}",
|
||||
image: base64,
|
||||
folder: '{{ $folder }}',
|
||||
filename: '{{ $filename }}'
|
||||
}).done((res) => {
|
||||
$('#cropperModal_{{ $id }}').modal('hide');
|
||||
Toast.success('Photo Updated!', 'Your image has been saved successfully.');
|
||||
// Reload page after toast shows
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
}).fail((err) => {
|
||||
Toast.error('Upload Failed', err.responseJSON?.message || 'An error occurred while uploading.');
|
||||
}).always(() => {
|
||||
btn.prop('disabled', false).text('Crop & Save Image');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Remove image function for form mode
|
||||
window.removeImage_{{ $id }} = function() {
|
||||
const previewContainer = $('#previewContainer_{{ $id }}');
|
||||
const borderRadius = '{{ $shape }}' === 'circle' ? '50%' : '8px';
|
||||
|
||||
// Clear hidden input
|
||||
$('#hiddenInput_{{ $id }}').val('');
|
||||
|
||||
// Reset preview to placeholder
|
||||
previewContainer.html(`
|
||||
<div id="preview_{{ $id }}"
|
||||
class="cropper-preview-placeholder"
|
||||
style="width: {{ $previewWidth }}px; height: {{ $previewHeight }}px; border-radius: ${borderRadius};">
|
||||
<i class="bi bi-image" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
`);
|
||||
previewContainer.removeClass('has-image');
|
||||
};
|
||||
|
||||
// Initialize remove button visibility if current image exists
|
||||
@if($currentImage && $mode === 'form')
|
||||
$('#previewContainer_{{ $id }}').addClass('has-image');
|
||||
@endif
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Reference in new issue
Block a user