Skip to content

Commit 5f893c3

Browse files
committed
improve
1 parent ea2a23c commit 5f893c3

File tree

5 files changed

+45
-89
lines changed

5 files changed

+45
-89
lines changed

options/locale/locale_en-US.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ uploaded_avatar_not_a_image = The uploaded file is not an image.
765765
uploaded_avatar_is_too_big = The uploaded file size (%d KiB) exceeds the maximum size (%d KiB).
766766
update_avatar_success = Your avatar has been updated.
767767
update_user_avatar_success = The user's avatar has been updated.
768-
cropper_prompt = Note: The saved image format after cropping is unified as PNG.
768+
cropper_prompt = You could edit the avatar image before saving, edited image will be saved as PNG.
769769

770770
change_password = Update Password
771771
old_password = Current Password

templates/user/settings/profile.tmpl

+3-16
Original file line numberDiff line numberDiff line change
@@ -127,22 +127,9 @@
127127
<input id="new-avatar" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
128128
</div>
129129

130-
<div class="inline field cropper-panel tw-hidden" id="cropper-panel">
131-
<div class="cropper-preview">
132-
<h3>{{ctx.Locale.Tr "preview"}}</h3>
133-
<div>
134-
<img id="cropper-result" class="ui avatar tw-align-middle">
135-
</div>
136-
</div>
137-
<div class="cropper-editor">
138-
<div>
139-
<h3>{{ctx.Locale.Tr "edit"}}</h3>
140-
<span>{{ctx.Locale.Tr "settings.cropper_prompt"}}</span>
141-
</div>
142-
<div class="cropper-wrapper">
143-
<img class="tw-hidden" id="cropper-source">
144-
</div>
145-
</div>
130+
<div class="field tw-pl-4 cropper-panel tw-hidden">
131+
<div>{{ctx.Locale.Tr "settings.cropper_prompt"}}</div>
132+
<div class="cropper-wrapper"><img class="cropper-source" src alt></div>
146133
</div>
147134

148135
<div class="field">

web_src/css/features/cropper.css

+3-33
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,6 @@
11
@import "cropperjs/dist/cropper.css";
22

3-
.cropper-panel {
4-
display: flex;
5-
flex-wrap: wrap;
6-
column-gap: 10px;
7-
}
8-
9-
.cropper-panel h3 {
10-
word-break: keep-all;
11-
}
12-
13-
.cropper-panel #cropper-result {
14-
overflow: hidden;
15-
width: 256px;
16-
height: 256px;
17-
max-width: 256px;
18-
max-height: 256px;
19-
}
20-
21-
.cropper-panel .cropper-editor {
22-
flex: 1;
23-
min-width: 300px;
24-
max-width: 100%;
25-
overflow: hidden;
26-
}
27-
28-
.cropper-panel .cropper-editor >div {
29-
display: flex;
30-
column-gap: 10px;
31-
}
32-
33-
.cropper-panel .cropper-editor .cropper-wrapper {
34-
height: 600px;
35-
max-height: 600px;
3+
.page-content.user.profile .cropper-panel .cropper-wrapper {
4+
max-width: 400px;
5+
max-height: 400px;
366
}

web_src/js/features/comp/Cropper.ts

+28-37
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,39 @@
11
import {showElem} from '../../utils/dom.ts';
22

3-
export async function initCompCropper() {
4-
const cropperContainer = document.querySelector('#cropper-panel');
5-
if (!cropperContainer) {
6-
return;
7-
}
3+
type CropperOpts = {
4+
container: HTMLElement,
5+
imageSource: HTMLImageElement,
6+
fileInput: HTMLInputElement,
7+
}
88

9+
export async function initCompCropper({container, fileInput, imageSource}:CropperOpts) {
910
const {default: Cropper} = await import(/* webpackChunkName: "cropperjs" */'cropperjs');
10-
11-
const source = document.querySelector('#cropper-source');
12-
const result = document.querySelector('#cropper-result');
13-
const input = document.querySelector('#new-avatar');
14-
15-
const done = function (url: string, filename: string): void {
16-
source.src = url;
17-
result.src = url;
18-
19-
if (input._cropper) {
20-
input._cropper.replace(url);
21-
} else {
22-
input._cropper = new Cropper(source, {
23-
aspectRatio: 1,
24-
viewMode: 1,
25-
autoCrop: false,
26-
crop() {
27-
const canvas = input._cropper.getCroppedCanvas();
28-
result.src = canvas.toDataURL();
29-
canvas.toBlob((blob) => {
30-
const file = new File([blob], filename, {type: 'image/png', lastModified: Date.now()});
31-
const container = new DataTransfer();
32-
container.items.add(file);
33-
input.files = container.files;
34-
});
35-
},
11+
let currentFileName = '', currentFileLastModified = 0;
12+
const cropper = new Cropper(imageSource, {
13+
aspectRatio: 1,
14+
viewMode: 2,
15+
autoCrop: false,
16+
crop() {
17+
const canvas = cropper.getCroppedCanvas();
18+
canvas.toBlob((blob) => {
19+
const croppedFileName = currentFileName.replace(/\.[^.]{3,4}$/, '.png');
20+
const croppedFile = new File([blob], croppedFileName, {type: 'image/png', lastModified: currentFileLastModified});
21+
const dataTransfer = new DataTransfer();
22+
dataTransfer.items.add(croppedFile);
23+
fileInput.files = dataTransfer.files;
3624
});
37-
}
38-
showElem(cropperContainer);
39-
};
25+
},
26+
});
4027

41-
input.addEventListener('change', (e: Event & {target: HTMLInputElement}) => {
28+
fileInput.addEventListener('input', (e: Event & {target: HTMLInputElement}) => {
4229
const files = e.target.files;
43-
4430
if (files?.length > 0) {
45-
done(URL.createObjectURL(files[0]), files[0].name);
31+
currentFileName = files[0].name;
32+
currentFileLastModified = files[0].lastModified;
33+
const fileURL = URL.createObjectURL(files[0]);
34+
imageSource.src = fileURL;
35+
cropper.replace(fileURL);
36+
showElem(container);
4637
}
4738
});
4839
}

web_src/js/features/user-settings.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import {hideElem, showElem} from '../utils/dom.ts';
22
import {initCompCropper} from './comp/Cropper.ts';
33

4+
function initUserSettingsAvatarCropper() {
5+
const fileInput = document.querySelector<HTMLInputElement>('#new-avatar');
6+
const container = document.querySelector<HTMLElement>('.user.settings.profile .cropper-panel');
7+
const imageSource = container.querySelector<HTMLImageElement>('.cropper-source');
8+
initCompCropper({container, fileInput, imageSource});
9+
}
10+
411
export function initUserSettings() {
5-
if (!document.querySelectorAll('.user.settings.profile').length) return;
6-
initCompCropper();
12+
if (!document.querySelector('.user.settings.profile')) return;
13+
14+
initUserSettingsAvatarCropper();
715

816
const usernameInput = document.querySelector('#username');
917
if (!usernameInput) return;

0 commit comments

Comments
 (0)