diff --git a/package-lock.json b/package-lock.json index 005c2e5fb5..e8c9d89e43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.0.1", "clippie": "4.1.3", + "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", "dropzone": "6.0.0-beta.2", @@ -6787,6 +6788,12 @@ } } }, + "node_modules/cropperjs": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.2.tgz", + "integrity": "sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", diff --git a/package.json b/package.json index c65f0617d8..a962c72c80 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.0.1", "clippie": "4.1.3", + "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", "dropzone": "6.0.0-beta.2", diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 9c7e2de218..829dfe97ff 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -127,6 +127,21 @@ + +
diff --git a/web_src/css/features/cropper.css b/web_src/css/features/cropper.css new file mode 100644 index 0000000000..74cd9a2808 --- /dev/null +++ b/web_src/css/features/cropper.css @@ -0,0 +1,22 @@ +@import "cropperjs/dist/cropper.css"; + +.cropper { + display: flex; + column-gap: 10px; +} + +.hidden { + display: none; +} + +.editor { + flex: 1; +} + +#result { + overflow: hidden; + width: 256px; + height: 256px; + max-width: 256px; + max-height: 256px; +} diff --git a/web_src/css/index.css b/web_src/css/index.css index 817f6997da..174a4a9cbc 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -40,6 +40,7 @@ @import "./features/codeeditor.css"; @import "./features/projects.css"; @import "./features/tribute.css"; +@import "./features/cropper.css"; @import "./features/console.css"; @import "./markup/content.css"; diff --git a/web_src/js/features/comp/Cropper.ts b/web_src/js/features/comp/Cropper.ts new file mode 100644 index 0000000000..5f6bed27cb --- /dev/null +++ b/web_src/js/features/comp/Cropper.ts @@ -0,0 +1,52 @@ +import Cropper from 'cropperjs'; + +export function initCompCropper() { + if (!document.querySelector('#cropper')) { + return; + } + + let filename; + const image = document.querySelector('#image'); + const result = document.querySelector('#result'); + const input = document.querySelector('#new-avatar'); + + const done = function (url) { + image.src = url; + + const cropper = new Cropper(image, { + aspectRatio: 1, + viewMode: 1, + crop() { + const canvas = cropper.getCroppedCanvas(); + result.src = canvas.toDataURL(); + canvas.toBlob((blob) => { + const file = new File([blob], filename, {type: 'image/jpeg', lastModified: Date.now()}); + const container = new DataTransfer(); + container.items.add(file); + input.files = container.files; + }); + }, + }); + document.querySelector('#cropper').classList.remove('hidden'); + }; + + input.addEventListener('change', (e) => { + const files = e.target.files; + + let reader; + let file; + if (files && files.length > 0) { + file = files[0]; + filename = file.name; + if (URL) { + done(URL.createObjectURL(file)); + } else if (FileReader) { + reader = new FileReader(); + reader.addEventListener('load', () => { + done(reader.result); + }); + reader.readAsDataURL(file); + } + } + }); +} diff --git a/web_src/js/index.ts b/web_src/js/index.ts index eeead37333..dcd2549554 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -49,6 +49,7 @@ import {initRepoRelease, initRepoReleaseNew} from './features/repo-release.ts'; import {initRepoEditor} from './features/repo-editor.ts'; import {initCompSearchUserBox} from './features/comp/SearchUserBox.ts'; import {initInstall} from './features/install.ts'; +import {initCompCropper} from './features/comp/Cropper.ts'; import {initCompWebHookEditor} from './features/comp/WebHookEditor.ts'; import {initRepoBranchButton} from './features/repo-branch.ts'; import {initCommonOrganization} from './features/common-organization.ts'; @@ -137,6 +138,7 @@ onDomReady(() => { initCompSearchUserBox, initCompWebHookEditor, + initCompCropper, initInstall,