diff --git a/app/Http/Controllers/ProfilesController.php b/app/Http/Controllers/ProfilesController.php index 34caee05..97e620e3 100644 --- a/app/Http/Controllers/ProfilesController.php +++ b/app/Http/Controllers/ProfilesController.php @@ -270,19 +270,6 @@ public function update(Profile $profile, string $section, ProfileUpdateRequest $ return redirect()->route('profiles.show', $profile->slug)->with('flash_message', 'Profile updated.'); } - public function updateImage(Profile $profile, ProfileImageRequest $request): RedirectResponse - { - return redirect()->route('profiles.edit', [$profile->slug, 'information'])->with('flash_message', $profile->processImage($request->file('image'), 'images')); - } - - /** - * Update a Profile's banner image - */ - public function updateBanner(Profile $profile, ProfileBannerImageRequest $request): RedirectResponse - { - return redirect()->route('profiles.edit', [$profile->slug, 'information'])->with('flash_message', $profile->processImage($request->file('banner_image'), 'banners')); - } - /** * Confirm deletion of a profile */ diff --git a/app/Http/Livewire/ImagePicker.php b/app/Http/Livewire/ImagePicker.php new file mode 100644 index 00000000..3c4157ff --- /dev/null +++ b/app/Http/Livewire/ImagePicker.php @@ -0,0 +1,185 @@ +save_function)) { // Validates save function only if set + $this->validateCallUserFunc('save_function'); + } + + if (isset($this->callback_function)) { // Validates callback function only if set + $this->validateCallUserFunc('callback_function'); + } + } + + /** + * Validates the image after a file has been selected and stores the updated image in the save function parameters for uploading + */ + public function updatedImage() + { + if ($this->image) { + $this->validate( + ['image' => "nullable|{$this->uploadedImageRules()}"], + ); + + $this->save_params[$this->image_param_name] = $this->image; + } + } + + public function save() + { + $this->authorize(...$this->auth_params); + + // Calls the model's save function if an image is uploaded + if ($this->image) { + $this->message = call_user_func([$this->model, $this->save_function], ...$this->save_params ?? []); + } + + // If a callback function is set, execute it (e.g., updating related records) + if (isset($this->callback_function)) { + call_user_func([$this->model, $this->callback_function], ...$this->callback_params ?? []); + } + + return redirect($this->redirect_route)->with('flash_message', $this->message); + } + + /** + * Validates that the provided method name exists on the model + * Prevents runtime errors if an invalid method is supplied + * + * @param string $function_name + * Name of the function to validate + */ + private function validateCallUserFunc($function_name) + { + $class_name = get_class($this->model); + + $this->validate([ + 'model' => [ + 'required', + function ($attribute, $value, $fail) use ($class_name) { + if (!class_exists($class_name) || !is_subclass_of($class_name, Model::class)) { + $fail('Please contact the app admin.'); + logger()->error("The class {$class_name} is not a valid Eloquent model."); + } + } + ], + + $function_name => [ + function ($attribute, $value, $fail) use ($class_name) { + /** @var string|null $value */ + if (!is_callable($value, true) || !method_exists($class_name, $value)) { + $fail('Please contact the app admin.'); + logger()->error("The method {$value} does not exist in {$class_name}."); + } + } + ], + ]); + } + + public function render() + { + return view('livewire.image-picker'); + } +} + diff --git a/app/Http/Livewire/ProfileHeaderEditorModal.php b/app/Http/Livewire/ProfileHeaderEditorModal.php new file mode 100644 index 00000000..44d2aecc --- /dev/null +++ b/app/Http/Livewire/ProfileHeaderEditorModal.php @@ -0,0 +1,75 @@ +fancy_header = $this->profile->hasFancyHeader(); + $this->fancy_header_right = $this->profile->hasFancyHeaderRight(); + + $this->avatar_settings = [ + 'existing_image_url' => $this->profile->image_url, + 'custom_key' => "profile-img", + 'custom_msg' => "This photo will appear on your profile page and as your application profile image - please use a high-quality image (300x300 pixels or larger).", + 'save_function' => 'processImage', + 'save_params' => [ + 'collection' => 'images', + ], + 'image_param_name' => 'new_image', + 'callback_function' => 'updateLayoutSettings', + 'callback_params' => [ + 'fancy_header' => false, + 'fancy_header_right' => $this->fancy_header_right, + ], + 'redirect_route' => route('profiles.show', $this->profile->slug), + 'message' => $message, + 'auth_params' => ['update', [Profile::class, $this->profile]], + ]; + + $this->cover_settings = [ + 'existing_image_url' => $this->profile->banner_url, + 'custom_key' => "banner-img", + 'custom_msg' => "This will use a full-width header style - please use a high-quality image (1280 × 720 pixels or larger).", + 'save_function' => 'processImage', + 'save_params' => [ + 'collection' => 'banners', + ], + 'image_param_name' => 'new_image', + 'callback_function' => 'updateLayoutSettings', + 'callback_params' => [ + 'fancy_header' => true, + 'fancy_header_right' => $this->fancy_header_right, + ], + 'redirect_route' => route('profiles.show', $this->profile->slug), + 'message' => $message, + 'partial_view' => 'livewire.partials._fancy-header-settings', + 'auth_params' => ['update', [Profile::class, $this->profile]], + ]; + } + + public function render() + { + return view('livewire.profile-header-editor-modal'); + } +} diff --git a/app/Http/Requests/ProfileBannerImageRequest.php b/app/Http/Requests/ProfileBannerImageRequest.php deleted file mode 100644 index ecc696e3..00000000 --- a/app/Http/Requests/ProfileBannerImageRequest.php +++ /dev/null @@ -1,36 +0,0 @@ - $this->uploadedImageRules(), - ]; - } - - /** - * Get the error messages for the defined validation rules. - * - * @return array - */ - public function messages() - { - return [ - 'banner_image.max' => $this->uploadedImageMessages('max'), - ]; - } - -} diff --git a/app/Http/Requests/ProfileImageRequest.php b/app/Http/Requests/ProfileImageRequest.php deleted file mode 100644 index 5988d970..00000000 --- a/app/Http/Requests/ProfileImageRequest.php +++ /dev/null @@ -1,36 +0,0 @@ - $this->uploadedImageRules(), - ]; - } - - /** - * Get the error messages for the defined validation rules. - * - * @return array - */ - public function messages() - { - return [ - 'image.max' => $this->uploadedImageMessages('max'), - ]; - } - -} diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php index 1e43383b..713508e7 100644 --- a/app/Http/Requests/ProfileUpdateRequest.php +++ b/app/Http/Requests/ProfileUpdateRequest.php @@ -48,8 +48,6 @@ public function informationRules(): array 'data.*.data.tertiary_url' => 'nullable|url', 'data.*.data.orc_id' => 'nullable|regex:/^[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$/', 'data.*.data.orc_id_managed' => 'required|boolean', - 'data.*.data.fancy_header' => 'required|boolean', - 'data.*.data.fancy_header_right' => 'required|boolean', ]; } diff --git a/app/Profile.php b/app/Profile.php index 04f34c7b..1c776d01 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -19,6 +19,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Http; /** * @method public() @@ -157,6 +158,18 @@ public function processImage($new_image, $collection = 'images'){ return $message; } + public function updateLayoutSettings($fancy_header, $fancy_header_right) + { + $profile_info = $this->information->first(); + + $profile_info->data = array_merge($profile_info->data ?? [], [ + 'fancy_header' => $fancy_header, + 'fancy_header_right' => (bool) $fancy_header_right, + ]); + + $profile_info->save(); + } + /** * Checks if this profile has publications managed by ORCID * @@ -573,7 +586,7 @@ public function getImageThumbUrlAttribute() */ public function getBannerUrlAttribute() { - return url($this->getFirstMediaUrl('banners', 'large') ?: '/img/default.png'); + return url($this->getFirstMediaUrl('banners', 'large')); } /** @@ -596,6 +609,21 @@ public function getApiUrlAttribute() return route('api.index', ['person' => $this->slug, 'with_data' => true]); } + public function hasFancyHeader() + { + return $this->information->first()->data['fancy_header'] ?? false; + } + + public function hasFancyHeaderRight() + { + return $this->information->first()->data['fancy_header_right'] ?? false; + } + + public function hasImage($collection = null) + { + return $this->hasMedia($collection); + } + /////////////// // Relations // /////////////// diff --git a/public/css/app.css b/public/css/app.css index 48bc5819..a08d4770 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -10461,7 +10461,7 @@ button.video-control .fa-play { background-attachment: fixed; background-position: center top; background-size: cover; - height: 85vh; + height: calc(70vh - 20px); position: relative; } @media (max-width: 992px) { @@ -10492,6 +10492,11 @@ button.video-control .fa-play { background: #f8f9fa; width: auto; } +.profile .fancy_header .edit_banner_button { + position: absolute; + bottom: 2px; + right: 2px; +} .profile .profile_list_photo { width: 100%; height: auto; @@ -10633,6 +10638,12 @@ button.video-control .fa-play { width: 100%; } +div.edit_photo_button { + position: absolute; + bottom: 2px; + right: 18px; +} + .student-card-img-overlay { position: absolute; top: 0; diff --git a/public/img/cover.png b/public/img/cover.png new file mode 100644 index 00000000..fd329ae5 Binary files /dev/null and b/public/img/cover.png differ diff --git a/public/js/app.js b/public/js/app.js index 8b9b4d0a..e35ff256 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -629,7 +629,7 @@ $(function () { //show preview of uploaded image $('input[type="file"]').on('change', function (e) { - return profiles.preview_selected_image(e); + profiles.preview_selected_image(e); }); // enable drag and drop sorting for items with sortable class diff --git a/public/mix-manifest.json b/public/mix-manifest.json index de5f1463..8632fb27 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,6 +1,6 @@ { - "/js/app.js": "/js/app.js?id=24be537863fbbe2259218dfb56ecef72", + "/js/app.js": "/js/app.js?id=f1170fe3e2888e47851541584fd2b3e9", "/js/manifest.js": "/js/manifest.js?id=dc9ead3d7857b522d7de22d75063453c", - "/css/app.css": "/css/app.css?id=bff46e69abe7a97b008296b99e4abadd", + "/css/app.css": "/css/app.css?id=70d6b8bf2d01a7ca63a37d3f170da78a", "/js/vendor.js": "/js/vendor.js?id=4d3313683b3a2faf8ca0278ce47f3880" } diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index c187b44f..bbaca78e 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -569,7 +569,9 @@ $(function() { $('.datepicker.month').datepicker(profiles.config.datepicker.month); //show preview of uploaded image - $('input[type="file"]').on('change', (e) => profiles.preview_selected_image(e)); + $('input[type="file"]').on('change', function(e) { + profiles.preview_selected_image(e); + }); // enable drag and drop sorting for items with sortable class if ($('.sortable').length > 0) { diff --git a/resources/assets/sass/_profile.scss b/resources/assets/sass/_profile.scss index 073cdb46..7252fca1 100644 --- a/resources/assets/sass/_profile.scss +++ b/resources/assets/sass/_profile.scss @@ -51,7 +51,7 @@ background-attachment: fixed; background-position: center top; background-size: cover; - height: 85vh; + height: calc(70vh - 20px); position: relative; @media (max-width: map-get($grid-breakpoints, lg)){ @@ -85,6 +85,12 @@ background: $light; width: auto; } + + .edit_banner_button { + position: absolute; + bottom: 2px; + right: 2px; + } } .profile_list_photo { @@ -240,3 +246,10 @@ .profile_photo{ width: 100% } + +div.edit_photo_button { + position: absolute; + bottom: 2px; + right: 18px; +} + diff --git a/resources/views/livewire/image-picker.blade.php b/resources/views/livewire/image-picker.blade.php new file mode 100644 index 00000000..854c5e41 --- /dev/null +++ b/resources/views/livewire/image-picker.blade.php @@ -0,0 +1,43 @@ +
+
+ @csrf +
+ @if ($errors->any()) + + @else +
+ @if($image) + + @else + + @endif +
+ @endif + +
+ + + {{ $custom_msg }} +
+
+ + @if(isset($partial_view)) + @include($partial_view) + @endif + +
+
+ + + +
+
+
+
\ No newline at end of file diff --git a/resources/views/livewire/partials/_fancy-header-settings.blade.php b/resources/views/livewire/partials/_fancy-header-settings.blade.php new file mode 100644 index 00000000..4ab6c581 --- /dev/null +++ b/resources/views/livewire/partials/_fancy-header-settings.blade.php @@ -0,0 +1,9 @@ +
+
+
+ + +
+ +
+
\ No newline at end of file diff --git a/resources/views/livewire/profile-header-editor-modal.blade.php b/resources/views/livewire/profile-header-editor-modal.blade.php new file mode 100644 index 00000000..651ab852 --- /dev/null +++ b/resources/views/livewire/profile-header-editor-modal.blade.php @@ -0,0 +1,79 @@ + diff --git a/resources/views/profiles/edit/information.blade.php b/resources/views/profiles/edit/information.blade.php index 422ebe09..758033a8 100644 --- a/resources/views/profiles/edit/information.blade.php +++ b/resources/views/profiles/edit/information.blade.php @@ -1,45 +1,7 @@

Edit {{$profile->name}}'s Contact Information

@foreach($data as $info) -
- {!! Form::open(['url' => route('profiles.update-image', [$profile->slug]), 'method' => 'POST', 'files' => true]) !!} - - -
-
-
-
- {!! Form::file('image', ['id' => 'file', 'name' => 'image', 'required' => 'true', 'accept' => 'image/*', 'class' => 'd-none form-control']) !!} - - {!! Form::inlineErrors('image') !!} -
-
- - {!! Form::close() !!} -
-
- {!! Form::open(['url' => route('profiles.update-banner', [$profile->slug]), 'method' => 'POST', 'files' => true]) !!} - - -
-
-
-
- {!! Form::file('banner_image', ['id' => 'banner', 'name' => 'banner_image', 'required' => 'true', 'accept' => 'image/*', 'class' => 'd-none form-control']) !!} - - {!! Form::inlineErrors('banner_image') !!} -
-
- - {!! Form::close() !!} -
-
-
-
+
{!! Form::model($profile, ['route' => ['profiles.update', 'profile' => $profile, 'section' => 'information']]) !!}
{!! Form::label('full_name', 'Display Name') !!} @@ -156,46 +118,6 @@

Refresh all publications via ORCID. All previous publications will be removed and fresh data will be pulled in at regular intervals. Keep unchecked to manually edit your publications.

-
-
-
- - id}.data.fancy_header", $info->fancy_header)) - value="1" - data-toggle="show" - data-toggle-target="#fancy_header_options" - > - -
- {{-- reset sub-options if main option is unchecked --}} - -
!old('data.show_accepting_students', $info->show_accepting_students) - ]) - > -
- id}.data.fancy_header_right", $info->fancy_header_right)) - value="1" - > - -
-
-
-
-

This will use a full-width header style - please make sure uploaded banner image is of sufficient quality!

-
-
diff --git a/resources/views/profiles/profile_card_example.blade.php b/resources/views/profiles/profile_card_example.blade.php new file mode 100644 index 00000000..363e9045 --- /dev/null +++ b/resources/views/profiles/profile_card_example.blade.php @@ -0,0 +1,84 @@ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + @foreach(range(1, 3) as $i) +
+ @endforeach +
+ \ No newline at end of file diff --git a/resources/views/profiles/show.blade.php b/resources/views/profiles/show.blade.php index 117af383..0e715881 100644 --- a/resources/views/profiles/show.blade.php +++ b/resources/views/profiles/show.blade.php @@ -37,8 +37,17 @@ @if(!$information->fancy_header)
{{ $profile->full_name }} + @if($editable) + + @endif +
@endif +
@@ -110,6 +119,14 @@ @endif
+ @if($editable & $information->fancy_header) + + @endif +