Profile picture frontend + backend
This commit is contained in:
parent
76cd92ee0e
commit
59818eb321
57
Core/API/Parameter/FloatType.class.php
Normal file
57
Core/API/Parameter/FloatType.class.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\API\Parameter;
|
||||||
|
|
||||||
|
class FloatType extends Parameter {
|
||||||
|
|
||||||
|
public float $minValue;
|
||||||
|
public float $maxValue;
|
||||||
|
public function __construct(string $name, float $minValue = PHP_FLOAT_MIN, float $maxValue = PHP_FLOAT_MAX,
|
||||||
|
bool $optional = FALSE, ?float $defaultValue = NULL, ?array $choices = NULL) {
|
||||||
|
$this->minValue = $minValue;
|
||||||
|
$this->maxValue = $maxValue;
|
||||||
|
parent::__construct($name, Parameter::TYPE_FLOAT, $optional, $defaultValue, $choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseParam($value): bool {
|
||||||
|
if (!parent::parseParam($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->value = $value;
|
||||||
|
if ($this->value < $this->minValue || $this->value > $this->maxValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeName(): string {
|
||||||
|
$typeName = parent::getTypeName();
|
||||||
|
$hasMin = $this->minValue > PHP_FLOAT_MIN;
|
||||||
|
$hasMax = $this->maxValue < PHP_FLOAT_MAX;
|
||||||
|
|
||||||
|
if ($hasMin || $hasMax) {
|
||||||
|
if ($hasMin && $hasMax) {
|
||||||
|
$typeName .= " ($this->minValue - $this->maxValue)";
|
||||||
|
} else if ($hasMin) {
|
||||||
|
$typeName .= " (> $this->minValue)";
|
||||||
|
} else if ($hasMax) {
|
||||||
|
$typeName .= " (< $this->maxValue)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString(): string {
|
||||||
|
$typeName = $this->getTypeName();
|
||||||
|
$str = "$typeName $this->name";
|
||||||
|
$defaultValue = (is_null($this->value) ? 'NULL' : $this->value);
|
||||||
|
if ($this->optional) {
|
||||||
|
$str = "[$str = $defaultValue]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
}
|
@ -121,6 +121,8 @@ namespace Core\API {
|
|||||||
namespace Core\API\User {
|
namespace Core\API\User {
|
||||||
|
|
||||||
use Core\API\Parameter\ArrayType;
|
use Core\API\Parameter\ArrayType;
|
||||||
|
use Core\API\Parameter\FloatType;
|
||||||
|
use Core\API\Parameter\IntegerType;
|
||||||
use Core\API\Parameter\Parameter;
|
use Core\API\Parameter\Parameter;
|
||||||
use Core\API\Parameter\StringType;
|
use Core\API\Parameter\StringType;
|
||||||
use Core\API\Template\Render;
|
use Core\API\Template\Render;
|
||||||
@ -1311,10 +1313,15 @@ namespace Core\API\User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UploadPicture extends UserAPI {
|
class UploadPicture extends UserAPI {
|
||||||
|
|
||||||
|
const MIN_SIZE = 150;
|
||||||
|
const MAX_SIZE = 800;
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
public function __construct(Context $context, bool $externalCall = false) {
|
||||||
// TODO: we should optimize the process here, we need an offset and size parameter to get a quadratic crop of the uploaded image
|
|
||||||
parent::__construct($context, $externalCall, [
|
parent::__construct($context, $externalCall, [
|
||||||
"scale" => new Parameter("scale", Parameter::TYPE_FLOAT, true, NULL),
|
"x" => new FloatType("x", 0, PHP_FLOAT_MAX, true, NULL),
|
||||||
|
"y" => new FloatType("y", 0, PHP_FLOAT_MAX, true, NULL),
|
||||||
|
"size" => new FloatType("size", self::MIN_SIZE, self::MAX_SIZE, true, NULL),
|
||||||
]);
|
]);
|
||||||
$this->loginRequired = true;
|
$this->loginRequired = true;
|
||||||
$this->forbidMethod("GET");
|
$this->forbidMethod("GET");
|
||||||
@ -1325,64 +1332,31 @@ namespace Core\API\User {
|
|||||||
*/
|
*/
|
||||||
protected function onTransform(\Imagick $im, $uploadDir): bool|string {
|
protected function onTransform(\Imagick $im, $uploadDir): bool|string {
|
||||||
|
|
||||||
$minSize = 75;
|
|
||||||
$maxSize = 500;
|
|
||||||
|
|
||||||
$width = $im->getImageWidth();
|
$width = $im->getImageWidth();
|
||||||
$height = $im->getImageHeight();
|
$height = $im->getImageHeight();
|
||||||
$doResize = false;
|
$maxPossibleSize = min($width, $height);
|
||||||
|
|
||||||
if ($width < $minSize || $height < $minSize) {
|
$cropX = $this->getParam("x");
|
||||||
if ($width < $height) {
|
$cropY = $this->getParam("y");
|
||||||
$newWidth = $minSize;
|
$cropSize = $this->getParam("size") ?? $maxPossibleSize;
|
||||||
$newHeight = intval(($minSize / $width) * $height);
|
|
||||||
} else {
|
if ($maxPossibleSize < self::MIN_SIZE) {
|
||||||
$newHeight = $minSize;
|
return $this->createError("Image must be at least " . self::MIN_SIZE . "x" . self::MIN_SIZE);
|
||||||
$newWidth = intval(($minSize / $height) * $width);
|
} else if ($cropSize > self::MAX_SIZE) {
|
||||||
|
return $this->createError("Crop must be at most " . self::MAX_SIZE . "x" . self::MAX_SIZE);
|
||||||
|
} else if ($cropSize > $maxPossibleSize) {
|
||||||
|
return $this->createError("Invalid crop size");
|
||||||
}
|
}
|
||||||
|
|
||||||
$doResize = true;
|
if ($cropX === null) {
|
||||||
} else if ($width > $maxSize || $height > $maxSize) {
|
$cropX = ($width > $height) ? ($width - $height) / 2 : 0;
|
||||||
if ($width > $height) {
|
|
||||||
$newWidth = $maxSize;
|
|
||||||
$newHeight = intval($height * ($maxSize / $width));
|
|
||||||
} else {
|
|
||||||
$newHeight = $maxSize;
|
|
||||||
$newWidth = intval($width * ($maxSize / $height));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$doResize = true;
|
if ($cropY === null) {
|
||||||
} else {
|
$cropY = ($height > $width) ? ($height - $width) / 2 : 0;
|
||||||
$newWidth = $width;
|
|
||||||
$newHeight = $height;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($width < $minSize || $height < $minSize) {
|
|
||||||
return $this->createError("Error processing image. Bad dimensions.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($doResize) {
|
|
||||||
$width = $newWidth;
|
|
||||||
$height = $newHeight;
|
|
||||||
$im->resizeImage($width, $height, \Imagick::FILTER_SINC, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = $this->getParam("size");
|
|
||||||
if (is_null($size)) {
|
|
||||||
$size = min($width, $height);
|
|
||||||
}
|
|
||||||
|
|
||||||
$offset = [$this->getParam("offsetX"), $this->getParam("offsetY")];
|
|
||||||
if ($size < $minSize or $size > $maxSize) {
|
|
||||||
return $this->createError("Invalid size. Must be in range of $minSize-$maxSize.");
|
|
||||||
}/* else if ($offset[0] < 0 || $offset[1] < 0 || $offset[0]+$size > $width || $offset[1]+$size > $height) {
|
|
||||||
return $this->createError("Offsets out of bounds.");
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if ($offset[0] !== 0 || $offset[1] !== 0 || $size !== $width || $size !== $height) {
|
|
||||||
$im->cropImage($size, $size, $offset[0], $offset[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$im->cropImage($cropSize, $cropSize, $cropX, $cropY);
|
||||||
$fileName = uuidv4() . ".jpg";
|
$fileName = uuidv4() . ".jpg";
|
||||||
$im->writeImage("$uploadDir/$fileName");
|
$im->writeImage("$uploadDir/$fileName");
|
||||||
$im->destroy();
|
$im->destroy();
|
||||||
|
@ -58,8 +58,13 @@ return [
|
|||||||
"user_list_placeholder" => "Keine Benutzer zum Anzeigen",
|
"user_list_placeholder" => "Keine Benutzer zum Anzeigen",
|
||||||
|
|
||||||
# profile picture
|
# profile picture
|
||||||
|
"remove_picture" => "Profilbild entfernen",
|
||||||
|
"remove_picture_text" => "Möchten Sie wirklich Ihr aktuelles Profilbild entfernen?",
|
||||||
"change_picture" => "Profilbild ändern",
|
"change_picture" => "Profilbild ändern",
|
||||||
"profile_picture_of" => "Profilbild von",
|
"profile_picture_of" => "Profilbild von",
|
||||||
|
"change_picture_title" => "Profilbild ändern",
|
||||||
|
"change_picture_text" => "Wähle ein Profilbild aus und passe die sichtbare Fläche an.",
|
||||||
|
"profile_picture_invalid_dimensions" => "Das Profilbild muss mindestens 150x150px groß sein.",
|
||||||
|
|
||||||
# dialogs
|
# dialogs
|
||||||
"fetch_group_members_error" => "Fehler beim Holen der Gruppenmitglieder",
|
"fetch_group_members_error" => "Fehler beim Holen der Gruppenmitglieder",
|
||||||
|
@ -60,8 +60,13 @@ return [
|
|||||||
"user_list_placeholder" => "No users to display",
|
"user_list_placeholder" => "No users to display",
|
||||||
|
|
||||||
# profile picture
|
# profile picture
|
||||||
|
"remove_picture" => "Remove profile picture",
|
||||||
|
"remove_picture_text" => "Do you really want to remove your current profile picture?",
|
||||||
"change_picture" => "Change profile picture",
|
"change_picture" => "Change profile picture",
|
||||||
"profile_picture_of" => "Profile Picture of",
|
"profile_picture_of" => "Profile Picture of",
|
||||||
|
"change_picture_title" => "Change Profile picture",
|
||||||
|
"change_picture_text" => "Choose a profile picture and adjust the visible area.",
|
||||||
|
"profile_picture_invalid_dimensions" => "The profile picture must have at least a size of 150x150.",
|
||||||
|
|
||||||
# dialogs
|
# dialogs
|
||||||
"fetch_group_members_error" => "Error fetching group members",
|
"fetch_group_members_error" => "Error fetching group members",
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
"name": "admin-panel",
|
"name": "admin-panel",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shared": "link:../shared",
|
"react": "^18.2.0",
|
||||||
"react": "^18.2.0"
|
"react-image-crop": "^11.0.5",
|
||||||
|
"shared": "link:../shared"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "HTTPS=true react-app-rewired start"
|
"dev": "HTTPS=true react-app-rewired start"
|
||||||
|
@ -1,16 +1,32 @@
|
|||||||
import {Box, Button, CircularProgress, Slider, styled} from "@mui/material";
|
import {
|
||||||
import {useCallback, useContext, useRef, useState} from "react";
|
Box,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
Dialog, DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
styled, TextField
|
||||||
|
} from "@mui/material";
|
||||||
|
import {useCallback, useContext, useState} from "react";
|
||||||
import {LocaleContext} from "shared/locale";
|
import {LocaleContext} from "shared/locale";
|
||||||
import PreviewProfilePicture from "./preview-picture";
|
import {Delete, Edit, Upload} from "@mui/icons-material";
|
||||||
import {Delete, Edit} from "@mui/icons-material";
|
|
||||||
import ProfilePicture from "shared/elements/profile-picture";
|
import ProfilePicture from "shared/elements/profile-picture";
|
||||||
|
import ReactCrop from 'react-image-crop'
|
||||||
|
|
||||||
|
import 'react-image-crop/dist/ReactCrop.css';
|
||||||
|
|
||||||
const ProfilePictureBox = styled(Box)((props) => ({
|
const ProfilePictureBox = styled(Box)((props) => ({
|
||||||
padding: props.theme.spacing(2),
|
padding: props.theme.spacing(1),
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateRows: "auto 60px",
|
gridTemplateRows: "auto calc(110px - " + props.theme.spacing(1) + ")",
|
||||||
gridGap: props.theme.spacing(2),
|
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyItems: "center",
|
||||||
|
"& img": {
|
||||||
|
maxHeight: 150,
|
||||||
|
width: "auto",
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const VerticalButtonBar = styled(Box)((props) => ({
|
const VerticalButtonBar = styled(Box)((props) => ({
|
||||||
@ -24,67 +40,46 @@ export default function EditProfilePicture(props) {
|
|||||||
|
|
||||||
// meta
|
// meta
|
||||||
const {translate: L} = useContext(LocaleContext);
|
const {translate: L} = useContext(LocaleContext);
|
||||||
// const [scale, setScale] = useState(100);
|
|
||||||
const scale = useRef(100);
|
|
||||||
const {api, showDialog, setProfile, profile, setDialogData, ...other} = props
|
const {api, showDialog, setProfile, profile, setDialogData, ...other} = props
|
||||||
|
|
||||||
const onUploadPicture = useCallback((data) => {
|
// data
|
||||||
api.uploadPicture(data, scale.current / 100.0).then((res) => {
|
const [crop, setCrop] = useState({ unit: 'px' });
|
||||||
if (!res.success) {
|
const [image, setImage] = useState({ loading: false, data: null, file: null });
|
||||||
showDialog(res.msg, L("Error uploading profile picture"));
|
|
||||||
} else {
|
// ui
|
||||||
|
const [isUploading, setUploading] = useState(false);
|
||||||
|
|
||||||
|
const onCloseDialog = useCallback((event = null, reason = null) => {
|
||||||
|
if (!reason || !["backdropClick", "escapeKeyDown"].includes(reason)) {
|
||||||
|
setImage({loading: false, data: null, file: null});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onUploadPicture = useCallback(() => {
|
||||||
|
if (!isUploading) {
|
||||||
|
setUploading(true);
|
||||||
|
api.uploadPicture(image.file, crop.width, crop.x, crop.y).then(res => {
|
||||||
|
setUploading(false);
|
||||||
|
if (res.success) {
|
||||||
|
onCloseDialog();
|
||||||
setProfile({...profile, profilePicture: res.profilePicture});
|
setProfile({...profile, profilePicture: res.profilePicture});
|
||||||
|
} else {
|
||||||
|
showDialog(res.msg, L("account.upload_profile_picture_error"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [api, scale.current, showDialog, profile]);
|
}
|
||||||
|
}, [api, image, crop, isUploading, showDialog, profile, onCloseDialog]);
|
||||||
|
|
||||||
const onRemoveImage = useCallback(() => {
|
const onRemoveImage = useCallback(() => {
|
||||||
api.removePicture().then((res) => {
|
api.removePicture().then((res) => {
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
showDialog(res.msg, L("Error removing profile picture"));
|
showDialog(res.msg, L("account.remove_profile_picture_error"));
|
||||||
} else {
|
} else {
|
||||||
setProfile({...profile, profilePicture: null});
|
setProfile({...profile, profilePicture: null});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [api, showDialog, profile]);
|
}, [api, showDialog, profile]);
|
||||||
|
|
||||||
const onOpenDialog = useCallback((file = null, data = null) => {
|
|
||||||
|
|
||||||
let img = null;
|
|
||||||
if (data !== null) {
|
|
||||||
img = new Image();
|
|
||||||
img.src = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDialogData({
|
|
||||||
show: true,
|
|
||||||
title: L("account.change_picture_title"),
|
|
||||||
text: L("account.change_picture_text"),
|
|
||||||
options: data === null ? [L("general.cancel")] : [L("general.apply"), L("general.cancel")],
|
|
||||||
inputs: data === null ? [{
|
|
||||||
key: "pfp-loading",
|
|
||||||
type: "custom",
|
|
||||||
element: CircularProgress,
|
|
||||||
}] : [
|
|
||||||
{
|
|
||||||
key: "pfp-preview",
|
|
||||||
type: "custom",
|
|
||||||
element: PreviewProfilePicture,
|
|
||||||
img: img,
|
|
||||||
scale: scale.current,
|
|
||||||
setScale: (v) => scale.current = v,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
onOption: (option) => {
|
|
||||||
if (option === 1 && file) {
|
|
||||||
onUploadPicture(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// scale.current = 100;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [setDialogData, onUploadPicture]);
|
|
||||||
|
|
||||||
const onSelectImage = useCallback(() => {
|
const onSelectImage = useCallback(() => {
|
||||||
let fileInput = document.createElement("input");
|
let fileInput = document.createElement("input");
|
||||||
fileInput.type = "file";
|
fileInput.type = "file";
|
||||||
@ -94,18 +89,40 @@ export default function EditProfilePicture(props) {
|
|||||||
if (file) {
|
if (file) {
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
onOpenDialog(file, e.target.result);
|
const imageData = e.target.result;
|
||||||
|
const img = new Image();
|
||||||
|
img.src = imageData;
|
||||||
|
img.onload = () => {
|
||||||
|
let croppedSize;
|
||||||
|
if (img.width > img.height) {
|
||||||
|
croppedSize = Math.min(800, img.height);
|
||||||
|
setCrop({ x: (img.width - img.height) / 2, y: 0, unit: "px", width: croppedSize, height: croppedSize });
|
||||||
|
} else if (img.width < img.height) {
|
||||||
|
croppedSize = Math.min(800, img.width);
|
||||||
|
setCrop({ x: 0, y: (img.height - img.width) / 2, unit: "px", width: croppedSize, height: croppedSize });
|
||||||
|
} else {
|
||||||
|
croppedSize = Math.min(800, img.width);
|
||||||
|
setCrop({ x: 0, y: 0, unit: "px", width: croppedSize, height: croppedSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenDialog();
|
if (croppedSize < 150) {
|
||||||
|
setImage({ loading: false, file: null, data: null });
|
||||||
|
showDialog(L("account.profile_picture_invalid_dimensions"), L("general.error"));
|
||||||
|
} else {
|
||||||
|
setImage({ loading: false, file: file, data: imageData });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setImage({ file: null, data: null, loading: true });
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
}, [onOpenDialog]);
|
}, [showDialog]);
|
||||||
|
|
||||||
|
return <>
|
||||||
return <ProfilePictureBox {...other}>
|
<ProfilePictureBox {...other}>
|
||||||
<ProfilePicture user={profile} onClick={onSelectImage} />
|
<ProfilePicture user={profile} onClick={onSelectImage} />
|
||||||
<VerticalButtonBar>
|
<VerticalButtonBar>
|
||||||
<Button variant="outlined" size="small"
|
<Button variant="outlined" size="small"
|
||||||
@ -118,9 +135,9 @@ export default function EditProfilePicture(props) {
|
|||||||
startIcon={<Delete />} color={"error"}
|
startIcon={<Delete />} color={"error"}
|
||||||
onClick={() => setDialogData({
|
onClick={() => setDialogData({
|
||||||
show: true,
|
show: true,
|
||||||
title: L("account.picture_remove_title"),
|
title: L("account.remove_picture"),
|
||||||
message: L("account.picture_remove_text"),
|
message: L("account.remove_picture_text"),
|
||||||
options: [L("general.confirm"), L("general.cancel")],
|
options: [L("general.cancel"), L("general.confirm")],
|
||||||
onOption: (option) => option === 1 ? onRemoveImage() : true
|
onOption: (option) => option === 1 ? onRemoveImage() : true
|
||||||
})}>
|
})}>
|
||||||
{L("account.remove_picture")}
|
{L("account.remove_picture")}
|
||||||
@ -128,4 +145,35 @@ export default function EditProfilePicture(props) {
|
|||||||
}
|
}
|
||||||
</VerticalButtonBar>
|
</VerticalButtonBar>
|
||||||
</ProfilePictureBox>
|
</ProfilePictureBox>
|
||||||
|
<Dialog open={image.loading || image.data !== null} maxWidth={"lg"}
|
||||||
|
onClose={onCloseDialog}>
|
||||||
|
<DialogTitle>
|
||||||
|
{L("account.change_picture_title")}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
{L("account.change_picture_text")}
|
||||||
|
</DialogContentText>
|
||||||
|
{image.data ?
|
||||||
|
<ReactCrop onChange={c => setCrop(c)} crop={crop} keepSelection={true}
|
||||||
|
aspect={1} circularCrop={true} disabled={isUploading}
|
||||||
|
maxWidth={800} maxHeight={800} minWidth={150} minHeight={150}>
|
||||||
|
<img src={image?.data} alt={"preview"} />
|
||||||
|
</ReactCrop> :
|
||||||
|
<CircularProgress />
|
||||||
|
}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant={"outlined"} color={"error"} onClick={onCloseDialog}
|
||||||
|
disabled={isUploading}>
|
||||||
|
{L("general.cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button variant={"outlined"} type={"submit"} onClick={onUploadPicture}
|
||||||
|
disabled={isUploading}
|
||||||
|
startIcon={isUploading ? <CircularProgress size={12} /> : <Upload />}>
|
||||||
|
{L(isUploading ? "general.uploading" : "general.submit")}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
}
|
}
|
@ -187,9 +187,11 @@ export default class API {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadPicture(file, scale=1.0) {
|
async uploadPicture(file, size, x = 0, y = 0) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("scale", scale);
|
formData.append("size", size);
|
||||||
|
formData.append("x", x);
|
||||||
|
formData.append("y", y);
|
||||||
formData.append("picture", file, file.name);
|
formData.append("picture", file, file.name);
|
||||||
let res = await this.apiCall("user/uploadPicture", formData);
|
let res = await this.apiCall("user/uploadPicture", formData);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
|
@ -13,6 +13,8 @@ const PicturePlaceholderBox = styled(Box)((props) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
background: "radial-gradient(circle closest-side, gray 98%, transparent 100%);",
|
background: "radial-gradient(circle closest-side, gray 98%, transparent 100%);",
|
||||||
containerType: "inline-size",
|
containerType: "inline-size",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
"& > span": {
|
"& > span": {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontSize: "30cqw",
|
fontSize: "30cqw",
|
||||||
|
@ -9112,6 +9112,11 @@ react-error-overlay@^6.0.11:
|
|||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
|
||||||
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
|
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
|
||||||
|
|
||||||
|
react-image-crop@^11.0.5:
|
||||||
|
version "11.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-image-crop/-/react-image-crop-11.0.5.tgz#c7abcf9cae28305d253d55d481158a594a937867"
|
||||||
|
integrity sha512-A/Y/kspOzki1zDL/bSgwWIY1X3CQ9F1QwpdnncWLBVAktnKfAZDIQnWmjXzuzEjZHDMsBlArytIcPBVi6DNklg==
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
|
Loading…
Reference in New Issue
Block a user