# Conflicts:
#	products/ASC.People/Client/src/components/pages/ProfileAction/Section/Body/Form/userForm.js
#	products/ASC.People/Client/src/components/pages/ProfileAction/Section/Header/index.js
#	products/ASC.People/Client/src/components/pages/ProfileAction/index.js
#	web/ASC.Web.Components/src/components/input-block/index.js
#	web/ASC.Web.Components/src/index.js
This commit is contained in:
Andrey Savihin 2019-08-28 18:23:16 +03:00
commit b6e46a305c
58 changed files with 2076 additions and 1184 deletions

View File

@ -28,8 +28,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
using Google.Protobuf;
namespace ASC.Common.Caching
{
public class AscCache : ICache
@ -143,7 +144,7 @@ namespace ASC.Common.Caching
public void ClearCache()
{
KafkaNotify.Publish(new AscCacheItem() { Id = Guid.NewGuid().ToString() }, CacheNotifyAction.Any);
KafkaNotify.Publish(new AscCacheItem() { Id = ByteString.CopyFrom(Guid.NewGuid().ToByteArray()) }, CacheNotifyAction.Any);
}
private MemoryCache GetCache()

View File

@ -12,29 +12,47 @@ using Google.Protobuf;
namespace ASC.Common.Caching
{
public class KafkaCache<T> : ICacheNotify<T> where T : IMessage<T>, new()
public class KafkaCache<T> : IDisposable, ICacheNotify<T> where T : IMessage<T>, new()
{
private ClientConfig ClientConfig { get; set; }
private ILog Log { get; set; }
private ConcurrentDictionary<CacheNotifyAction, CancellationTokenSource> Cts { get; set; }
private ConcurrentDictionary<string, CancellationTokenSource> Cts { get; set; }
private ConcurrentDictionary<string, Action<T>> Actions { get; set; }
private MemoryCacheNotify<T> MemoryCacheNotify { get; set; }
private string ChannelName { get; } = $"ascchannel{typeof(T).Name}";
private ProtobufSerializer<T> ValueSerializer { get; } = new ProtobufSerializer<T>();
private ProtobufDeserializer<T> ValueDeserializer { get; } = new ProtobufDeserializer<T>();
private ProtobufSerializer<AscCacheItem> KeySerializer { get; } = new ProtobufSerializer<AscCacheItem>();
private ProtobufDeserializer<AscCacheItem> KeyDeserializer { get; } = new ProtobufDeserializer<AscCacheItem>();
private IProducer<AscCacheItem, T> Producer { get; }
private Guid Key { get; set; }
public KafkaCache()
{
Log = LogManager.GetLogger("ASC");
Cts = new ConcurrentDictionary<CacheNotifyAction, CancellationTokenSource>();
Cts = new ConcurrentDictionary<string, CancellationTokenSource>();
Actions = new ConcurrentDictionary<string, Action<T>>();
Key = Guid.NewGuid();
var settings = ConfigurationManager.GetSetting<KafkaSettings>("kafka");
if (settings != null && !string.IsNullOrEmpty(settings.BootstrapServers))
{
ClientConfig = new ClientConfig { BootstrapServers = settings.BootstrapServers };
var config = new ProducerConfig(ClientConfig);
Producer = new ProducerBuilder<AscCacheItem, T>(config)
.SetErrorHandler((_, e) => Log.Error(e))
.SetKeySerializer(KeySerializer)
.SetValueSerializer(ValueSerializer)
.Build();
}
else
{
MemoryCacheNotify = new MemoryCacheNotify<T>();
}
}
public async void Publish(T obj, CacheNotifyAction cacheNotifyAction)
public void Publish(T obj, CacheNotifyAction cacheNotifyAction)
{
if (ClientConfig == null)
{
@ -42,16 +60,25 @@ namespace ASC.Common.Caching
return;
}
var config = new ProducerConfig(ClientConfig);
using var p = new ProducerBuilder<Null, T>(config)
.SetErrorHandler((_, e) => Log.Error(e))
.SetValueSerializer(new ProtobufSerializer<T>())
.Build();
try
{
var dr = await p.ProduceAsync(GetChannelName(cacheNotifyAction), new Message<Null, T>() { Value = obj });
var channelName = GetChannelName(cacheNotifyAction);
if (Actions.TryGetValue(channelName, out var onchange))
{
onchange(obj);
}
var message = new Message<AscCacheItem, T>
{
Value = obj,
Key = new AscCacheItem
{
Id = ByteString.CopyFrom(Key.ToByteArray())
}
};
Producer.ProduceAsync(channelName, message);
}
catch (ProduceException<Null, string> e)
{
@ -70,23 +97,24 @@ namespace ASC.Common.Caching
MemoryCacheNotify.Subscribe(onchange, cacheNotifyAction);
return;
}
Cts[cacheNotifyAction] = new CancellationTokenSource();
var channelName = GetChannelName(cacheNotifyAction);
Cts[channelName] = new CancellationTokenSource();
Actions[channelName] = onchange;
void action()
{
var conf = new ConsumerConfig(ClientConfig)
{
GroupId = Guid.NewGuid().ToString(),
EnableAutoCommit = true
GroupId = Guid.NewGuid().ToString()
};
using var c = new ConsumerBuilder<Ignore, T>(conf)
using var c = new ConsumerBuilder<AscCacheItem, T>(conf)
.SetErrorHandler((_, e) => Log.Error(e))
.SetValueDeserializer(new ProtobufDeserializer<T>())
.SetKeyDeserializer(KeyDeserializer)
.SetValueDeserializer(ValueDeserializer)
.Build();
c.Assign(new TopicPartition(GetChannelName(cacheNotifyAction), new Partition()));
c.Assign(new TopicPartition(channelName, new Partition()));
try
{
@ -94,10 +122,10 @@ namespace ASC.Common.Caching
{
try
{
var cr = c.Consume(Cts[cacheNotifyAction].Token);
if (cr != null && cr.Value != null)
var cr = c.Consume(Cts[channelName].Token);
if (cr != null && cr.Value != null && !(new Guid(cr.Key.Id.ToByteArray())).Equals(Key) && Actions.TryGetValue(channelName, out var act))
{
onchange(cr.Value);
act(cr.Value);
}
}
catch (ConsumeException e)
@ -118,17 +146,42 @@ namespace ASC.Common.Caching
private string GetChannelName(CacheNotifyAction cacheNotifyAction)
{
return $"ascchannel{typeof(T).Name}{cacheNotifyAction}";
return $"{ChannelName}{cacheNotifyAction}";
}
public void Unsubscribe(CacheNotifyAction action)
{
Cts.TryGetValue(action, out var source);
Cts.TryGetValue(GetChannelName(action), out var source);
if (source != null)
{
source.Cancel();
}
}
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
Producer.Dispose();
}
disposedValue = true;
}
}
~KafkaCache()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public class KafkaSettings
@ -141,23 +194,23 @@ namespace ASC.Common.Caching
private readonly Dictionary<string, List<Action<T>>> actions = new Dictionary<string, List<Action<T>>>();
public void Publish(T obj, CacheNotifyAction action)
{
if (actions.TryGetValue(GetKey(action), out var onchange) && onchange != null)
{
{
if (actions.TryGetValue(GetKey(action), out var onchange) && onchange != null)
{
foreach (var a in onchange)
{
a(obj);
}
}
}
}
public void Subscribe(Action<T> onchange, CacheNotifyAction notifyAction)
{
if (onchange != null)
if (onchange != null)
{
var key = GetKey(notifyAction);
actions.TryAdd(key, new List<Action<T>>());
actions[key].Add(onchange);
actions.TryAdd(key, new List<Action<T>>());
actions[key].Add(onchange);
}
}
@ -171,4 +224,4 @@ namespace ASC.Common.Caching
return $"{typeof(T).Name}{cacheNotifyAction}";
}
}
}
}

View File

@ -22,4 +22,16 @@ namespace ASC.Common.Caching
public T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
=> parser.ParseFrom(data.ToArray());
}
public static class GuidExtension
{
public static ByteString ToByteString(this Guid id)
{
return ByteString.CopyFrom(id.ToByteArray());
}
public static Guid FromByteString(this ByteString id)
{
return new Guid(id.ToByteArray());
}
}
}

View File

@ -54,35 +54,42 @@ namespace System.Web
public static Uri GetUrlRewriter(IHeaderDictionary headers, HttpRequest request)
{
if (request.Query != null && request.Query.Count > 0)
{
var rewriterUri = ParseRewriterUrl(request.Query[UrlRewriterHeader]);
if (rewriterUri != null)
if (headers != null)
{
var h = headers[UrlRewriterHeader];
var rewriterUri = !string.IsNullOrEmpty(h) ? ParseRewriterUrl(h) : null;
if (request != null && rewriterUri != null)
{
var result = new UriBuilder(request.GetDisplayUrl())
var result = new UriBuilder()
{
Scheme = rewriterUri.Scheme,
Host = rewriterUri.Host,
Port = rewriterUri.Port
};
};
result.Query = request.QueryString.Value;
result.Path = request.Path.Value;
return result.Uri;
}
}
}
if (request != null && request.Query != null)
{
var h = request.Query[UrlRewriterHeader];
var rewriterUri = !string.IsNullOrEmpty(h) ? ParseRewriterUrl(h) : null;
if (rewriterUri != null)
{
var result = new UriBuilder()
{
Scheme = rewriterUri.Scheme,
Host = rewriterUri.Host,
Port = rewriterUri.Port
};
result.Query = request.QueryString.Value;
result.Path = request.Path.Value;
if (headers != null && !string.IsNullOrEmpty(headers[UrlRewriterHeader]))
{
var rewriterUri = ParseRewriterUrl(headers[UrlRewriterHeader]);
if (rewriterUri != null)
{
var result = new UriBuilder(request.GetDisplayUrl())
{
Scheme = rewriterUri.Scheme,
Host = rewriterUri.Host,
Port = rewriterUri.Port
};
return result.Uri;
}
}
}
return request.Url();
}

View File

