Merge branch 'master' of github.com:ONLYOFFICE/CommunityServer-AspNetCore
This commit is contained in:
commit
396b08527f
@ -52,12 +52,24 @@ namespace ASC.Api.Core.Auth
|
||||
case ConfirmType.EmailChange:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + SecurityContext.CurrentAccount.ID, key, validInterval);
|
||||
break;
|
||||
case ConfirmType.PasswordChange:
|
||||
var userHash = Request.TryGetValue("p", out var p) && p == "1";
|
||||
var hash = string.Empty;
|
||||
|
||||
if (userHash)
|
||||
{
|
||||
var tenantId = CoreContext.TenantManager.GetCurrentTenant().TenantId;
|
||||
hash = CoreContext.Authentication.GetUserPasswordHash(tenantId, CoreContext.UserManager.GetUserByEmail(tenantId, _email).ID);
|
||||
}
|
||||
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)), key, validInterval);
|
||||
break;
|
||||
default:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type, key, validInterval);
|
||||
break;
|
||||
}
|
||||
|
||||
var claims = new List<Claim>()
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim(ClaimTypes.Role, _type.ToString())
|
||||
};
|
||||
|
@ -102,7 +102,8 @@ namespace ASC.Core.Data
|
||||
var groupQuery = new SqlQuery("core_usergroup cug")
|
||||
.Select("cug.userid")
|
||||
.Where(Exp.EqColumns("cug.tenant", "u.tenant"))
|
||||
.Where(Exp.EqColumns("u.id", "cug.userid"));
|
||||
.Where(Exp.EqColumns("u.id", "cug.userid"))
|
||||
.Where("cug.removed", false);
|
||||
|
||||
foreach (var groups in includeGroups)
|
||||
{
|
||||
|
@ -102,7 +102,7 @@ namespace ASC.Resource.Manager
|
||||
|
||||
using var resXResourceWriter = new ResXResourceWriter(zipFileName);
|
||||
|
||||
foreach (var word in toAdd.Where(r => r != null && !string.IsNullOrEmpty(r.ValueTo)).OrderBy(x => x.Title))
|
||||
foreach (var word in toAdd.Where(r => r != null && (!string.IsNullOrEmpty(r.ValueTo) || language == "Neutral")).OrderBy(x => x.Title))
|
||||
{
|
||||
resXResourceWriter.AddResource(word.Title, word.ValueTo);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { connect } from 'react-redux'
|
||||
import { Avatar, Button, Textarea, toastr, AvatarEditor } from 'asc-web-components'
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts, mapGroupsToGroupSelectorOptions, mapGroupSelectorOptionsToGroups, filterGroupSelectorOptions } from "../../../../../store/people/selectors";
|
||||
import { createProfile, updateAvatar } from '../../../../../store/profile/actions';
|
||||
import { createProfile, loadAvatar } from '../../../../../store/profile/actions';
|
||||
import { MainContainer, AvatarContainer, MainFieldsContainer } from './FormFields/Form'
|
||||
import TextField from './FormFields/TextField'
|
||||
import PasswordField from './FormFields/PasswordField'
|
||||
@ -414,6 +414,6 @@ export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
createProfile,
|
||||
updateAvatar
|
||||
loadAvatar
|
||||
}
|
||||
)(withRouter(withTranslation()(CreateUserForm)));
|
@ -4,7 +4,7 @@ import { connect } from 'react-redux'
|
||||
import { Avatar, Button, Textarea, Text, toastr, ModalDialog, TextInput, AvatarEditor } from 'asc-web-components'
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts, mapGroupsToGroupSelectorOptions, mapGroupSelectorOptionsToGroups, filterGroupSelectorOptions } from "../../../../../store/people/selectors";
|
||||
import { updateProfile, updateAvatar } from '../../../../../store/profile/actions';
|
||||
import { updateProfile, loadAvatar, createThumbnailsAvatar, deleteAvatar } from '../../../../../store/profile/actions';
|
||||
import { sendInstructionsToChangePassword, sendInstructionsToChangeEmail } from "../../../../../store/services/api";
|
||||
import { MainContainer, AvatarContainer, MainFieldsContainer } from './FormFields/Form'
|
||||
import TextField from './FormFields/TextField'
|
||||
@ -46,14 +46,14 @@ class UpdateUserForm extends React.Component {
|
||||
this.openAvatarEditor = this.openAvatarEditor.bind(this);
|
||||
this.onSaveAvatar = this.onSaveAvatar.bind(this);
|
||||
this.onCloseAvatarEditor = this.onCloseAvatarEditor.bind(this);
|
||||
|
||||
|
||||
this.onLoadFileAvatar = this.onLoadFileAvatar.bind(this);
|
||||
|
||||
this.onShowGroupSelector = this.onShowGroupSelector.bind(this);
|
||||
this.onCloseGroupSelector = this.onCloseGroupSelector.bind(this);
|
||||
this.onSearchGroups = this.onSearchGroups.bind(this);
|
||||
this.onSelectGroups = this.onSelectGroups.bind(this);
|
||||
this.onRemoveGroup = this.onRemoveGroup.bind(this);
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@ -87,6 +87,12 @@ class UpdateUserForm extends React.Component {
|
||||
allOptions: allOptions,
|
||||
options: [...allOptions],
|
||||
selected: selected
|
||||
},
|
||||
avatar: {
|
||||
tmpFile:"",
|
||||
image: profile.avatarDefault ? "data:image/png;base64," + profile.avatarDefault : null,
|
||||
defaultWidth: 0,
|
||||
defaultHeight: 0
|
||||
}
|
||||
};
|
||||
|
||||
@ -295,27 +301,86 @@ class UpdateUserForm extends React.Component {
|
||||
}
|
||||
|
||||
openAvatarEditor(){
|
||||
let avatarDefault = this.state.profile.avatarDefault ? "data:image/png;base64," + this.state.profile.avatarDefault : null;
|
||||
let _this = this;
|
||||
if(avatarDefault !== null){
|
||||
let img = new Image();
|
||||
img.onload = function () {
|
||||
_this.setState({
|
||||
avatar:{
|
||||
defaultWidth: img.width,
|
||||
defaultHeight: img.height
|
||||
}
|
||||
})
|
||||
};
|
||||
img.src = avatarDefault;
|
||||
}
|
||||
this.setState({
|
||||
visibleAvatarEditor: true,
|
||||
});
|
||||
}
|
||||
onSaveAvatar(result) {
|
||||
this.props.updateAvatar(this.state.profile.id, result)
|
||||
onLoadFileAvatar(file) {
|
||||
let data = new FormData();
|
||||
let _this = this;
|
||||
data.append("file", file);
|
||||
data.append("Autosave", false);
|
||||
this.props.loadAvatar(this.state.profile.id, data)
|
||||
.then((result) => {
|
||||
let stateCopy = Object.assign({}, this.state);
|
||||
stateCopy.visibleAvatarEditor = false;
|
||||
if(result.data.response.success){
|
||||
stateCopy.profile.avatarMax = result.data.response.data.max;
|
||||
}else{
|
||||
stateCopy.profile.avatarMax = result.data.response.max && result.data.response.max;
|
||||
}
|
||||
toastr.success("Success");
|
||||
this.setState(stateCopy);
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
var stateCopy = Object.assign({}, _this.state);
|
||||
stateCopy.avatar = {
|
||||
tmpFile: result.data.response.data,
|
||||
image: result.data.response.data,
|
||||
defaultWidth: img.width,
|
||||
defaultHeight: img.height
|
||||
}
|
||||
_this.setState(stateCopy);
|
||||
};
|
||||
img.src = result.data.response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message);
|
||||
});
|
||||
}
|
||||
onSaveAvatar(isUpdate, result) {
|
||||
if(isUpdate){
|
||||
this.props.createThumbnailsAvatar(this.state.profile.id, {
|
||||
x: Math.round(result.x*this.state.avatar.defaultWidth - result.width/2),
|
||||
y: Math.round(result.y*this.state.avatar.defaultHeight - result.height/2),
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
tmpFile: this.state.avatar.tmpFile
|
||||
})
|
||||
.then((result) => {
|
||||
if(result.status === 200){
|
||||
let stateCopy = Object.assign({}, this.state);
|
||||
stateCopy.visibleAvatarEditor = false;
|
||||
stateCopy.avatar.tmpFile = '';
|
||||
stateCopy.profile.avatarMax = result.data.response.max + '?_='+Math.floor(Math.random() * Math.floor(10000));
|
||||
toastr.success("Success");
|
||||
this.setState(stateCopy);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message);
|
||||
});
|
||||
}else{
|
||||
this.props.deleteAvatar(this.state.profile.id)
|
||||
.then((result) => {
|
||||
if(result.status === 200){
|
||||
let stateCopy = Object.assign({}, this.state);
|
||||
stateCopy.visibleAvatarEditor = false;
|
||||
stateCopy.profile.avatarMax = result.data.response.big;
|
||||
toastr.success("Success");
|
||||
this.setState(stateCopy);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
onCloseAvatarEditor() {
|
||||
this.setState({
|
||||
visibleAvatarEditor: false,
|
||||
@ -375,11 +440,13 @@ class UpdateUserForm extends React.Component {
|
||||
editLabel={t("EditPhoto")}
|
||||
editAction={this.openAvatarEditor}
|
||||
/>
|
||||
<AvatarEditor
|
||||
image={profile.avatarDefault ? "data:image/png;base64,"+profile.avatarDefault : null}
|
||||
visible={this.state.visibleAvatarEditor}
|
||||
onClose={this.onCloseAvatarEditor}
|
||||
onSave={this.onSaveAvatar} />
|
||||
<AvatarEditor
|
||||
image={this.state.avatar.image}
|
||||
visible={this.state.visibleAvatarEditor}
|
||||
onClose={this.onCloseAvatarEditor}
|
||||
onSave={this.onSaveAvatar}
|
||||
onLoadFile={this.onLoadFileAvatar}
|
||||
chooseFileLabel="Drop files here, or click to select files"/>
|
||||
</AvatarContainer>
|
||||
<MainFieldsContainer>
|
||||
<TextChangeField
|
||||
@ -555,6 +622,8 @@ export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
updateProfile,
|
||||
updateAvatar
|
||||
loadAvatar,
|
||||
deleteAvatar,
|
||||
createThumbnailsAvatar
|
||||
}
|
||||
)(withRouter(withTranslation()(UpdateUserForm)));
|
@ -91,26 +91,36 @@ export function updateProfile(profile) {
|
||||
});
|
||||
};
|
||||
};
|
||||
export function updateAvatar(profileId, images) {
|
||||
export function loadAvatar(profileId, data) {
|
||||
return (dispatch, getState) => {
|
||||
if (images.croppedImage) {
|
||||
return api.updateAvatar(
|
||||
profileId,
|
||||
{
|
||||
autosave: true,
|
||||
base64CroppedImage: images.croppedImage.split(',')[1],
|
||||
base64DefaultImage: images.defaultImage.split(',')[1]
|
||||
}
|
||||
).then(res => {
|
||||
return api.loadAvatar(
|
||||
profileId,
|
||||
data
|
||||
).then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
};
|
||||
};
|
||||
export function createThumbnailsAvatar(profileId, data) {
|
||||
return (dispatch, getState) => {
|
||||
return api.createThumbnailsAvatar(
|
||||
profileId,
|
||||
data
|
||||
).then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
};
|
||||
};
|
||||
export function deleteAvatar(profileId) {
|
||||
return (dispatch, getState) => {
|
||||
return api.deleteAvatar(profileId)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
} else {
|
||||
return api.deleteAvatar(profileId).then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -73,10 +73,15 @@ export function updateUser(data) {
|
||||
? fakeApi.updateUser()
|
||||
: axios.put(`${API_URL}/people/${data.id}`, data);
|
||||
}
|
||||
export function updateAvatar(profileId, data) {
|
||||
export function loadAvatar(profileId, data) {
|
||||
return IS_FAKE
|
||||
? fakeApi.updateAvatar()
|
||||
: axios.post(`${API_URL}/people/${profileId}/photo/cropped`, data);
|
||||
? fakeApi.loadAvatar()
|
||||
: axios.post(`${API_URL}/people/${profileId}/photo`, data);
|
||||
}
|
||||
export function createThumbnailsAvatar(profileId, data) {
|
||||
return IS_FAKE
|
||||
? fakeApi.createThumbnailsAvatar()
|
||||
: axios.post(`${API_URL}/people/${profileId}/photo/thumbnails.json`, data);
|
||||
}
|
||||
export function deleteAvatar(profileId) {
|
||||
|
||||
|
@ -277,7 +277,10 @@ export function updateUser(data) {
|
||||
return fakeResponse(data);
|
||||
}
|
||||
|
||||
export function updateAvatar(data) {
|
||||
export function loadAvatar(data) {
|
||||
return fakeResponse(data);
|
||||
}
|
||||
export function createThumbnailsAvatar(data) {
|
||||
return fakeResponse(data);
|
||||
}
|
||||
export function deleteAvatar(data) {
|
||||
|
@ -588,7 +588,7 @@ namespace ASC.Employee.Core.Controllers
|
||||
|
||||
return new ThumbnailsDataWrapper(Tenant, user.ID);
|
||||
}
|
||||
|
||||
|
||||
public FormFile Base64ToImage(string base64String, string fileName)
|
||||
{
|
||||
byte[] imageBytes = Convert.FromBase64String(base64String);
|
||||
@ -617,7 +617,7 @@ namespace ASC.Employee.Core.Controllers
|
||||
|
||||
SecurityContext.DemandPermissions(Tenant, new UserSecurityProvider(userId), Constants.Action_EditUser);
|
||||
|
||||
var userPhoto = Base64ToImage(model.base64CroppedImage, "userPhoto_"+ userId.ToString());
|
||||
var userPhoto = Base64ToImage(model.base64CroppedImage, "userPhoto_" + userId.ToString());
|
||||
var defaultUserPhoto = Base64ToImage(model.base64DefaultImage, "defaultPhoto" + userId.ToString());
|
||||
|
||||
if (userPhoto.Length > SetupInfo.MaxImageUploadSize)
|
||||
@ -693,9 +693,11 @@ namespace ASC.Employee.Core.Controllers
|
||||
return result;
|
||||
}
|
||||
[Create("{userid}/photo")]
|
||||
public FileUploadResult UploadMemberPhoto(string userid, UploadPhotoModel model)
|
||||
public FileUploadResult UploadMemberPhoto(string userid, IFormCollection model)
|
||||
{
|
||||
var result = new FileUploadResult();
|
||||
bool autosave = Boolean.Parse(model["Autosave"]);
|
||||
|
||||
try
|
||||
{
|
||||
if (model.Files.Count != 0)
|
||||
@ -730,7 +732,7 @@ namespace ASC.Employee.Core.Controllers
|
||||
|
||||
CheckImgFormat(data);
|
||||
|
||||
if (model.Autosave)
|
||||
if (autosave)
|
||||
{
|
||||
if (data.Length > SetupInfo.MaxImageUploadSize)
|
||||
throw new ImageSizeLimitException();
|
||||
@ -867,7 +869,7 @@ namespace ASC.Employee.Core.Controllers
|
||||
}
|
||||
|
||||
[Update("{userid}/password")]
|
||||
[Authorize(AuthenticationSchemes = "confirm", Roles = "EmailChange,Administrators")]
|
||||
[Authorize(AuthenticationSchemes = "confirm", Roles = "PasswordChange,EmailChange,Administrators")]
|
||||
public EmployeeWraperFull ChangeUserPassword(Guid userid, MemberModel memberModel)
|
||||
{
|
||||
ApiContext.AuthByClaim();
|
||||
|
@ -1,7 +1,124 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from "react";
|
||||
import { withRouter } from "react-router";
|
||||
import { connect } from 'react-redux';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import styled from "styled-components";
|
||||
import { Button, TextInput, PageLayout, Text } from "asc-web-components";
|
||||
//import { useTranslation } from "react-i18next";
|
||||
import { welcomePageTitle } from "../../../../helpers/customNames";
|
||||
//import { login } from '../../../../../src/store/auth/actions';
|
||||
|
||||
const changePhoneForm = (props) => {
|
||||
return (<span>{props.location.pathname}</span>);
|
||||
const BodyStyle = styled.div`
|
||||
margin: 70px auto 0 auto;
|
||||
max-width: 432px;
|
||||
|
||||
.edit-header {
|
||||
.header-logo {
|
||||
max-width: 216px;
|
||||
max-height: 35px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
word-wrap: break-word;
|
||||
margin: 8px 0;
|
||||
text-align: left;
|
||||
font-size: 24px;
|
||||
color: #116d9d;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-text {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
|
||||
`;
|
||||
|
||||
const PhoneForm = props => {
|
||||
const { t, currentPhone } = props;
|
||||
|
||||
const [phone, setPhone] = useState(currentPhone);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const subTitleTranslation = `Enter mobile phone number`;
|
||||
const infoTranslation = `Your current mobile phone number`;
|
||||
const subInfoTranslation = `The two-factor authentication is enabled to provide additional portal security.
|
||||
Enter your mobile phone number to continue work on the portal.
|
||||
Mobile phone number must be entered using an international format with country code.`;
|
||||
const phonePlaceholder = `Phone`;
|
||||
const buttonTranslation = `Enter number`;
|
||||
|
||||
const onSubmit = () => {
|
||||
console.log("onSubmit CHANGE");
|
||||
};
|
||||
|
||||
const onKeyPress = target => {
|
||||
if (target.code === "Enter") onSubmit();
|
||||
};
|
||||
|
||||
const simplePhoneMask = new Array(15).fill(/\d/);
|
||||
|
||||
return (
|
||||
<BodyStyle>
|
||||
<div className="edit-header">
|
||||
<img className="header-logo" src="images/dark_general.png" alt="Logo" />
|
||||
<div className="header-title">
|
||||
{t("CustomWelcomePageTitle", { welcomePageTitle })}
|
||||
</div>
|
||||
</div>
|
||||
<Text.Body className="edit-text" isBold fontSize={14}>{subTitleTranslation}</Text.Body>
|
||||
<Text.Body fontSize={13}>{infoTranslation}: <b>+{currentPhone}</b></Text.Body>
|
||||
<Text.Body className="edit-text" fontSize={13}>{subInfoTranslation}</Text.Body>
|
||||
<TextInput
|
||||
id="phone"
|
||||
name="phone"
|
||||
type="text"
|
||||
size="huge"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
autocomple="off"
|
||||
placeholder={phonePlaceholder}
|
||||
onChange={event => {
|
||||
setPhone(event.target.value);
|
||||
onKeyPress(event.target);
|
||||
}}
|
||||
value={phone}
|
||||
hasError={false}
|
||||
isDisabled={isLoading}
|
||||
onKeyDown={event => onKeyPress(event.target)}
|
||||
guide={false}
|
||||
mask={simplePhoneMask}
|
||||
className="edit-input"
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
size="big"
|
||||
tabIndex={3}
|
||||
label={
|
||||
isLoading ? t("LoadingProcessing") : buttonTranslation
|
||||
}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</BodyStyle>
|
||||
);
|
||||
}
|
||||
|
||||
export default changePhoneForm;
|
||||
const ChangePhoneForm = props => {
|
||||
return <PageLayout sectionBodyContent={<PhoneForm {...props} />} />;
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isLoaded: state.auth.isLoaded,
|
||||
currentPhone: state.auth.user.mobilePhone
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(withTranslation()(ChangePhoneForm)));
|
@ -1809,7 +1809,9 @@ asap@~2.0.6:
|
||||
rc-tree "^2.1.2"
|
||||
react-autosize-textarea "^7.0.0"
|
||||
react-avatar-edit "^0.8.3"
|
||||
react-avatar-editor "^11.0.7"
|
||||
react-custom-scrollbars "^4.2.1"
|
||||
react-dropzone "^10.1.8"
|
||||
react-text-mask "^5.4.3"
|
||||
react-toastify "^5.3.2"
|
||||
react-virtualized-auto-sizer "^1.0.2"
|
||||
@ -1897,6 +1899,13 @@ atob@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
attr-accept@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52"
|
||||
integrity sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==
|
||||
dependencies:
|
||||
core-js "^2.5.0"
|
||||
|
||||
autoprefixer@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
|
||||
@ -2961,7 +2970,7 @@ core-js@3.1.4:
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07"
|
||||
integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==
|
||||
|
||||
core-js@^2.4.0, core-js@^2.6.4:
|
||||
core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.4:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
|
||||
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
|
||||
@ -4313,6 +4322,13 @@ file-loader@3.0.1:
|
||||
loader-utils "^1.0.2"
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
file-selector@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0"
|
||||
integrity sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
filesize@3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
|
||||
@ -8736,6 +8752,13 @@ react-avatar-edit@^0.8.3:
|
||||
dependencies:
|
||||
konva "2.5.1"
|
||||
|
||||
react-avatar-editor@^11.0.7:
|
||||
version "11.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-avatar-editor/-/react-avatar-editor-11.0.7.tgz#021053cfeaa138407b79279ee5a0384f273f0c54"
|
||||
integrity sha512-GbNYBd1/L1QyuU9VRvOW0hSkW1R0XSneOWZFgqI5phQf6dX+dF/G3/AjiJ0hv3JWh2irMQ7DL0oYDKzwtTnNBQ==
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-custom-scrollbars@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db"
|
||||
@ -8786,6 +8809,15 @@ react-dom@^16.9.0:
|
||||
prop-types "^15.6.2"
|
||||
scheduler "^0.15.0"
|
||||
|
||||
react-dropzone@^10.1.8:
|
||||
version "10.1.9"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.1.9.tgz#8093ecd7d2dc4002280eb2dac1d5fa4216c800ee"
|
||||
integrity sha512-7iqALZ0mzk+4g/AsYxEy3QyWPMTVQYKQVkYUe9zIbH18u+pi7EBDg010KEwfIX6jeTDH2qP0E6/eUnXvBYrovA==
|
||||
dependencies:
|
||||
attr-accept "^1.1.3"
|
||||
file-selector "^0.1.11"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-error-overlay@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.1.tgz#b8d3cf9bb991c02883225c48044cb3ee20413e0f"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "asc-web-components",
|
||||
"version": "1.0.94",
|
||||
"version": "1.0.95",
|
||||
"description": "Ascensio System SIA component library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/asc-web-components.js",
|
||||
@ -103,7 +103,9 @@
|
||||
"rc-tree": "^2.1.2",
|
||||
"react-autosize-textarea": "^7.0.0",
|
||||
"react-avatar-edit": "^0.8.3",
|
||||
"react-avatar-editor": "^11.0.7",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dropzone": "^10.1.8",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"react-text-mask": "^5.4.3",
|
||||
"react-toastify": "^5.3.2",
|
||||
|
@ -28,7 +28,6 @@ Required to display user avatar editor on page.
|
||||
| `headerLabel` | `string` | - | | `Edit Photo` | |
|
||||
| `saveButtonLabel` | `string` | - | | `Save` | |
|
||||
| `cancelButtonLabel` | `string` | - | | `Cancel` | |
|
||||
| `maxSizeErrorLabel` | `string` | - | | `File is too big` | |
|
||||
| `maxSize` | `number` | - | | `1` | Max size of image |
|
||||
| `onSave` | `function` | - | | | |
|
||||
| `onClose` | `function` | - | | | |
|
@ -20,12 +20,22 @@ class AvatarEditorStory extends React.Component {
|
||||
this.openEditor = this.openEditor.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onSave = this.onSave.bind(this);
|
||||
this.onLoadFile = this.onLoadFile.bind(this)
|
||||
this.onImageChange = this.onImageChange.bind(this)
|
||||
|
||||
}
|
||||
onSave(result){
|
||||
action('onSave')(result);
|
||||
onImageChange(img){
|
||||
action('onLoadFile');
|
||||
this.setState({
|
||||
userImage: img
|
||||
})
|
||||
}
|
||||
onLoadFile(file){
|
||||
action('onLoadFile')(file);
|
||||
}
|
||||
onSave(isUpdate, data){
|
||||
action('onSave')(isUpdate, data);
|
||||
this.setState({
|
||||
userImage: result.croppedImage,
|
||||
isOpen: false
|
||||
})
|
||||
}
|
||||
@ -54,7 +64,8 @@ class AvatarEditorStory extends React.Component {
|
||||
visible={this.state.isOpen}
|
||||
onClose={this.onClose}
|
||||
onSave={this.onSave}
|
||||
/>
|
||||
onImageChange={this.onImageChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,145 +1,68 @@
|
||||
import React, { memo } from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
import PropTypes from 'prop-types'
|
||||
import ModalDialog from '../modal-dialog'
|
||||
import Button from '../button'
|
||||
import { Text } from '../text'
|
||||
import Avatar from 'react-avatar-edit'
|
||||
import { default as ASCAvatar } from '../avatar/index'
|
||||
|
||||
const StyledASCAvatar = styled(ASCAvatar)`
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
`;
|
||||
const StyledAvatarContainer = styled.div`
|
||||
text-align: center;
|
||||
div:first-child {
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
class AvatarEditorBody extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
croppedImage: null,
|
||||
src: this.props.image,
|
||||
hasMaxSizeError: false
|
||||
}
|
||||
this.onCrop = this.onCrop.bind(this)
|
||||
this.onClose = this.onClose.bind(this)
|
||||
this.onBeforeFileLoad = this.onBeforeFileLoad.bind(this)
|
||||
this.onFileLoad = this.onFileLoad.bind(this)
|
||||
|
||||
}
|
||||
onClose() {
|
||||
this.props.onCloseEditor();
|
||||
this.setState({ croppedImage: null })
|
||||
}
|
||||
onCrop(croppedImage) {
|
||||
this.props.onCropImage(croppedImage);
|
||||
this.setState({ croppedImage })
|
||||
}
|
||||
onBeforeFileLoad(elem) {
|
||||
if (elem.target.files[0].size > this.props.maxSize * 1000000) {
|
||||
this.setState({
|
||||
hasMaxSizeError: true
|
||||
});
|
||||
elem.target.value = "";
|
||||
}else if(this.state.hasMaxSizeError){
|
||||
this.setState({
|
||||
hasMaxSizeError: false
|
||||
});
|
||||
}
|
||||
}
|
||||
onFileLoad(file){
|
||||
let reader = new FileReader();
|
||||
let _this = this;
|
||||
reader.onloadend = () => {
|
||||
_this.props.onFileLoad(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<StyledAvatarContainer>
|
||||
<Avatar
|
||||
width={400}
|
||||
height={295}
|
||||
imageWidth={400}
|
||||
cropRadius={50}
|
||||
onCrop={this.onCrop}
|
||||
onClose={this.onClose}
|
||||
onBeforeFileLoad={this.onBeforeFileLoad}
|
||||
onFileLoad={this.onFileLoad}
|
||||
label={this.props.label}
|
||||
src={this.state.src}
|
||||
/>
|
||||
{this.state.croppedImage && (
|
||||
<div>
|
||||
<StyledASCAvatar
|
||||
size='max'
|
||||
role='user'
|
||||
source={this.state.croppedImage}
|
||||
editing={false}
|
||||
/>
|
||||
<StyledASCAvatar
|
||||
size='big'
|
||||
role='user'
|
||||
source={this.state.croppedImage}
|
||||
editing={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.hasMaxSizeError &&
|
||||
<Text.Body as='span' color="#ED7309" isBold={true}>
|
||||
{this.props.maxSizeErrorLabel}
|
||||
</Text.Body>
|
||||
}
|
||||
</StyledAvatarContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
import AvatarEditorBody from './sub-components/avatar-editor-body'
|
||||
|
||||
class AvatarEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
defaultImage: null,
|
||||
croppedImage: null,
|
||||
visible: props.value
|
||||
};
|
||||
isContainsFile: !!this.props.image,
|
||||
visible: props.visible,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height:0
|
||||
}
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
|
||||
this.onCropImage = this.onCropImage.bind(this);
|
||||
this.onCloseEditor = this.onCloseEditor.bind(this);
|
||||
|
||||
this.onFileLoad = this.onFileLoad.bind(this);
|
||||
this.onSaveButtonClick = this.onSaveButtonClick.bind(this);
|
||||
this.onImageChange = this.onImageChange.bind(this);
|
||||
this.onLoadFileError = this.onLoadFileError.bind(this);
|
||||
this.onLoadFile = this.onLoadFile.bind(this);
|
||||
this.onPositionChange = this.onPositionChange.bind(this);
|
||||
|
||||
this.onDeleteImage = this.onDeleteImage.bind(this)
|
||||
|
||||
}
|
||||
onFileLoad(file){
|
||||
this.setState({ defaultImage: file });
|
||||
onImageChange(file){
|
||||
if(typeof this.props.onImageChange === 'function') this.props.onImageChange(file);
|
||||
}
|
||||
onDeleteImage(){
|
||||
this.setState({
|
||||
isContainsFile: false
|
||||
})
|
||||
if(typeof this.props.onDeleteImage === 'function') this.props.onDeleteImage();
|
||||
}
|
||||
onPositionChange(data){
|
||||
this.setState(data);
|
||||
}
|
||||
onLoadFileError(error) {
|
||||
switch (error) {
|
||||
case 0:
|
||||
|
||||
break;
|
||||
case 1:
|
||||
|
||||
break;
|
||||
case 2:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
onLoadFile(file) {
|
||||
if(typeof this.props.onLoadFile === 'function') this.props.onLoadFile(file);
|
||||
this.setState({ isContainsFile: true });
|
||||
}
|
||||
|
||||
onSaveButtonClick() {
|
||||
this.props.onSave({
|
||||
defaultImage: this.state.defaultImage ? this.state.defaultImage : this.props.image,
|
||||
croppedImage: this.state.croppedImage
|
||||
});
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
onCloseEditor() {
|
||||
this.setState({
|
||||
croppedImage: null
|
||||
});
|
||||
}
|
||||
onCropImage(result) {
|
||||
this.setState({
|
||||
croppedImage: result
|
||||
this.props.onSave(this.state.isContainsFile, {
|
||||
x: this.state.x,
|
||||
y: this.state.y,
|
||||
width: this.state.width,
|
||||
height: this.state.height
|
||||
});
|
||||
}
|
||||
|
||||
@ -160,13 +83,15 @@ class AvatarEditor extends React.Component {
|
||||
headerContent={this.props.headerLabel}
|
||||
bodyContent={
|
||||
<AvatarEditorBody
|
||||
maxSize={this.props.maxSize}
|
||||
onImageChange={this.onImageChange}
|
||||
onPositionChange={this.onPositionChange}
|
||||
onLoadFileError={this.onLoadFileError}
|
||||
onLoadFile={this.onLoadFile}
|
||||
deleteImage={this.onDeleteImage}
|
||||
maxSize={this.props.maxSize * 1000000} // megabytes to bytes
|
||||
accept={this.props.accept}
|
||||
image={this.props.image}
|
||||
onCropImage={this.onCropImage}
|
||||
onCloseEditor={this.onCloseEditor}
|
||||
label={this.props.chooseFileLabel}
|
||||
maxSizeErrorLabel={this.props.maxSizeErrorLabel}
|
||||
onFileLoad={this.onFileLoad}
|
||||
chooseFileLabel={this.props.chooseFileLabel}
|
||||
/>
|
||||
}
|
||||
footerContent={[
|
||||
@ -198,15 +123,17 @@ AvatarEditor.propTypes = {
|
||||
image: PropTypes.string,
|
||||
cancelButtonLabel: PropTypes.string,
|
||||
maxSize: PropTypes.number,
|
||||
|
||||
accept: PropTypes.arrayOf(PropTypes.string),
|
||||
onSave: PropTypes.func,
|
||||
onClose: PropTypes.func
|
||||
onClose: PropTypes.func,
|
||||
onDeleteImage: PropTypes.func,
|
||||
onLoadFile: PropTypes.func,
|
||||
onImageChange: PropTypes.func,
|
||||
};
|
||||
|
||||
AvatarEditor.defaultProps = {
|
||||
visible: false,
|
||||
maxSize: 1, //1MB
|
||||
chooseFileLabel: 'Choose a file',
|
||||
headerLabel: 'Edit Photo',
|
||||
saveButtonLabel: 'Save',
|
||||
cancelButtonLabel: 'Cancel',
|
||||
|
@ -0,0 +1,303 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Dropzone from 'react-dropzone'
|
||||
import ReactAvatarEditor from 'react-avatar-editor'
|
||||
import PropTypes from 'prop-types'
|
||||
import { default as ASCAvatar } from '../../avatar/index'
|
||||
import accepts from 'attr-accept'
|
||||
|
||||
import { tablet } from '../../../utils/device';
|
||||
|
||||
const CloseButton = styled.a`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 33px;
|
||||
top: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
&:before, &:after {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
content: ' ';
|
||||
height: 16px;
|
||||
width: 1px;
|
||||
background-color: #D8D8D8;
|
||||
}
|
||||
&:before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
&:after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
@media ${tablet} {
|
||||
right: calc(50% - 147px);
|
||||
}
|
||||
`;
|
||||
|
||||
const DropZoneContainer = styled.div`
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 1px dashed #ccc;
|
||||
text-align: center;
|
||||
padding: 10em 0;
|
||||
margin: 0 auto;
|
||||
p{
|
||||
margin: 0;
|
||||
cursor: default
|
||||
}
|
||||
`;
|
||||
const StyledAvatarContainer = styled.div`
|
||||
text-align: center;
|
||||
|
||||
.custom-range{
|
||||
width: 300px;
|
||||
}
|
||||
.avatar-container{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.editor-container{
|
||||
display: inline-block;
|
||||
width: calc(100% - 170px);
|
||||
position: relative;
|
||||
@media ${tablet} {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
`;
|
||||
const StyledASCAvatar = styled(ASCAvatar)`
|
||||
display: block;
|
||||
@media ${tablet} {
|
||||
display: none
|
||||
}
|
||||
`;
|
||||
|
||||
class AvatarEditorBody extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
image: this.props.image ? this.props.image : "",
|
||||
scale: 1,
|
||||
croppedImage: ''
|
||||
}
|
||||
|
||||
this.setEditorRef = React.createRef();
|
||||
|
||||
this.handleScale = this.handleScale.bind(this);
|
||||
this.onWheel = this.onWheel.bind(this);
|
||||
|
||||
this.onTouchStart = this.onTouchStart.bind(this);
|
||||
this.onTouchMove = this.onTouchMove.bind(this);
|
||||
this.onTouchEnd = this.onTouchEnd.bind(this);
|
||||
|
||||
this.distance = this.distance.bind(this);
|
||||
|
||||
this.onImageChange = this.onImageChange.bind(this);
|
||||
this.onImageReady = this.onImageReady.bind(this);
|
||||
this.deleteImage = this.deleteImage.bind(this);
|
||||
|
||||
this.onDropAccepted = this.onDropAccepted.bind(this);
|
||||
|
||||
this.onPositionChange = this.onPositionChange.bind(this);
|
||||
|
||||
}
|
||||
|
||||
onPositionChange(position) {
|
||||
this.props.onPositionChange({
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: this.setEditorRef.current.getImage().width,
|
||||
height: this.setEditorRef.current.getImage().height
|
||||
});
|
||||
}
|
||||
onDropRejected(rejectedFiles) {
|
||||
if (!accepts(rejectedFiles[0], this.props.accept)) {
|
||||
this.props.onLoadFileError(0);
|
||||
return;
|
||||
} else if (rejectedFiles[0].size > this.props.maxSize) {
|
||||
this.props.onLoadFileError(1);
|
||||
return;
|
||||
}
|
||||
this.props.onLoadFileError(2);
|
||||
}
|
||||
onDropAccepted(acceptedFiles) {
|
||||
this.setState({
|
||||
image: acceptedFiles[0]
|
||||
});
|
||||
this.props.onLoadFile(acceptedFiles[0]);
|
||||
}
|
||||
deleteImage() {
|
||||
this.setState({
|
||||
image: '',
|
||||
croppedImage: ''
|
||||
});
|
||||
this.props.deleteImage();
|
||||
}
|
||||
onImageChange() {
|
||||
this.setState({
|
||||
croppedImage: this.setEditorRef.current.getImage().toDataURL()
|
||||
});
|
||||
this.props.onImageChange(this.setEditorRef.current.getImage().toDataURL());
|
||||
}
|
||||
dist = 0
|
||||
scaling = false
|
||||
curr_scale = 1.0
|
||||
scale_factor = 1.0
|
||||
distance(p1, p2) {
|
||||
return (Math.sqrt(Math.pow((p1.clientX - p2.clientX), 2) + Math.pow((p1.clientY - p2.clientY), 2)));
|
||||
}
|
||||
onTouchStart(evt) {
|
||||
evt.preventDefault();
|
||||
var tt = evt.targetTouches;
|
||||
if (tt.length >= 2) {
|
||||
this.dist = this.distance(tt[0], tt[1]);
|
||||
this.scaling = true;
|
||||
} else {
|
||||
this.scaling = false;
|
||||
}
|
||||
}
|
||||
onTouchMove(evt) {
|
||||
evt.preventDefault();
|
||||
var tt = evt.targetTouches;
|
||||
if (this.scaling) {
|
||||
this.curr_scale = this.distance(tt[0], tt[1]) / this.dist * this.scale_factor;
|
||||
let scale = (Math.round(this.curr_scale) * 10) / 10;
|
||||
this.setState({
|
||||
scale: scale < 1 ? 1 : scale > 5 ? 5 : scale
|
||||
});
|
||||
}
|
||||
}
|
||||
onTouchEnd(evt) {
|
||||
var tt = evt.targetTouches;
|
||||
if (tt.length < 2) {
|
||||
this.scaling = false;
|
||||
if (this.curr_scale < 1) {
|
||||
this.scale_factor = 1;
|
||||
} else {
|
||||
if (this.curr_scale > 5) {
|
||||
this.scale_factor = 5;
|
||||
} else {
|
||||
this.scale_factor = this.curr_scale;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.scaling = true;
|
||||
}
|
||||
}
|
||||
onWheel(e) {
|
||||
e = e || window.event;
|
||||
const delta = e.deltaY || e.detail || e.wheelDelta;
|
||||
let scale = delta > 0 && this.state.scale === 1 ? 1 : this.state.scale - (delta / 100) * 0.1;
|
||||
scale = Math.round(scale * 10) / 10;
|
||||
this.setState({
|
||||
scale: scale < 1 ? 1 : scale > 5 ? 5 : scale
|
||||
});
|
||||
}
|
||||
|
||||
handleScale = e => {
|
||||
const scale = parseFloat(e.target.value);
|
||||
this.setState({ scale })
|
||||
};
|
||||
onImageReady() {
|
||||
this.setState({
|
||||
croppedImage: this.setEditorRef.current.getImage().toDataURL()
|
||||
});
|
||||
this.props.onPositionChange({
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
width: this.setEditorRef.current.getImage().width,
|
||||
height: this.setEditorRef.current.getImage().height
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onWheel={this.onWheel}
|
||||
onTouchStart={this.onTouchStart}
|
||||
onTouchMove={this.onTouchMove}
|
||||
onTouchEnd={this.onTouchEnd}
|
||||
>
|
||||
{this.state.image === '' ?
|
||||
<Dropzone
|
||||
onDropAccepted={this.onDropAccepted}
|
||||
onDropRejected={this.onDropRejected}
|
||||
maxSize={this.props.maxSize}>
|
||||
{({ getRootProps, getInputProps }) => (
|
||||
<DropZoneContainer
|
||||
{...getRootProps()}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<p>{this.props.chooseFileLabel}</p>
|
||||
</DropZoneContainer>
|
||||
)}
|
||||
</Dropzone> :
|
||||
<StyledAvatarContainer>
|
||||
<div className='editor-container'>
|
||||
<ReactAvatarEditor
|
||||
ref={this.setEditorRef}
|
||||
width={250}
|
||||
borderRadius={200}
|
||||
scale={this.state.scale}
|
||||
height={250}
|
||||
className="react-avatar-editor"
|
||||
image={this.state.image}
|
||||
color={[0, 0, 0, .5]}
|
||||
onImageChange={this.onImageChange}
|
||||
onPositionChange={this.onPositionChange}
|
||||
onImageReady={this.onImageReady}
|
||||
/>
|
||||
<input
|
||||
id='scale'
|
||||
type='range'
|
||||
className='custom-range'
|
||||
onChange={this.handleScale}
|
||||
min={this.state.allowZoomOut ? '0.1' : '1'}
|
||||
max='5'
|
||||
step='0.01'
|
||||
value={this.state.scale}
|
||||
/>
|
||||
<CloseButton onClick={this.deleteImage}></CloseButton>
|
||||
</div>
|
||||
<div className='avatar-container'>
|
||||
<StyledASCAvatar
|
||||
size='max'
|
||||
role='user'
|
||||
source={this.state.croppedImage}
|
||||
editing={false}
|
||||
/>
|
||||
<StyledASCAvatar
|
||||
size='big'
|
||||
role='user'
|
||||
source={this.state.croppedImage}
|
||||
editing={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</StyledAvatarContainer>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AvatarEditorBody.propTypes = {
|
||||
onImageChange: PropTypes.func,
|
||||
onPositionChange: PropTypes.func,
|
||||
onLoadFileError: PropTypes.func,
|
||||
onLoadFile: PropTypes.func,
|
||||
deleteImage: PropTypes.func,
|
||||
maxSize: PropTypes.number,
|
||||
image: PropTypes.string,
|
||||
accept: PropTypes.arrayOf(PropTypes.string),
|
||||
chooseFileLabel: PropTypes.string,
|
||||
};
|
||||
|
||||
AvatarEditorBody.defaultProps = {
|
||||
accept: ['image/png', 'image/jpeg'],
|
||||
maxSize: Number.MAX_SAFE_INTEGER,
|
||||
chooseFileLabel: "Drop files here, or click to select files"
|
||||
};
|
||||
export default AvatarEditorBody;
|
@ -2615,6 +2615,13 @@ atob@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
attr-accept@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52"
|
||||
integrity sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==
|
||||
dependencies:
|
||||
core-js "^2.5.0"
|
||||
|
||||
autoprefixer@^9.4.9:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
|
||||
@ -5321,6 +5328,13 @@ file-loader@^3.0.1:
|
||||
loader-utils "^1.0.2"
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
file-selector@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0"
|
||||
integrity sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
file-system-cache@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f"
|
||||
@ -9520,6 +9534,13 @@ react-avatar-edit@^0.8.3:
|
||||
dependencies:
|
||||
konva "2.5.1"
|
||||
|
||||
react-avatar-editor@^11.0.7:
|
||||
version "11.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-avatar-editor/-/react-avatar-editor-11.0.7.tgz#021053cfeaa138407b79279ee5a0384f273f0c54"
|
||||
integrity sha512-GbNYBd1/L1QyuU9VRvOW0hSkW1R0XSneOWZFgqI5phQf6dX+dF/G3/AjiJ0hv3JWh2irMQ7DL0oYDKzwtTnNBQ==
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-clientside-effect@^1.2.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.2.tgz#6212fb0e07b204e714581dd51992603d1accc837"
|
||||
@ -9610,6 +9631,15 @@ react-draggable@^3.1.1:
|
||||
classnames "^2.2.5"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-dropzone@^10.1.8:
|
||||
version "10.1.8"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.1.8.tgz#348895a3ee9efe7c0f6a2f19642f04704c170757"
|
||||
integrity sha512-Lm6+TxIDf/my4i3VdYmufRcrJ4SUbSTJP3HB49V2+HNjZwLI4NKVkaNRHwwSm9CEuzMP+6SW7pT1txc1uBPfDg==
|
||||
dependencies:
|
||||
attr-accept "^1.1.3"
|
||||
file-selector "^0.1.11"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-element-to-jsx-string@^14.0.2:
|
||||
version "14.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-14.0.3.tgz#64f50fdbf6ba154d6439da3d7307f79069b94d58"
|
||||
|
@ -904,6 +904,9 @@ ONLYOFFICE-Team</value>
|
||||
<data name="subject_change_tfa" xml:space="preserve">
|
||||
<value>$ {LetterLogoText}. Anfrage zur Änderung der Sicherungscodes</value>
|
||||
</data>
|
||||
<data name="subject_confirm_owner_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Änderung des Portalsbesitzer</value>
|
||||
</data>
|
||||
<data name="subject_dns_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Änderung der Portaladresse</value>
|
||||
</data>
|
||||
|
@ -1621,6 +1621,9 @@ El enlace es válido solo durante 7 días.</value>
|
||||
<data name="subject_change_tfa" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Solicitud de cambio de códigos de respaldo</value>
|
||||
</data>
|
||||
<data name="subject_confirm_owner_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Cambio de dueño de portal</value>
|
||||
</data>
|
||||
<data name="subject_dns_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Cambio de dirección de portal</value>
|
||||
</data>
|
||||
|
@ -996,6 +996,9 @@ Meilleures salutations,
|
||||
<data name="subject_change_tfa" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Demande de changement des codes de sauvegarde</value>
|
||||
</data>
|
||||
<data name="subject_confirm_owner_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Changement du propriétaire du portail </value>
|
||||
</data>
|
||||
<data name="subject_dns_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Changement de l'adresse du portail</value>
|
||||
</data>
|
||||
|
@ -351,6 +351,35 @@ Vai su Impostazioni >> Comuni >> Personalizzazione.</value>
|
||||
<data name="pattern_enterprise_admin_customize_portal_v10_item_regional_hdr" xml:space="preserve">
|
||||
<value>Suggerimento #2: Regola le impostazioni regionali</value>
|
||||
</data>
|
||||
<data name="pattern_enterprise_admin_without_activity_v10" xml:space="preserve">
|
||||
<value>Salve, $UserName!
|
||||
|
||||
Ci auguriamo che ti piaccia usare ONLYOFFICE. Come sta procedendo con la versione di prova? Gradiremmo molto il tuo feedback!
|
||||
|
||||
Per favore, non esitare a contattarci a "support@onlyoffice.com":"mailto: support@onlyoffice.com" tutte le volte che avete domande o idee, suggerimenti.</value>
|
||||
</data>
|
||||
<data name="pattern_enterprise_guest_activation_v10" xml:space="preserve">
|
||||
<value>Salve, $UserName!
|
||||
|
||||
$__AuthorName ti ha invitato come ospite su "${__VirtualRootPath}":"${__VirtualRootPath}". Accetta l'invito cliccando il link:
|
||||
|
||||
$GreenButton
|
||||
|
||||
Il link è valido solo per 7 giorni.
|
||||
|
||||
Otterrai più consigli su come usare il tuo web-office. Puoi cancellare le sottoscrizioni dalla pagina del tuo profilo in ogni momento e riabilitarle.</value>
|
||||
</data>
|
||||
<data name="pattern_enterprise_whitelabel_guest_activation_v10" xml:space="preserve">
|
||||
<value>Salve, $UserName!
|
||||
|
||||
$__AuthorName ti ha invitato come ospite su "${__VirtualRootPath}":"${__VirtualRootPath}". Accetta l'invito cliccando il link:
|
||||
|
||||
$GreenButton
|
||||
|
||||
Il link è valido solo per 7 giorni.
|
||||
|
||||
Otterrai più consigli su come usare il tuo web-office. Puoi cancellare le sottoscrizioni dalla pagina del tuo profilo in ogni momento e riabilitarle.</value>
|
||||
</data>
|
||||
<data name="pattern_for_admin_notify" xml:space="preserve">
|
||||
<value>h1.Messaggio dal portale "${__VirtualRootPath}":"${__VirtualRootPath}"
|
||||
|
||||
@ -919,6 +948,9 @@ Team di supporto ONLYOFFICE®
|
||||
<data name="pattern_saas_admin_user_docs_tips_v10_item_review_hdr" xml:space="preserve">
|
||||
<value>Revisiona, commenta e discuti</value>
|
||||
</data>
|
||||
<data name="pattern_saas_admin_user_docs_tips_v10_item_share" xml:space="preserve">
|
||||
<value>Condividi documenti a singoli o gruppi di utenti. Scegli il livello di accesso: sola lettura, commento, compilazione modulo, revisione o accesso completo.</value>
|
||||
</data>
|
||||
<data name="pattern_saas_admin_user_docs_tips_v10_item_share_hdr" xml:space="preserve">
|
||||
<value>Condividi documenti</value>
|
||||
</data>
|
||||
@ -960,6 +992,17 @@ il team ONLYOFFICE
|
||||
<data name="pattern_saas_admin_welcome_v10_item_usertrack" xml:space="preserve">
|
||||
<value>Traccia Accesso utenti e altre azioni.</value>
|
||||
</data>
|
||||
<data name="pattern_saas_admin_without_activity_v10" xml:space="preserve">
|
||||
<value>Salve, $UserName!
|
||||
|
||||
Ci auguriamo che ti piaccia usare ONLYOFFICE. Come sta procedendo con la versione di prova? Gradiremmo molto il tuo feedback!
|
||||
|
||||
Per favore, non esitare a contattarci a "support@onlyoffice.com":"mailto: support@onlyoffice.com" tutte le volte che avete domande o idee, suggerimenti.
|
||||
|
||||
Distinti saluti,
|
||||
Squadra ONLYOFFICE
|
||||
"www.onlyoffice.com":"http://onlyoffice.com/"</value>
|
||||
</data>
|
||||
<data name="pattern_saas_guest_activation_v10" xml:space="preserve">
|
||||
<value>Salve!
|
||||
|
||||
@ -971,6 +1014,25 @@ Ti invieremo anche suggerimenti utili, ultime notizie su ONLYOFFICE e offerte sp
|
||||
|
||||
Cordialmente,
|
||||
il team di ONLYOFFICE
|
||||
"www.onlyoffice.com":"http://onlyoffice.com/"</value>
|
||||
</data>
|
||||
<data name="pattern_saas_guest_welcome_v10" xml:space="preserve">
|
||||
<value>Salve!
|
||||
|
||||
Il tuo profilo ospite è stato aggiunto con successo a "${__VirtualRootPath}":"${__VirtualRootPath}". Adesso puoi:
|
||||
|
||||
# Modificare il tuo "profilo":"$MyStaffLink".
|
||||
# Visualizza e commenta il contenuto disponibile nella "Community":"${__VirtualRootPath}/products/community/" e "Progetti":"${__VirtualRootPath}/products/projects/".
|
||||
# Aggiungi e scarica i file disponibili per te in "Documenti":"${__VirtualRootPath}/products/files/".
|
||||
# Organizza il tuo piano con il "Calendario":"${__VirtualRootPath}/addons/calendar/" incorporato.
|
||||
# Usa la Use "Chat":"${__VirtualRootPath}/addons/talk/" per scambiare messaggi istantanei.
|
||||
|
||||
$GreenButton
|
||||
|
||||
Se hai bisogno di aiuto, consulta il nostro "Centro assistenza":"${__HelpLink}" o chiedi alla nostra "squadra di supporto":"https://support.onlyoffice.com".
|
||||
|
||||
Cordialmente,
|
||||
la squadra ONLYOFFICE
|
||||
"www.onlyoffice.com":"http://onlyoffice.com/"</value>
|
||||
</data>
|
||||
<data name="pattern_saas_user_activation_v10" xml:space="preserve">
|
||||
@ -1080,6 +1142,9 @@ il team di ONLYOFFICE</value>
|
||||
<data name="subject_change_tfa" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Richiesta di modifica dei codici di backup</value>
|
||||
</data>
|
||||
<data name="subject_confirm_owner_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Cambio del proprietario del portale</value>
|
||||
</data>
|
||||
<data name="subject_dns_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Cambio dell'indirizzo del portale.</value>
|
||||
</data>
|
||||
@ -1089,6 +1154,9 @@ il team di ONLYOFFICE</value>
|
||||
<data name="subject_enterprise_admin_customize_portal_v10" xml:space="preserve">
|
||||
<value>Personalizza ONLYOFFICE</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_admin_invite_teammates_v10" xml:space="preserve">
|
||||
<value>Invita i tuoi compagni di squadra</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_admin_payment_warning_before7_v10" xml:space="preserve">
|
||||
<value>Notifica di rinnovo ONLYOFFICE®</value>
|
||||
</data>
|
||||
@ -1110,9 +1178,15 @@ il team di ONLYOFFICE</value>
|
||||
<data name="subject_enterprise_admin_without_activity_v10" xml:space="preserve">
|
||||
<value>Attenzionato dal team ONLYOFFICE </value>
|
||||
</data>
|
||||
<data name="subject_enterprise_guest_activation_v10" xml:space="preserve">
|
||||
<value>Unirsi su ${__VirtualRootPath}</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_guest_welcome_v10" xml:space="preserve">
|
||||
<value>Benvenuti sul vostro web-office</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_user_activation_v10" xml:space="preserve">
|
||||
<value>Unirsi su ${__VirtualRootPath}</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_user_welcome_v10" xml:space="preserve">
|
||||
<value>Benvenuti sul vostro web-office</value>
|
||||
</data>
|
||||
@ -1131,9 +1205,15 @@ il team di ONLYOFFICE</value>
|
||||
<data name="subject_enterprise_whitelabel_admin_welcome_v10" xml:space="preserve">
|
||||
<value>Rendi il tuo ufficio online più sicuro</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_whitelabel_guest_activation_v10" xml:space="preserve">
|
||||
<value>Unirsi su ${__VirtualRootPath}</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_whitelabel_guest_welcome_v10" xml:space="preserve">
|
||||
<value>Benvenuti sul vostro web-office</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_whitelabel_user_activation_v10" xml:space="preserve">
|
||||
<value>Unirsi su ${__VirtualRootPath}</value>
|
||||
</data>
|
||||
<data name="subject_enterprise_whitelabel_user_welcome_v10" xml:space="preserve">
|
||||
<value>Benvenuti sul vostro web-office</value>
|
||||
</data>
|
||||
@ -1260,6 +1340,9 @@ il team di ONLYOFFICE</value>
|
||||
<data name="subject_saas_admin_trial_warning_after5_v10" xml:space="preserve">
|
||||
<value>Estendi il periodo di prova ONLYOFFICE e ottieni sconti</value>
|
||||
</data>
|
||||
<data name="subject_saas_admin_trial_warning_before5_v10" xml:space="preserve">
|
||||
<value>Usa questo sconto prima che il periodo di prova finisca</value>
|
||||
</data>
|
||||
<data name="subject_saas_admin_user_apps_tips_v10" xml:space="preserve">
|
||||
<value>Ottieni apps di ONLYOFFICE gratis</value>
|
||||
</data>
|
||||
@ -1275,9 +1358,15 @@ il team di ONLYOFFICE</value>
|
||||
<data name="subject_saas_admin_without_activity_v10" xml:space="preserve">
|
||||
<value>Attenzionato dal team ONLYOFFICE </value>
|
||||
</data>
|
||||
<data name="subject_saas_guest_activation_v10" xml:space="preserve">
|
||||
<value>Unirsi su ${__VirtualRootPath}</value>
|
||||
</data>
|
||||
<data name="subject_saas_guest_welcome_v10" xml:space="preserve">
|
||||
<value>Benvenuto nel tuo ONLYOFFICE!</value>
|
||||
</data>
|
||||
<data name="subject_saas_user_activation_v10" xml:space="preserve">
|
||||
<value>Unirsi su ${__VirtualRootPath}</value>
|
||||
</data>
|
||||
<data name="subject_saas_user_welcome_v10" xml:space="preserve">
|
||||
<value>Benvenuto nel tuo ONLYOFFICE!</value>
|
||||
</data>
|
||||
|
@ -1182,6 +1182,9 @@ h3.Email
|
||||
h3.Request content
|
||||
$Body</value>
|
||||
</data>
|
||||
<data name="pattern_restore_completed" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="pattern_restore_started" xml:space="preserve">
|
||||
<value>h1.Portal restoration started
|
||||
|
||||
@ -1634,6 +1637,9 @@ The link is only valid for 7 days.</value>
|
||||
<data name="subject_change_tfa" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Backup codes change request</value>
|
||||
</data>
|
||||
<data name="subject_confirm_owner_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Change of portal owner</value>
|
||||
</data>
|
||||
<data name="subject_dns_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Change of portal address</value>
|
||||
</data>
|
||||
|
@ -1624,6 +1624,9 @@ $GreenButton
|
||||
<data name="subject_change_tfa" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Запрос на изменение резервных кодов</value>
|
||||
</data>
|
||||
<data name="subject_confirm_owner_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Смена владельца портала</value>
|
||||
</data>
|
||||
<data name="subject_dns_change" xml:space="preserve">
|
||||
<value>${LetterLogoText}. Изменение адреса портала</value>
|
||||
</data>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<patterns>
|
||||
<formatter type="ASC.Notify.Patterns.NVelocityPatternFormatter, ASC.Common" />
|
||||
<formatter type="ASC.Notify.Patterns.NVelocityPatternFormatter, ASC.Core.Common" />
|
||||
|
||||
<!-- self_profile_updated -->
|
||||
<pattern id="self_profile_updated" sender="email.sender">
|
||||
@ -161,7 +161,7 @@ $activity.Key
|
||||
|
||||
<!-- owner_confirm_change -->
|
||||
<pattern id="owner_confirm_change">
|
||||
<subject resource="subject_confirm_owner_change|ASC.Web.Core.PublicResources.WebstudioNotifyPatternResource,ASC.Web.Core" />
|
||||
<subject resource="|subject_confirm_owner_change|ASC.Web.Core.PublicResources.WebstudioNotifyPatternResource,ASC.Web.Core" />
|
||||
<body styler="ASC.Notify.Textile.TextileStyler,ASC.Notify.Textile" resource="|pattern_confirm_owner_change|ASC.Web.Core.PublicResources.WebstudioNotifyPatternResource,ASC.Web.Core" />
|
||||
</pattern>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user