Merge branch 'master' of https://github.com/ONLYOFFICE/CommunityServer-AspNetCore
# 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:
commit
b6e46a305c
@ -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()
|
||||
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -3,5 +3,5 @@
|
||||
package ASC.Common;
|
||||
|
||||
message AscCacheItem {
|
||||
string Id = 1;
|
||||
bytes Id = 1;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -3,6 +3,6 @@
|
||||
package ASC.Core.Caching;
|
||||
|
||||
message UserInfoCacheItem {
|
||||
string ID = 1;
|
||||
bytes ID = 1;
|
||||
int32 Tenant = 2;
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
23
products/ASC.People/Client/config-overrides.js
Normal file
23
products/ASC.People/Client/config-overrides.js
Normal 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;
|
||||
}
|
@ -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/"
|
||||
},
|
||||
|
@ -10,7 +10,7 @@ const SectionPagingContent = ({
|
||||
onLoading,
|
||||
selectedCount
|
||||
}) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const onNextClick = useCallback(
|
||||
e => {
|
||||
if (!filter.hasNext()) {
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
@ -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"
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
@ -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;
|
||||
|
@ -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")
|
||||
);
|
||||
|
||||
|
55
products/ASC.People/Client/src/resourceConfig.json
Normal file
55
products/ASC.People/Client/src/resourceConfig.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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==
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
"cSpell.words": [
|
||||
"Romb",
|
||||
"Rombs",
|
||||
"combobox",
|
||||
"reactstrap"
|
||||
]
|
||||
}
|
||||
|
@ -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",
|
||||
|
308
web/ASC.Web.Components/src/components/advanced-selector/index.js
Normal file
308
web/ASC.Web.Components/src/components/advanced-selector/index.js
Normal 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;
|
@ -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'
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
@ -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>
|
||||
)
|
||||
}
|
@ -35,6 +35,7 @@ const StyledHideFilterButton = styled.div`
|
||||
`;
|
||||
const StyledHideFilter = styled.div`
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
`;
|
||||
const StyledPopoverBody = styled(PopoverBody)`
|
||||
border-radius: 6px;
|
@ -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: '',
|
||||
|
@ -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;
|
@ -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();
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -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'
|
7
web/ASC.Web.Components/src/utils/array.js
Normal file
7
web/ASC.Web.Components/src/utils/array.js
Normal 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));
|
||||
};
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
44
web/ASC.Web.Storybook/stories/advanced-selector/README.md
Normal file
44
web/ASC.Web.Storybook/stories/advanced-selector/README.md
Normal 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` | - | - | | |
|
109
web/ASC.Web.Storybook/stories/advanced-selector/index.stories.js
Normal file
109
web/ASC.Web.Storybook/stories/advanced-selector/index.stories.js
Normal 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>
|
||||
);
|
||||
});
|
@ -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'},
|
||||
|
@ -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>
|
||||
));
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user