@ -67,7 +67,8 @@ namespace ASC.Common.Utils
OlsonTimeZoneId = olsonTimeZone,
WindowsTimeZoneId = row.Attribute("other").Value,
Territory = row.Attribute("territory").Value
};
};
_mapZones = _mapZones.ToList();
}
catch (Exception error)
{

View File

@ -3,5 +3,5 @@
package ASC.Common;
message AscCacheItem {
string Id = 1;
bytes Id = 1;
}

View File

@ -145,14 +145,14 @@ namespace ASC.Core.Caching
public UserInfo SaveUser(int tenant, UserInfo user)
{
user = service.SaveUser(tenant, user);
cacheUserInfoItem.Publish(new UserInfoCacheItem() { ID = user.ID.ToString(), Tenant = tenant }, CacheNotifyAction.InsertOrUpdate);
cacheUserInfoItem.Publish(new UserInfoCacheItem() { ID = user.ID.ToByteString(), Tenant = tenant }, CacheNotifyAction.InsertOrUpdate);
return user;
}
public void RemoveUser(int tenant, Guid id)
{
service.RemoveUser(tenant, id);
cacheUserInfoItem.Publish(new UserInfoCacheItem { Tenant = tenant, ID = id.ToString() }, CacheNotifyAction.Any);
cacheUserInfoItem.Publish(new UserInfoCacheItem { Tenant = tenant, ID = id.ToByteString() }, CacheNotifyAction.Any);
}
public byte[] GetUserPhoto(int tenant, Guid id)
@ -256,7 +256,7 @@ namespace ASC.Core.Caching
{
if (CoreContext.Configuration.Personal && userInfo != null)
{
var key = GetUserCacheKeyForPersonal(userInfo.Tenant, Guid.Parse(userInfo.ID));
var key = GetUserCacheKeyForPersonal(userInfo.Tenant, userInfo.ID.FromByteString());
cache.Remove(key);
}

View File

@ -26,6 +26,7 @@
using System;
using System.Diagnostics;
using ASC.Common.Caching;
using ASC.Core.Caching;
namespace ASC.Core
@ -79,17 +80,11 @@ namespace ASC.Core
public static implicit operator UserGroupRef(UserGroupRefCacheItem cache)
{
var result = new UserGroupRef();
if (Guid.TryParse(cache.UserId, out var userId))
var result = new UserGroupRef
{
result.UserId = userId;
}
if (Guid.TryParse(cache.GroupId, out var groupId))
{
result.GroupId = groupId;
}
UserId = cache.UserId.FromByteString(),
GroupId = cache.GroupId.FromByteString()
};
if (Enum.TryParse<UserGroupRefType>(cache.RefType, out var refType))
{
@ -107,8 +102,8 @@ namespace ASC.Core
{
return new UserGroupRefCacheItem
{
GroupId = cache.GroupId.ToString(),
UserId = cache.UserId.ToString(),
GroupId = cache.GroupId.ToByteString(),
UserId = cache.UserId.ToByteString(),
RefType = cache.RefType.ToString(),
LastModified = cache.LastModified.Ticks,
Removed = cache.Removed,

View File

@ -3,8 +3,8 @@
package ASC.Core.Caching;
message UserGroupRefCacheItem {
string UserId = 1;
string GroupId = 2;
bytes UserId = 1;
bytes GroupId = 2;
bool Removed = 3;
string RefType = 4;
int64 LastModified = 5;

View File

@ -3,6 +3,6 @@
package ASC.Core.Caching;
message UserInfoCacheItem {
string ID = 1;
bytes ID = 1;
int32 Tenant = 2;
}

View File

@ -42,11 +42,13 @@ namespace ASC.Data.Storage
{
private const string DefaultTenantName = "default";
private static readonly ICacheNotify<DataStoreCacheItem> Cache;
private static readonly Lazy<Configuration.Storage> Section;
static StorageFactory()
{
Cache = new KafkaCache<DataStoreCacheItem>();
Cache.Subscribe((r) => DataStoreCache.Remove(r.TenantId, r.Module), CacheNotifyAction.Remove);
Section = new Lazy<Configuration.Storage>(() => CommonServiceProvider.GetService<Configuration.Storage>(), true);
}
public static IDataStore GetStorage(string tenant, string module)
@ -78,7 +80,7 @@ namespace ASC.Data.Storage
var store = DataStoreCache.Get(tenant, module);
if (store == null)
{
var section = CommonServiceProvider.GetService<Configuration.Storage>();
var section = Section.Value;
if (section == null)
{
throw new InvalidOperationException("config section not found");
@ -98,7 +100,7 @@ namespace ASC.Data.Storage
//Make tennant path
tenant = TennantPath.CreatePath(tenant);
var section = CommonServiceProvider.GetService<Configuration.Storage>();
var section = Section.Value;
if (section == null)
{
throw new InvalidOperationException("config section not found");
@ -110,7 +112,7 @@ namespace ASC.Data.Storage
public static IEnumerable<string> GetModuleList(string configpath, bool exceptDisabledMigration = false)
{
var section = CommonServiceProvider.GetService<Configuration.Storage>();
var section = Section.Value;
return section.Module
.Where(x => x.Visible)
.Where(x => !exceptDisabledMigration || !x.DisableMigrate)
@ -119,7 +121,7 @@ namespace ASC.Data.Storage
public static IEnumerable<string> GetDomainList(string configpath, string modulename)
{
var section = CommonServiceProvider.GetService<Configuration.Storage>();
var section = Section.Value;
if (section == null)
{
throw new ArgumentException("config section not found");
@ -218,7 +220,7 @@ namespace ASC.Data.Storage
private static IDataStore GetDataStore(string tenant, string module, DataStoreConsumer consumer, IQuotaController controller)
{
var storage = CommonServiceProvider.GetService<Configuration.Storage>();
var storage = Section.Value;
var moduleElement = storage.GetModuleElement(module);
if (moduleElement == null)
{

View File

@ -0,0 +1,23 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = (config) => {
config.plugins.push(
new CopyWebpackPlugin(
[
{
from: path.join('src', path.sep, '**', path.sep, 'locales', path.sep, '**'),
to: 'locales',
transformPath(targetPath) {
const reversedArrayOfFolders = path.dirname(targetPath).split(path.sep).reverse();
const localePath = reversedArrayOfFolders.pop();
const finalPath = path.join(path.sep, localePath, path.sep, reversedArrayOfFolders[2], path.sep, reversedArrayOfFolders[0], path.sep, path.basename(targetPath));
return finalPath;
},
}
]
)
);
return config;
}

View File

@ -38,6 +38,7 @@
"devDependencies": {
"ajv": "^6.10.0",
"babel-eslint": "10.0.1",
"copy-webpack-plugin": "^5.0.4",
"cross-env": "^5.2.0",
"eslint": "^5.16.0",
"eslint-config-react-app": "^4.0.1",
@ -45,6 +46,7 @@
"eslint-plugin-import": "^2.18.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.14.2",
"react-app-rewired": "^2.1.3",
"redux-devtools-extension": "^2.13.8",
"rimraf": "2.6.3"
},
@ -52,9 +54,9 @@
"extends": "react-app"
},
"scripts": {
"start": "rimraf ./build && react-scripts start",
"build": "react-scripts build",
"test": "cross-env CI=true react-scripts test --env=jsdom",
"start": "rimraf ./build && react-app-rewired start",
"build": "react-app-rewired build",
"test": "cross-env CI=true react-app-rewired test --env=jsdom",
"eject": "react-scripts eject",
"lint": "eslint ./src/"
},

View File

@ -10,7 +10,7 @@ const SectionPagingContent = ({
onLoading,
selectedCount
}) => {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const onNextClick = useCallback(
e => {
if (!filter.hasNext()) {

View File

@ -129,7 +129,7 @@ const ProfileInfo = (props) => {
const { isVisitor, email, activationStatus, department, title, mobilePhone, sex, workFrom, birthday, location, cultureName, currentCulture } = props.profile;
const isAdmin = props.isAdmin;
const isSelf = props.isSelf;
const { t, i18n } = useTranslation();
const t = props.t;
const type = isVisitor ? "Guest" : "Employee";
const language = cultureName || currentCulture;
@ -147,7 +147,7 @@ const ProfileInfo = (props) => {
<InfoContainer>
<InfoItem>
<InfoItemLabel>
{t('Resource:UserType')}:
{t('UserType')}:
</InfoItemLabel>
<InfoItemValue>
{type}
@ -156,7 +156,7 @@ const ProfileInfo = (props) => {
{email &&
<InfoItem>
<InfoItemLabel>
{t('Resource:Email')}:
{t('Email')}:
</InfoItemLabel>
<InfoItemValue>
<Link
@ -167,18 +167,18 @@ const ProfileInfo = (props) => {
onClick={onEmailClick}
>
{activationStatus === 2 && (isAdmin || isSelf) &&
<IconButtonWrapper isBefore={true} title='Pending'>
<IconButtonWrapper isBefore={true} title={t('PendingTitle')}>
<IconButton color='#C96C27' size={16} iconName='DangerIcon' isFill={true} />
</IconButtonWrapper>
}
{email}
{(isAdmin || isSelf) &&
<IconButtonWrapper title='Change e-mail' >
<IconButtonWrapper title={t('EmailChangeButton')} >
<IconButton color="#A3A9AE" size={16} iconName='AccessEditIcon' isFill={true} />
</IconButtonWrapper>
}
{activationStatus === 2 && (isAdmin || isSelf) &&
<IconButtonWrapper title='Send invitation once again'>
<IconButtonWrapper title={t('SendInviteAgain')}>
<IconButton color="#A3A9AE" size={16} iconName='FileActionsConvertIcon' isFill={true} />
</IconButtonWrapper>
}
@ -209,12 +209,12 @@ const ProfileInfo = (props) => {
{(mobilePhone || isSelf) &&
<InfoItem>
<InfoItemLabel>
Phone:
{t('PhoneLbl')}:
</InfoItemLabel>
<InfoItemValue>
{mobilePhone}
{(isAdmin || isSelf) &&
<IconButtonWrapper title='Change phone' >
<IconButtonWrapper title={t('PhoneChange')} >
<IconButton color="#A3A9AE" size={16} iconName='AccessEditIcon' isFill={true} />
</IconButtonWrapper>
}
@ -224,7 +224,7 @@ const ProfileInfo = (props) => {
{sex &&
<InfoItem>
<InfoItemLabel>
{t('Resource:Sex')}:
{t('Sex')}:
</InfoItemLabel>
<InfoItemValue>
{formatedSex}
@ -244,7 +244,7 @@ const ProfileInfo = (props) => {
{birthday &&
<InfoItem>
<InfoItemLabel>
{t('Resource:Birthdate')}:
{t('Birthdate')}:
</InfoItemLabel>
<InfoItemValue>
{birthDayDate}
@ -254,7 +254,7 @@ const ProfileInfo = (props) => {
{location &&
<InfoItem>
<InfoItemLabel>
{t('Resource:Location')}:
{t('Location')}:
</InfoItemLabel>
<InfoItemValue>
{location}
@ -264,7 +264,7 @@ const ProfileInfo = (props) => {
{isSelf &&
<InfoItem>
<InfoItemLabel>
{t('Resource:Language')}:
{t('Language')}:
</InfoItemLabel>
<InfoItemValue>
{language}
@ -276,7 +276,7 @@ const ProfileInfo = (props) => {
};
const SectionBodyContent = props => {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { profile, history, settings, isAdmin, viewer } = props;
const contacts = profile.contacts && getUserContacts(profile.contacts);
@ -308,20 +308,20 @@ const SectionBodyContent = props => {
<EditButtonWrapper>
<Button
size="big"
label={t("Resource:EditUserDialogTitle")}
label={t("EditUserDialogTitle")}
onClick={onEditProfileClick}
/>
</EditButtonWrapper>
)}
</AvatarWrapper>
<ProfileInfo profile={profile} isSelf={isSelf} isAdmin={isAdmin} />
<ProfileInfo profile={profile} isSelf={isSelf} isAdmin={isAdmin} t={t}/>
{isSelf && (
<ToggleWrapper isSelf={true} >
<ToggleContent label={t('Resource:Subscriptions')} isOpen={true} >
<ToggleContent label={t('Subscriptions')} isOpen={true} >
<Text.Body as="span">
<Button
size="big"
label="Edit subscriptions"
label={t('EditSubscriptionsBtn')}
primary={true}
onClick={onEditSubscriptionsClick}
/>
@ -331,21 +331,21 @@ const SectionBodyContent = props => {
)}
{profile.notes && (
<ToggleWrapper>
<ToggleContent label={t('Resource:Comments')} isOpen={true} >
<ToggleContent label={t('Comments')} isOpen={true} >
<Text.Body as="span">{profile.notes}</Text.Body>
</ToggleContent>
</ToggleWrapper>
)}
{profile.contacts && (
<ToggleWrapper isContacts={true} >
<ToggleContent label={t('Resource:ContactInformation')} isOpen={true} >
<ToggleContent label={t('ContactInformation')} isOpen={true} >
<Text.Body as="span">{infoContacts}</Text.Body>
</ToggleContent>
</ToggleWrapper>
)}
{profile.contacts && (
<ToggleWrapper isContacts={true} >
<ToggleContent label={t('Resource:SocialProfiles')} isOpen={true} >
<ToggleContent label={t('SocialProfiles')} isOpen={true} >
<Text.Body as="span">{socialContacts}</Text.Body>
</ToggleContent>
</ToggleWrapper>

View File

@ -9,6 +9,7 @@ import {
import { withRouter } from "react-router";
import { isAdmin, isMe } from "../../../../../store/auth/selectors";
import { getUserStatus } from "../../../../../store/people/selectors";
import { useTranslation } from 'react-i18next';
const wrapperStyle = {
display: "flex",
@ -67,7 +68,7 @@ const SectionHeaderContent = props => {
toastr.success("Context action: Invite again");
};
const getUserContextOptions = (user, viewer) => {
const getUserContextOptions = (user, viewer, t) => {
let status = "";
@ -81,32 +82,32 @@ const SectionHeaderContent = props => {
return [
{
key: "edit",
label: "Edit profile",
label: t('EditUserDialogTitle'),
onClick: onEditClick.bind(this, user)
},
{
key: "edit-photo",
label: "Edit Photo",
label: t('EditPhoto'),
onClick: onEditPhoto
},
{
key: "change-email",
label: "Change e-mail",
label: t('EmailChangeButton'),
onClick: onChangeEmailClick
},
{
key: "change-phone",
label: "Change phone",
label: t('PhoneChange'),
onClick: onChangePhoneClick
},
{
key: "change-password",
label: "Change password",
label: t('PasswordChangeButton'),
onClick: onChangePasswordClick
},
{
key: "disable",
label: "Disable",
label: t('DisableUserButton'),
onClick: onDisableClick
}
];
@ -114,27 +115,27 @@ const SectionHeaderContent = props => {
return [
{
key: "enable",
label: "Enable",
label: t('EnableUserButton'),
onClick: onEnableClick
},
{
key: "edit-photo",
label: "Edit photo",
label: t('EditPhoto'),
onClick: onEditPhoto
},
{
key: "reassign-data",
label: "Reassign data",
label: t('ReassignData'),
onClick: onReassignDataClick
},
{
key: "delete-personal-data",
label: "Delete personal data",
label: t('RemoveData'),
onClick: onDeletePersonalDataClick.bind(this, user)
},
{
key: "delete-profile",
label: "Delete profile",
label: t('DeleteSelfProfile'),
onClick: onDeleteProfileClick
}
];
@ -142,22 +143,22 @@ const SectionHeaderContent = props => {
return [
{
key: "edit",
label: "Edit",
label: t('EditButton'),
onClick: onEditClick.bind(this, user)
},
{
key: "edit-photo",
label: "Edit Photo",
label: t('EditPhoto'),
onClick: onEditPhoto
},
{
key: "invite-again",
label: "Invite again",
label: t('InviteAgainLbl'),
onClick: onInviteAgainClick
},
{
key: "key5",
label: "Disable",
label: t('DisableUserButton'),
onClick: onDisableClick
}
];
@ -166,7 +167,8 @@ const SectionHeaderContent = props => {
}
};
const contextOptions = () => getUserContextOptions(profile, viewer);
const { t } = useTranslation();
const contextOptions = () => getUserContextOptions(profile, viewer, t);
return (
<div style={wrapperStyle}>
@ -180,12 +182,12 @@ const SectionHeaderContent = props => {
</div>
<Text.ContentHeader truncate={true} style={textStyle}>
{profile.displayName}
{profile.isLDAP && " (LDAP)"}
{profile.isLDAP && ` (${t('LDAPLbl')})`}
</Text.ContentHeader>
{(isAdmin || isMe(viewer, profile.userName)) && (
<ContextMenuButton
directionX="right"
title="Actions"
title={t('Actions')}
iconName="VerticalDotsIcon"
size={16}
color="#A3A9AE"

View File

@ -0,0 +1,25 @@
import i18n from 'i18next';
import Backend from 'i18next-xhr-backend';
import config from '../../../../package.json';
const newInstance = i18n.createInstance();
newInstance
.use(Backend)
.init({
fallbackLng: 'en',
debug: true,
backend: {
loadPath: `${config.homepage}/locales/Profile/{{lng}}/{{ns}}.json`,
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
export default newInstance;

View File

@ -5,6 +5,8 @@ import { PageLayout, Loader } from "asc-web-components";
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
import { SectionHeaderContent, SectionBodyContent } from './Section';
import { setProfile, fetchProfile, resetProfile } from '../../../store/profile/actions';
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
class Profile extends React.Component {
constructor(props) {
@ -33,26 +35,29 @@ class Profile extends React.Component {
const { profile } = this.props;
return (
profile
? <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={
<SectionHeaderContent profile={profile} />
}
sectionBodyContent={
<SectionBodyContent profile={profile} />
}
/>
: <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionBodyContent={
<Loader className="pageLoader" type="rombs" size={40} />
}
/>
<I18nextProvider i18n={i18n}>
{profile
?
<PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={
<SectionHeaderContent profile={profile} />
}
sectionBodyContent={
<SectionBodyContent profile={profile} />
}
/>
: <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionBodyContent={
<Loader className="pageLoader" type="rombs" size={40} />
}
/>}
</I18nextProvider>
);
};
};

View File

@ -0,0 +1,30 @@
{
"UserType": "Type",
"Email": "Email",
"Sex": "Sex",
"Birthdate": "Date of Birth",
"Location": "Location",
"Language": "Language",
"EditUserDialogTitle": "Edit Profile",
"Subscriptions": "Subscriptions",
"Comments": "Comments",
"ContactInformation": "Contact Information",
"PendingTitle": "Pending",
"EmailChangeButton": "Change email",
"SendInviteAgain": "Send invitation once again",
"EditPhoto": "Edit Photo",
"PasswordChangeButton": "Change password",
"DisableUserButton": "Disable",
"EnableUserButton": "Enable",
"ReassignData": "Reassign data",
"RemoveData": "Delete personal data",
"DeleteSelfProfile": "Delete profile",
"EditButton": "Edit",
"Actions": "Actions",
"PhoneChange": "Change phone",
"PhoneLbl": "Phone",
"EditSubscriptionsBtn": "Edit subscriptions",
"InviteAgainLbl": "Invite again",
"LDAPLbl": "LDAP"
}

View File

@ -3,6 +3,7 @@ import styled from 'styled-components';
import { connect } from 'react-redux';
import { withRouter } from "react-router";
import { IconButton, Text } from 'asc-web-components';
import { useTranslation } from 'react-i18next';
const Wrapper = styled.div`
display: flex;
@ -15,12 +16,13 @@ const Header = styled(Text.ContentHeader)`
const SectionHeaderContent = (props) => {
const {profile, history, settings} = props;
const { t } = useTranslation();
const headerText = profile && profile.displayName
? profile.displayName
: profile.isVisitor
? "New guest"
: "New employee";
: profile.isVisitor
? t('NewGuest')
: t('NewEmployee');
const onClick = useCallback(() => {
history.push(settings.homepage)

View File

@ -0,0 +1,25 @@
import i18n from 'i18next';
import Backend from 'i18next-xhr-backend';
import config from '../../../../package.json';
const newInstance = i18n.createInstance();
newInstance
.use(Backend)
.init({
fallbackLng: 'en',
debug: true,
backend: {
loadPath: `${config.homepage}/locales/ProfileAction/{{lng}}/{{ns}}.json`,
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
export default newInstance;

View File

@ -5,6 +5,8 @@ import { PageLayout, Loader } from "asc-web-components";
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
import { SectionHeaderContent, CreateUserForm, UpdateUserForm } from './Section';
import { setProfile, fetchProfile, resetProfile } from '../../../store/profile/actions';
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
class ProfileAction extends React.Component {
componentDidMount() {
@ -37,7 +39,8 @@ class ProfileAction extends React.Component {
const { profile } = this.props;
return (
profile
<I18nextProvider i18n={i18n}>
{profile
? <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
@ -50,7 +53,8 @@ class ProfileAction extends React.Component {
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionBodyContent={<Loader className="pageLoader" type="rombs" size={40} />}
/>
/>}
</I18nextProvider>
);
}
}

View File

@ -0,0 +1,22 @@
{
"EditPhoto": "Edit Photo",
"FirstName": "First Name",
"LastName": "Last Name",
"Email": "Email",
"Password": "Password",
"Birthdate": "Date of Birth",
"Sex": "Sex",
"Location": "Location",
"Comments": "Comments",
"SaveButton": "Save",
"CancelButton": "Cancel",
"ActivationLink": "Activation link",
"AddPhoto": "Add photo",
"TemporaryPassword": "Temporary password",
"SexMale": "Male",
"SexFemale": "Female",
"RequiredField": "Required field",
"NewEmployee": "New employee",
"NewGuest": "New guest"
}

View File

@ -4,8 +4,9 @@ import Backend from 'i18next-xhr-backend';
import { initReactI18next } from 'react-i18next';
import config from '../package.json';
const newInstance = i18n.createInstance();
i18n
newInstance
// load translation using xhr -> see /public/locales
// learn more: https://github.com/i18next/i18next-xhr-backend
.use(Backend)
@ -18,10 +19,11 @@ i18n
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
lng: 'en',
lng: 'ru',
fallbackLng: 'en',
debug: true,
ns: ['PeopleJSResource', 'PeopleResource', 'Resource', 'UserControlsCommonResource'],
defaultNS: 'PeopleJSResource',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
@ -34,4 +36,4 @@ i18n
}
});
export default i18n;
export default newInstance;

View File

@ -7,7 +7,8 @@ import { AUTH_KEY } from "./helpers/constants";
import store from "./store/store";
import "./custom.scss";
import App from "./App";
import "./i18n";
import i18n from "./i18n";
import {I18nextProvider} from "react-i18next";
import * as serviceWorker from "./serviceWorker";
import { setIsLoaded, getUserInfo } from "./store/auth/actions";
@ -23,9 +24,11 @@ else {
}
ReactDOM.render(
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<App />
</Provider>,
</Provider>
</I18nextProvider>,
document.getElementById("root")
);

View File

@ -0,0 +1,55 @@
{
"Article" :
{
},
"pages":
{
"Profile" :
{
"Resource.UserType" : "UserType",
"Resource.Email" : "Email",
"Resource.Sex" : "Sex",
"Resource.Birthdate" : "Birthdate",
"Resource.Location" : "Location",
"Resource.Language" : "Language",
"Resource.EditUserDialogTitle" : "EditUserDialogTitle",
"Resource.Subscriptions" : "Subscriptions",
"Resource.Comments" : "Comments",
"Resource.ContactInformation" : "ContactInformation",
"Resource.SocialProfiles" : "SocialProfiles",
"Resource.PendingTitle" : "PendingTitle",
"Resource.EmailChangeButton" : "EmailChangeButton",
"Resource.SendInviteAgain" : "SendInviteAgain",
"Resource.EditPhoto" : "EditPhoto",
"Resource.PasswordChangeButton" : "PasswordChangeButton",
"Resource.DisableUserButton" : "DisableUserButton",
"Resource.EnableUserButton" : "EnableUserButton",
"Resource.ReassignData" : "ReassignData",
"Resource.RemoveData" : "RemoveData",
"Resource.DeleteSelfProfile" : "DeleteSelfProfile",
"Resource.EditButton" : "EditButton",
"Resource.Actions" : "Actions"
},
"ProfileAction": {
"Resource.EditPhoto" : "EditPhoto",
"Resource.FirstName" : "FirstName",
"Resource.LastName" : "LastName",
"Resource.Email" : "Email",
"Resource.Password" : "Password",
"Resource.Birthdate" : "Birthdate",
"Resource.Sex" : "Sex",
"Resource.Location" : "Location",
"Resource.Comments" : "Comments",
"Resource.SaveButton" : "SaveButton",
"Resource.CancelButton" : "CancelButton"
}
},
"Layout":
{
"Resource.Profile" : "Profile",
"Resource.AboutCompanyTitle" : "AboutCompanyTitle",
"Resource.LogoutButton" : "LogoutButton"
}
}

View File

@ -1522,9 +1522,9 @@ acorn-globals@^4.1.0, acorn-globals@^4.3.0:
acorn-walk "^6.0.1"
acorn-jsx@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
version "5.0.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f"
integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==
acorn-walk@^6.0.1:
version "6.2.0"
@ -1553,11 +1553,16 @@ add-px-to-style@1.0.0:
resolved "https://registry.yarnpkg.com/add-px-to-style/-/add-px-to-style-1.0.0.tgz#d0c135441fa8014a8137904531096f67f28f263a"
integrity sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo=
address@1.1.0, address@^1.0.1:
address@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/address/-/address-1.1.0.tgz#ef8e047847fcd2c5b6f50c16965f924fd99fe709"
integrity sha512-4diPfzWbLEIElVG4AnqP+00SULlPzNuyJFNnmMrLgyaxG6tZXJ1sn7mjBu4fHrJE+Yp/jgylOweJn2xsLMFggQ==
address@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
ajv-errors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
@ -1598,13 +1603,6 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0:
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
ansi-escapes@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
integrity sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==
dependencies:
type-fest "^0.5.2"
ansi-html@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@ -1759,13 +1757,13 @@ asap@~2.0.6:
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
"asc-web-components@file:../../../packages/asc-web-components":
version "1.0.0"
version "1.0.5"
dependencies:
"@emotion/core" "10.0.14"
prop-types "^15.7.2"
rc-tree "^2.1.2"
react-autosize-textarea "^7.0.0"
react-calendar "file:../../../packages/react-calendar/react-calendar-v2.13.2.tgz"
react-calendar "file:C:/Users/Daniil.Senkiv/AppData/Local/Yarn/Cache/v4/npm-asc-web-components-1.0.5-4a208de7-5906-4bbb-ba7c-f886a5d7f9a4-1566891423511/packages/react-calendar/react-calendar-v2.13.2.tgz"
react-custom-scrollbars "^4.2.1"
react-datepicker "^2.7.0"
react-toastify "^5.3.2"
@ -2325,7 +2323,7 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
cacache@^11.0.2:
cacache@^11.0.2, cacache@^11.3.3:
version "11.3.3"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc"
integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==
@ -2346,9 +2344,9 @@ cacache@^11.0.2:
y18n "^4.0.0"
cacache@^12.0.2:
version "12.0.2"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.2.tgz#8db03205e36089a3df6954c66ce92541441ac46c"
integrity sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==
version "12.0.3"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390"
integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==
dependencies:
bluebird "^3.5.5"
chownr "^1.1.1"
@ -2509,9 +2507,9 @@ chardet@^0.7.0:
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4:
version "2.1.6"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
dependencies:
anymatch "^2.0.0"
async-each "^1.0.1"
@ -2581,13 +2579,6 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
dependencies:
restore-cursor "^3.1.0"
cli-width@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
@ -2877,6 +2868,24 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-webpack-plugin@^5.0.4:
version "5.0.4"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655"
integrity sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==
dependencies:
cacache "^11.3.3"
find-cache-dir "^2.1.0"
glob-parent "^3.1.0"
globby "^7.1.1"
is-glob "^4.0.1"
loader-utils "^1.2.3"
minimatch "^3.0.4"
normalize-path "^3.0.0"
p-limit "^2.2.0"
schema-utils "^1.0.0"
serialize-javascript "^1.7.0"
webpack-log "^2.0.0"
core-js-compat@^3.1.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.2.1.tgz#0cbdbc2e386e8e00d3b85dc81c848effec5b8150"
@ -3089,9 +3098,9 @@ css-select@^2.0.0:
nth-check "^1.0.2"
css-to-react-native@^2.2.2:
version "2.3.1"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.3.1.tgz#cf0f61e0514846e2d4dc188b0886e29d8bef64a2"
integrity sha512-yO+oEx1Lf+hDKasqQRVrAvzMCz825Huh1VMlEEDlRWyAhFb/FWb6I0KpEF1PkyKQ7NEdcx9d5M2ZEWgJAsgPvQ==
version "2.3.2"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.3.2.tgz#e75e2f8f7aa385b4c3611c52b074b70a002f2e7d"
integrity sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
@ -3264,9 +3273,9 @@ data-urls@^1.0.0, data-urls@^1.1.0:
whatwg-url "^7.0.0"
date-fns@^v2.0.0-beta.1:
version "2.0.0-beta.5"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-beta.5.tgz#90885db3772802d55519cd12acd49de56aca1059"
integrity sha512-GS5yi964NDFNoja9yOdWFj9T97T67yLrUeJZgddHaVfc/6tHWtX7RXocuubmZkNzrZUZ9BqBOW7jTR5OoWjJ1w==
version "2.0.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.1.tgz#c5f30e31d3294918e6b6a82753a4e719120e203d"
integrity sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw==
date-now@^0.1.4:
version "0.1.4"
@ -3455,6 +3464,13 @@ dir-glob@2.0.0:
arrify "^1.0.1"
path-type "^3.0.0"
dir-glob@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
dependencies:
path-type "^3.0.0"
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@ -3585,7 +3601,7 @@ dotenv-expand@4.2.0:
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-4.2.0.tgz#def1f1ca5d6059d24a766e587942c21106ce1275"
integrity sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU=
dotenv@6.2.0:
dotenv@6.2.0, dotenv@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
@ -3619,9 +3635,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.191:
version "1.3.235"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.235.tgz#8d0d52c9ec76d12189f2f2d265a17d57f41d20dc"
integrity sha512-xNabEDbMIKPLQd6xgv4nyyeMaWXIKSJr6G51ZhUemHhbz6kjZAYcygA8CvfEcMF+Mt5eLmDWaLmfSOWdQxzBVQ==
version "1.3.241"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.241.tgz#859dc49ab7f90773ed698767372d384190f60cb1"
integrity sha512-Gb9E6nWZlbgjDDNe5cAvMJixtn79krNJ70EDpq/M10lkGo7PGtBUe7Y0CYVHsBScRwi6ybCS+YetXAN9ysAHDg==
elliptic@^6.0.0:
version "6.5.0"
@ -3641,11 +3657,6 @@ emoji-regex@^7.0.1, emoji-regex@^7.0.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@ -3904,9 +3915,9 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3:
estraverse "^4.1.1"
eslint-utils@^1.3.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.0.tgz#e2c3c8dba768425f897cf0f9e51fe2e241485d4c"
integrity sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==
version "1.4.2"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
dependencies:
eslint-visitor-keys "^1.0.0"
@ -4227,13 +4238,6 @@ figures@^2.0.0:
dependencies:
escape-string-regexp "^1.0.5"
figures@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.0.0.tgz#756275c964646163cc6f9197c7a0295dbfd04de9"
integrity sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==
dependencies:
escape-string-regexp "^1.0.5"
file-entry-cache@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
@ -4643,6 +4647,18 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
globby@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680"
integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA=
dependencies:
array-union "^1.0.1"
dir-glob "^2.0.0"
glob "^7.1.2"
ignore "^3.3.5"
pify "^3.0.0"
slash "^1.0.0"
globule@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
@ -5179,21 +5195,21 @@ inquirer@6.5.0:
through "^2.3.6"
inquirer@^6.2.2:
version "6.5.1"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.1.tgz#8bfb7a5ac02dac6ff641ac4c5ff17da112fcdb42"
integrity sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==
version "6.5.2"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
dependencies:
ansi-escapes "^4.2.1"
ansi-escapes "^3.2.0"
chalk "^2.4.2"
cli-cursor "^3.1.0"
cli-cursor "^2.1.0"
cli-width "^2.0.0"
external-editor "^3.0.3"
figures "^3.0.0"
lodash "^4.17.15"
mute-stream "0.0.8"
figures "^2.0.0"
lodash "^4.17.12"
mute-stream "0.0.7"
run-async "^2.2.0"
rxjs "^6.4.0"
string-width "^4.1.0"
string-width "^2.1.0"
strip-ansi "^5.1.0"
through "^2.3.6"
@ -5390,11 +5406,6 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-generator-fn@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
@ -5407,7 +5418,7 @@ is-glob@^3.1.0:
dependencies:
is-extglob "^2.1.0"
is-glob@^4.0.0:
is-glob@^4.0.0, is-glob@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
@ -5523,11 +5534,6 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
is-wsl@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.0.tgz#94369bbeb2249ef07b831b1b08590e686330ccbb"
integrity sha512-pFTjpv/x5HRj8kbZ/Msxi9VrvtOMRBqaDi3OIcbwPI3OuH+r3lLxVWukLITBaOGJIbA/w2+M1eVmVa4XNQlAmQ==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@ -6578,9 +6584,9 @@ mem@^4.0.0:
p-is-promise "^2.0.0"
"memoize-one@>=3.1.1 <6", memoize-one@^5.0.0:
version "5.0.5"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.5.tgz#8cd3809555723a07684afafcd6f756072ac75d7e"
integrity sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ==
version "5.1.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
version "0.4.1"
@ -6711,7 +6717,7 @@ mimic-fn@^1.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
mimic-fn@^2.0.0, mimic-fn@^2.1.0:
mimic-fn@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
@ -6767,9 +6773,9 @@ minimist@~0.0.1:
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
minipass@^2.2.1, minipass@^2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
version "2.4.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.4.0.tgz#38f0af94f42fb6f34d3d7d82a90e2c99cd3ff485"
integrity sha512-6PmOuSP4NnZXzs2z6rbwzLJu/c5gdzYg1mRI/WIYdx45iiX7T+a4esOzavD6V/KmBzAaopFSTZPZcUx73bqKWA==
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
@ -6865,11 +6871,6 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
mute-stream@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nan@^2.12.1, nan@^2.13.2:
version "2.14.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
@ -6991,15 +6992,15 @@ node-modules-regexp@^1.0.0:
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
node-notifier@^5.4.2:
version "5.4.2"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.2.tgz#a1111b8c1a4c3eb68d98815cc04a899456b03f1a"
integrity sha512-85nkTziazE2dR4pyoLxMwz0b9MmxFQPVXYs/WlWI7CPtBkARJOV+89khdNjpbclXIJDECQYnTvh1xuZV3WHkCA==
version "5.4.3"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50"
integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==
dependencies:
growly "^1.3.0"
is-wsl "^2.1.0"
semver "^6.3.0"
is-wsl "^1.1.0"
semver "^5.5.0"
shellwords "^0.1.1"
which "^1.3.1"
which "^1.3.0"
node-pre-gyp@^0.12.0:
version "0.12.0"
@ -7018,9 +7019,9 @@ node-pre-gyp@^0.12.0:
tar "^4"
node-releases@^1.1.25:
version "1.1.27"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.27.tgz#b19ec8add2afe9a826a99dceccc516104c1edaf4"
integrity sha512-9iXUqHKSGo6ph/tdXVbHFbhRVQln4ZDTIBJCzsa90HimnBYc5jw8RWYt4wBYFHehGyC3koIz5O4mb2fHrbPOuA==
version "1.1.28"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.28.tgz#503c3c70d0e4732b84e7aaa2925fbdde10482d4a"
integrity sha512-AQw4emh6iSXnCpDiFe0phYcThiccmkNWMZnFZ+lDJjAP8J0m2fVd59duvUUyuTirQOhIAajTFkzG6FHCLBO59g==
dependencies:
semver "^5.3.0"
@ -7278,13 +7279,6 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
onetime@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5"
integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==
dependencies:
mimic-fn "^2.1.0"
open@^6.3.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9"
@ -7402,7 +7396,7 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"
p-limit@^2.0.0:
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
@ -7691,9 +7685,9 @@ popper.js@^1.14.4:
integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
portfinder@^1.0.9:
version "1.0.22"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.22.tgz#abd10a488b5696e98ee25c60731f8ae0b76f8ddd"
integrity sha512-aZuwaz9ujJsyE8C5kurXAD8UmRxsJr+RtZWyQRvRk19Z2ri5uuHw5YS4tDBZrJlOS9Zw96uAbBuPb6W4wgvV5A==
version "1.0.23"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.23.tgz#894db4bcc5daf02b6614517ce89cd21a38226b82"
integrity sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==
dependencies:
async "^1.5.2"
debug "^2.2.0"
@ -8580,9 +8574,9 @@ raw-body@2.4.0:
unpipe "1.0.0"
rc-animate@^2.6.0:
version "2.9.2"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.9.2.tgz#5964767805c886f1bdc7563d3935a74912a0b78f"
integrity sha512-rkJjeJgfbDqVjVX1/QTRfS7PiCq3AnmeYo840cVcuC4pXq4k4yAQMsC2v5BPXXdawC04vnyO4/qHQdbx9ANaiw==
version "2.10.0"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.10.0.tgz#d2224cee4700cc9e9836700eb47af6b6e41a080c"
integrity sha512-gZM3WteZO0e3X8B71KP0bs95EY2tAPRuiZyKnlhdLpOjTX/64SrhDZM3pT2Z8mJjKWNiiB5q2SSSf+BD8ljwVw==
dependencies:
babel-runtime "6.x"
classnames "^2.2.6"
@ -8606,16 +8600,15 @@ rc-tree@^2.1.2:
warning "^4.0.3"
rc-util@^4.5.1, rc-util@^4.8.0:
version "4.10.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.10.0.tgz#25da93c78f996a23998c76922f75d54387c9e9fc"
integrity sha512-bDjf3Yx4rs/777G3TQ3J2Ii8AeEa/z8duR8efBQKH+ShuumtCvWiRA0/iAL2WZNdG3U1z+EIbocNNdcUpsPT7Q==
version "4.11.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.11.0.tgz#cf437dcff74ca08a8565ae14f0368acb3a650796"
integrity sha512-nB29kXOXsSVjBkWfH+Z1GVh6tRg7XGZtZ0Yfie+OI0stCDixGQ1cPrS6iYxlg+AV2St6COCK5MFrCmpTgghh0w==
dependencies:
add-dom-event-listener "^1.1.0"
babel-runtime "6.x"
prop-types "^15.5.10"
react-lifecycles-compat "^3.0.4"
shallowequal "^0.2.2"
warning "^4.0.3"
rc@^1.2.7:
version "1.2.8"
@ -8639,6 +8632,15 @@ react-app-polyfill@^1.0.1:
regenerator-runtime "0.13.3"
whatwg-fetch "3.0.0"
react-app-rewired@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/react-app-rewired/-/react-app-rewired-2.1.3.tgz#5ae8583ecc9f9f968d40b735d2abbe871378a52f"
integrity sha512-NXC2EsQrnEMV7xD70rHcBq0B4PSEzjY/K2m/e+GRgit2jZO/uZApnpCZSKvIX2leLRN69Sqf2id0VXZ1F62CDw==
dependencies:
cross-spawn "^6.0.5"
dotenv "^6.2.0"
semver "^5.6.0"
react-autosize-textarea@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/react-autosize-textarea/-/react-autosize-textarea-7.0.0.tgz#4f633e4238de7ba73c1da8fdc307353c50f1c5ab"
@ -8743,9 +8745,9 @@ react-lifecycles-compat@^3.0.4:
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-onclickoutside@^6.7.1:
version "6.8.0"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.8.0.tgz#9f91b5b3ed59f4d9e43fd71620dc200773a4d569"
integrity sha512-5Q4Rn7QLEoh7WIe66KFvYIpWJ49GeHoygP1/EtJyZjXKgrWH19Tf0Ty3lWyQzrEEDyLOwUvvmBFSE3dcDdvagA==
version "6.9.0"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f"
integrity sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A==
react-popper@^1.0.2, react-popper@^1.3.3:
version "1.3.4"
@ -9110,9 +9112,9 @@ regex-not@^1.0.0, regex-not@^1.0.2:
safe-regex "^1.1.0"
regexp-tree@^0.1.6:
version "0.1.11"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.11.tgz#c9c7f00fcf722e0a56c7390983a7a63dd6c272f3"
integrity sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==
version "0.1.12"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.12.tgz#28eaaa6e66eeb3527c15108a3ff740d9e574e420"
integrity sha512-TsXZ8+cv2uxMEkLfgwO0E068gsNMLfuYwMMhiUxf0Kw2Vcgzq93vgl6wIlIYuPmfMqMjfQ9zAporiozqCnwLuQ==
regexpp@^2.0.1:
version "2.0.1"
@ -9302,14 +9304,6 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
dependencies:
onetime "^5.1.0"
signal-exit "^3.0.2"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@ -9531,9 +9525,9 @@ send@0.17.1:
statuses "~1.5.0"
serialize-javascript@^1.4.0, serialize-javascript@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
version "1.8.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.8.0.tgz#9515fc687232e2321aea1ca7a529476eb34bb480"
integrity sha512-3tHgtF4OzDmeKYj6V9nSyceRS0UJ3C7VqyD2Yj28vC/z2j6jG5FmFGahOKMD9CrglxTm3tETr87jEypaYV8DUg==
serve-index@^1.7.2:
version "1.9.1"
@ -9965,15 +9959,6 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff"
integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^5.2.0"
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@ -10230,9 +10215,9 @@ terser@^3.16.1:
source-map-support "~0.5.10"
terser@^4.1.2:
version "4.1.4"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.1.4.tgz#4478b6a08bb096a61e793fea1a4434408bab936c"
integrity sha512-+ZwXJvdSwbd60jG0Illav0F06GDJF0R4ydZ21Q3wGAFKoBGyJGo34F63vzJHgvYxc1ukOtIjvwEvl9MkjzM6Pg==
version "4.2.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.2.1.tgz#1052cfe17576c66e7bc70fcc7119f22b155bdac1"
integrity sha512-cGbc5utAcX4a9+2GGVX4DsenG6v0x3glnDi5hx8816X1McEAwPlPgRtXPJzSBsbpILxZ8MQMT0KvArLuE0HP5A==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
@ -10450,11 +10435,6 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-fest@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2"
integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==
type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@ -10669,9 +10649,9 @@ utils-merge@1.0.1:
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@^3.0.1, uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
validate-npm-package-license@^3.0.1:
version "3.0.4"
@ -10926,7 +10906,7 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which@1, which@^1.2.9, which@^1.3.1:
which@1, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==

View File

@ -646,7 +646,7 @@ namespace ASC.Employee.Core.Controllers
}
else
{
result.Data = UserPhotoManager.SaveTempPhoto(data, SetupInfo.MaxImageUploadSize, UserPhotoManager.OriginalFotoSize.Width, UserPhotoManager.OriginalFotoSize.Height);
result.Data = UserPhotoManager.SaveTempPhoto(Tenant.TenantId, data, SetupInfo.MaxImageUploadSize, UserPhotoManager.OriginalFotoSize.Width, UserPhotoManager.OriginalFotoSize.Height);
}
result.Success = true;
@ -733,13 +733,13 @@ namespace ASC.Employee.Core.Controllers
if (!string.IsNullOrEmpty(thumbnailsModel.TmpFile))
{
var fileName = Path.GetFileName(thumbnailsModel.TmpFile);
var data = UserPhotoManager.GetTempPhotoData(fileName);
var data = UserPhotoManager.GetTempPhotoData(Tenant.TenantId, fileName);
var settings = new UserPhotoThumbnailSettings(thumbnailsModel.X, thumbnailsModel.Y, thumbnailsModel.Width, thumbnailsModel.Height);
settings.SaveForUser(user.ID);
UserPhotoManager.SaveOrUpdatePhoto(Tenant, user.ID, data);
UserPhotoManager.RemoveTempPhoto(fileName);
UserPhotoManager.RemoveTempPhoto(Tenant.TenantId, fileName);
}
else
{

View File

@ -32,6 +32,7 @@ using System.Linq;
using System.Net;
using System.ServiceModel.Security;
using System.Web;
using ASC.Api.Collections;
using ASC.Api.Core;
using ASC.Api.Utils;
@ -391,7 +392,7 @@ namespace ASC.Api.Settings
var logoDict = new Dictionary<int, string>();
model.Logo.ToList().ForEach(n => logoDict.Add(n.Key, n.Value));
_tenantWhiteLabelSettings.SetLogo(logoDict);
_tenantWhiteLabelSettings.SetLogo(Tenant.TenantId, logoDict);
}
_tenantWhiteLabelSettings.LogoText = model.LogoText;
@ -849,13 +850,13 @@ namespace ASC.Api.Settings
if (existItem.SmallImg != item.SmallImg)
{
StorageHelper.DeleteLogo(existItem.SmallImg);
existItem.SmallImg = StorageHelper.SaveTmpLogo(item.SmallImg);
existItem.SmallImg = StorageHelper.SaveTmpLogo(Tenant.TenantId, item.SmallImg);
}
if (existItem.BigImg != item.BigImg)
{
StorageHelper.DeleteLogo(existItem.BigImg);
existItem.BigImg = StorageHelper.SaveTmpLogo(item.BigImg);
existItem.BigImg = StorageHelper.SaveTmpLogo(Tenant.TenantId, item.BigImg);
}
exist = true;
@ -865,8 +866,8 @@ namespace ASC.Api.Settings
if (!exist)
{
item.Id = Guid.NewGuid();
item.SmallImg = StorageHelper.SaveTmpLogo(item.SmallImg);
item.BigImg = StorageHelper.SaveTmpLogo(item.BigImg);
item.SmallImg = StorageHelper.SaveTmpLogo(Tenant.TenantId, item.SmallImg);
item.BigImg = StorageHelper.SaveTmpLogo(Tenant.TenantId, item.BigImg);
settings.Items.Add(item);
}

View File

@ -11,6 +11,7 @@
"cSpell.words": [
"Romb",
"Rombs",
"combobox",
"reactstrap"
]
}

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-components",
"version": "1.0.8",
"version": "1.0.15",
"description": "Ascensio System SIA component library",
"license": "AGPL-3.0",
"main": "dist/asc-web-components.cjs.js",

View File

@ -0,0 +1,308 @@
import React, { memo } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import SearchInput from "../search-input";
import CustomScrollbarsVirtualList from "../scrollbar/custom-scrollbars-virtual-list";
import { FixedSizeList } from "react-window";
import Link from "../link";
import Checkbox from "../checkbox";
import Button from "../button";
import ComboBox from "../combobox";
import { isArrayEqual } from "../../utils/array";
import findIndex from "lodash/findIndex";
import filter from "lodash/filter";
const Container = ({
value,
placeholder,
isMultiSelect,
mode,
width,
maxHeight,
isDisabled,
onSearchChanged,
options,
selectedOptions,
buttonLabel,
selectAllLabel,
groups,
selectedGroups,
onChangeGroup,
...props
}) => <div {...props} />;
const StyledContainer = styled(Container)`
${props => (props.width ? `width: ${props.width}px;` : "")}
.options_searcher {
margin-bottom: 12px;
}
.options_group_selector {
margin-bottom: 12px;
}
.option_select_all_checkbox {
margin-bottom: 12px;
margin-left: 8px;
}
.options_list {
.option {
line-height: 32px;
cursor: pointer;
.option_checkbox {
margin-left: 8px;
}
.option_link {
padding-left: 8px;
}
&:hover {
background-color: #eceef1;
}
}
}
.add_members_btn {
margin: 16px 0;
}
`;
class AdvancedSelector extends React.Component {
constructor(props) {
super(props);
const groups = this.convertGroups(this.props.groups);
const currentGroup = this.getCurrentGroup(groups);
this.state = {
selectedOptions: this.props.selectedOptions || [],
selectedAll: this.props.selectedAll || false,
groups: groups,
currentGroup: currentGroup
};
}
componentDidUpdate(prevProps) {
if (!isArrayEqual(this.props.selectedOptions, prevProps.selectedOptions)) {
this.setState({ selectedOptions: this.props.selectedOptions });
}
if (this.props.isMultiSelect !== prevProps.isMultiSelect) {
this.setState({ selectedOptions: [] });
}
if (this.props.selectedAll !== prevProps.selectedAll) {
this.setState({ selectedAll: this.props.selectedAll });
}
if (!isArrayEqual(this.props.groups, prevProps.groups)) {
const groups = this.convertGroups(this.props.groups);
const currentGroup = this.getCurrentGroup(groups);
this.setState({ groups, currentGroup });
}
}
convertGroups = groups => {
if (!groups) return [];
const wrappedGroups = groups.map(this.convertGroup);
return wrappedGroups;
};
convertGroup = group => {
return {
key: group.key,
label: `${group.label} (${group.total})`,
total: group.total
};
};
getCurrentGroup = (groups) => {
const currentGroup = groups.length > 0 ? groups[0] : "No groups";
return currentGroup;
}
onButtonClick = () => {
this.props.onSelect &&
this.props.onSelect(
this.state.selectedAll ? this.props.options : this.state.selectedOptions
);
};
onSelect = option => {
this.props.onSelect && this.props.onSelect(option);
};
onChange = (option, e) => {
const { selectedOptions } = this.state;
const newSelectedOptions = e.target.checked
? [...selectedOptions, option]
: filter(selectedOptions, obj => obj.key !== option.key);
//console.log("onChange", option, e.target.checked, newSelectedOptions);
this.setState({
selectedOptions: newSelectedOptions,
selectedAll: newSelectedOptions.length === this.props.options.length
});
};
onSelectedAllChange = e => {
this.setState({
selectedAll: e.target.checked,
selectedOptions: e.target.checked ? this.props.options : []
});
};
onCurrentGroupChange = group => {
this.setState({
currentGroup: group
});
this.props.onChangeGroup && this.props.onChangeGroup(group);
};
renderRow = ({ data, index, style }) => {
const option = data[index];
var isChecked =
this.state.selectedAll ||
findIndex(this.state.selectedOptions, { key: option.key }) > -1;
//console.log("renderRow", option, isChecked, this.state.selectedOptions);
return (
<div className="option" style={style} key={option.key}>
{this.props.isMultiSelect ? (
<Checkbox
label={option.label}
isChecked={isChecked}
className="option_checkbox"
onChange={this.onChange.bind(this, option)}
/>
) : (
<Link
as="span"
truncate={true}
className="option_link"
onClick={this.onSelect.bind(this, option)}
>
{option.label}
</Link>
)}
</div>
);
};
render() {
const {
value,
placeholder,
maxHeight,
isDisabled,
onSearchChanged,
options,
isMultiSelect,
buttonLabel,
selectAllLabel
} = this.props;
const { selectedOptions, selectedAll, currentGroup, groups } = this.state;
console.log("AdvancedSelector render()", currentGroup, options);
return (
<StyledContainer {...this.props}>
<SearchInput
className="options_searcher"
isDisabled={isDisabled}
size="base"
scale={true}
isNeedFilter={false}
placeholder={placeholder}
value={value}
onChange={onSearchChanged}
onClearSearch={onSearchChanged.bind(this, "")}
/>
{groups && groups.length > 0 && (
<ComboBox
className="options_group_selector"
isDisabled={isDisabled}
options={groups}
selectedOption={currentGroup}
dropDownMaxHeight={200}
scaled={true}
size="content"
onSelect={this.onCurrentGroupChange}
/>
)}
{isMultiSelect && (
<Checkbox
label={selectAllLabel}
isChecked={selectedAll || selectedOptions.length === options.length}
isIndeterminate={!selectedAll && selectedOptions.length > 0}
className="option_select_all_checkbox"
onChange={this.onSelectedAllChange}
/>
)}
<FixedSizeList
className="options_list"
height={maxHeight}
itemSize={32}
itemCount={options.length}
itemData={options}
outerElementType={CustomScrollbarsVirtualList}
>
{this.renderRow.bind(this)}
</FixedSizeList>
{isMultiSelect && (
<Button
className="add_members_btn"
primary={true}
size="big"
label={buttonLabel}
scale={true}
isDisabled={
!this.state.selectedOptions || !this.state.selectedOptions.length
}
onClick={this.onButtonClick}
/>
)}
</StyledContainer>
);
}
}
AdvancedSelector.propTypes = {
value: PropTypes.string,
placeholder: PropTypes.string,
isMultiSelect: PropTypes.bool,
mode: PropTypes.oneOf(["base", "compact"]),
width: PropTypes.number,
maxHeight: PropTypes.number,
isDisabled: PropTypes.bool,
onSearchChanged: PropTypes.func,
options: PropTypes.array.isRequired,
selectedOptions: PropTypes.array,
groups: PropTypes.array,
selectedGroups: PropTypes.array,
selectedAll: PropTypes.bool,
selectAllLabel: PropTypes.string,
buttonLabel: PropTypes.string,
onSelect: PropTypes.func,
onChangeGroup: PropTypes.func,
};
AdvancedSelector.defaultProps = {
isMultiSelect: false,
width: 325,
maxHeight: 545,
mode: "base",
buttonLabel: "Add members",
selectAllLabel: "Select all"
};
export default AdvancedSelector;

View File

@ -26,7 +26,10 @@ const ComboBoxDateStyle = styled.div`
const CalendarStyle = styled.div`
min-width: 280px;
max-width: 293px;
/*width: 100%;*/
width: 325px;
border-radius: 6px;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
@ -40,15 +43,16 @@ const CalendarStyle = styled.div`
font-weight: bold;
font-size: 13px;
text-align: center;
/*cursor: default;*/
${props => props.disabled ? "pointer-events: none;" : "pointer-events: auto;"}
.calendar-month {
${HoverStyle}
}
.calendar-month_neighboringMonth {
color: #ECEEF1;
pointer-events: none;
${HoverStyle}
&:hover {color: #333;}
}
.calendar-month_weekend {
@ -61,27 +65,26 @@ const CalendarStyle = styled.div`
}
.calendar-month_selected-day {
background-color: ${props => props.color};
border-radius: 16px;
cursor: pointer;
color: #fff;
}
.calendar-month_disabled {
pointer-events: none;
}
`;
const Weekday = styled.div`
display: flex;
overflow: hidden;
/*flex-basis: 100%;*/
flex-basis: 14.2857%;
/*min-height: 32px;*/
/*min-width: 32px;*/
padding-left: 4px;
`;
const Weekdays = styled.div`
display: flex;
margin-bottom: 15px;
`;
const Month = styled.div`
@ -99,8 +102,6 @@ const Day = styled.div`
padding: 4px;
text-align: center;
line-height: 2.5em;
/*min-height: 32px;*/
/*min-width: 32px;*/
`;
const AbbrDay = styled.abbr`
@ -120,43 +121,85 @@ class Calendar extends Component {
};
selectedYear = (value) => {
onSelectYear = (value) => {
this.setState({ openToDate: new Date(value.key, this.state.openToDate.getMonth()) });
}
selectedMonth = (value) => {
onSelectMonth = (value) => {
this.setState({ openToDate: new Date(this.state.openToDate.getFullYear(), value.key) });
}
getListMonth = (date1, date2) => {
const monthList = new Array();
for (let i = date1; i <= date2; i++) {
onDayClick = (day) => {
let month = this.state.openToDate.getMonth() + 1;
let year = this.state.openToDate.getFullYear();
const date = new Date(month + "/" + day + "/" + year);
const days = new Date(year, month, 0).getDate();
if (day < 0) {
if (month === 1) { month = 13, year -= 1 }
const prevDays = new Date(year, (month - 1), 0).getDate();
const prevDate = new Date((month - 1) + "/" + (prevDays + day + 1) + "/" + year);
this.setState({ selectedDate: prevDate, openToDate: prevDate });
}
else if (day > days) {
if (month === 12) { month = 0, year += 1 }
const nextDate = new Date(month + 1 + "/" + (day - days) + "/" + year);
this.setState({ selectedDate: nextDate, openToDate: nextDate });
}
else if (this.formatSelectedDate(date) != this.formatSelectedDate(this.state.selectedDate)) {
this.setState({ selectedDate: date });
this.props.onChange && this.props.onChange(date);
}
}
getListMonth = (minMonth, maxMonth) => {
const monthList = [];
for (let i = minMonth; i <= maxMonth; i++) {
monthList.push({ key: `${i}`, label: `${this.state.months[i]}` });
}
return monthList;
}
getArrayMonth = () => {
let date1 = this.props.minDate.getMonth();
let date2 = this.props.maxDate.getMonth();
let monthList = new Array();
if (this.props.minDate.getFullYear() !== this.props.maxDate.getFullYear()) {
monthList = this.getListMonth(0, 11);
} else { monthList = this.getListMonth(date1, date2); }
return monthList;
const minDate = this.props.minDate;
const maxDate = this.props.maxDate;
if (this.state.openToDate.getFullYear() === minDate.getFullYear()) {
return this.getListMonth(minDate.getMonth(), 11);
}
else if (this.state.openToDate.getFullYear() === maxDate.getFullYear()) {
return this.getListMonth(0, maxDate.getMonth());
}
else if (minDate.getFullYear() !== maxDate.getFullYear()) {
return this.getListMonth(0, 11);
} else { return this.getListMonth(minDate.getMonth(), maxDate.getMonth()); }
}
getCurrentMonth = () => {
let month = this.getArrayMonth();
let selectedMonth = month.find(x => x.key == this.state.openToDate.getMonth());
return (selectedMonth);
const openToDate = this.state.openToDate;
const month = this.getArrayMonth();
const selectedMonth = month.find(x => x.key == openToDate.getMonth());
if (!selectedMonth) {
const key = month[0].key;
const key2 = Number(key) + 1;
const date = new Date(openToDate.getFullYear() + "/" + key2 + "/" + "01");
this.state.openToDate = date;
}
return selectedMonth ? selectedMonth : month[0];
}
getArrayYears = () => {
let date1 = this.props.minDate.getFullYear();
let date2 = this.props.maxDate.getFullYear();
const minDate = this.props.minDate.getFullYear();
const maxDate = this.props.maxDate.getFullYear();
const yearList = [];
for (let i = date1; i <= date2; i++) {
for (let i = minDate; i <= maxDate; i++) {
let newDate = new Date(i, 0, 1);
yearList.push({ key: `${i}`, label: `${moment(newDate).format('YYYY')}` });
}
@ -164,63 +207,45 @@ class Calendar extends Component {
}
getCurrentYear = () => {
let year = this.getArrayYears();
year = year.find(x => x.key == this.state.openToDate.getFullYear());
return (year);
}
selectedYear = (value) => {
this.setState({ openToDate: new Date(value.key, this.state.openToDate.getMonth()) });
}
selectedMonth = (value) => {
this.setState({ openToDate: new Date(this.state.openToDate.getFullYear(), value.key) });
return this.getArrayYears().find(x => x.key == this.state.openToDate.getFullYear());
}
formatSelectedDate = (date) => {
return (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getFullYear();
}
onDayClick = (day) => {
let year = this.state.openToDate.getFullYear();
let month = this.state.openToDate.getMonth() + 1;
let date = new Date(month + "/" + day + "/" + year);
if (this.formatSelectedDate(date) != this.formatSelectedDate(this.state.selectedDate)) {
this.setState({ selectedDate: date })
this.props.onChange && this.props.onChange(date);
}
}
formatDate = (date) => {
return (date.getMonth() + 1) + "/" + 1 + "/" + date.getFullYear();
}
compareDays = () => {
var date1 = this.formatDate(this.state.openToDate);
var date2 = this.formatDate(this.state.selectedDate);
return (date1 === date2) ? true : false;
const openDate = this.formatDate(this.state.openToDate);
const selectedDate = this.formatDate(this.state.selectedDate);
return (openDate === selectedDate) ? true : false;
}
firstDayOfMonth = () => {
const selectedDate = this.state.openToDate;
const firstDay = moment(selectedDate).locale("en").startOf("month").format("d");
return firstDay;
let day = firstDay - 1;
if (day < 0) { day = 6; }
return day;
};
getWeekDays = () => {
let arrayWeekDays = [];
const weekdays = moment.weekdaysMin(true);
const weekdays = moment.weekdaysMin();
weekdays.push(weekdays.shift());
let className = "";
for (let i = 0; i < weekdays.length; i++) {
let className = "";
(i === 6 || i === 5) ? className = "calendar-month_weekdays_weekend" : className = "calendar-month_weekdays";
arrayWeekDays.push(<Weekday className={className} key={weekdays[i]}>{weekdays[i]}</Weekday>)
(i >= 5) ? className = "calendar-month_weekdays_weekend" : className = "calendar-month_weekdays";
arrayWeekDays.push(<Weekday className={className} key={weekdays[i]}><AbbrDay>{weekdays[i]}</AbbrDay></Weekday>)
}
return arrayWeekDays;
}
getDays = () => {
let keys = 0;
let prevMonthDays = this.firstDayOfMonth();
@ -229,50 +254,92 @@ class Calendar extends Component {
const days = new Date(year, month, 0).getDate();
let prevDays = new Date(year, month - 1, 0).getDate();
const arrayDays = [];
let className = "calendar-month_neighboringMonth";
// Days + Weekend days
const open = this.state.openToDate;
const max = this.props.maxDate;
const min = this.props.minDate;
//Disable preview month
let disablePrevMonth = null;
if (open.getFullYear() === min.getFullYear() && open.getMonth() === min.getMonth()) {
disablePrevMonth = "calendar-month_disabled";
}
// Show neighboring days in prev month
while (prevMonthDays != 0) {
arrayDays.unshift(
<Day key={--keys} className={disablePrevMonth} >
<AbbrDay
onClick={this.onDayClick.bind(this, keys)}
className={className} >
{prevDays--}
</AbbrDay>
</Day>
);
//console.log("loop");
prevMonthDays--;
}
keys = 0;
//Disable max days in month
let disableClass, maxDay, minDay;
if (open.getFullYear() === max.getFullYear() && open.getMonth() >= max.getMonth()) {
if (open.getMonth() === max.getMonth()) { maxDay = max.getDate(); }
else { maxDay = null; }
}
//Disable min days in month
else if (open.getFullYear() === min.getFullYear() && open.getMonth() >= min.getMonth()) {
if (open.getMonth() === min.getMonth()) { minDay = min.getDate(); }
else { minDay = null; }
}
// Show days in month and weekend days
let seven = 7;
let className = "";
const dateNow = this.state.selectedDate.getDate();
const isEqual = this.compareDays();
const temp = 1;
prevMonthDays = this.firstDayOfMonth();
for (let i = 1; i <= days; i++) {
if (i === (seven - prevMonthDays - 1)) { className = "calendar-month_weekend"; }
if (i === (seven - prevMonthDays - temp)) { className = "calendar-month_weekend"; }
else if (i === (seven - prevMonthDays)) { seven += 7; className = "calendar-month_weekend"; }
else { className = "calendar-month"; }
if (i === dateNow && isEqual) { className = "calendar-month_selected-day" }
if (i === dateNow && this.compareDays()) { className = "calendar-month_selected-day" }
if (i > maxDay || i < minDay) { disableClass = "calendar-month_disabled"; }
else { disableClass = null; }
arrayDays.push(
<Day key={keys++}>
<Day key={keys++} className={disableClass} >
<AbbrDay onClick={this.onDayClick.bind(this, i)} className={className}>{i}</AbbrDay>
</Day>
);
}
// Neighboring Month Days (Prev month)
while (prevMonthDays != 0) {
arrayDays.unshift(
<Day key={keys++}>
<AbbrDay className="calendar-month_neighboringMonth" >
{prevDays--}
</AbbrDay>
</Day>
);
prevMonthDays--;
//console.log("loop");
}
//Calculating Neighboring Month Days
let maxDays = 35; // max days in month table (no)
//Calculating neighboring days in next month
let maxDays = 42; // max days in month table
const firstDay = this.firstDayOfMonth();
if (firstDay > 5 && days >= 30) { maxDays += 7; }
else if (firstDay >= 5 && days > 30) { maxDays += 7; }
else if (firstDay >= 5 && days > 30) { maxDays += 7; }
if (maxDays > 42) { maxDays -= 7; }
//Neighboring Month Days (Next month)
//Disable next month days
let disableClass2 = null;
if (open.getFullYear() === max.getFullYear() && open.getMonth() >= max.getMonth()) {
disableClass2 = "calendar-month_disabled";
}
//Show neighboring days in next month
let nextDay = 1;
for (let i = days; i < maxDays - firstDay; i++) {
if (firstDay == 0 && days == 28) { break; }
arrayDays.push(
<Day key={keys++}>
<AbbrDay className="calendar-month_neighboringMonth" >
<Day key={keys++} className={disableClass2} >
<AbbrDay
onClick={this.onDayClick.bind(this, i + 1)}
className="calendar-month_neighboringMonth" >
{nextDay++}
</AbbrDay>
</Day>
@ -281,21 +348,23 @@ class Calendar extends Component {
return arrayDays;
}
render() {
//console.log("render");
moment.locale(this.props.language);
moment.locale(this.props.locale);
this.state.months = moment.months();
const disabled = this.props.disabled;
const dropDownSize = this.getArrayYears().length > 6 ? 180 : undefined;
const dropDownSizeMonth = this.getArrayMonth().length > 4 ? 180 : undefined;
const dropDownSizeYear = this.getArrayYears().length > 4 ? 180 : undefined;
return (
<CalendarStyle color={this.props.themeColor}>
<CalendarStyle color={this.props.themeColor} disabled={disabled}>
<ComboBoxStyle>
<ComboBox
scaled={true}
dropDownMaxHeight={180}
onSelect={this.selectedMonth.bind(this)}
dropDownMaxHeight={dropDownSizeMonth}
onSelect={this.onSelectMonth.bind(this)}
selectedOption={this.getCurrentMonth()}
options={this.getArrayMonth()}
isDisabled={disabled}
@ -303,8 +372,8 @@ class Calendar extends Component {
<ComboBoxDateStyle>
<ComboBox
scaled={true}
dropDownMaxHeight={dropDownSize}
onSelect={this.selectedYear.bind(this)}
dropDownMaxHeight={dropDownSizeYear}
onSelect={this.onSelectYear.bind(this)}
selectedOption={this.getCurrentYear()}
options={this.getArrayYears()}
isDisabled={disabled}
@ -331,8 +400,8 @@ Calendar.propTypes = {
openToDate: PropTypes.instanceOf(Date),
minDate: PropTypes.instanceOf(Date),
maxDate: PropTypes.instanceOf(Date),
language: PropTypes.string,
//disabled: PropTypes.bool,
locale: PropTypes.string,
disabled: PropTypes.bool,
//size: PropTypes.string
}
@ -342,7 +411,7 @@ Calendar.defaultProps = {
minDate: new Date("1970/01/01"),
maxDate: new Date("3000/01/01"),
themeColor: '#ED7309',
language: moment.locale(),
locale: moment.locale(),
//size: 'base'
}

View File

@ -58,9 +58,9 @@ const CalendarStyle = styled.div`
/*flex-basis: 11.2857% !important;*/
${props => props.size === 'base' ?
'margin-left: 9px;' :
'margin 10px 7px 0 7px;'
}
'margin-left: 9px;' :
'margin 10px 7px 0 7px;'
}
}
.react-calendar__tile:disabled { background-color: #fff; }
@ -143,19 +143,35 @@ class Calendar extends Component {
}
getArrayMonth = () => {
let date1 = this.props.minDate.getMonth();
let date2 = this.props.maxDate.getMonth();
let monthList = new Array();
if (this.props.minDate.getFullYear() !== this.props.maxDate.getFullYear()) {
monthList = this.getListMonth(0, 11);
} else { monthList = this.getListMonth(date1, date2); }
return monthList;
const minDate = this.props.minDate;
const maxDate = this.props.maxDate;
if (this.state.openToDate.getFullYear() === minDate.getFullYear()) {
return this.getListMonth(minDate.getMonth(), 11);
}
else if (this.state.openToDate.getFullYear() === maxDate.getFullYear()) {
return this.getListMonth(0, maxDate.getMonth());
}
else if (minDate.getFullYear() !== maxDate.getFullYear()) {
return this.getListMonth(0, 11);
} else { return this.getListMonth(minDate.getMonth(), maxDate.getMonth()); }
}
getCurrentMonth = () => {
let month = this.getArrayMonth();
let selected_month = month.find(x => x.key == this.state.openToDate.getMonth());
return (selected_month);
const openToDate = this.state.openToDate;
const month = this.getArrayMonth();
const selectedMonth = month.find(x => x.key == openToDate.getMonth());
if (!selectedMonth) {
const key = month[0].key;
const key2 = Number(key) + 1;
const date = new Date(openToDate.getFullYear() + "/" + key2 + "/" + "01");
this.state.openToDate = date;
}
return selectedMonth ? selectedMonth : month[0];
}
onChange = (date) => {

View File

@ -108,7 +108,7 @@ class Checkbox extends React.Component {
const colorProps = this.props.isDisabled ? {color: disableColor} : {};
return (
<Label htmlFor={this.props.id} isDisabled={this.props.isDisabled}>
<Label htmlFor={this.props.id} isDisabled={this.props.isDisabled} className={this.props.className}>
<HiddenInput
type="checkbox"
checked={this.state.checked}

View File

@ -164,7 +164,7 @@ class ComboBox extends React.PureComponent {
};
render() {
console.log("ComboBox render");
//console.log("ComboBox render");
const { dropDownMaxHeight, isDisabled, directionX, directionY, scaled, children, options, noBorder } = this.props;
const { isOpen, selectedOption } = this.state;

View File

@ -9,7 +9,7 @@ const StyledCloseButton = styled.div`
const CloseButton = props => {
//console.log("CloseButton render");
return (
<StyledCloseButton>
<StyledCloseButton className={props.className}>
<IconButton
color={"#D8D8D8"}
hoverColor={"#333"}
@ -21,7 +21,6 @@ const CloseButton = props => {
onClick={!props.isDisabled ? ((e) => props.onClick()) : undefined}
/>
</StyledCloseButton>
);
};

View File

@ -0,0 +1,223 @@
import React from 'react';
import styled from 'styled-components';
import FilterButton from './filter-button';
import HideFilter from './hide-filter';
import ComboBox from '../combobox';
import CloseButton from './close-button';
import isEqual from 'lodash/isEqual';
const StyledFilterBlock = styled.div`
display: flex;
align-items: center;
`;
const StyledFilterItem = styled.div`
display: ${props => props.block ? 'block' : 'inline-block'};
margin-bottom: ${props => props.block ? '3px' : '0'};
position: relative;
height: 100%;
padding: 3px 44px 3px 7px;
margin-right: 2px;
border: 1px solid #ECEEF1;
border-radius: 3px;
background-color: #F8F9F9;
font-weight: 600;
font-size: 13px;
line-height: 15px;
&:last-child{
margin-bottom: 0;
}
`;
const StyledCloseButtonBlock = styled.div`
display: flex;
align-items: center;
position: absolute;
height: 100%;
width: 25px;
border-left: 1px solid #ECEEF1;
right: 0;
top: 1px;
`;
const StyledComboBox = styled(ComboBox)`
display: inline-block;
background: transparent;
cursor: pointer;
vertical-align: middle;
margin-left: -10px;
`;
const StyledFilterName = styled.span`
line-height: 18px;
margin-left: 5px;
`;
class FilterItem extends React.Component {
constructor(props) {
super(props);
this.state = {
id: this.props.id
};
this.onSelect = this.onSelect.bind(this);
}
onSelect(option) {
this.props.onSelectFilterItem(null, {
key: option.group + "_" + option.key,
label: option.label,
group: option.group,
inSubgroup: !!option.inSubgroup
});
}
render() {
return (
<StyledFilterItem key={this.state.id} id={this.state.id} block={this.props.block} >
{this.props.groupLabel}:
{this.props.groupItems.length > 1 ?
<StyledComboBox
options={this.props.groupItems}
isDisabled={this.props.isDisabled}
onSelect={this.onSelect}
selectedOption={{
key: this.state.id,
label: this.props.label
}}
size='content'
scaled={false}
noBorder={true}
opened={this.props.opened}
></StyledComboBox>
: <StyledFilterName>{this.props.label}</StyledFilterName>
}
<StyledCloseButtonBlock>
<CloseButton
isDisabled={this.props.isDisabled}
onClick={!this.props.isDisabled ? ((e) => this.props.onClose(e, this.props.id)) : undefined}
/>
</StyledCloseButtonBlock>
</StyledFilterItem>
);
}
}
class FilterBlock extends React.Component {
constructor(props) {
super(props);
this.state = {
hideFilterItems: this.props.hideFilterItems || [],
openFilterItems: this.props.openFilterItems || []
};
this.getData = this.getData.bind(this);
this.getFilterItems = this.getFilterItems.bind(this);
this.onDeleteFilterItem = this.onDeleteFilterItem.bind(this);
}
onDeleteFilterItem(e, key){
this.props.onDeleteFilterItem(key);
}
getFilterItems() {
const _this = this;
let result = [];
let openItems = [];
let hideItems = [];
if (this.state.openFilterItems.length > 0) {
openItems = this.state.openFilterItems.map(function (item) {
return <FilterItem
block={false}
isDisabled={_this.props.isDisabled}
key={item.key}
groupItems={_this.props.getFilterData().filter(function (t) {
return (t.group == item.group && t.group != t.key);
})}
onSelectFilterItem={_this.props.onClickFilterItem}
id={item.key}
groupLabel={item.groupLabel}
label={item.label}
opened={item.key.indexOf('_-1') == -1 ? false : true}
onClose={_this.onDeleteFilterItem}>
</FilterItem>
});
}
if (this.state.hideFilterItems.length > 0) {
hideItems.push(
<HideFilter key="hide-filter" count={this.state.hideFilterItems.length} isDisabled={this.props.isDisabled}>
{
this.state.hideFilterItems.map(function (item) {
return <FilterItem
block={true}
isDisabled={_this.props.isDisabled}
key={item.key}
groupItems={_this.props.getFilterData().filter(function (t) {
return (t.group == item.group && t.group != t.key);
})}
onSelectFilterItem={_this.props.onClickFilterItem}
id={item.key}
groupLabel={item.groupLabel}
opened={false}
label={item.label}
onClose={_this.onDeleteFilterItem}>
</FilterItem>
})
}
</HideFilter>
);
}
result = hideItems.concat(openItems);
return result;
}
getData() {
const _this = this;
const d = this.props.getFilterData();
let result = [];
d.forEach(element => {
if (!element.inSubgroup) {
element.onClick = !element.isSeparator && !element.isHeader && !element.disabled ? ((e) => _this.props.onClickFilterItem(e, element)) : undefined;
element.key = element.group != element.key ? element.group + "_" + element.key : element.key;
if (element.subgroup != undefined) {
if (d.findIndex(x => x.group === element.subgroup) == -1) element.disabled = true;
}
result.push(element);
}
});
return result;
}
componentDidUpdate() {
this.props.onRender();
}
shouldComponentUpdate(nextProps, nextState) {
if(!isEqual(this.props, nextProps)){
if (!isEqual(this.props.hideFilterItems, nextProps.hideFilterItems) || !isEqual(this.props.openFilterItems, nextProps.openFilterItems)) {
this.setState({
hideFilterItems: nextProps.hideFilterItems,
openFilterItems: nextProps.openFilterItems
});
return false;
}
return true;
}
if(this.props.isResizeUpdate){
return true;
}
return !isEqual(this.state, nextState);
}
render(){
const _this = this;
return(
<>
<StyledFilterBlock ref={this.filterWrapper} id='filter-items-container'>
{this.getFilterItems()}
</StyledFilterBlock>
<FilterButton id='filter-button' iconSize={this.props.iconSize} getData={_this.getData} isDisabled={this.props.isDisabled} />
</>
);
}
}
export default FilterBlock;

View File

@ -6,14 +6,14 @@ class FilterButton extends React.PureComponent{
//console.log('render FilterButton)
return(
<ContextMenuButton
title={'Actions'}
iconName={'RectangleFilterIcon'}
title='Actions'
iconName='RectangleFilterIcon'
color='#A3A9AE'
size={this.props.iconSize}
isDisabled={this.props.isDisabled}
getData={this.props.getData}
iconHoverName={'RectangleFilterHoverIcon'}
iconClickName={'RectangleFilterClickIcon'}
iconHoverName='RectangleFilterHoverIcon'
iconClickName='RectangleFilterClickIcon'
></ContextMenuButton>
)
}

View File

@ -35,6 +35,7 @@ const StyledHideFilterButton = styled.div`
`;
const StyledHideFilter = styled.div`
display: inline-block;
height: 100%;
`;
const StyledPopoverBody = styled(PopoverBody)`
border-radius: 6px;

View File

@ -2,91 +2,147 @@ import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import SearchInput from '../search-input';
import ComboBox from '../combobox'
import IconButton from '../icon-button';
import isEqual from 'lodash/isEqual';
import throttle from 'lodash/throttle';
import FilterBlock from './filter-block';
import SortComboBox from './sort-combobox';
const StyledFilterInput = styled.div`
min-width: 380px;
`;
const StyledIconButton = styled.div`
transform: ${state => state.sortDirection ? 'scale(1, -1)' : 'scale(1)'};
`;
const StyledSearchInput = styled.div`
display: block;
float: left;
width: calc(80% - 8px);
`;
const StyledComboBox = styled(ComboBox)`
display: block;
float: left;
width: 20%;
margin-left: 8px;
const StyledFilterBlock = styled.div`
display: flex;
`;
const cloneObjectsArray = function (props) {
return _.map(props, _.clone);;
}
const convertToInternalData = function (fullDataArray, inputDataArray) {
const filterItems = [];
for (let i = 0; i < inputDataArray.length; i++) {
const filterValue = fullDataArray.find(x => ((x.key === inputDataArray[i].key.replace(inputDataArray[i].group + "_", '')) && x.group === inputDataArray[i].group && !x.inSubgroup));
if (filterValue) {
inputDataArray[i].key = inputDataArray[i].group + "_" + inputDataArray[i].key;
inputDataArray[i].label = filterValue.label;
inputDataArray[i].groupLabel = !fullDataArray.inSubgroup ? fullDataArray.find(x => (x.group === inputDataArray[i].group)).label : inputDataArray[i].groupLabel;
filterItems.push(inputDataArray[i]);
} else {
filterValue = fullDataArray.find(x => ((x.key === inputDataArray[i].key.replace(inputDataArray[i].group + "_", '')) && x.group === inputDataArray[i].group && x.inSubgroup));
if (filterValue) {
inputDataArray[i].key = inputDataArray[i].group + "_" + inputDataArray[i].key;
inputDataArray[i].label = filterValue.label;
inputDataArray[i].groupLabel = fullDataArray.find(x => (x.subgroup === inputDataArray[i].group)).label;
filterItems.push(inputDataArray[i]);
} else {
filterValue = fullDataArray.find(x => ((x.subgroup === inputDataArray[i].group)));
if (filterValue) {
const subgroupItems = fullDataArray.filter(t => t.group === filterValue.subgroup);
if (subgroupItems.length > 1) {
inputDataArray[i].key = inputDataArray[i].group + "_-1";
inputDataArray[i].label = filterValue.defaultSelectLabel;
inputDataArray[i].groupLabel = fullDataArray.find(x => (x.subgroup === inputDataArray[i].group)).label;
filterItems.push(inputDataArray[i]);
} else if (subgroupItems.length === 1) {
class SortComboBox extends React.Component {
constructor(props) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect(item) {
this.props.onSelect(item);
}
shouldComponentUpdate(nextProps, nextState) {
return !isEqual(this.props, nextProps);
}
render() {
return (
<StyledComboBox
options={this.props.options}
isDisabled={this.props.isDisabled}
onSelect={this.onSelect}
selectedOption={this.props.selectedOption}
>
<StyledIconButton sortDirection={this.props.sortDirection}>
<IconButton
color={"#D8D8D8"}
hoverColor={"#333"}
clickColor={"#333"}
size={10}
iconName={'ZASortingIcon'}
isFill={true}
isDisabled={this.props.isDisabled}
onClick={this.props.onButtonClick}
/>
</StyledIconButton>
</StyledComboBox>
);
const selectFilterItem = {
key: subgroupItems[0].group + "_" + subgroupItems[0].key,
group: subgroupItems[0].group,
label: subgroupItems[0].label,
groupLabel: fullDataArray.find(x => x.subgroup === subgroupItems[0].group).label,
inSubgroup: true
};
filterItems.push(selectFilterItem);
}
}
}
}
}
return filterItems;
}
class FilterInput extends React.Component {
constructor(props) {
super(props);
this.isResizeUpdate = false;
this.minWidth = 190;
function getDefaultFilterData() {
const filterData = props.getFilterData();
const filterItems = [];
const selectedFilterData = cloneObjectsArray(props.selectedFilterData.filterValues);
selectedFilterData.forEach(defaultFilterValue => {
const filterValue = filterData.find(x => ((x.key === defaultFilterValue.key.replace(defaultFilterValue.group + "_", '')) && x.group === defaultFilterValue.group));
let groupLabel = '';
const groupFilterItem = filterData.find(x => (x.key === defaultFilterValue.group));
if (groupFilterItem != undefined) {
groupLabel = groupFilterItem.label;
} else {
const subgroupFilterItem = filterData.find(x => (x.subgroup === defaultFilterValue.group))
if (subgroupFilterItem != undefined) {
groupLabel = subgroupFilterItem.label;
}
}
if (filterValue != undefined) {
defaultFilterValue.key = defaultFilterValue.group + "_" + defaultFilterValue.key;
defaultFilterValue.label = filterValue.label;
defaultFilterValue.groupLabel = groupLabel;
filterItems.push(defaultFilterValue);
}
});
return filterItems;
}
this.state = {
sortDirection: props.selectedFilterData.sortDirection == "asc" ? true : false,
sortDirection: props.selectedFilterData.sortDirection === "asc" ? true : false,
sortId: props.getSortData().findIndex(x => x.key === props.selectedFilterData.sortId) != -1 ? props.selectedFilterData.sortId : props.getSortData().length > 0 ? props.getSortData()[0].key : "",
filterValues: props.selectedFilterData.filterValues,
searchText: props.selectedFilterData.inputValue || props.value
searchText: props.selectedFilterData.inputValue || props.value,
filterValues: props.selectedFilterData ? getDefaultFilterData() : [],
openFilterItems: [],
hideFilterItems: []
};
this.timerId = null;
this.searchWrapper = React.createRef();
this.filterWrapper = React.createRef();
this.onClickSortItem = this.onClickSortItem.bind(this);
this.onSortDirectionClick = this.onSortDirectionClick.bind(this);
this.onSearch = this.onSearch.bind(this);
this.onChangeFilter = this.onChangeFilter.bind(this);
this.setFilterTimer = this.setFilterTimer.bind(this);
this.onSearchChanged = this.onSearchChanged.bind(this);
this.getDefaultSelectedIndex = this.getDefaultSelectedIndex.bind(this);
this.updateFilter = this.updateFilter.bind(this);
this.onClickFilterItem = this.onClickFilterItem.bind(this);
this.getFilterData = this.getFilterData.bind(this);
this.onFilterRender = this.onFilterRender.bind(this);
this.onDeleteFilterItem = this.onDeleteFilterItem.bind(this);
this.clearFilter = this.clearFilter.bind(this);
this.throttledResize = throttle(this.resize, 300);
}
resize = () => {
this.isResizeUpdate = true;
this.setState({
filterValues: this.state.filterValues,
openFilterItems: this.state.filterValues,
hideFilterItems: []
})
}
getDefaultSelectedIndex() {
const sortData = this.props.getSortData();
if (sortData.length > 0) {
let defaultIndex = sortData.findIndex(x => x.key === this.state.sortId);
const defaultIndex = sortData.findIndex(x => x.key === this.state.sortId);
return defaultIndex != -1 ? defaultIndex : 0;
}
return 0;
@ -95,10 +151,117 @@ class FilterInput extends React.Component {
this.setState({ sortId: item.key });
this.onFilter(this.state.filterValues, item.key, this.state.sortDirection ? "asc" : "desc");
}
onSortDirectionClick(e) {
onSortDirectionClick() {
this.onFilter(this.state.filterValues, this.state.sortId, !this.state.sortDirection ? "asc" : "desc");
this.setState({ sortDirection: !this.state.sortDirection });
}
onSearchChanged(value) {
this.setState({ searchText: value });
this.onFilter(this.state.filterValues, this.state.sortId, this.state.sortDirection ? "asc" : "desc",value);
}
onSearch(result) {
this.onFilter(result.filterValues, this.state.sortId, this.state.sortDirection ? "asc" : "desc");
}
getFilterData() {
const _this = this;
const d = this.props.getFilterData();
const result = [];
d.forEach(element => {
if (!element.inSubgroup) {
element.onClick = !element.isSeparator && !element.isHeader && !element.disabled ? ((e) => _this.props.onClickFilterItem(e, element)) : undefined;
element.key = element.group != element.key ? element.group + "_" + element.key : element.key;
if (element.subgroup != undefined) {
if (d.findIndex(x => x.group === element.subgroup) === -1) element.disabled = true;
}
result.push(element);
}
});
return result;
}
clearFilter() {
this.setState({
searchText:'',
filterValues: [],
openFilterItems: [],
hideFilterItems: []
});
this.onFilter([], this.state.sortId, this.state.sortDirection ? "asc" : "desc", '');
}
updateFilter(inputFilterItems) {
const currentFilterItems = inputFilterItems || cloneObjectsArray(this.state.filterValues);
const fullWidth = this.searchWrapper.current.getBoundingClientRect().width;
const filterWidth = this.filterWrapper.current.getBoundingClientRect().width;
const filterArr = Array.from(Array.from(this.filterWrapper.current.children).find(x => x.id === 'filter-items-container').children);
const filterButton = Array.from(Array.from(this.filterWrapper.current.children).find(x => x.id != 'filter-items-container').children)[0];
if (fullWidth <= this.minWidth) {
this.setState({
openFilterItems: [],
hideFilterItems: cloneObjectsArray(currentFilterItems)
});
} else if (filterWidth > fullWidth / 2) {
let newOpenFilterItems = cloneObjectsArray(currentFilterItems);
let newHideFilterItems = [];
let elementsWidth = 0;
Array.from(filterArr).forEach(element => {
elementsWidth = elementsWidth + element.getBoundingClientRect().width;
});
if (elementsWidth >= (fullWidth / 3) - filterButton.getBoundingClientRect().width) {
for (let i = 0; i < filterArr.length; i++) {
if (elementsWidth > (fullWidth / 3) - filterButton.getBoundingClientRect().width) {
elementsWidth = elementsWidth - filterArr[i].getBoundingClientRect().width;
const hiddenItem = currentFilterItems.find(x => x.key === filterArr[i].getAttribute('id'));
if (hiddenItem) newHideFilterItems.push(hiddenItem);
newOpenFilterItems.splice(newOpenFilterItems.findIndex(x => x.key === filterArr[i].getAttribute('id')), 1);
}
};
}
this.setState({
openFilterItems: newOpenFilterItems,
hideFilterItems: newHideFilterItems
});
} else {
this.setState({
openFilterItems: currentFilterItems.slice(),
hideFilterItems: []
});
}
}
onDeleteFilterItem(key) {
const currentFilterItems = this.state.filterValues.slice();
const indexFilterItem = currentFilterItems.findIndex(x => x.key === key);
if (indexFilterItem != -1) {
currentFilterItems.splice(indexFilterItem, 1);
}
this.setState({
filterValues: currentFilterItems,
openFilterItems: currentFilterItems,
hideFilterItems: []
});
let filterValues = cloneObjectsArray(currentFilterItems);
filterValues = filterValues.map(function (item) {
item.key = item.key.replace(item.group + "_", '');
return item;
})
this.onFilter(filterValues.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "asc" : "desc");
}
onFilter(filterValues, sortId, sortDirection, searchText) {
let cloneFilterValues = cloneObjectsArray(filterValues);
cloneFilterValues = cloneFilterValues.map(function (item) {
item.key = item.key.replace(item.group + "_", '');
return item;
})
this.props.onFilter({
inputValue: searchText != undefined ? searchText : this.state.searchText,
filterValues: cloneFilterValues.filter(item => item.key != '-1'),
sortId: sortId,
sortDirection: sortDirection
});
}
onChangeFilter(result) {
this.setState({
searchText: result.inputValue,
@ -106,43 +269,118 @@ class FilterInput extends React.Component {
});
this.onFilter(result.filterValues, this.state.sortId, this.state.sortDirection ? "asc" : "desc", result.inputValue);
}
onSearch(result) {
this.onFilter(result.filterValues, this.state.sortId, this.state.sortDirection ? "asc" : "desc");
onFilterRender() {
if (this.isResizeUpdate) {
this.isResizeUpdate = false;
}
const fullWidth = this.searchWrapper.current.getBoundingClientRect().width;
const filterWidth = this.filterWrapper.current.getBoundingClientRect().width;
if (fullWidth <= this.minWidth || filterWidth > fullWidth / 2) this.updateFilter();
}
onClickFilterItem(event, filterItem) {
const currentFilterItems = cloneObjectsArray(this.state.filterValues);
if (!!filterItem.subgroup) {
const indexFilterItem = currentFilterItems.findIndex(x => x.group === filterItem.subgroup);
if (indexFilterItem != -1) {
currentFilterItems.splice(indexFilterItem, 1);
}
const subgroupItems = this.props.getFilterData().filter(t => t.group === filterItem.subgroup);
if (subgroupItems.length > 1) {
const selectFilterItem = {
key: filterItem.subgroup + "_-1",
group: filterItem.subgroup,
label: filterItem.defaultSelectLabel,
groupLabel: filterItem.label,
inSubgroup: true
};
if (indexFilterItem != -1)
currentFilterItems.splice(indexFilterItem, 0, selectFilterItem);
else
currentFilterItems.push(selectFilterItem);
this.setState({
filterValues: currentFilterItems,
openFilterItems: currentFilterItems,
hideFilterItems: []
});
} else if (subgroupItems.length === 1) {
const selectFilterItem = {
key: subgroupItems[0].group + "_" + subgroupItems[0].key,
group: subgroupItems[0].group,
label: subgroupItems[0].label,
groupLabel: this.props.getFilterData().find(x => x.subgroup === subgroupItems[0].group).label,
inSubgroup: true
};
if (indexFilterItem != -1)
currentFilterItems.splice(indexFilterItem, 0, selectFilterItem);
else
currentFilterItems.push(selectFilterItem);
const clone = cloneObjectsArray(currentFilterItems.filter(item => item.key != '-1'));
clone.map(function (item) {
item.key = item.key.replace(item.group + "_", '');
return item;
})
this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "asc" : "desc");
this.setState({
filterValues: currentFilterItems,
openFilterItems: currentFilterItems,
hideFilterItems: []
});
}
} else {
const filterItems = this.getFilterData();
const indexFilterItem = currentFilterItems.findIndex(x => x.group === filterItem.group);
if (indexFilterItem != -1) {
currentFilterItems.splice(indexFilterItem, 1);
}
const selectFilterItem = {
key: filterItem.key,
group: filterItem.group,
label: filterItem.label,
groupLabel: filterItem.inSubgroup ? filterItems.find(x => x.subgroup === filterItem.group).label : filterItems.find(x => x.key === filterItem.group).label
};
if (indexFilterItem != -1)
currentFilterItems.splice(indexFilterItem, 0, selectFilterItem);
else
currentFilterItems.push(selectFilterItem);
this.setState({
filterValues: currentFilterItems,
openFilterItems: currentFilterItems,
hideFilterItems: []
});
const clone = cloneObjectsArray(currentFilterItems.filter(item => item.key != '-1'));
clone.map(function (item) {
item.key = item.key.replace(item.group + "_", '');
return item;
})
this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "asc" : "desc");
}
onFilter(filterValues, sortId, sortDirection, searchText) {
let result = {
inputValue: searchText != undefined ? searchText : this.state.searchText,
filterValues: filterValues,
sortId: sortId,
sortDirection: sortDirection
};
this.props.onFilter(result);
}
setFilterTimer() {
this.timerId && clearTimeout(this.timerId);
this.timerId = null;
this.timerId = setTimeout(() => {
this.onSearch({ filterValues: this.state.filterValues });
clearTimeout(this.timerId);
this.timerId = null;
}, this.props.refreshTimeout);
componentDidMount() {
window.addEventListener('resize', this.throttledResize);
}
onSearchChanged(e) {
this.setState({ searchText: e.target.value });
if (this.props.autoRefresh)
this.setFilterTimer();
componentWillUnmount() {
window.removeEventListener('resize', this.throttledResize);
}
shouldComponentUpdate(nextProps, nextState) {
if (!isEqual(this.props.selectedFilterData, nextProps.selectedFilterData)) {
let internalFilterData = cloneObjectsArray(this.state.filterValues);
if (!!nextProps.selectedFilterData.filterValues) {
internalFilterData = convertToInternalData(this.props.getFilterData(), cloneObjectsArray(nextProps.selectedFilterData.filterValues));
this.updateFilter(internalFilterData);
}
this.setState(
{
sortDirection: nextProps.selectedFilterData.sortDirection === "asc" ? true : false,
sortId: this.props.getSortData().findIndex(x => x.key === nextProps.selectedFilterData.sortId) != -1 ? nextProps.selectedFilterData.sortId : "",
filterValues: nextProps.selectedFilterData.filterValues || this.state.filterValues,
filterValues: internalFilterData,
searchText: nextProps.selectedFilterData.inputValue || this.props.value
}
);
@ -155,15 +393,29 @@ class FilterInput extends React.Component {
this.props.value != nextProps.value)
return true;
if (this.isResizeUpdate) {
return true;
}
return !isEqual(this.state, nextState);
}
render() {
//console.log("FilterInput render");
let iconSize = 32;
switch (this.props.size) {
case 'base':
iconSize = 32;
break;
case 'middle':
case 'big':
case 'huge':
iconSize = 41;
break;
default:
break;
}
return (
<StyledFilterInput>
<StyledSearchInput>
<StyledFilterInput className={this.props.className}>
<StyledSearchInput ref={this.searchWrapper}>
<SearchInput
id={this.props.id}
isDisabled={this.props.isDisabled}
@ -176,8 +428,25 @@ class FilterInput extends React.Component {
onChangeFilter={this.onChangeFilter}
value={this.state.searchText}
selectedFilterData={this.state.filterValues}
showClearButton={this.state.filterValues.length > 0}
onClearSearch={this.clearFilter}
onChange={this.onSearchChanged}
/>
>
<StyledFilterBlock ref={this.filterWrapper}>
<FilterBlock
openFilterItems={this.state.openFilterItems}
hideFilterItems={this.state.hideFilterItems}
iconSize={iconSize}
getFilterData={this.props.getFilterData}
onClickFilterItem={this.onClickFilterItem}
onDeleteFilterItem={this.onDeleteFilterItem}
isResizeUpdate={this.isResizeUpdate}
onRender={this.onFilterRender}
isDisabled={this.props.isDisabled}
/>
</StyledFilterBlock>
</SearchInput>
</StyledSearchInput>
<SortComboBox
@ -196,13 +465,11 @@ class FilterInput extends React.Component {
FilterInput.protoTypes = {
autoRefresh: PropTypes.bool,
refreshTimeout: PropTypes.number,
selectedFilterData: PropTypes.object,
};
FilterInput.defaultProps = {
autoRefresh: true,
refreshTimeout: 1000,
selectedFilterData: {
sortDirection: false,
sortId: '',

View File

@ -0,0 +1,52 @@
import React from 'react';
import isEqual from 'lodash/isEqual';
import ComboBox from '../combobox'
import IconButton from '../icon-button';
import styled from 'styled-components';
const StyledIconButton = styled.div`
transform: ${state => state.sortDirection ? 'scale(1, -1)' : 'scale(1)'};
`;
const StyledComboBox = styled(ComboBox)`
display: block;
float: left;
width: 20%;
margin-left: 8px;
`;
class SortComboBox extends React.Component {
constructor(props) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect(item) {
this.props.onSelect(item);
}
shouldComponentUpdate(nextProps, nextState) {
return !isEqual(this.props, nextProps);
}
render() {
return (
<StyledComboBox
options={this.props.options}
isDisabled={this.props.isDisabled}
onSelect={this.onSelect}
selectedOption={this.props.selectedOption}
>
<StyledIconButton sortDirection={this.props.sortDirection}>
<IconButton
color={"#D8D8D8"}
hoverColor={"#333"}
clickColor={"#333"}
size={10}
iconName={'ZASortingIcon'}
isFill={true}
isDisabled={this.props.isDisabled}
onClick={this.props.onButtonClick}
/>
</StyledIconButton>
</StyledComboBox>
);
}
}
export default SortComboBox;

View File

@ -3,10 +3,8 @@ import styled from 'styled-components';
import PropTypes from 'prop-types';
import GroupButton from '../group-button';
import DropDownItem from '../drop-down-item';
import differenceWith from 'lodash/differenceWith';
import isEqual from 'lodash/isEqual';
import isEmpty from 'lodash/isEmpty';
import throttle from 'lodash/throttle';
import { isArrayEqual } from '../../utils/array';
const StyledGroupButtonsMenu = styled.div`
position: sticky;
@ -68,10 +66,6 @@ class GroupButtonsMenu extends React.PureComponent {
this.throttledResize = throttle(this.updateMenu, 300);
}
isArrayEqual = (x, y) => {
return isEmpty(differenceWith(x, y, isEqual));
};
closeMenu = (e) => {
this.setState({ visible: false });
this.props.onClose && this.props.onClose(e);
@ -96,7 +90,7 @@ class GroupButtonsMenu extends React.PureComponent {
this.setState({ visible: this.props.visible });
}
if(!this.isArrayEqual(this.props.menuItems, prevProps.menuItems)){
if(!isArrayEqual(this.props.menuItems, prevProps.menuItems)){
this.setState({ priorityItems: this.props.menuItems, });
this.updateMenu();
}

View File

@ -45,9 +45,9 @@ const StyledInputGroup = styled(CustomInputGroup)`
const InputBlock = React.forwardRef((props, ref) => {
//console.log("InputBlock render");
const { onChange, value, children, size } = props;
let iconButtonSize = 0;
if (typeof props.iconSize == "number" && props.iconSize > 0) {
iconButtonSize = props.iconSize;
} else {
@ -84,6 +84,7 @@ const InputBlock = React.forwardRef((props, ref) => {
<TextInput
id={props.id}
name={props.name}
type={props.type}
value={value}
isDisabled={props.isDisabled}
hasError={props.hasError}
@ -107,13 +108,13 @@ const InputBlock = React.forwardRef((props, ref) => {
&&
<InputGroupAddon addonType="append">
<StyledIconBlock>
<IconButton
size={iconButtonSize}
color={props.iconColor}
iconName={props.iconName}
isFill={props.isIconFill}
isDisabled={props.isDisabled}
onClick={typeof props.onIconClick == 'function' ? onIconClick : undefined } />
<IconButton
size={iconButtonSize}
color={props.iconColor}
iconName={props.iconName}
isFill={props.isIconFill}
isDisabled={props.isDisabled}
onClick={typeof props.onIconClick == 'function' ? onIconClick : undefined} />
</StyledIconBlock>
</InputGroupAddon>
}

View File

@ -1,32 +1,47 @@
import React from 'react';
import Scrollbar from '../scrollbar';
import React from "react";
import Scrollbar from "../scrollbar";
class CustomScrollbars extends React.Component {
refSetter = (scrollbarsRef, forwardedRef) => {
if (scrollbarsRef) {
forwardedRef(scrollbarsRef.view);
} else {
forwardedRef(null);
}
};
render() {
const { onScroll, forwardedRef, style, children } = this.props;
class CustomScrollbars extends React.Component {
refSetter = (scrollbarsRef, forwardedRef) => {
if (scrollbarsRef) {
forwardedRef(scrollbarsRef.view);
} else {
forwardedRef(null);
}
};
render() {
const {
onScroll,
forwardedRef,
style,
children,
className,
stype
} = this.props;
//console.log("CustomScrollbars", this.props);
return (
<Scrollbar
ref={scrollbarsRef => this.refSetter.bind(this, scrollbarsRef, forwardedRef)}
ref={scrollbarsRef =>
this.refSetter.bind(this, scrollbarsRef, forwardedRef)
}
style={{ ...style, overflow: "hidden" }}
onScroll={onScroll}
stype="mediumBlack"
stype={stype}
className={className}
>
{children}
</Scrollbar>
);
};
};
const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => (
<CustomScrollbars {...props} forwardedRef={ref} />
));
}
}
export default CustomScrollbarsVirtualList;
CustomScrollbars.defaultProps = {
stype: "mediumBlack"
};
const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => (
<CustomScrollbars {...props} forwardedRef={ref} />
));
export default CustomScrollbarsVirtualList;

View File

@ -2,571 +2,104 @@ import React from "react";
import PropTypes from "prop-types";
import styled from 'styled-components';
import InputBlock from '../input-block';
import ComboBox from '../combobox';
import FilterButton from './filter-button';
import HideFilter from './hide-filter';
import CloseButton from './close-button';
import isEqual from 'lodash/isEqual';
const StyledSearchInput = styled.div`
min-width: 200px;
font-family: Open Sans;
font-style: normal;
`;
const StyledFilterBlock = styled.div`
display: flex;
align-items: center;
`;
const StyledFilterItem = styled.div`
display: ${props => props.block ? 'block' : 'inline-block'};
margin-bottom: ${props => props.block ? '3px' : '0'};
position: relative;
height: 100%;
padding: 3px 44px 3px 7px;
margin-right: 2px;
border: 1px solid #ECEEF1;
border-radius: 3px;
background-color: #F8F9F9;
font-weight: 600;
font-size: 13px;
line-height: 15px;
&:last-child{
margin-bottom: 0;
}
`;
const StyledCloseButtonBlock = styled.div`
display: flex;
align-items: center;
position: absolute;
height: 100%;
width: 25px;
border-left: 1px solid #ECEEF1;
right: 0;
top: 0;
`;
const StyledComboBox = styled(ComboBox)`
display: inline-block;
background: transparent;
cursor: pointer;
vertical-align: middle;
margin-left: -10px;
`;
const StyledFilterName = styled.span`
line-height: 18px;
margin-left: 5px;
`;
class FilterItem extends React.Component {
constructor(props) {
super(props);
this.state = {
id: this.props.id
};
this.onSelect = this.onSelect.bind(this);
}
onSelect(option) {
this.props.onSelectFilterItem(null, {
key: option.group + "_" + option.key,
label: option.label,
group: option.group,
inSubgroup: !!option.inSubgroup
});
}
render() {
return (
<StyledFilterItem key={this.state.id} id={this.state.id} block={this.props.block} >
{this.props.groupLabel}:
{this.props.groupItems.length > 1 ?
<StyledComboBox
options={this.props.groupItems}
isDisabled={this.props.isDisabled}
onSelect={this.onSelect}
selectedOption={{
key: this.state.id,
label: this.props.label
}}
size='content'
scaled={false}
noBorder={true}
opened={this.props.opened}
></StyledComboBox>
: <StyledFilterName>{this.props.label}</StyledFilterName>
}
<StyledCloseButtonBlock>
<CloseButton
isDisabled={this.props.isDisabled}
onClick={!this.props.isDisabled ? ((e) => this.props.onClose(e, this.props.id)) : undefined}
/>
</StyledCloseButtonBlock>
</StyledFilterItem>
);
}
}
const cloneObjectsArray = function (props) {
return _.map(props, _.clone);;
}
const convertToInternalData = function (fullDataArray, inputDataArray) {
let filterItems = [];
for (let i = 0; i < inputDataArray.length; i++) {
let filterValue = fullDataArray.find(x => ((x.key === inputDataArray[i].key.replace(inputDataArray[i].group + "_", '')) && x.group === inputDataArray[i].group && !x.inSubgroup));
if (filterValue) {
inputDataArray[i].key = inputDataArray[i].group + "_" + inputDataArray[i].key;
inputDataArray[i].label = filterValue.label;
inputDataArray[i].groupLabel = !fullDataArray.inSubgroup ? fullDataArray.find(x => (x.group === inputDataArray[i].group)).label : inputDataArray[i].groupLabel;
filterItems.push(inputDataArray[i]);
} else {
filterValue = fullDataArray.find(x => ((x.key === inputDataArray[i].key.replace(inputDataArray[i].group + "_", '')) && x.group === inputDataArray[i].group && x.inSubgroup));
if (filterValue) {
inputDataArray[i].key = inputDataArray[i].group + "_" + inputDataArray[i].key;
inputDataArray[i].label = filterValue.label;
inputDataArray[i].groupLabel = fullDataArray.find(x => (x.subgroup === inputDataArray[i].group)).label;
filterItems.push(inputDataArray[i]);
} else {
filterValue = fullDataArray.find(x => ((x.subgroup === inputDataArray[i].group)));
if (filterValue) {
let subgroupItems = fullDataArray.filter(function (t) {
return (t.group == filterValue.subgroup);
});
if (subgroupItems.length > 1) {
inputDataArray[i].key = inputDataArray[i].group + "_-1";
inputDataArray[i].label = filterValue.defaultSelectLabel;
inputDataArray[i].groupLabel = fullDataArray.find(x => (x.subgroup === inputDataArray[i].group)).label;
filterItems.push(inputDataArray[i]);
} else if (subgroupItems.length == 1) {
let selectFilterItem = {
key: subgroupItems[0].group + "_" + subgroupItems[0].key,
group: subgroupItems[0].group,
label: subgroupItems[0].label,
groupLabel: fullDataArray.find(x => x.subgroup === subgroupItems[0].group).label,
inSubgroup: true
};
filterItems.push(selectFilterItem);
}
}
}
}
}
return filterItems;
}
class SearchInput extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
this.timerId = null;
function getDefaultFilterData() {
let filterData = props.getFilterData();
let filterItems = [];
let selectedFilterData = cloneObjectsArray(props.selectedFilterData);
selectedFilterData.forEach(defaultFilterValue => {
let filterValue = filterData.find(x => ((x.key === defaultFilterValue.key.replace(defaultFilterValue.group + "_", '')) && x.group === defaultFilterValue.group));
let groupLabel = '';
let groupFilterItem = filterData.find(x => (x.key === defaultFilterValue.group));
if (groupFilterItem != undefined) {
groupLabel = groupFilterItem.label;
} else {
let subgroupFilterItem = filterData.find(x => (x.subgroup === defaultFilterValue.group))
if (subgroupFilterItem != undefined) {
groupLabel = subgroupFilterItem.label;
}
}
if (filterValue != undefined) {
defaultFilterValue.key = defaultFilterValue.group + "_" + defaultFilterValue.key;
defaultFilterValue.label = filterValue.label;
defaultFilterValue.groupLabel = groupLabel;
filterItems.push(defaultFilterValue);
}
});
return filterItems;
}
this.minWidth = 190;
this.isResizeUpdate = false;
this.state = {
inputValue: props.value,
filterItems: props.selectedFilterData && props.isNeedFilter ? getDefaultFilterData() : [],
openFilterItems: [],
hideFilterItems: []
};
this.searchWrapper = React.createRef();
this.filterWrapper = React.createRef();
this.onClickDropDownItem = this.onClickDropDownItem.bind(this);
this.getData = this.getData.bind(this);
this.clearFilter = this.clearFilter.bind(this);
this.onDeleteFilterItem = this.onDeleteFilterItem.bind(this);
this.getFilterItems = this.getFilterItems.bind(this);
this.updateFilter = this.updateFilter.bind(this);
this.clearSearch = this.clearSearch.bind(this);
this.onInputChange = this.onInputChange.bind(this);
this.resize = this.resize.bind(this);
this.throttledResize = _.throttle(this.resize, 300);
this.setSearchTimer = this.setSearchTimer.bind(this);
}
onClickDropDownItem(event, filterItem) {
let currentFilterItems = cloneObjectsArray(this.state.filterItems);
if (!!filterItem.subgroup) {
let indexFilterItem = currentFilterItems.findIndex(x => x.group === filterItem.subgroup);
if (indexFilterItem != -1) {
currentFilterItems.splice(indexFilterItem, 1);
}
let subgroupItems = this.props.getFilterData().filter(function (t) {
return (t.group == filterItem.subgroup);
});
if (subgroupItems.length > 1) {
let selectFilterItem = {
key: filterItem.subgroup + "_-1",
group: filterItem.subgroup,
label: filterItem.defaultSelectLabel,
groupLabel: filterItem.label,
inSubgroup: true
};
if (indexFilterItem != -1)
currentFilterItems.splice(indexFilterItem, 0, selectFilterItem);
else
currentFilterItems.push(selectFilterItem);
this.setState({ filterItems: currentFilterItems });
this.updateFilter(currentFilterItems);
} else if (subgroupItems.length == 1) {
let selectFilterItem = {
key: subgroupItems[0].group + "_" + subgroupItems[0].key,
group: subgroupItems[0].group,
label: subgroupItems[0].label,
groupLabel: this.props.getFilterData().find(x => x.subgroup === subgroupItems[0].group).label,
inSubgroup: true
};
if (indexFilterItem != -1)
currentFilterItems.splice(indexFilterItem, 0, selectFilterItem);
else
currentFilterItems.push(selectFilterItem);
let clone = cloneObjectsArray(currentFilterItems.filter(function (item) {
return item.key != '-1';
}));
clone.map(function (item) {
item.key = item.key.replace(item.group + "_", '');
return item;
})
if (typeof this.props.onChangeFilter === "function")
this.props.onChangeFilter({
inputValue: this.state.inputValue,
filterValues: this.props.isNeedFilter ?
clone.filter(function (item) {
return item.key != '-1';
}) :
null
});
this.setState({ filterItems: currentFilterItems });
this.updateFilter(currentFilterItems);
}
} else {
let filterItems = this.getData();
let indexFilterItem = currentFilterItems.findIndex(x => x.group === filterItem.group);
if (indexFilterItem != -1) {
currentFilterItems.splice(indexFilterItem, 1);
}
let selectFilterItem = {
key: filterItem.key,
group: filterItem.group,
label: filterItem.label,
groupLabel: filterItem.inSubgroup ? filterItems.find(x => x.subgroup === filterItem.group).label : filterItems.find(x => x.key === filterItem.group).label
};
if (indexFilterItem != -1)
currentFilterItems.splice(indexFilterItem, 0, selectFilterItem);
else
currentFilterItems.push(selectFilterItem);
this.setState({ filterItems: currentFilterItems });
this.updateFilter(currentFilterItems);
let clone = cloneObjectsArray(currentFilterItems.filter(function (item) {
return item.key != '-1';
}));
clone.map(function (item) {
item.key = item.key.replace(item.group + "_", '');
return item;
})
if (typeof this.props.onChangeFilter === "function")
this.props.onChangeFilter({
inputValue: this.state.inputValue,
filterValues: this.props.isNeedFilter ?
clone.filter(function (item) {
return item.key != '-1';
}) :
null
});
}
}
getData() {
let _this = this;
let d = this.props.getFilterData();
let result = [];
d.forEach(element => {
if (!element.inSubgroup) {
element.onClick = !element.isSeparator && !element.isHeader && !element.disabled ? ((e) => _this.onClickDropDownItem(e, element)) : undefined;
element.key = element.group != element.key ? element.group + "_" + element.key : element.key;
if (element.subgroup != undefined) {
if (d.findIndex(x => x.group === element.subgroup) == -1) element.disabled = true;
}
result.push(element);
}
});
return result;
}
clearFilter() {
clearSearch(){
if (this.input.current) this.input.current.clearInput();
this.setState({
inputValue: '',
filterItems: [],
openFilterItems: [],
hideFilterItems: []
inputValue: ''
});
this.props.onChangeFilter({
inputValue: '',
filterValues: []
});
}
onDeleteFilterItem(e, key) {
let currentFilterItems = this.state.filterItems.slice();
let indexFilterItem = currentFilterItems.findIndex(x => x.key === key);
if (indexFilterItem != -1) {
currentFilterItems.splice(indexFilterItem, 1);
}
this.setState({ filterItems: currentFilterItems });
this.updateFilter(currentFilterItems);
let filterValues = cloneObjectsArray(currentFilterItems);
filterValues = filterValues.map(function (item) {
item.key = item.key.replace(item.group + "_", '');
return item;
})
if (typeof this.props.onChangeFilter === "function")
this.props.onChangeFilter({
inputValue: this.state.inputValue,
filterValues: this.props.isNeedFilter ?
filterValues.filter(function (item) {
return item.key != '-1';
}) : null
});
}
getFilterItems() {
let _this = this;
let result = [];
let openItems = [];
let hideItems = [];
if ((this.state.filterItems.length > 0 && this.state.hideFilterItems.length === 0 && this.state.openFilterItems.length === 0) || this.state.openFilterItems.length > 0) {
const filterItemsArray = this.state.openFilterItems.length > 0 ? this.state.openFilterItems : this.state.filterItems;
openItems = filterItemsArray.map(function (item) {
return <FilterItem
isDisabled={_this.props.isDisabled}
key={item.key}
groupItems={_this.props.getFilterData().filter(function (t) {
return (t.group == item.group && t.group != t.key);
})}
onSelectFilterItem={_this.onClickDropDownItem}
id={item.key}
groupLabel={item.groupLabel}
label={item.label}
opened={item.key.indexOf('_-1') == -1 ? false : true}
onClose={_this.onDeleteFilterItem}>
</FilterItem>
});
}
if (this.state.hideFilterItems.length > 0) {
hideItems.push(
<HideFilter key="hide-filter" count={this.state.hideFilterItems.length} isDisabled={this.props.isDisabled}>
{
this.state.hideFilterItems.map(function (item) {
return <FilterItem
block={true}
isDisabled={_this.props.isDisabled}
key={item.key}
groupItems={_this.props.getFilterData().filter(function (t) {
return (t.group == item.group && t.group != t.key);
})}
onSelectFilterItem={_this.onClickDropDownItem}
id={item.key}
groupLabel={item.groupLabel}
opened={false}
label={item.label}
onClose={_this.onDeleteFilterItem}>
</FilterItem>
})
}
</HideFilter>
);
}
result = hideItems.concat(openItems);
return result;
}
updateFilter(inputFilterItems) {
const currentFilterItems = inputFilterItems || cloneObjectsArray(this.state.filterItems);
let fullWidth = this.searchWrapper.current.getBoundingClientRect().width;
let filterWidth = this.filterWrapper.current.getBoundingClientRect().width;
if (fullWidth <= this.minWidth) {
this.setState({ openFilterItems: [] });
this.setState({ hideFilterItems: currentFilterItems.slice() });
} else if (filterWidth > fullWidth / 2) {
let newOpenFilterItems = cloneObjectsArray(currentFilterItems);
let newHideFilterItems = [];
let elementsWidth = 0;
Array.from(this.filterWrapper.current.children).forEach(element => {
elementsWidth = elementsWidth + element.getBoundingClientRect().width;
});
if (elementsWidth >= fullWidth / 3) {
let filterArr = Array.from(this.filterWrapper.current.children);
for (let i = 0; i < filterArr.length; i++) {
if (elementsWidth > fullWidth / 3) {
elementsWidth = elementsWidth - filterArr[i].getBoundingClientRect().width;
newHideFilterItems.push(currentFilterItems.find(x => x.key === filterArr[i].getAttribute('id')));
newOpenFilterItems.splice(newOpenFilterItems.findIndex(x => x.key === filterArr[i].getAttribute('id')), 1);
}
};
}
this.setState({ openFilterItems: newOpenFilterItems });
this.setState({ hideFilterItems: newHideFilterItems });
} else {
this.setState({ openFilterItems: currentFilterItems.slice() });
this.setState({ hideFilterItems: [] });
}
}
if(typeof this.props.onClearSearch === 'function') this.props.onClearSearch();
};
onInputChange(e) {
this.setState({
inputValue: e.target.value
});
this.props.onChange(e)
}
resize() {
this.isResizeUpdate = true;
//this.forceUpdate();
this.setState({
openFilterItems: this.state.filterItems,
hideFilterItems: []
})
}
componentDidUpdate() {
if (this.props.isNeedFilter) {
const fullWidth = this.searchWrapper.current.getBoundingClientRect().width;
const filterWidth = this.filterWrapper.current.getBoundingClientRect().width;
if (fullWidth <= this.minWidth || filterWidth > fullWidth / 2) this.updateFilter();
}
}
componentDidMount() {
window.addEventListener('resize', this.throttledResize);
if (this.props.isNeedFilter) this.updateFilter();
}
componentWillUnmount(){
window.removeEventListener('resize', this.throttledResize);
if (this.props.autoRefresh)
this.setSearchTimer(e.target.value);
}
setSearchTimer(value) {
this.timerId && clearTimeout(this.timerId);
this.timerId = null;
this.timerId = setTimeout(() => {
this.props.onChange(value);
clearTimeout(this.timerId);
this.timerId = null;
}, this.props.refreshTimeout);
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.value != nextProps.value || !isEqual(this.props.selectedFilterData, nextProps.selectedFilterData)) {
if (this.props.value != nextProps.value) {
this.setState({ inputValue: nextProps.value })
}
if (!isEqual(this.props.selectedFilterData, nextProps.selectedFilterData) && this.props.isNeedFilter) {
const internalFilterData = convertToInternalData(this.props.getFilterData(), cloneObjectsArray(nextProps.selectedFilterData));
this.setState({ filterItems: internalFilterData });
this.updateFilter(internalFilterData);
}
if (this.props.value != nextProps.value) {
this.setState({ inputValue: nextProps.value });
return true;
}
if (this.props.id != nextProps.id ||
this.props.isDisabled != nextProps.isDisabled ||
this.props.size != nextProps.size ||
this.props.placeholder != nextProps.placeholder ||
this.props.isNeedFilter != nextProps.isNeedFilter
) {
return true;
}
if (this.isResizeUpdate) {
this.isResizeUpdate = false;
return true;
}
return !isEqual(this.state, nextState);
return (!isEqual(this.state, nextState) || !isEqual(this.props, nextProps));
}
render() {
//console.log("Search input render");
let _this = this;
let iconSize = 32;
let clearButtonSize = 15;
switch (this.props.size) {
case 'base':
iconSize = 32;
clearButtonSize = !!this.state.inputValue || this.state.filterItems.length > 0 ? 12 : 15;
clearButtonSize = !!this.state.inputValue || this.props.showClearButton > 0 ? 12 : 15;
break;
case 'middle':
iconSize = 41;
clearButtonSize = !!this.state.inputValue || this.state.filterItems.length > 0 ? 16 : 18;
clearButtonSize = !!this.state.inputValue || this.props.showClearButton > 0 ? 16 : 18;
break;
case 'big':
iconSize = 41;
clearButtonSize = !!this.state.inputValue || this.state.filterItems.length > 0 ? 19 : 21;
clearButtonSize = !!this.state.inputValue || this.props.showClearButton > 0 ? 19 : 21;
break;
case 'huge':
iconSize = 41;
clearButtonSize = !!this.state.inputValue || this.state.filterItems.length > 0 ? 22 : 24;
clearButtonSize = !!this.state.inputValue || this.props.showClearButton > 0 ? 22 : 24;
break;
default:
break;
}
return (
<StyledSearchInput className={this.props.className} ref={this.searchWrapper}>
<StyledSearchInput className={this.props.className}>
<InputBlock
ref={this.input}
id={this.props.id}
name={this.props.name}
isDisabled={this.props.isDisabled}
iconName={!!this.state.inputValue || this.state.filterItems.length > 0 ? "CrossIcon" : "SearchIcon"}
iconName={!!this.state.inputValue || this.props.showClearButton > 0 ? "CrossIcon" : "SearchIcon"}
isIconFill={true}
iconSize={clearButtonSize}
iconColor={"#A3A9AE"}
onIconClick={!!this.state.inputValue || this.state.filterItems.length > 0 ? this.clearFilter : undefined}
onIconClick={!!this.state.inputValue || this.props.showClearButton ? this.clearSearch : undefined}
size={this.props.size}
scale={true}
value={this.state.inputValue}
placeholder={this.props.placeholder}
onChange={this.onInputChange}
>
{this.props.isNeedFilter &&
<StyledFilterBlock ref={this.filterWrapper}>
{this.getFilterItems()}
</StyledFilterBlock>
}
{this.props.children}
{this.props.isNeedFilter &&
<FilterButton iconSize={iconSize} getData={_this.getData} isDisabled={this.props.isDisabled} />
}
</InputBlock>
</StyledSearchInput>
);
@ -580,19 +113,20 @@ SearchInput.propTypes = {
scale: PropTypes.bool,
placeholder: PropTypes.string,
onChange: PropTypes.func,
getFilterData: PropTypes.func,
isNeedFilter: PropTypes.bool,
isDisabled: PropTypes.bool,
selectedFilterData: PropTypes.array
showClearButton: PropTypes.bool,
refreshTimeout: PropTypes.number,
autoRefresh: PropTypes.bool
};
SearchInput.defaultProps = {
autoRefresh: true,
size: 'base',
value: '',
scale: false,
isNeedFilter: false,
isDisabled: false,
selectedFilterData: []
refreshTimeout: 1000,
showClearButton: false
};
export default SearchInput;

View File

@ -50,4 +50,5 @@ export { default as EmptyScreenContainer} from './components/empty-screen-contai
export { default as CustomScrollbarsVirtualList } from './components/scrollbar/custom-scrollbars-virtual-list'
export { default as RowContent } from './components/row-content'
export { default as NewCalendar } from './components/calendar-new'
export { default as AdvancedSelector } from './components/advanced-selector'
export { default as FieldContainer } from './components/field-container'

View File

@ -0,0 +1,7 @@
import differenceWith from "lodash/differenceWith";
import isEqual from "lodash/isEqual";
import isEmpty from "lodash/isEmpty";
export const isArrayEqual = (arr1, arr2) => {
return isEmpty(differenceWith(arr1, arr2, isEqual));
};

View File

@ -109,7 +109,7 @@ namespace ASC.Web.Studio.UserControls.CustomNavigation
private const string Base64Start = "data:image/png;base64,";
public static string SaveTmpLogo(string tmpLogoPath)
public static string SaveTmpLogo(int tenantId, string tmpLogoPath)
{
if (string.IsNullOrEmpty(tmpLogoPath)) return null;
@ -126,9 +126,9 @@ namespace ASC.Web.Studio.UserControls.CustomNavigation
var fileName = Path.GetFileName(tmpLogoPath);
data = UserPhotoManager.GetTempPhotoData(fileName);
data = UserPhotoManager.GetTempPhotoData(tenantId, fileName);
UserPhotoManager.RemoveTempPhoto(fileName);
UserPhotoManager.RemoveTempPhoto(tenantId, fileName);
return SaveLogo(fileName, data);
}

View File

@ -97,7 +97,7 @@ namespace ASC.Web.Core.Users
public class UserPhotoManager
{
private static readonly IDictionary<Guid, IDictionary<Size, string>> Photofiles = new Dictionary<Guid, IDictionary<Size, string>>();
private static readonly ConcurrentDictionary<CacheSize, ConcurrentDictionary<Guid, string>> Photofiles = new ConcurrentDictionary<CacheSize, ConcurrentDictionary<Guid, string>>();
private static readonly ICacheNotify<UserPhotoManagerCacheItem> CacheNotify;
static UserPhotoManager()
@ -108,35 +108,22 @@ namespace ASC.Web.Core.Users
CacheNotify.Subscribe((data) =>
{
var userId = new Guid(data.UserID);
var size = new Size(data.Size.Width, data.Size.Height);
lock (Photofiles)
{
if (!Photofiles.ContainsKey(userId))
{
Photofiles[userId] = new ConcurrentDictionary<Size, string>();
}
Photofiles[userId][size] = data.FileName;
}
var userId = new Guid(data.UserID.ToByteArray());
Photofiles.GetOrAdd(data.Size, (r) => new ConcurrentDictionary<Guid, string>())[userId] = data.FileName;
}, CacheNotifyAction.InsertOrUpdate);
CacheNotify.Subscribe((data) =>
{
var userId = new Guid(data.UserID);
var size = new Size(data.Size.Width, data.Size.Height);
var userId = new Guid(data.UserID.ToByteArray());
var size = FromCahe(data.Size);
try
{
lock (Photofiles)
{
Photofiles.Remove(userId);
}
var storage = GetDataStore();
storage.DeleteFiles("", data.UserID + "*.*", false);
SetCacheLoadedForTenant(false);
Photofiles.TryGetValue(CacheSize.Big, out var dict);
dict?.TryRemove(userId, out _);
//var storage = GetDataStore();
//storage.DeleteFiles("", data.UserID + "*.*", false);
//SetCacheLoadedForTenant(false);
}
catch { }
}, CacheNotifyAction.Remove);
@ -237,35 +224,17 @@ namespace ASC.Web.Core.Users
public static Size OriginalFotoSize
{
get { return new Size(1280, 1280); }
}
public static Size OriginalFotoSize { get; } = new Size(1280, 1280);
public static Size RetinaFotoSize
{
get { return new Size(360, 360); }
}
public static Size RetinaFotoSize { get; } = new Size(360, 360);
public static Size MaxFotoSize
{
get { return new Size(200, 200); }
}
public static Size MaxFotoSize { get; } = new Size(200, 200);
public static Size BigFotoSize
{
get { return new Size(82, 82); }
}
public static Size BigFotoSize { get; } = new Size(82, 82);
public static Size MediumFotoSize
{
get { return new Size(48, 48); }
}
public static Size MediumFotoSize { get; } = new Size(48, 48);
public static Size SmallFotoSize
{
get { return new Size(32, 32); }
}
public static Size SmallFotoSize { get; } = new Size(32, 32);
private static readonly string _defaultRetinaAvatar = "default_user_photo_size_360-360.png";
private static readonly string _defaultAvatar = "default_user_photo_size_200-200.png";
@ -284,7 +253,7 @@ namespace ASC.Web.Core.Users
public static string GetPhotoAbsoluteWebPath(Tenant tenant, Guid userID)
{
var path = SearchInCache(userID, Size.Empty, out _);
var path = SearchInCache(tenant.TenantId, userID, Size.Empty, out _);
if (!string.IsNullOrEmpty(path)) return path;
try
@ -336,7 +305,7 @@ namespace ASC.Web.Core.Users
private static string GetSizedPhotoAbsoluteWebPath(int tenantId, Guid userID, Size size, out bool isdef)
{
var res = SearchInCache(userID, size, out isdef);
var res = SearchInCache(tenantId, userID, size, out isdef);
if (!string.IsNullOrEmpty(res)) return res;
try
@ -362,15 +331,16 @@ namespace ASC.Web.Core.Users
return GetDefaultPhotoAbsoluteWebPath(size);
}
private static string GetDefaultPhotoAbsoluteWebPath(Size size)
{
if (size == RetinaFotoSize) return WebImageSupplier.GetAbsoluteWebPath(_defaultRetinaAvatar);
if (size == MaxFotoSize) return WebImageSupplier.GetAbsoluteWebPath(_defaultAvatar);
if (size == BigFotoSize) return WebImageSupplier.GetAbsoluteWebPath(_defaultBigAvatar);
if (size == SmallFotoSize) return WebImageSupplier.GetAbsoluteWebPath(_defaultSmallAvatar);
if (size == MediumFotoSize) return WebImageSupplier.GetAbsoluteWebPath(_defaultMediumAvatar);
return GetDefaultPhotoAbsoluteWebPath();
}
private static string GetDefaultPhotoAbsoluteWebPath(Size size) =>
size switch
{
Size(var w, var h) when w == RetinaFotoSize.Width && h == RetinaFotoSize.Height => WebImageSupplier.GetAbsoluteWebPath(_defaultRetinaAvatar),
Size(var w, var h) when w == MaxFotoSize.Width && h == MaxFotoSize.Height => WebImageSupplier.GetAbsoluteWebPath(_defaultAvatar),
Size(var w, var h) when w == BigFotoSize.Width && h == BigFotoSize.Height => WebImageSupplier.GetAbsoluteWebPath(_defaultBigAvatar),
Size(var w, var h) when w == SmallFotoSize.Width && h == SmallFotoSize.Height => WebImageSupplier.GetAbsoluteWebPath(_defaultSmallAvatar),
Size(var w, var h) when w == MediumFotoSize.Width && h == MediumFotoSize.Height => WebImageSupplier.GetAbsoluteWebPath(_defaultMediumAvatar),
_ => GetDefaultPhotoAbsoluteWebPath()
};
//Regex for parsing filenames into groups with id's
private static readonly Regex ParseFile =
@ -381,9 +351,10 @@ namespace ASC.Web.Core.Users
private static readonly HashSet<int> TenantDiskCache = new HashSet<int>();
private static readonly object DiskCacheLoaderLock = new object();
private static bool IsCacheLoadedForTenant()
private static bool IsCacheLoadedForTenant(int tenantId)
{
return TenantDiskCache.Contains(TenantProvider.CurrentTenantID);
//
return TenantDiskCache.Contains(tenantId);
}
private static bool SetCacheLoadedForTenant(bool isLoaded)
@ -392,25 +363,25 @@ namespace ASC.Web.Core.Users
}
private static string SearchInCache(Guid userId, Size size, out bool isDef)
private static string SearchInCache(int tenantId, Guid userId, Size size, out bool isDef)
{
if (!IsCacheLoadedForTenant())
LoadDiskCache();
if (!IsCacheLoadedForTenant(tenantId))
LoadDiskCache(tenantId);
isDef = false;
string fileName;
lock (Photofiles)
{
if (!Photofiles.ContainsKey(userId)) return null;
if (size != Size.Empty && !Photofiles[userId].ContainsKey(size)) return null;
string fileName = null;
Photofiles.TryGetValue(ToCache(size), out var photo);
if (size != Size.Empty)
fileName = Photofiles[userId][size];
else
fileName = Photofiles[userId]
.Select(x => x.Value)
.FirstOrDefault(x => !string.IsNullOrEmpty(x) && x.Contains("_orig_"));
if (size != Size.Empty)
{
photo?.TryGetValue(userId, out fileName);
}
else
{
fileName = photo?
.Select(x => x.Value)
.FirstOrDefault(x => !string.IsNullOrEmpty(x) && x.Contains("_orig_"));
}
if (fileName != null && fileName.StartsWith("default"))
{
@ -420,7 +391,7 @@ namespace ASC.Web.Core.Users
if (!string.IsNullOrEmpty(fileName))
{
var store = GetDataStore();
var store = GetDataStore(tenantId);
return store.GetUri(fileName).ToString();
}
@ -428,15 +399,15 @@ namespace ASC.Web.Core.Users
}
private static void LoadDiskCache()
private static void LoadDiskCache(int tenantId)
{
lock (DiskCacheLoaderLock)
{
if (!IsCacheLoadedForTenant())
if (!IsCacheLoadedForTenant(tenantId))
{
try
{
var listFileNames = GetDataStore().ListFilesRelative("", "", "*.*", false);
var listFileNames = GetDataStore(tenantId).ListFilesRelative("", "", "*.*", false);
foreach (var fileName in listFileNames)
{
//Try parse fileName
@ -470,15 +441,15 @@ namespace ASC.Web.Core.Users
{
if (CacheNotify != null)
{
CacheNotify.Publish(new UserPhotoManagerCacheItem { UserID = userID.ToString() }, CacheNotifyAction.Remove);
CacheNotify.Publish(new UserPhotoManagerCacheItem { UserID = Google.Protobuf.ByteString.CopyFrom(userID.ToByteArray()) }, CacheNotifyAction.Remove);
}
}
private static void AddToCache(Guid userId, Size size, string fileName)
private static void AddToCache(Guid userID, Size size, string fileName)
{
if (CacheNotify != null)
{
CacheNotify.Publish(new UserPhotoManagerCacheItem { UserID = userId.ToString(), Size = new CacheSize() { Height = size.Height, Width = size.Width }, FileName = fileName }, CacheNotifyAction.InsertOrUpdate);
CacheNotify.Publish(new UserPhotoManagerCacheItem { UserID = Google.Protobuf.ByteString.CopyFrom(userID.ToByteArray()), Size = ToCache(size), FileName = fileName }, CacheNotifyAction.InsertOrUpdate);
}
}
@ -513,7 +484,7 @@ namespace ASC.Web.Core.Users
ClearCache(userID);
}
var store = GetDataStore();
var store = GetDataStore(tenant.TenantId);
var photoUrl = GetDefaultPhotoAbsoluteWebPath();
if (data != null && data.Length > 0)
@ -634,7 +605,7 @@ namespace ASC.Web.Core.Users
if (data == null || data.Length <= 0) throw new UnknownImageFormatException();
if (maxFileSize != -1 && data.Length > maxFileSize) throw new ImageWeightLimitException();
var resizeTask = new ResizeWorkerItem(userID, data, maxFileSize, size, GetDataStore(), UserPhotoThumbnailSettings.LoadForUser(userID));
var resizeTask = new ResizeWorkerItem(userID, data, maxFileSize, size, GetDataStore(tenantId), UserPhotoThumbnailSettings.LoadForUser(userID));
if (now)
{
@ -692,25 +663,25 @@ namespace ASC.Web.Core.Users
}
}
public static string GetTempPhotoAbsoluteWebPath(string fileName)
public static string GetTempPhotoAbsoluteWebPath(int tenantId, string fileName)
{
return GetDataStore().GetUri(_tempDomainName, fileName).ToString();
return GetDataStore(tenantId).GetUri(_tempDomainName, fileName).ToString();
}
public static string SaveTempPhoto(byte[] data, long maxFileSize, int maxWidth, int maxHeight)
public static string SaveTempPhoto(int tenantId, byte[] data, long maxFileSize, int maxWidth, int maxHeight)
{
data = TryParseImage(data, maxFileSize, new Size(maxWidth, maxHeight), out var imgFormat, out var width, out var height);
var fileName = Guid.NewGuid() + "." + CommonPhotoManager.GetImgFormatName(imgFormat);
var store = GetDataStore();
var store = GetDataStore(tenantId);
using var stream = new MemoryStream(data);
return store.Save(_tempDomainName, fileName, stream).ToString();
}
public static byte[] GetTempPhotoData(string fileName)
public static byte[] GetTempPhotoData(int tenantId, string fileName)
{
using var s = GetDataStore().GetReadStream(_tempDomainName, fileName);
using var s = GetDataStore(tenantId).GetReadStream(_tempDomainName, fileName);
var data = new MemoryStream();
var buffer = new byte[1024 * 10];
while (true)
@ -722,9 +693,9 @@ namespace ASC.Web.Core.Users
return data.ToArray();
}
public static string GetSizedTempPhotoAbsoluteWebPath(string fileName, int newWidth, int newHeight)
public static string GetSizedTempPhotoAbsoluteWebPath(int tenantId, string fileName, int newWidth, int newHeight)
{
var store = GetDataStore();
var store = GetDataStore(tenantId);
if (store.IsFile(_tempDomainName, fileName))
{
using var s = store.GetReadStream(_tempDomainName, fileName);
@ -752,13 +723,13 @@ namespace ASC.Web.Core.Users
return GetDefaultPhotoAbsoluteWebPath(new Size(newWidth, newHeight));
}
public static void RemoveTempPhoto(string fileName)
public static void RemoveTempPhoto(int tenantId, string fileName)
{
var index = fileName.LastIndexOf('.');
var fileNameWithoutExt = (index != -1) ? fileName.Substring(0, index) : fileName;
try
{
var store = GetDataStore();
var store = GetDataStore(tenantId);
store.DeleteFiles(_tempDomainName, "", fileNameWithoutExt + "*.*", false);
}
catch { };
@ -780,14 +751,14 @@ namespace ASC.Web.Core.Users
return null;
}
public static string SaveThumbnail(Guid userID, Image img, ImageFormat format)
public static string SaveThumbnail(int tenantId, Guid userID, Image img, ImageFormat format)
{
var moduleID = Guid.Empty;
var widening = CommonPhotoManager.GetImgFormatName(format);
var size = img.Size;
var fileName = string.Format("{0}{1}_size_{2}-{3}.{4}", (moduleID == Guid.Empty ? "" : moduleID.ToString()), userID, img.Width, img.Height, widening);
var store = GetDataStore();
var store = GetDataStore(tenantId);
string photoUrl;
using (var s = new MemoryStream(CommonPhotoManager.SaveToBytes(img)))
{
@ -799,17 +770,17 @@ namespace ASC.Web.Core.Users
return photoUrl;
}
public static byte[] GetUserPhotoData(Guid userId, Size size)
public static byte[] GetUserPhotoData(int tenantId, Guid userId, Size size)
{
try
{
var pattern = string.Format("{0}_size_{1}-{2}.*", userId, size.Width, size.Height);
var fileName = GetDataStore().ListFilesRelative("", "", pattern, false).FirstOrDefault();
var fileName = GetDataStore(tenantId).ListFilesRelative("", "", pattern, false).FirstOrDefault();
if (string.IsNullOrEmpty(fileName)) return null;
using var s = GetDataStore().GetReadStream("", fileName);
using var s = GetDataStore(tenantId).GetReadStream("", fileName);
var data = new MemoryStream();
var buffer = new byte[1024 * 10];
while (true)
@ -827,10 +798,33 @@ namespace ASC.Web.Core.Users
}
}
private static IDataStore GetDataStore()
private static IDataStore GetDataStore(int tenantId)
{
return StorageFactory.GetStorage(TenantProvider.CurrentTenantID.ToString(), "userPhotos");
return StorageFactory.GetStorage(tenantId.ToString(), "userPhotos");
}
private static Size FromCahe(CacheSize cacheSize) =>
cacheSize switch
{
CacheSize.Big => BigFotoSize,
CacheSize.Max => MaxFotoSize,
CacheSize.Medium => MediumFotoSize,
CacheSize.Original => OriginalFotoSize,
CacheSize.Retina => RetinaFotoSize,
CacheSize.Small => SmallFotoSize,
_ => OriginalFotoSize,
};
private static CacheSize ToCache(Size size) =>
size switch
{
Size(var w, var h) when w == RetinaFotoSize.Width && h == RetinaFotoSize.Height => CacheSize.Retina,
Size(var w, var h) when w == MaxFotoSize.Width && h == MaxFotoSize.Height => CacheSize.Max,
Size(var w, var h) when w == BigFotoSize.Width && h == BigFotoSize.Height => CacheSize.Big,
Size(var w, var h) when w == SmallFotoSize.Width && h == SmallFotoSize.Height => CacheSize.Small,
Size(var w, var h) when w == MediumFotoSize.Width && h == MediumFotoSize.Height => CacheSize.Medium,
_ => CacheSize.Original
};
}
#region Exception Classes
@ -952,4 +946,10 @@ namespace ASC.Web.Core.Users
};
}
}
public static class SizeExtend
{
public static void Deconstruct(this Size size, out int w, out int h) =>
(w, h) = (size.Width, size.Height);
}
}

View File

@ -160,7 +160,7 @@ namespace ASC.Web.Core.Users
foreach (var item in bitmaps)
{
using var mainImgBitmap = MainImgBitmap(tenantId);
UserPhotoManager.SaveThumbnail(UserId, item.Bitmap, mainImgBitmap.RawFormat);
UserPhotoManager.SaveThumbnail(tenantId, UserId, item.Bitmap, mainImgBitmap.RawFormat);
}
}
}

View File

@ -211,7 +211,7 @@ namespace ASC.Web.Core.WhiteLabel
ResizeLogo(type, generalFileName, data, -1, generalSize, store);
}
public void SetLogo(Dictionary<int, string> logo)
public void SetLogo(int tenantId, Dictionary<int, string> logo)
{
var xStart = @"data:image/png;base64,";
@ -229,10 +229,10 @@ namespace ASC.Web.Core.WhiteLabel
{
var fileName = Path.GetFileName(currentLogoPath);
fileExt = fileName.Split('.').Last();
data = UserPhotoManager.GetTempPhotoData(fileName);
data = UserPhotoManager.GetTempPhotoData(tenantId, fileName);
try
{
UserPhotoManager.RemoveTempPhoto(fileName);
UserPhotoManager.RemoveTempPhoto(tenantId, fileName);
}
catch (Exception ex)
{

View File

@ -3,15 +3,18 @@
package ASC.Web.Core.Users;
message UserPhotoManagerCacheItem {
string UserID = 1;
bytes UserID = 1;
CacheSize Size = 2;
string FileName = 3;
}
message CacheSize {
int32 Width = 1;
int32 Height = 2;
enum CacheSize {
Max = 0;
Retina = 1;
Big = 2;
Medium = 3;
Small = 4;
Original = 5;
}

View File

@ -0,0 +1,44 @@
# AdvancedSelector
## Usage
```js
import { AdvancedSelector } from 'asc-web-components';
```
#### Description
Required to select some advanced data.
#### Usage
```js
let options = [{key: "self", label: "Me"}];
options = [...options, ...[...Array(100).keys()].map(
index => {
return { key: `user${index}`, label: `User ${index+1} of ${optionsCount}` };
}
)];
<AdvancedSelector
placeholder="Search users"
onSearchChanged={(e) => console.log(e.target.value)}
options={options}
isMultiSelect={false}
buttonLabel="Add members"
onSelect={(selectedOptions) => console.log("onSelect", selectedOptions)}
/>
```
#### Properties
| Props | Type | Required | Values | Default | Description |
| ------------------ | -------- | :------: | ----------------------------------------- | --------- | ----------------------------------------------------- |
| `placeholder` | `string` | - | | | |
| `options` | `array of objects` | - | | | |
| `isMultiSelect` | `bool` | - | - | | |
| `buttonLabel` | `string` | - | - | | |
| `onSearchChanged` | `func` | - | - | | |
| `onSelect` | `func` | - | - | | |

View File

@ -0,0 +1,109 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import { withKnobs, text, number } from "@storybook/addon-knobs/react";
import withReadme from "storybook-readme/with-readme";
import Readme from "./README.md";
import { AdvancedSelector } from "asc-web-components";
import Section from "../../.storybook/decorators/section";
import { boolean } from "@storybook/addon-knobs/dist/deprecated";
import { ArrayValue } from "react-values";
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
storiesOf("Components|AdvancedSelector", module)
.addDecorator(withKnobs)
.addDecorator(withReadme(Readme))
.add("base", () => {
const optionsCount = number("Users count", 1000);
const groups = [
{
key: "group-all",
label: "All groups",
total: 0
},
{
key: "group-dev",
label: "Development",
total: 0
},
{
key: "group-management",
label: "Management",
total: 0
},
{
key: "group-marketing",
label: "Marketing",
total: 0
},
{
key: "group-mobile",
label: "Mobile",
total: 0
},
{
key: "group-support",
label: "Support",
total: 0
},
{
key: "group-web",
label: "Web",
total: 0
}
];
const options = Array.from({ length: optionsCount }, (v, index) => {
const additional_group = groups[getRandomInt(1, 6)];
groups[0].total++;
additional_group.total++;
return {
key: `user${index}`,
groups: ["group-all", additional_group.key],
label: `User${index + 1} (All groups, ${additional_group.label})`
};
});
return (
<Section>
<ArrayValue
defaultValue={options}
onChange={() => console.log("ArrayValue onChange")}
>
{({ value, set }) => (
<AdvancedSelector
placeholder={text("placeholder", "Search users")}
onSearchChanged={value => {
set(options.filter(option => {
return (
option.label.indexOf(value) > -1
);
}));
}}
options={value}
groups={groups}
selectedGroups={[groups[0]]}
isMultiSelect={boolean("isMultiSelect", true)}
buttonLabel={text("buttonLabel", "Add members")}
selectAllLabel={text("selectAllLabel", "Select all")}
onSelect={selectedOptions => {
console.log("onSelect", selectedOptions);
}}
onChangeGroup={group => {
set(options.filter(option => {
return (
option.groups &&
option.groups.length > 0 &&
option.groups.indexOf(group.key) > -1
);
}));
}}
/>
)}
</ArrayValue>
</Section>
);
});

View File

@ -18,6 +18,9 @@ function getData() {
{ key: 'filter-type', group: 'filter-type', label: 'Type', isHeader: true },
{ key: '0', group: 'filter-type', label: 'Folders' },
{ key: '1', group: 'filter-type', label: 'Employee' },
{ key: 'filter-test', group: 'filter-test', label: 'Test', isHeader: true },
{ key: '0', group: 'filter-test', label: 'test1' },
{ key: '1', group: 'filter-test', label: 'test2' },
{ key: 'filter-other', group: 'filter-other', label: 'Other', isHeader: true },
{ key: '0', group: 'filter-other', subgroup: 'filter-groups', defaultSelectLabel: 'Select', label: 'Groups' },
{ key: '0', inSubgroup: true, group: 'filter-groups', label: 'Administration'},

View File

@ -17,8 +17,6 @@ function myDateKnob(name, defaultValue) {
const locales = moment.locales();
const arraySize = ['base', 'big'];
storiesOf('Components|Input', module)
.addDecorator(withKnobs)
.addDecorator(withReadme(Readme))
@ -28,14 +26,13 @@ storiesOf('Components|Input', module)
onChange={date => {
action('Selected date')(date);
}}
//disabled={boolean('disabled', false)}
disabled={boolean('disabled', false)}
themeColor={color('themeColor', '#ED7309')}
selectedDate={myDateKnob('selectedDate', new Date())}
openToDate={myDateKnob('openToDate', new Date())}
minDate={myDateKnob('minDate', new Date("2010/02/01"))}
maxDate={myDateKnob('maxDate', new Date("2019/09/01"))}
language={select('location', locales, 'en')}
//size={select('size', arraySize, 'base')}
minDate={myDateKnob('minDate', new Date("2018/05/15"))}
maxDate={myDateKnob('maxDate', new Date("2020/09/15"))}
locale={select('location', locales, 'en')}
/>
</Section>
));

View File

@ -46,8 +46,8 @@ class SearchStory extends React.Component {
return(
<Section>
<StringValue
onChange={e => {
action('onChange')(e);
onChange={value => {
action('onChange')(value);
}
}
>
@ -64,14 +64,10 @@ class SearchStory extends React.Component {
isDisabled={boolean('isDisabled', false)}
size={select('size', sizeOptions, 'base')}
scale={boolean('scale', false)}
isNeedFilter={boolean('isNeedFilter', true)}
getFilterData={getData}
selectedFilterData={this.state.selectedFilterData}
placeholder={text('placeholder', 'Search')}
onChangeFilter={(result) => {console.log(result)}}
value={value}
onChange={e => {
set(e.target.value);
onChange={value => {
set(value);
}}
/>
</Section>