add cropperjs

This commit is contained in:
dog 2024-01-22 14:06:32 +03:00
parent 73854fe5be
commit 3429b36502
4 changed files with 113 additions and 106 deletions

6
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.8.0",
"license": "MIT",
"dependencies": {
"cropperjs": "1.6.1",
"form-data": "4.0.0",
"i18next": "22.4.15",
"i18next-icu": "2.3.0",
@ -7865,6 +7866,11 @@
"dev": true,
"peer": true
},
"node_modules/cropperjs": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.1.tgz",
"integrity": "sha512-F4wsi+XkDHCOMrHMYjrTEE4QBOrsHHN5/2VsVAaRq8P7E5z7xQpT75S+f/9WikmBEailas3+yo+6zPIomW+NOA=="
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",

View File

@ -30,6 +30,7 @@
"typecheck:watch": "tsc --noEmit --watch"
},
"dependencies": {
"cropperjs": "1.6.1",
"form-data": "4.0.0",
"i18next": "22.4.15",
"i18next-icu": "2.3.0",

View File

@ -1 +1,105 @@
export { ImageCropper } from './ImageCropper'
import { createSignal, Show } from 'solid-js'
import { createStore } from 'solid-js/store'
import Cropper from 'cropperjs'
import styles from './ImageCropper.module.scss'
export const ImageCropper = (props) => {
let cropImage
const [state, setState] = createStore({
error: null,
loading: false,
file: {},
croppedImage: null,
}),
[dropZoneActive, setDropZoneActive] = createSignal(false),
[uploading, setUploading] = createSignal(false),
[preview, setPreview] = createSignal(null),
[cropper, setCropper] = createSignal(null),
noPropagate = (e) => {
e.preventDefault()
},
uploadFile = async (file) => {
if (!file) return
setUploading(true)
setState('loading', true)
setState('file', file)
try {
const reader = new FileReader()
reader.onload = (e) => {
setPreview(e.target.result)
setCropper(
new Cropper(cropImage, {
aspectRatio: 1 / 1,
viewMode: 1,
rotatable: false,
}),
)
}
reader.readAsDataURL(file)
} catch (e) {
console.error('upload failed', e)
const message = e instanceof Error ? e.message : String(e)
setState('error', message)
}
setState('loading', false)
setUploading(false)
},
handleFileDrop = async (e) => {
e.preventDefault()
setDropZoneActive(false)
uploadFile(e.dataTransfer.files[0])
},
handleFileInput = async (e) => {
e.preventDefault()
uploadFile(e.currentTarget.files[0])
}
return (
<>
<Show when={preview() !== null}>
<div>
<div>
<img ref={cropImage} src={preview()} alt="cropper" class="block max-w-full h-96 w-96" />
</div>
<button
type="button"
class="inline-flex items-center gap-x-1.5 rounded-md bg-indigo-600 py-2 px-3 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 mt-2"
onClick={() => {
setState('croppedImage', cropper().getCroppedCanvas().toDataURL(state.file.type))
props.saveImage(state)
}}
>
Save
</button>
</div>
</Show>
<Show when={preview() === null}>
<form class="min-h-96 min-w-96">
<div
id="dropzone"
class={`${dropZoneActive() ? 'bg-green-100' : ''} ${
uploading() && 'opacity-50'
} place-content-center place-items-center h-96 w-96 border-2 border-gray-300 border-dashed rounded-md sm:flex p-2 m-2`}
onDragEnter={() => (uploading() ? undefined : setDropZoneActive(true))}
onDragLeave={() => setDropZoneActive(false)}
onDragOver={noPropagate}
onDrop={(event) => (uploading() ? noPropagate(event) : handleFileDrop(event))}
>
<div class="">upload</div>
<input
id="image-upload"
name="file"
type="file"
disabled={uploading()}
multiple={false}
onInput={handleFileInput}
class="sr-only"
/>
</div>
<div class="h-8" />
</form>
</Show>
</>
)
}

View File

@ -1,105 +1 @@
import { createSignal, Show } from 'solid-js'
import { createStore } from 'solid-js/store'
import Cropper from 'cropperjs'
import styles from './ImageCropper.module.scss'
export default function ImageCropper(props) {
let cropImage
const [state, setState] = createStore({
error: null,
loading: false,
file: {},
croppedImage: null,
}),
[dropZoneActive, setDropZoneActive] = createSignal(false),
[uploading, setUploading] = createSignal(false),
[preview, setPreview] = createSignal(null),
[cropper, setCropper] = createSignal(null),
noPropagate = (e) => {
e.preventDefault()
},
uploadFile = async (file) => {
if (!file) return
setUploading(true)
setState('loading', true)
setState('file', file)
try {
const reader = new FileReader()
reader.onload = (e) => {
setPreview(e.target.result)
setCropper(
new Cropper(cropImage, {
aspectRatio: 1 / 1,
viewMode: 1,
rotatable: false,
}),
)
}
reader.readAsDataURL(file)
} catch (e) {
console.error('upload failed', e)
const message = e instanceof Error ? e.message : String(e)
setState('error', message)
}
setState('loading', false)
setUploading(false)
},
handleFileDrop = async (e) => {
e.preventDefault()
setDropZoneActive(false)
uploadFile(e.dataTransfer.files[0])
},
handleFileInput = async (e) => {
e.preventDefault()
uploadFile(e.currentTarget.files[0])
}
return (
<>
<Show when={preview() !== null}>
<div>
<div>
<img ref={cropImage} src={preview()} alt="cropper" class="block max-w-full h-96 w-96" />
</div>
<button
type="button"
class="inline-flex items-center gap-x-1.5 rounded-md bg-indigo-600 py-2 px-3 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 mt-2"
onClick={() => {
setState('croppedImage', cropper().getCroppedCanvas().toDataURL(state.file.type))
props.saveImage(state)
}}
>
Save
</button>
</div>
</Show>
<Show when={preview() === null}>
<form class="min-h-96 min-w-96">
<div
id="dropzone"
class={`${dropZoneActive() ? 'bg-green-100' : ''} ${
uploading() && 'opacity-50'
} place-content-center place-items-center h-96 w-96 border-2 border-gray-300 border-dashed rounded-md sm:flex p-2 m-2`}
onDragEnter={() => (uploading() ? undefined : setDropZoneActive(true))}
onDragLeave={() => setDropZoneActive(false)}
onDragOver={noPropagate}
onDrop={(event) => (uploading() ? noPropagate(event) : handleFileDrop(event))}
>
<div class="">upload</div>
<input
id="image-upload"
name="file"
type="file"
disabled={uploading()}
multiple={false}
onInput={handleFileInput}
class="sr-only"
/>
</div>
<div class="h-8" />
</form>
</Show>
</>
)
}
export { ImageCropper } from './ImageCropper'