Merge branch 'feature/files' of https://github.com/ONLYOFFICE/AppServer into feature/files

This commit is contained in:
NikolayRechkin 2020-07-02 11:03:03 +03:00
commit 478e03530b
31 changed files with 1015 additions and 812 deletions

View File

@ -29,6 +29,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ASC.Common.Logging; using ASC.Common.Logging;
using ASC.Common.Notify.Patterns; using ASC.Common.Notify.Patterns;
using ASC.Notify.Channels; using ASC.Notify.Channels;
@ -36,6 +37,7 @@ using ASC.Notify.Cron;
using ASC.Notify.Messages; using ASC.Notify.Messages;
using ASC.Notify.Patterns; using ASC.Notify.Patterns;
using ASC.Notify.Recipients; using ASC.Notify.Recipients;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -360,9 +362,10 @@ namespace ASC.Notify.Engine
PrepareRequestFillPatterns(request); PrepareRequestFillPatterns(request);
PrepareRequestFillTags(request); PrepareRequestFillTags(request);
} }
catch (Exception) catch (Exception ex)
{ {
responses.Add(new SendResponse(request.NotifyAction, null, request.Recipient, SendResult.Impossible)); responses.Add(new SendResponse(request.NotifyAction, null, request.Recipient, SendResult.Impossible));
log.Error("Prepare", ex);
} }
if (request.SenderNames != null && request.SenderNames.Length > 0) if (request.SenderNames != null && request.SenderNames.Length > 0)

View File

@ -26,10 +26,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\products\ASC.Files\Server\ASC.Files.csproj" /> <ProjectReference Include="..\..\..\products\ASC.Files\Server\ASC.Files.csproj" />
<ProjectReference Include="..\..\..\web\ASC.Web.Core\ASC.Web.Core.csproj" />
<ProjectReference Include="..\..\ASC.Common\ASC.Common.csproj" /> <ProjectReference Include="..\..\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\..\ASC.Core.Common\ASC.Core.Common.csproj" /> <ProjectReference Include="..\..\ASC.Core.Common\ASC.Core.Common.csproj" />
<ProjectReference Include="..\..\ASC.Data.Storage\ASC.Data.Storage.csproj" /> <ProjectReference Include="..\..\ASC.Data.Storage\ASC.Data.Storage.csproj" />
<ProjectReference Include="..\ASC.Notify\ASC.Notify.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Protobuf Include="protos\BackupProgress.proto" /> <Protobuf Include="protos\BackupProgress.proto" />

View File

@ -24,32 +24,37 @@
*/ */
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ASC.Common; using ASC.Common;
using ASC.Common.Utils; using ASC.Common.Utils;
using ASC.Web.Studio.Core.Notify;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace ASC.Data.Backup.Service namespace ASC.Data.Backup.Service
{ {
internal class BackupServiceLauncher : IHostedService internal class BackupServiceLauncher : IHostedService
{ {
public IServiceProvider ServiceProvider { get; }
private BackupCleanerService CleanerService { get; set; } private BackupCleanerService CleanerService { get; set; }
private BackupSchedulerService SchedulerService { get; set; } private BackupSchedulerService SchedulerService { get; set; }
private BackupWorker BackupWorker { get; set; } private BackupWorker BackupWorker { get; set; }
private IConfiguration Configuration { get; set; } private IConfiguration Configuration { get; set; }
public BackupServiceNotifier BackupServiceNotifier { get; } public BackupServiceNotifier BackupServiceNotifier { get; }
public BackupServiceLauncher( public BackupServiceLauncher(
IServiceProvider serviceProvider,
BackupCleanerService cleanerService, BackupCleanerService cleanerService,
BackupSchedulerService schedulerService, BackupSchedulerService schedulerService,
BackupWorker backupWorker, BackupWorker backupWorker,
IConfiguration configuration, IConfiguration configuration,
BackupServiceNotifier backupServiceNotifier) BackupServiceNotifier backupServiceNotifier)
{ {
ServiceProvider = serviceProvider;
CleanerService = cleanerService; CleanerService = cleanerService;
SchedulerService = schedulerService; SchedulerService = schedulerService;
BackupWorker = backupWorker; BackupWorker = backupWorker;
@ -58,7 +63,9 @@ namespace ASC.Data.Backup.Service
} }
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
NotifyConfiguration.Configure(ServiceProvider);
var settings = Configuration.GetSetting<BackupSettings>("backup"); var settings = Configuration.GetSetting<BackupSettings>("backup");
BackupWorker.Start(settings); BackupWorker.Start(settings);

View File

@ -25,79 +25,143 @@
using System; using System;
using System.Linq;
using ASC.Common; using ASC.Common;
using ASC.Common.Logging; using ASC.Core;
using Microsoft.Extensions.Options; using ASC.Core.Tenants;
using ASC.Notify; using ASC.Core.Users;
using ASC.Notify.Model;
using ASC.Notify.Patterns;
using ASC.Notify.Recipients;
using ASC.Web.Core.Users;
using ASC.Web.Studio.Core.Notify;
using ASC.Web.Studio.Utility;
using Microsoft.Extensions.DependencyInjection;
namespace ASC.Data.Backup namespace ASC.Data.Backup
{ {
public class NotifyHelper public class NotifyHelper
{ {
private const string NotifyService = "ASC.Web.Studio.Core.Notify.StudioNotifyService, ASC.Web.Studio"; public IServiceProvider ServiceProvider { get; }
private const string MethodTransferStart = "MigrationPortalStart";
private const string MethodTransferCompleted = "MigrationPortalSuccess"; public NotifyHelper(IServiceProvider serviceProvider)
private const string MethodTransferError = "MigrationPortalError"; {
private const string MethodBackupCompleted = "SendMsgBackupCompleted"; ServiceProvider = serviceProvider;
private const string MethodRestoreStarted = "SendMsgRestoreStarted"; }
private const string MethodRestoreCompleted = "SendMsgRestoreCompleted";
private readonly ILog log;
private readonly NotifyService notifyService;
public NotifyHelper(IOptionsMonitor<ILog> options, NotifyService notifyService) public void SendAboutTransferStart(Tenant tenant, string targetRegion, bool notifyUsers)
{ {
this.notifyService = notifyService; MigrationNotify(tenant, Actions.MigrationPortalStart, targetRegion, string.Empty, notifyUsers);
log = options.CurrentValue;
}
public void SendAboutTransferStart(int tenantId, string targetRegion, bool notifyUsers)
{
SendNotification(MethodTransferStart, tenantId, targetRegion, notifyUsers);
} }
public void SendAboutTransferComplete(int tenantId, string targetRegion, string targetAddress, bool notifyOnlyOwner) public void SendAboutTransferComplete(Tenant tenant, string targetRegion, string targetAddress, bool notifyOnlyOwner)
{ {
SendNotification(MethodTransferCompleted, tenantId, targetRegion, targetAddress, !notifyOnlyOwner); MigrationNotify(tenant, Actions.MigrationPortalSuccess, targetRegion, targetAddress, !notifyOnlyOwner);
} }
public void SendAboutTransferError(int tenantId, string targetRegion, string resultAddress, bool notifyOnlyOwner) public void SendAboutTransferError(Tenant tenant, string targetRegion, string resultAddress, bool notifyOnlyOwner)
{ {
SendNotification(MethodTransferError, tenantId, targetRegion, resultAddress, !notifyOnlyOwner); MigrationNotify(tenant, !string.IsNullOrEmpty(targetRegion) ? Actions.MigrationPortalError : Actions.MigrationPortalServerFailure, targetRegion, resultAddress, !notifyOnlyOwner);
} }
public void SendAboutBackupCompleted(int tenantId, Guid userId, string link) public void SendAboutBackupCompleted(Guid userId)
{ {
SendNotification(MethodBackupCompleted, tenantId, userId, link); using var scope = ServiceProvider.CreateScope();
var userManager = scope.ServiceProvider.GetService<UserManager>();
var studioNotifyHelper = scope.ServiceProvider.GetService<StudioNotifyHelper>();
var notifySource = scope.ServiceProvider.GetService<StudioNotifySource>();
var displayUserSettingsHelper = scope.ServiceProvider.GetService<DisplayUserSettingsHelper>();
var client = WorkContext.NotifyContext.NotifyService.RegisterClient(notifySource, scope);
client.SendNoticeToAsync(
Actions.BackupCreated,
new[] { studioNotifyHelper.ToRecipient(userId) },
new[] { StudioNotifyService.EMailSenderName },
new TagValue(Tags.OwnerName, userManager.GetUsers(userId).DisplayUserName(displayUserSettingsHelper)));
} }
public void SendAboutRestoreStarted(int tenantId, bool notifyAllUsers) public void SendAboutRestoreStarted(Tenant tenant, bool notifyAllUsers)
{ {
SendNotification(MethodRestoreStarted, tenantId, notifyAllUsers); using var scope = ServiceProvider.CreateScope();
var userManager = scope.ServiceProvider.GetService<UserManager>();
var studioNotifyHelper = scope.ServiceProvider.GetService<StudioNotifyHelper>();
var notifySource = scope.ServiceProvider.GetService<StudioNotifySource>();
var displayUserSettingsHelper = scope.ServiceProvider.GetService<DisplayUserSettingsHelper>();
var client = WorkContext.NotifyContext.NotifyService.RegisterClient(notifySource, scope);
var owner = userManager.GetUsers(tenant.OwnerId);
var users =
notifyAllUsers
? studioNotifyHelper.RecipientFromEmail(userManager.GetUsers(EmployeeStatus.Active).Where(r => r.ActivationStatus == EmployeeActivationStatus.Activated).Select(u => u.Email).ToList(), false)
: owner.ActivationStatus == EmployeeActivationStatus.Activated ? studioNotifyHelper.RecipientFromEmail(owner.Email, false) : new IDirectRecipient[0];
client.SendNoticeToAsync(
Actions.RestoreStarted,
users,
new[] { StudioNotifyService.EMailSenderName });
} }
public void SendAboutRestoreCompleted(int tenantId, bool notifyAllUsers) public void SendAboutRestoreCompleted(Tenant tenant, bool notifyAllUsers)
{ {
SendNotification(MethodRestoreCompleted, tenantId, notifyAllUsers); using var scope = ServiceProvider.CreateScope();
} var userManager = scope.ServiceProvider.GetService<UserManager>();
var studioNotifyHelper = scope.ServiceProvider.GetService<StudioNotifyHelper>();
private void SendNotification(string method, int tenantId, params object[] args) var notifySource = scope.ServiceProvider.GetService<StudioNotifySource>();
{ var displayUserSettingsHelper = scope.ServiceProvider.GetService<DisplayUserSettingsHelper>();
try var client = WorkContext.NotifyContext.NotifyService.RegisterClient(notifySource, scope);
{
notifyService.InvokeSendMethod(NotifyService, method, tenantId, args); var owner = userManager.GetUsers(tenant.OwnerId);
}
catch (Exception error) var users =
{ notifyAllUsers
log.Warn("Error while sending notification", error); ? userManager.GetUsers(EmployeeStatus.Active).Select(u => studioNotifyHelper.ToRecipient(u.ID)).ToArray()
} : new[] { studioNotifyHelper.ToRecipient(owner.ID) };
client.SendNoticeToAsync(
Actions.RestoreCompleted,
users,
new[] { StudioNotifyService.EMailSenderName },
new TagValue(Tags.OwnerName, owner.DisplayUserName(displayUserSettingsHelper)));
}
private void MigrationNotify(Tenant tenant, INotifyAction action, string region, string url, bool notify)
{
using var scope = ServiceProvider.CreateScope();
var userManager = scope.ServiceProvider.GetService<UserManager>();
var studioNotifyHelper = scope.ServiceProvider.GetService<StudioNotifyHelper>();
var notifySource = scope.ServiceProvider.GetService<StudioNotifySource>();
var client = WorkContext.NotifyContext.NotifyService.RegisterClient(notifySource, scope);
var users = userManager.GetUsers()
.Where(u => notify ? u.ActivationStatus.HasFlag(EmployeeActivationStatus.Activated) : u.IsOwner(tenant))
.Select(u => studioNotifyHelper.ToRecipient(u.ID))
.ToArray();
if (users.Any())
{
client.SendNoticeToAsync(
action,
users,
new[] { StudioNotifyService.EMailSenderName },
new TagValue(Tags.RegionName, TransferResourceHelper.GetRegionDescription(region)),
new TagValue(Tags.PortalUrl, url));
}
} }
} }
public static class NotifyHelperExtension public static class NotifyHelperExtension
{ {
public static DIHelper AddNotifyHelperService(this DIHelper services) public static DIHelper AddNotifyHelperService(this DIHelper services)
{ {
services.TryAddScoped<NotifyHelper>(); services.TryAddSingleton<NotifyHelper>();
return services
.AddNotifyService(); return services
.AddNotifyConfiguration()
.AddStudioNotifySourceService()
.AddUserManagerService()
.AddStudioNotifyHelperService()
.AddDisplayUserSettingsService();
} }
} }
} }

View File

@ -376,7 +376,8 @@ namespace ASC.Data.Backup.Service
var backupWorker = scope.ServiceProvider.GetService<BackupWorker>(); var backupWorker = scope.ServiceProvider.GetService<BackupWorker>();
var backupName = string.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}.{2}", tenantManager.GetTenant(TenantId).TenantAlias, DateTime.UtcNow, ArchiveFormat); var tenant = tenantManager.GetTenant(TenantId);
var backupName = string.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}.{2}", tenant.TenantAlias, DateTime.UtcNow, ArchiveFormat);
var tempFile = Path.Combine(TempFolder, backupName); var tempFile = Path.Combine(TempFolder, backupName);
var storagePath = tempFile; var storagePath = tempFile;
try try
@ -424,10 +425,11 @@ namespace ASC.Data.Backup.Service
if (UserId != Guid.Empty && !IsScheduled) if (UserId != Guid.Empty && !IsScheduled)
{ {
notifyHelper.SendAboutBackupCompleted(TenantId, UserId, Link); notifyHelper.SendAboutBackupCompleted(UserId);
} }
IsCompleted = true; IsCompleted = true;
backupWorker.PublishProgress(this);
} }
catch (Exception error) catch (Exception error)
{ {
@ -437,6 +439,15 @@ namespace ASC.Data.Backup.Service
} }
finally finally
{ {
try
{
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
Log.Error("publish", error);
}
try try
{ {
if (!(storagePath == tempFile && StorageType == BackupStorageType.Local)) if (!(storagePath == tempFile && StorageType == BackupStorageType.Local))
@ -503,13 +514,13 @@ namespace ASC.Data.Backup.Service
var tempFile = PathHelper.GetTempFileName(TempFolder); var tempFile = PathHelper.GetTempFileName(TempFolder);
try try
{ {
notifyHelper.SendAboutRestoreStarted(TenantId, Notify); tenant = tenantManager.GetTenant(TenantId);
notifyHelper.SendAboutRestoreStarted(tenant, Notify);
var storage = backupStorageFactory.GetBackupStorage(StorageType, TenantId, StorageParams); var storage = backupStorageFactory.GetBackupStorage(StorageType, TenantId, StorageParams);
storage.Download(StoragePath, tempFile); storage.Download(StoragePath, tempFile);
Percentage = 10; Percentage = 10;
tenant = tenantManager.GetTenant(TenantId);
tenant.SetStatus(TenantStatus.Restoring); tenant.SetStatus(TenantStatus.Restoring);
tenantManager.SaveTenant(tenant); tenantManager.SaveTenant(tenant);
@ -536,7 +547,7 @@ namespace ASC.Data.Backup.Service
var tenants = tenantManager.GetTenants(); var tenants = tenantManager.GetTenants();
foreach (var t in tenants) foreach (var t in tenants)
{ {
notifyHelper.SendAboutRestoreCompleted(t.TenantId, Notify); notifyHelper.SendAboutRestoreCompleted(t, Notify);
} }
} }
} }
@ -557,14 +568,17 @@ namespace ASC.Data.Backup.Service
// sleep until tenants cache expires // sleep until tenants cache expires
Thread.Sleep(TimeSpan.FromMinutes(2)); Thread.Sleep(TimeSpan.FromMinutes(2));
notifyHelper.SendAboutRestoreCompleted(restoredTenant.TenantId, Notify); notifyHelper.SendAboutRestoreCompleted(restoredTenant, Notify);
} }
Percentage = 75; Percentage = 75;
backupWorker.PublishProgress(this);
File.Delete(tempFile); File.Delete(tempFile);
Percentage = 100; Percentage = 100;
backupWorker.PublishProgress(this);
} }
catch (Exception error) catch (Exception error)
{ {
@ -579,6 +593,15 @@ namespace ASC.Data.Backup.Service
} }
finally finally
{ {
try
{
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
Log.Error("publish", error);
}
if (File.Exists(tempFile)) if (File.Exists(tempFile))
{ {
File.Delete(tempFile); File.Delete(tempFile);
@ -649,11 +672,12 @@ namespace ASC.Data.Backup.Service
var backupWorker = scope.ServiceProvider.GetService<BackupWorker>(); var backupWorker = scope.ServiceProvider.GetService<BackupWorker>();
var tempFile = PathHelper.GetTempFileName(TempFolder); var tempFile = PathHelper.GetTempFileName(TempFolder);
var alias = tenantManager.GetTenant(TenantId).TenantAlias; var tenant = tenantManager.GetTenant(TenantId);
var alias = tenant.TenantAlias;
try try
{ {
notifyHelper.SendAboutTransferStart(TenantId, TargetRegion, Notify); notifyHelper.SendAboutTransferStart(tenant, TargetRegion, Notify);
var transferProgressItem = scope.ServiceProvider.GetService<TransferPortalTask>(); var transferProgressItem = scope.ServiceProvider.GetService<TransferPortalTask>();
transferProgressItem.Init(TenantId, ConfigPaths[CurrentRegion], ConfigPaths[TargetRegion], Limit, TempFolder); transferProgressItem.Init(TenantId, ConfigPaths[CurrentRegion], ConfigPaths[TargetRegion], Limit, TempFolder);
transferProgressItem.ProgressChanged += (sender, args) => transferProgressItem.ProgressChanged += (sender, args) =>
@ -668,7 +692,8 @@ namespace ASC.Data.Backup.Service
transferProgressItem.RunJob(); transferProgressItem.RunJob();
Link = GetLink(alias, false); Link = GetLink(alias, false);
notifyHelper.SendAboutTransferComplete(TenantId, TargetRegion, Link, !Notify); notifyHelper.SendAboutTransferComplete(tenant, TargetRegion, Link, !Notify);
backupWorker.PublishProgress(this);
} }
catch (Exception error) catch (Exception error)
{ {
@ -676,10 +701,19 @@ namespace ASC.Data.Backup.Service
Error = error; Error = error;
Link = GetLink(alias, true); Link = GetLink(alias, true);
notifyHelper.SendAboutTransferError(TenantId, TargetRegion, Link, !Notify); notifyHelper.SendAboutTransferError(tenant, TargetRegion, Link, !Notify);
} }
finally finally
{ {
try
{
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
Log.Error("publish", error);
}
if (File.Exists(tempFile)) if (File.Exists(tempFile))
{ {
File.Delete(tempFile); File.Delete(tempFile);

View File

@ -156,6 +156,7 @@ namespace ASC.Notify
} }
dbContext.SaveChanges(); dbContext.SaveChanges();
tx.Commit();
return messages; return messages;
} }

View File

@ -1,7 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { toastr, utils } from "asc-web-components"; import { toastr, utils } from "asc-web-components";
import { api } from "asc-web-common";
import TreeFolders from "./TreeFolders"; import TreeFolders from "./TreeFolders";
import { import {
setFilter, setFilter,
@ -17,8 +16,7 @@ class ArticleBodyContent extends React.Component {
state = { state = {
expandedKeys: this.props.filter.treeFolders, expandedKeys: this.props.filter.treeFolders,
data: this.props.data, data: this.props.data,
showNewFilesPanel: false, showNewFilesPanel: false
newFiles: []
}; };
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -31,6 +29,9 @@ class ArticleBodyContent extends React.Component {
this.setState({ expandedKeys: filter.treeFolders }); this.setState({ expandedKeys: filter.treeFolders });
} }
//console.log(prevProps.data);
//console.log(data);
if (!utils.array.isArrayEqual(prevProps.data, data)) { if (!utils.array.isArrayEqual(prevProps.data, data)) {
this.setState({ data }); this.setState({ data });
} }
@ -41,7 +42,7 @@ class ArticleBodyContent extends React.Component {
return true; return true;
} }
return false; return true;
} }
onSelect = data => { onSelect = data => {
@ -60,28 +61,12 @@ class ArticleBodyContent extends React.Component {
onShowNewFilesPanel = (folderId) => { onShowNewFilesPanel = (folderId) => {
const { showNewFilesPanel } = this.state; const { showNewFilesPanel } = this.state;
const { onLoading } = this.props; this.setState({showNewFilesPanel: !showNewFilesPanel, newFolderId: [folderId]});
if(typeof(folderId) === "number") {
onLoading(true);
api.files
.getNewFiles(folderId)
.then((res) =>
this.setState({
showNewFilesPanel: !showNewFilesPanel,
newFiles: res,
newFolderId: folderId
})
)
.catch((err) => toastr.error(err))
.finally(() => onLoading(false));
} else {
this.setState({showNewFilesPanel: !showNewFilesPanel});
}
}; };
setNewFilesCount = (id, filesCount) => { setNewFilesCount = (folderPath, filesCount) => {
const data = this.state.data; const data = this.state.data;
const dataItem = data.find(x => x.id === id); const dataItem = data.find(x => x.id === folderPath[0]);
dataItem.newItems = filesCount ? filesCount : dataItem.newItems - 1; dataItem.newItems = filesCount ? filesCount : dataItem.newItems - 1;
this.setState({ data }); this.setState({ data });
} }
@ -106,7 +91,7 @@ class ArticleBodyContent extends React.Component {
isShare isShare
} = this.props; } = this.props;
const { showNewFilesPanel, expandedKeys, newFiles, newFolderId } = this.state; const { showNewFilesPanel, expandedKeys, newFolderId } = this.state;
//console.log("Article Body render", this.props, this.state.expandedKeys); //console.log("Article Body render", this.props, this.state.expandedKeys);
return ( return (
@ -118,7 +103,10 @@ class ArticleBodyContent extends React.Component {
setNewFilesCount={this.setNewFilesCount} setNewFilesCount={this.setNewFilesCount}
onLoading={onLoading} onLoading={onLoading}
folderId={newFolderId} folderId={newFolderId}
files={newFiles} treeFolders={data}
setTreeFolders={setTreeFolders}
//setNewItems={this.setNewItems}
/> />
)} )}
<TreeFolders <TreeFolders

View File

@ -5,10 +5,11 @@ import { withRouter } from "react-router";
import { withTranslation } from "react-i18next"; import { withTranslation } from "react-i18next";
import styled from "styled-components"; import styled from "styled-components";
import { RowContent, Link, Text, Icons, Badge, toastr } from "asc-web-components"; import { RowContent, Link, Text, Icons, Badge, toastr } from "asc-web-components";
import { constants } from 'asc-web-common'; import { constants, api } from 'asc-web-common';
import { createFile, createFolder, renameFolder, updateFile, setFilter, fetchFiles } from '../../../../../store/files/actions'; import { createFile, createFolder, renameFolder, updateFile, fetchFiles, setTreeFolders } from '../../../../../store/files/actions';
import { canWebEdit, isImage, isSound, isVideo, canConvert, getTitleWithoutExst } from '../../../../../store/files/selectors'; import { canWebEdit, isImage, isSound, isVideo, canConvert, getTitleWithoutExst } from '../../../../../store/files/selectors';
import store from "../../../../../store/store"; import store from "../../../../../store/store";
import { NewFilesPanel } from "../../../../panels";
import EditingWrapperComponent from "./EditingWrapperComponent"; import EditingWrapperComponent from "./EditingWrapperComponent";
const { FileAction } = constants; const { FileAction } = constants;
@ -25,7 +26,10 @@ class FilesRowContent extends React.PureComponent {
this.state = { this.state = {
itemTitle: titleWithoutExt, itemTitle: titleWithoutExt,
editingId: props.fileAction.id editingId: props.fileAction.id,
showNewFilesPanel: false,
newFolderId: [],
newItems: props.item.new
//loading: false //loading: false
}; };
} }
@ -180,20 +184,48 @@ class FilesRowContent extends React.PureComponent {
history.push(`${settings.homepage}/${fileId}/history`); history.push(`${settings.homepage}/${fileId}/history`);
} }
onBadgeClick = () => {
const { showNewFilesPanel } = this.state;
const { item, treeFolders, setTreeFolders, rootFolderId, newItems, filter } = this.props;
if (item.fileExst) {
api.files
.markAsRead([], [item.id])
.then(() => {
const data = treeFolders;
const dataItem = data.find((x) => x.id === rootFolderId);
dataItem.newItems = newItems ? dataItem.newItems - 1 : 0;//////newItems
setTreeFolders(data);
fetchFiles(this.props.selectedFolder.id, filter.clone(), store.dispatch);
})
.catch((err) => toastr.error(err))
} else {
const newFolderId = this.props.selectedFolder.pathParts;
newFolderId.push(item.id);
this.setState({
showNewFilesPanel: !showNewFilesPanel,
newFolderId,
});
}
}
onShowNewFilesPanel = () => {
const { showNewFilesPanel } = this.state;
this.setState({showNewFilesPanel: !showNewFilesPanel});
};
render() { render() {
const { t, item, fileAction, isLoading, isTrashFolder } = this.props; const { t, item, fileAction, isLoading, isTrashFolder, onLoading, folders } = this.props;
const { itemTitle, editingId/*, loading*/ } = this.state; const { itemTitle, editingId, showNewFilesPanel, newItems, newFolderId } = this.state;
const { const {
contentLength, contentLength,
updated, updated,
createdBy, createdBy,
fileExst, fileExst,
filesCount, filesCount,
fileStatus,
foldersCount, foldersCount,
fileStatus,
id, id,
versionGroup, versionGroup
newItems
} = item; } = item;
const SimpleFilesRowContent = styled(RowContent)` const SimpleFilesRowContent = styled(RowContent)`
@ -239,6 +271,7 @@ class FilesRowContent extends React.PureComponent {
const isEdit = (id === editingId) && (fileExst === fileAction.extension); const isEdit = (id === editingId) && (fileExst === fileAction.extension);
const linkStyles = isTrashFolder ? { noHover: true } : { onClick: this.onFilesClick }; const linkStyles = isTrashFolder ? { noHover: true } : { onClick: this.onFilesClick };
const showNew = item.new && item.new > 0;
return isEdit return isEdit
? <EditingWrapperComponent ? <EditingWrapperComponent
@ -252,6 +285,16 @@ class FilesRowContent extends React.PureComponent {
cancelUpdateItem={this.cancelUpdateItem} cancelUpdateItem={this.cancelUpdateItem}
/> />
: ( : (
<>
{showNewFilesPanel && (
<NewFilesPanel
visible={showNewFilesPanel}
onClose={this.onShowNewFilesPanel}
onLoading={onLoading}
folderId={newFolderId}
folders={folders}
/>
)}
<SimpleFilesRowContent <SimpleFilesRowContent
sideColor="#333" sideColor="#333"
isFile={fileExst} isFile={fileExst}
@ -340,7 +383,7 @@ class FilesRowContent extends React.PureComponent {
fontWeight={800} fontWeight={800}
label={`New`} label={`New`}
maxWidth="50px" maxWidth="50px"
onClick={this.onShowVersionHistory} onClick={this.onBadgeClick}
padding="0 5px" padding="0 5px"
data-id={id} data-id={id}
/> />
@ -348,7 +391,7 @@ class FilesRowContent extends React.PureComponent {
</div> </div>
: :
<div className='badges'> <div className='badges'>
{ newItems && newItems > 0 && { !!showNew &&
<Badge <Badge
className='badge-version' className='badge-version'
backgroundColor="#ED7309" backgroundColor="#ED7309"
@ -358,7 +401,7 @@ class FilesRowContent extends React.PureComponent {
fontWeight={800} fontWeight={800}
label={newItems} label={newItems}
maxWidth="50px" maxWidth="50px"
onClick={this.onShowVersionHistory} onClick={this.onBadgeClick}
padding="0 5px" padding="0 5px"
data-id={id} data-id={id}
/> />
@ -406,12 +449,13 @@ class FilesRowContent extends React.PureComponent {
: `${t("TitleDocuments")}: ${filesCount} / ${t("TitleSubfolders")}: ${foldersCount}`} : `${t("TitleDocuments")}: ${filesCount} / ${t("TitleSubfolders")}: ${foldersCount}`}
</Text> </Text>
</SimpleFilesRowContent> </SimpleFilesRowContent>
</>
) )
} }
}; };
function mapStateToProps(state) { function mapStateToProps(state) {
const { filter, fileAction, selectedFolder, treeFolders } = state.files; const { filter, fileAction, selectedFolder, treeFolders, folders } = state.files;
const { settings } = state.auth; const { settings } = state.auth;
const indexOfTrash = 3; const indexOfTrash = 3;
@ -420,10 +464,15 @@ function mapStateToProps(state) {
fileAction, fileAction,
parentFolder: selectedFolder.id, parentFolder: selectedFolder.id,
isTrashFolder: treeFolders[indexOfTrash].id === selectedFolder.id, isTrashFolder: treeFolders[indexOfTrash].id === selectedFolder.id,
settings settings,
treeFolders,
rootFolderId: selectedFolder.pathParts[0],
newItems: selectedFolder.new,
selectedFolder,
folders
} }
} }
export default connect(mapStateToProps, { createFile, createFolder, updateFile, renameFolder, setFilter })( export default connect(mapStateToProps, { createFile, createFolder, updateFile, renameFolder, setTreeFolders })(
withRouter(withTranslation()(FilesRowContent)) withRouter(withTranslation()(FilesRowContent))
); );

View File

@ -79,6 +79,7 @@ class SectionFilterContent extends React.Component {
const sortBy = data.sortId; const sortBy = data.sortId;
const sortOrder = const sortOrder =
data.sortDirection === "desc" ? "descending" : "ascending"; data.sortDirection === "desc" ? "descending" : "ascending";
const viewAs = data.viewAs;
const authorType = getAuthorType(data.filterValues); const authorType = getAuthorType(data.filterValues);
const withSubfolders = getSearchParams(data.filterValues); const withSubfolders = getSearchParams(data.filterValues);
@ -95,6 +96,7 @@ class SectionFilterContent extends React.Component {
newFilter.page = 0; newFilter.page = 0;
newFilter.sortBy = sortBy; newFilter.sortBy = sortBy;
newFilter.sortOrder = sortOrder; newFilter.sortOrder = sortOrder;
newFilter.viewAs = viewAs;
newFilter.filterType = filterType; newFilter.filterType = filterType;
newFilter.search = search; newFilter.search = search;
newFilter.authorType = authorType; newFilter.authorType = authorType;
@ -207,14 +209,21 @@ class SectionFilterContent extends React.Component {
getSortData = () => { getSortData = () => {
const { t } = this.props; const { t } = this.props;
return [ const commonOptions = [
{ key: "lastModifiedDate", label: t("ByLastModifiedDate"), default: true }, { key: "lastModifiedDate", label: t("ByLastModifiedDate"), default: true },
{ key: "creationDate", label: t("ByCreationDate"), default: true }, { key: "creationDate", label: t("ByCreationDate"), default: true },
{ key: "title", label: t("ByTitle"), default: true }, { key: "title", label: t("ByTitle"), default: true },
{ key: "type", label: t("ByType"), default: true }, { key: "type", label: t("ByType"), default: true },
{ key: "size", label: t("BySize"), default: true }, { key: "size", label: t("BySize"), default: true },
{ key: "author", label: t("ByAuthor"), default: true }, { key: "author", label: t("ByAuthor"), default: true }
]; ];
const viewSettings = [
{ key: "row", label: t("ViewList"), isSetting: true, default: true },
{ key: "tile", label: t("ViewTiles"), isSetting: true, default: true }
];
//TODO: Need use mobile detect for better result
return window.innerWidth < 460 ? [...commonOptions,...viewSettings] : commonOptions;
}; };
getSelectedFilterData = () => { getSelectedFilterData = () => {
@ -222,7 +231,8 @@ class SectionFilterContent extends React.Component {
const selectedFilterData = { const selectedFilterData = {
filterValues: [], filterValues: [],
sortDirection: filter.sortOrder === "ascending" ? "asc" : "desc", sortDirection: filter.sortOrder === "ascending" ? "asc" : "desc",
sortId: filter.sortBy sortId: filter.sortBy,
viewAs: filter.viewAs
}; };
selectedFilterData.inputValue = filter.search; selectedFilterData.inputValue = filter.search;
@ -269,12 +279,12 @@ class SectionFilterContent extends React.Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return (!isEqual(this.props.filter, nextProps.filter) || this.props.selectedFolderId !== nextProps.selectedFolderId || this.state.isReady !== nextState.isReady); return (!isEqual(this.props.filter, nextProps.filter) || this.props.selectedFolderId !== nextProps.selectedFolderId || this.state.isReady !== nextState.isReady);
} }
render() { render() {
const selectedFilterData = this.getSelectedFilterData(); const selectedFilterData = this.getSelectedFilterData();
const { t, i18n } = this.props; const { t, i18n } = this.props;
const filterColumnCount = window.innerWidth < 500 ? {} : {filterColumnCount: 3} const filterColumnCount = window.innerWidth < 500 ? {} : { filterColumnCount: 3 }
return ( return (
<FilterInput <FilterInput
getFilterData={this.getData} getFilterData={this.getData}

View File

@ -82,5 +82,7 @@
"TooltipElementMoveMessage": "Move {{element}}", "TooltipElementMoveMessage": "Move {{element}}",
"TooltipElementsMoveMessage": "Move {{element}} elements", "TooltipElementsMoveMessage": "Move {{element}} elements",
"TooltipElementCopyMessage": "Copy {{element}}", "TooltipElementCopyMessage": "Copy {{element}}",
"TooltipElementsCopyMessage": "Copy {{element}} elements" "TooltipElementsCopyMessage": "Copy {{element}} elements",
"ViewList": "List",
"ViewTiles": "Tiles"
} }

View File

@ -82,5 +82,7 @@
"TooltipElementMoveMessage": "Переместить {{element}}", "TooltipElementMoveMessage": "Переместить {{element}}",
"TooltipElementsMoveMessage": "Переместить {{element}} элемента(ов)", "TooltipElementsMoveMessage": "Переместить {{element}} элемента(ов)",
"TooltipElementCopyMessage": "Скопировать {{element}}", "TooltipElementCopyMessage": "Скопировать {{element}}",
"TooltipElementsCopyMessage": "Скопировать {{element}} элемента(ов)" "TooltipElementsCopyMessage": "Скопировать {{element}} элемента(ов)",
"ViewList": "Список",
"ViewTiles": "Плитки"
} }

View File

@ -26,7 +26,7 @@ import {
StyledFooter StyledFooter
} from "../StyledPanels"; } from "../StyledPanels";
import { getFileIcon, getFolderIcon, canWebEdit, isImage, isSound, isVideo } from "../../../store/files/selectors"; import { getFileIcon, getFolderIcon, canWebEdit, isImage, isSound, isVideo } from "../../../store/files/selectors";
import { fetchFiles, setMediaViewerData } from '../../../store/files/actions'; import { fetchFiles, setMediaViewerData, setTreeFolders } from '../../../store/files/actions';
import store from "../../../store/store"; import store from "../../../store/store";
const { changeLanguage } = commonUtils; const { changeLanguage } = commonUtils;
@ -36,6 +36,20 @@ class NewFilesPanelComponent extends React.Component {
super(props); super(props);
changeLanguage(i18n); changeLanguage(i18n);
this.state = { files: [] };
}
componentDidMount() {
const { folderId, onLoading } = this.props;
onLoading(true);
api.files
.getNewFiles(folderId[folderId.length - 1])
.then(files =>
this.setState({ files })
)
.catch((err) => toastr.error(err))
.finally(() => onLoading(false));
} }
getItemIcon = (item, isEdit) => { getItemIcon = (item, isEdit) => {
@ -57,16 +71,29 @@ class NewFilesPanelComponent extends React.Component {
}; };
onMarkAsRead = () => { onMarkAsRead = () => {
const { folderId, onClose, setNewFilesCount } = this.props; const { folderId, onClose } = this.props;
const markAsReadFiles = true;
const folderIds = [];
const fileIds = [];
for(let item of this.state.files) {
if(item.fileExst) {
fileIds.push(item.id);
} else {
folderIds.push(item.id);
}
}
api.files api.files
.markAsRead([folderId], []) .markAsRead(folderIds, fileIds)
.then(() => setNewFilesCount(folderId, 0)) .then(() => this.setNewFilesCount(folderId, markAsReadFiles))
.catch(err => toastr.error(err)) .catch(err => toastr.error(err))
.finally(() => onClose()); .finally(() => onClose());
}; };
onNewFilesClick = item => { onNewFilesClick = item => {
const { setNewFilesCount, onClose, /*onLoading,*/ folderId } = this.props; const { onClose, /*onLoading,*/ folderId } = this.props;
const folderIds = []; const folderIds = [];
const fileId = []; const fileId = [];
const isFile = item.fileExst; const isFile = item.fileExst;
@ -75,9 +102,10 @@ class NewFilesPanelComponent extends React.Component {
//onLoading(true); //onLoading(true);
//api.files.markAsRead([], [])
api.files.markAsRead(folderIds, fileId) api.files.markAsRead(folderIds, fileId)
.then(() => { .then(() => {
setNewFilesCount(folderId); this.setNewFilesCount(folderId, false, item);
this.onFilesClick(item); this.onFilesClick(item);
}) })
.catch(err => toastr.error(err)) .catch(err => toastr.error(err))
@ -111,10 +139,54 @@ class NewFilesPanelComponent extends React.Component {
} }
}; };
setNewFilesCount = (folderPath, markAsReadAll, item) => {
const { treeFolders, setTreeFolders, folders, files, filter } = this.props;
const newFilter = filter.clone();
const data = treeFolders;
let dataItem;
const loop = (index, newData) => {
dataItem = newData.find(x => x.id === folderPath[index]);
if(index === folderPath.length - 1) {
const rootItem = data.find(x => x.id === folderPath[0]);
const newFilesCounter = dataItem.newItems ? dataItem.newItems : dataItem.new;
rootItem.newItems = markAsReadAll ? rootItem.newItems - newFilesCounter : rootItem.newItems - 1;
dataItem.newItems = markAsReadAll ? 0 : newFilesCounter - 1;
fetchFiles(this.props.selectedFolder.id, newFilter, store.dispatch);
return;
} else { loop(index + 1, dataItem.folders); }
}
if(folderPath.length > 1) {
loop(0, data);
} else {
dataItem = data.find(x => x.id === folderPath[0]);
dataItem.newItems = markAsReadAll ? 0 : dataItem.newItems - 1;
if(item && item.fileExst) {
const fileItem = files.find(x => x.id === item.id && x.fileExst);
if(fileItem) {
fileItem.new = markAsReadAll ? 0 : fileItem.new - 1;
} else {
const filesFolder = folders.find(x => x.id === item.folderId);
filesFolder.new = markAsReadAll ? 0 : filesFolder.new - 1;
}
fetchFiles(this.props.selectedFolder.id, newFilter, store.dispatch);
} else if(item && !item.fileExst) {
const folderItem = folders.find(x => x.id === item.id && !x.fileExst);
folderItem.new = markAsReadAll ? 0 : folderItem.new - 1;
}
}
setTreeFolders(data);
}
render() { render() {
//console.log("NewFiles panel render"); //console.log("NewFiles panel render");
const { t, visible, onClose, files } = this.props; const { t, visible, onClose } = this.props;
const { files } = this.state;
const zIndex = 310; const zIndex = 310;
return ( return (
@ -192,8 +264,8 @@ const NewFilesPanel = (props) => (
); );
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { filter } = state.files const { filter, files, folders, treeFolders, selectedFolder } = state.files
return { filter }; return { filter, files, folders, treeFolders, selectedFolder };
}; };
export default connect(mapStateToProps, { setMediaViewerData })(withRouter(NewFilesPanel)); export default connect(mapStateToProps, { setMediaViewerData, setTreeFolders })(withRouter(NewFilesPanel));

View File

@ -5,6 +5,7 @@ export const FILTER_TYPE = "filterType";
export const SEARCH = "search"; export const SEARCH = "search";
export const SORT_BY = "sortby"; export const SORT_BY = "sortby";
export const SORT_ORDER = "sortorder"; export const SORT_ORDER = "sortorder";
export const VIEW_AS = "viewas";
export const PAGE = "page"; export const PAGE = "page";
export const PAGE_COUNT = "pagecount"; export const PAGE_COUNT = "pagecount";
export const FOLDER = "folder"; export const FOLDER = "folder";

View File

@ -4,6 +4,7 @@ import {
SEARCH, SEARCH,
SORT_BY, SORT_BY,
SORT_ORDER, SORT_ORDER,
VIEW_AS,
PAGE, PAGE,
PAGE_COUNT, PAGE_COUNT,
AUTHOR_TYPE, AUTHOR_TYPE,
@ -28,6 +29,7 @@ export function getFilterByLocation(location) {
defaultFilter.withSubfolders; defaultFilter.withSubfolders;
const search = urlFilter[SEARCH] || defaultFilter.search; const search = urlFilter[SEARCH] || defaultFilter.search;
const sortBy = urlFilter[SORT_BY] || defaultFilter.sortBy; const sortBy = urlFilter[SORT_BY] || defaultFilter.sortBy;
const viewAs = urlFilter[VIEW_AS] || defaultFilter.viewAs;
const sortOrder = urlFilter[SORT_ORDER] || defaultFilter.sortOrder; const sortOrder = urlFilter[SORT_ORDER] || defaultFilter.sortOrder;
const page = (urlFilter[PAGE] && (+urlFilter[PAGE]-1)) || defaultFilter.page; const page = (urlFilter[PAGE] && (+urlFilter[PAGE]-1)) || defaultFilter.page;
const pageCount = const pageCount =
@ -41,6 +43,7 @@ export function getFilterByLocation(location) {
defaultFilter.total, defaultFilter.total,
sortBy, sortBy,
sortOrder, sortOrder,
viewAs,
filterType, filterType,
withSubfolders, withSubfolders,
search, search,

View File

@ -188,7 +188,7 @@ export function fetchFiles(folderId, filter, dispatch) {
dispatch(setFolders(data.folders)); dispatch(setFolders(data.folders));
dispatch(setFiles(data.files)); dispatch(setFiles(data.files));
//dispatch(setSelected("close")); //TODO: need close but it`s crash first select, need new logic //dispatch(setSelected("close")); //TODO: need close but it`s crash first select, need new logic
return dispatch(setSelectedFolder({ folders: data.folders, ...data.current, pathParts: data.pathParts })); return dispatch(setSelectedFolder({ folders: data.folders, ...data.current, pathParts: data.pathParts, ...{new: data.new} }));
}) })
} }
@ -360,6 +360,7 @@ export function moveToFolder(destFolderId, folderIds, fileIds, conflictResolveTy
}; };
}; };
/*export function deleteGroup(id) { /*export function deleteGroup(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
const { people } = getState(); const { people } = getState();

View File

@ -1,6 +1,6 @@
{ {
"name": "asc-web-common", "name": "asc-web-common",
"version": "1.0.167", "version": "1.0.170",
"description": "Ascensio System SIA common components and solutions library", "description": "Ascensio System SIA common components and solutions library",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"files": [ "files": [

View File

@ -5,6 +5,7 @@ const DEFAULT_PAGE_COUNT = 25;
const DEFAULT_TOTAL = 0; const DEFAULT_TOTAL = 0;
const DEFAULT_SORT_BY = "lastModifiedDate"; const DEFAULT_SORT_BY = "lastModifiedDate";
const DEFAULT_SORT_ORDER = "ascending"; const DEFAULT_SORT_ORDER = "ascending";
const DEFAULT_VIEW = "row";
const DEFAULT_FILTER_TYPE = null; const DEFAULT_FILTER_TYPE = null;
const DEFAULT_SEARCH_TYPE = true; //withSubfolders const DEFAULT_SEARCH_TYPE = true; //withSubfolders
const DEFAULT_SEARCH = null; const DEFAULT_SEARCH = null;
@ -29,6 +30,7 @@ class FilesFilter {
total = DEFAULT_TOTAL, total = DEFAULT_TOTAL,
sortBy = DEFAULT_SORT_BY, sortBy = DEFAULT_SORT_BY,
sortOrder = DEFAULT_SORT_ORDER, sortOrder = DEFAULT_SORT_ORDER,
viewAs = DEFAULT_VIEW,
filterType = DEFAULT_FILTER_TYPE, filterType = DEFAULT_FILTER_TYPE,
withSubfolders = DEFAULT_SEARCH_TYPE, withSubfolders = DEFAULT_SEARCH_TYPE,
search = DEFAULT_SEARCH, search = DEFAULT_SEARCH,
@ -41,6 +43,7 @@ class FilesFilter {
this.pageCount = pageCount; this.pageCount = pageCount;
this.sortBy = sortBy; this.sortBy = sortBy;
this.sortOrder = sortOrder; this.sortOrder = sortOrder;
this.viewAs = viewAs;
this.filterType = filterType; this.filterType = filterType;
this.withSubfolders = withSubfolders; this.withSubfolders = withSubfolders;
this.search = search; this.search = search;
@ -106,6 +109,7 @@ class FilesFilter {
this.total, this.total,
this.sortBy, this.sortBy,
this.sortOrder, this.sortOrder,
this.viewAs,
this.filterType, this.filterType,
this.withSubfolders, this.withSubfolders,
this.search, this.search,
@ -124,6 +128,7 @@ class FilesFilter {
this.search === filter.search && this.search === filter.search &&
this.sortBy === filter.sortBy && this.sortBy === filter.sortBy &&
this.sortOrder === filter.sortOrder && this.sortOrder === filter.sortOrder &&
this.viewAs === filter.viewAs &&
this.page === filter.page && this.page === filter.page &&
this.selectedItem.key === filter.selectedItem.key && this.selectedItem.key === filter.selectedItem.key &&
this.folder === filter.folder && this.folder === filter.folder &&

View File

@ -61,7 +61,8 @@ export function getFoldersTree() {
title: folder.title, title: folder.title,
access: folder.access, access: folder.access,
foldersCount: folder.foldersCount, foldersCount: folder.foldersCount,
rootFolderType: folder.rootFolderType rootFolderType: folder.rootFolderType,
newItems: folder.new
} }
}) : null, }) : null,
pathParts: data.pathParts, pathParts: data.pathParts,

View File

@ -5,6 +5,7 @@ import isEqual from 'lodash/isEqual';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import FilterBlock from './sub-components/FilterBlock'; import FilterBlock from './sub-components/FilterBlock';
import SortComboBox from './sub-components/SortComboBox'; import SortComboBox from './sub-components/SortComboBox';
import ViewSelector from './sub-components/ViewSelector';
import map from 'lodash/map'; import map from 'lodash/map';
import clone from 'lodash/clone'; import clone from 'lodash/clone';
import StyledFilterInput from './StyledFilterInput'; import StyledFilterInput from './StyledFilterInput';
@ -92,6 +93,7 @@ class FilterInput extends React.Component {
this.state = { this.state = {
sortDirection: props.selectedFilterData.sortDirection === "desc" ? true : false, sortDirection: props.selectedFilterData.sortDirection === "desc" ? true : false,
viewAs: props.selectedFilterData.viewAs,
sortId: props.getSortData().findIndex(x => x.key === props.selectedFilterData.sortId) != -1 ? props.selectedFilterData.sortId : props.getSortData().length > 0 ? props.getSortData()[0].key : "", sortId: props.getSortData().findIndex(x => x.key === props.selectedFilterData.sortId) != -1 ? props.selectedFilterData.sortId : props.getSortData().length > 0 ? props.getSortData()[0].key : "",
searchText: props.selectedFilterData.inputValue || props.value, searchText: props.selectedFilterData.inputValue || props.value,
@ -120,6 +122,8 @@ class FilterInput extends React.Component {
this.onDeleteFilterItem = this.onDeleteFilterItem.bind(this); this.onDeleteFilterItem = this.onDeleteFilterItem.bind(this);
this.clearFilter = this.clearFilter.bind(this); this.clearFilter = this.clearFilter.bind(this);
this.onClickViewSelector = this.onClickViewSelector.bind(this);
this.throttledResize = throttle(this.resize, 300); this.throttledResize = throttle(this.resize, 300);
} }
@ -230,9 +234,15 @@ class FilterInput extends React.Component {
}) })
} }
onChangeSortDirection(key) { onChangeSortDirection(key) {
this.onFilter(this.state.filterValues, this.state.sortId, key ? "desc" : "asc"); this.onFilter(this.state.filterValues, this.state.sortId, key ? "desc" : "asc", this.state.viewAs);
this.setState({ sortDirection: !!key }); this.setState({ sortDirection: !!key });
} }
onClickViewSelector(item) {
const itemId = (item.target && item.target.dataset.for) || item;
const viewAs = itemId.indexOf("row") === -1 ? "tile" : "row"
this.onFilter(this.state.filterValues, this.state.sortId, this.state.sortDirection ? "desc" : "asc", viewAs);
this.setState({ viewAs: viewAs });
}
getDefaultSelectedIndex() { getDefaultSelectedIndex() {
const sortData = this.props.getSortData(); const sortData = this.props.getSortData();
if (sortData.length > 0) { if (sortData.length > 0) {
@ -243,19 +253,19 @@ class FilterInput extends React.Component {
} }
onClickSortItem(key) { onClickSortItem(key) {
this.setState({ sortId: key }); this.setState({ sortId: key });
this.onFilter(this.state.filterValues, key, this.state.sortDirection ? "desc" : "asc"); this.onFilter(this.state.filterValues, key, this.state.sortDirection ? "desc" : "asc", this.state.viewAs);
} }
onSortDirectionClick() { onSortDirectionClick() {
this.onFilter(this.state.filterValues, this.state.sortId, !this.state.sortDirection ? "desc" : "asc"); this.onFilter(this.state.filterValues, this.state.sortId, !this.state.sortDirection ? "desc" : "asc", this.state.viewAs);
this.setState({ sortDirection: !this.state.sortDirection }); this.setState({ sortDirection: !this.state.sortDirection });
} }
onSearchChanged(value) { onSearchChanged(value) {
this.setState({ searchText: value }); this.setState({ searchText: value });
this.onFilter(this.state.filterValues, this.state.sortId, this.state.sortDirection ? "desc" : "asc", value); this.onFilter(this.state.filterValues, this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs, value);
} }
onSearch(result) { onSearch(result) {
this.onFilter(result.filterValues, this.state.sortId, this.state.sortDirection ? "desc" : "asc"); this.onFilter(result.filterValues, this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs);
} }
getFilterData() { getFilterData() {
const _this = this; const _this = this;
@ -280,7 +290,7 @@ class FilterInput extends React.Component {
openFilterItems: [], openFilterItems: [],
hideFilterItems: [] hideFilterItems: []
}); });
this.onFilter([], this.state.sortId, this.state.sortDirection ? "desc" : "asc", ''); this.onFilter([], this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs, '');
} }
updateFilter(inputFilterItems) { updateFilter(inputFilterItems) {
const currentFilterItems = inputFilterItems || cloneObjectsArray(this.state.filterValues); const currentFilterItems = inputFilterItems || cloneObjectsArray(this.state.filterValues);
@ -344,9 +354,9 @@ class FilterInput extends React.Component {
item.key = item.key.replace(item.group + "_", ''); item.key = item.key.replace(item.group + "_", '');
return item; return item;
}) })
this.onFilter(filterValues.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc"); this.onFilter(filterValues.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs);
} }
onFilter(filterValues, sortId, sortDirection, searchText) { onFilter(filterValues, sortId, sortDirection, viewAs, searchText) {
let cloneFilterValues = cloneObjectsArray(filterValues); let cloneFilterValues = cloneObjectsArray(filterValues);
cloneFilterValues = cloneFilterValues.map(function (item) { cloneFilterValues = cloneFilterValues.map(function (item) {
item.key = item.key.replace(item.group + "_", ''); item.key = item.key.replace(item.group + "_", '');
@ -356,7 +366,8 @@ class FilterInput extends React.Component {
inputValue: searchText != undefined ? searchText : this.state.searchText, inputValue: searchText != undefined ? searchText : this.state.searchText,
filterValues: cloneFilterValues.filter(item => item.key != '-1'), filterValues: cloneFilterValues.filter(item => item.key != '-1'),
sortId: sortId, sortId: sortId,
sortDirection: sortDirection sortDirection: sortDirection,
viewAs: viewAs
}); });
} }
onChangeFilter(result) { onChangeFilter(result) {
@ -364,7 +375,7 @@ class FilterInput extends React.Component {
searchText: result.inputValue, searchText: result.inputValue,
filterValues: result.filterValues, filterValues: result.filterValues,
}); });
this.onFilter(result.filterValues, this.state.sortId, this.state.sortDirection ? "desc" : "asc", result.inputValue); this.onFilter(result.filterValues, this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs, result.inputValue);
} }
onFilterRender() { onFilterRender() {
if (this.isResizeUpdate) { if (this.isResizeUpdate) {
@ -419,7 +430,7 @@ class FilterInput extends React.Component {
}) })
this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc"); this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs);
} }
return; return;
@ -467,7 +478,7 @@ class FilterInput extends React.Component {
item.key = item.key.replace(item.group + "_", ''); item.key = item.key.replace(item.group + "_", '');
return item; return item;
}) })
this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc"); this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs);
this.setState({ this.setState({
filterValues: currentFilterItems, filterValues: currentFilterItems,
openFilterItems: currentFilterItems, openFilterItems: currentFilterItems,
@ -503,7 +514,7 @@ class FilterInput extends React.Component {
item.key = item.key.replace(item.group + "_", ''); item.key = item.key.replace(item.group + "_", '');
return item; return item;
}) })
this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc"); this.onFilter(clone.filter(item => item.key != '-1'), this.state.sortId, this.state.sortDirection ? "desc" : "asc", this.state.viewAs);
} }
} }
@ -517,7 +528,7 @@ class FilterInput extends React.Component {
/* eslint-enable react/prop-types */ /* eslint-enable react/prop-types */
const { searchText, filterValues, openFilterItems, const { searchText, filterValues, openFilterItems,
hideFilterItems, sortId, sortDirection } = this.state; hideFilterItems, sortId, sortDirection, viewAs } = this.state;
// console.log("filter input render, openFilterItems", openFilterItems, 'hideFilterItems', hideFilterItems); // console.log("filter input render, openFilterItems", openFilterItems, 'hideFilterItems', hideFilterItems);
let iconSize = 33; let iconSize = 33;
@ -574,6 +585,7 @@ class FilterInput extends React.Component {
options={getSortData()} options={getSortData()}
isDisabled={isDisabled} isDisabled={isDisabled}
onChangeSortId={this.onClickSortItem} onChangeSortId={this.onClickSortItem}
onChangeView={this.onClickViewSelector}
onChangeSortDirection={this.onChangeSortDirection} onChangeSortDirection={this.onChangeSortDirection}
selectedOption={getSortData().length > 0 ? getSortData().find(x => x.key === sortId) : {}} selectedOption={getSortData().length > 0 ? getSortData().find(x => x.key === sortId) : {}}
onButtonClick={this.onSortDirectionClick} onButtonClick={this.onSortDirectionClick}
@ -581,6 +593,11 @@ class FilterInput extends React.Component {
directionAscLabel={directionAscLabel} directionAscLabel={directionAscLabel}
directionDescLabel={directionDescLabel} directionDescLabel={directionDescLabel}
/> />
<ViewSelector
isDisabled={isDisabled}
onClickViewSelector={this.onClickViewSelector}
viewAs={viewAs}
/>
</StyledFilterInput> </StyledFilterInput>
); );

View File

@ -43,7 +43,8 @@ class FilterStory extends React.Component {
inputValue: "text", inputValue: "text",
filterValues: [ filterValues: [
{key: "1", group: "filter-status"} {key: "1", group: "filter-status"}
] ],
viewAs: "row"
} }
}; };
this.buttonClick = this.buttonClick.bind(this); this.buttonClick = this.buttonClick.bind(this);

View File

@ -17,7 +17,10 @@ const StyledFilterInput = styled.div`
.styled-search-input { .styled-search-input {
display: block; display: block;
float: left; float: left;
width: calc(100% - 140px); width: calc(100% - 212px);
@media (max-width: 460px) {
width: calc(100% - 140px);
}
@media ${mobile} { @media ${mobile} {
width: calc(100% - 58px); width: calc(100% - 58px);
} }
@ -121,8 +124,50 @@ const StyledFilterInput = styled.div`
color: #333; color: #333;
} }
} }
`;
export const StyledViewSelector = styled.div`
display: flex;
float: left;
width: 64px;
margin-left: 8px;
@media (max-width: 460px) {
display:none;
}
.view-selector-button{
border: 1px solid ${props => props.isDisabled ? '#ECEEF1' : '#D0D5DA'};
border-radius: 3px;
padding: 7px;
${props => props.isDisabled && 'background-color: #F8F9F9;' }
svg{
pointer-events: none;
}
&.active{
background-color:#A3A9AE;
border-color: #A3A9AE;
}
&:hover{
${props => !props.isDisabled && 'background-color: #A3A9AE;' }
${props => !props.isDisabled && 'border-color: #A3A9AE;' }
}
&:first-child{
border-right: none;
border-top-right-radius:0;
border-bottom-right-radius:0;
}
&:last-child{
border-left: none;
border-top-left-radius:0;
border-bottom-left-radius:0;
}
}
`; `;
export const StyledFilterItem = styled.div` export const StyledFilterItem = styled.div`
@ -228,4 +273,11 @@ export const StyledIconButton = styled.div`
transform: ${state => !state.sortDirection ? 'scale(1, -1)' : 'scale(1)'}; transform: ${state => !state.sortDirection ? 'scale(1, -1)' : 'scale(1)'};
`; `;
export const StyledIconWrapper = styled.div`
display: inline-flex;
width: 32px;
height: 100%;
`;
export default StyledFilterInput; export default StyledFilterInput;

View File

@ -5,137 +5,168 @@ import PropTypes from 'prop-types';
import { StyledIconButton } from '../StyledFilterInput'; import { StyledIconButton } from '../StyledFilterInput';
class SortComboBox extends React.Component { class SortComboBox extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const { sortDirection } = props; const { sortDirection } = props;
this.state = { this.state = {
sortDirection sortDirection
}
this.combobox = React.createRef();
}
onButtonClick = () => {
const { onChangeSortDirection } = this.props;
const { sortDirection } = this.state;
typeof onChangeSortDirection === 'function' && onChangeSortDirection(+(sortDirection === 0 ? 1 : 0));
this.setState({
sortDirection: sortDirection === 0 ? 1 : 0
});
} }
onChangeSortId = (e) => { this.combobox = React.createRef();
const { onChangeSortId } = this.props; }
typeof onChangeSortId === 'function' && onChangeSortId(e.target.value); onButtonClick = () => {
} const { onChangeSortDirection } = this.props;
onChangeSortDirection = (e) => { const { sortDirection } = this.state;
const sortDirection = +e.target.value; typeof onChangeSortDirection === 'function' && onChangeSortDirection(+(sortDirection === 0 ? 1 : 0));
const { onChangeSortDirection } = this.props; this.setState({
this.setState({ sortDirection }); sortDirection: sortDirection === 0 ? 1 : 0
typeof onChangeSortDirection === 'function' && onChangeSortDirection(sortDirection); });
} }
shouldComponentUpdate(nextProps, nextState) {
//TODO
/*const comboboxText = this.combobox.current.ref.current.children[0].children[1];
if(comboboxText.scrollWidth > Math.round(comboboxText.getBoundingClientRect().width)){
comboboxText.style.opacity = "0";
}else{
comboboxText.style.opacity = "1";
}*/
const { sortDirection } = nextProps;
if (this.props.sortDirection !== sortDirection) {
this.setState({
sortDirection
});
return true;
}
return (!isEqual(this.props, nextProps) || !isEqual(this.state, nextState));
}
render() {
const { options, directionAscLabel, directionDescLabel, isDisabled,
selectedOption } = this.props;
const { sortDirection } = this.state;
let sortArray = options.map(function (item) {
item.value = item.key
return item;
});
let sortDirectionArray = [
{ value: '0', label: directionAscLabel },
{ value: '1', label: directionDescLabel }
];
const advancedOptions = ( onChangeSortId = (e) => {
<> const { onChangeSortId } = this.props;
<DropDownItem noHover > typeof onChangeSortId === 'function' && onChangeSortId(e.target.value);
<RadioButtonGroup }
fontWeight={600}
isDisabled={isDisabled} onChangeView = (e) => {
name={'direction'} const { onChangeView } = this.props;
onClick={this.onChangeSortDirection} typeof onChangeView === 'function' && onChangeView(e.target.value);
options={sortDirectionArray} }
orientation='vertical'
selected={sortDirection.toString()} onChangeSortDirection = (e) => {
spacing='0px' const sortDirection = +e.target.value;
/> const { onChangeSortDirection } = this.props;
</DropDownItem> this.setState({ sortDirection });
<DropDownItem isSeparator /> typeof onChangeSortDirection === 'function' && onChangeSortDirection(sortDirection);
<DropDownItem noHover > }
<RadioButtonGroup shouldComponentUpdate(nextProps, nextState) {
fontWeight={600} //TODO
isDisabled={isDisabled} /*const comboboxText = this.combobox.current.ref.current.children[0].children[1];
name={'sort'} if(comboboxText.scrollWidth > Math.round(comboboxText.getBoundingClientRect().width)){
onClick={this.onChangeSortId} comboboxText.style.opacity = "0";
options={sortArray} }else{
orientation='vertical' comboboxText.style.opacity = "1";
selected={selectedOption.key} }*/
spacing='0px' const { sortDirection } = nextProps;
/> if (this.props.sortDirection !== sortDirection) {
</DropDownItem> this.setState({
</> sortDirection
); });
return ( return true;
<ComboBox }
advancedOptions={advancedOptions} return (!isEqual(this.props, nextProps) || !isEqual(this.state, nextState));
className='styled-sort-combobox' }
directionX="right" render() {
const { options, directionAscLabel, directionDescLabel, isDisabled,
selectedOption } = this.props;
const { sortDirection } = this.state;
let settingsArray = options.filter(item => {
item.value = item.key
return item.isSetting === true;
});
let sortArray = options.filter(item => {
item.value = item.key
return item.isSetting !== true;
});
let sortDirectionArray = [
{ value: '0', label: directionAscLabel },
{ value: '1', label: directionDescLabel }
];
const advancedOptions = (
<>
<DropDownItem noHover >
<RadioButtonGroup
fontWeight={600}
isDisabled={isDisabled}
name={'direction'}
onClick={this.onChangeSortDirection}
options={sortDirectionArray}
orientation='vertical'
selected={sortDirection.toString()}
spacing='0px'
/>
</DropDownItem>
<DropDownItem isSeparator />
<DropDownItem noHover >
<RadioButtonGroup
fontWeight={600}
isDisabled={isDisabled}
name={'sort'}
onClick={this.onChangeSortId}
options={sortArray}
orientation='vertical'
selected={selectedOption.key}
spacing='0px'
/>
</DropDownItem>
{settingsArray.length !== 0 &&
<>
<DropDownItem isSeparator />
<DropDownItem noHover >
<RadioButtonGroup
fontWeight={600}
isDisabled={isDisabled} isDisabled={isDisabled}
options={[]} name={'view'}
ref={this.combobox} onClick={this.onChangeView}
scaled={true} options={settingsArray}
selectedOption={selectedOption} orientation='vertical'
size="content" selected={selectedOption.key}
> spacing='0px'
<StyledIconButton sortDirection={!!sortDirection}> />
<IconButton </DropDownItem>
clickColor={"#333"} </>
color={"#A3A9AE"} }
hoverColor={"#333"} </>
iconName={'ZASortingIcon'} );
isDisabled={isDisabled} return (
isFill={true} <ComboBox
onClick={this.onButtonClick} advancedOptions={advancedOptions}
size={10} className='styled-sort-combobox'
/> directionX="right"
</StyledIconButton> isDisabled={isDisabled}
</ComboBox> options={[]}
); ref={this.combobox}
} scaled={true}
selectedOption={selectedOption}
size="content"
>
<StyledIconButton sortDirection={!!sortDirection}>
<IconButton
clickColor={"#333"}
color={"#A3A9AE"}
hoverColor={"#333"}
iconName={'ZASortingIcon'}
isDisabled={isDisabled}
isFill={true}
onClick={this.onButtonClick}
size={10}
/>
</StyledIconButton>
</ComboBox>
);
}
} }
SortComboBox.propTypes = { SortComboBox.propTypes = {
directionAscLabel: PropTypes.string, directionAscLabel: PropTypes.string,
directionDescLabel: PropTypes.string, directionDescLabel: PropTypes.string,
isDisabled: PropTypes.bool, isDisabled: PropTypes.bool,
onButtonClick: PropTypes.func, onButtonClick: PropTypes.func,
onChangeSortDirection: PropTypes.func, onChangeSortDirection: PropTypes.func,
onChangeSortId: PropTypes.func, onChangeSortId: PropTypes.func,
sortDirection: PropTypes.number, onChangeView: PropTypes.func,
sortDirection: PropTypes.number,
} }
SortComboBox.defaultProps = { SortComboBox.defaultProps = {
isDisabled: false, isDisabled: false,
sortDirection: 0 sortDirection: 0
} }

View File

@ -0,0 +1,60 @@
import React from 'react';
import { IconButton } from 'asc-web-components';
import PropTypes from 'prop-types';
import { StyledViewSelector } from '../StyledFilterInput';
class ViewSelector extends React.Component {
constructor(props) {
super(props)
this.state = {
viewAs: props.viewAs
}
}
render(){
const { isDisabled, viewAs} = this.props;
return(
<StyledViewSelector isDisabled={isDisabled}>
<IconButton
className={`view-selector-button ${viewAs === "row" ? "active" : ""}`}
color={viewAs === "row" ? "#ffffff" : "#A3A9AE"}
hoverColor={"#ffffff"}
clickColor={"#ffffff"}
iconName={'FilterViewSelectorRowIcon'}
isDisabled={isDisabled}
isFill={true}
onClick={(item) => this.props.onClickViewSelector(item)}
size={16}
id="rowSelectorButton"
/>
<IconButton
className={`view-selector-button ${viewAs === "tile" ? "active" : ""}`}
color={viewAs === "tile" ? "#ffffff" : "#A3A9AE"}
hoverColor={"#ffffff"}
clickColor={"#ffffff"}
iconName={'FilterViewSelectorTileIcon'}
isDisabled={isDisabled}
isFill={true}
onClick={(item) => this.props.onClickViewSelector(item)}
size={16}
id="tileSelectorButton"
/>
</StyledViewSelector>
)
}
}
ViewSelector.propTypes = {
isDisabled: PropTypes.bool,
viewAs: PropTypes.string,
onClickViewSelector: PropTypes.func
}
ViewSelector.defaultProps = {
isDisabled: false
}
export default ViewSelector;

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15C16 14.4477 15.5523 14 15 14H1C0.447715 14 0 14.4477 0 15ZM0 8C0 8.55228 0.447715 9 1 9H15C15.5523 9 16 8.55228 16 8C16 7.44771 15.5523 7 15 7H1C0.447715 7 0 7.44771 0 8ZM1 2C0.447715 2 0 1.55229 0 1C0 0.447715 0.447715 0 1 0H15C15.5523 0 16 0.447715 16 1C16 1.55229 15.5523 2 15 2H1Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 516 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V6C0 6.55228 0.447715 7 1 7H6C6.55228 7 7 6.55228 7 6V1C7 0.447715 6.55228 0 6 0H1ZM1 9C0.447715 9 0 9.44771 0 10V15C0 15.5523 0.447715 16 1 16H6C6.55228 16 7 15.5523 7 15V10C7 9.44772 6.55228 9 6 9H1ZM9 1C9 0.447715 9.44772 0 10 0H15C15.5523 0 16 0.447715 16 1V6C16 6.55228 15.5523 7 15 7H10C9.44771 7 9 6.55228 9 6V1ZM10 9C9.44772 9 9 9.44771 9 10V15C9 15.5523 9.44771 16 10 16H15C15.5523 16 16 15.5523 16 15V10C16 9.44772 15.5523 9 15 9H10Z" fill="#A3A9AE"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -133,6 +133,8 @@ import OrigCrossSidebarIcon from './cross.sidebar.react.svg';
import OrigCheckboxIcon from './checkbox.react.svg'; import OrigCheckboxIcon from './checkbox.react.svg';
import OrigCheckboxCheckedIcon from './checkbox.checked.react.svg'; import OrigCheckboxCheckedIcon from './checkbox.checked.react.svg';
import OrigCheckboxIndeterminateIcon from './checkbox.indeterminate.react.svg'; import OrigCheckboxIndeterminateIcon from './checkbox.indeterminate.react.svg';
import OrigFilterViewSelectorRowIcon from './filter.view.selector.row.react.svg';
import OrigFilterViewSelectorTileIcon from './filter.view.selector.tile.react.svg';
import OrigEyeIcon from './eye.react.svg'; import OrigEyeIcon from './eye.react.svg';
import OrigEyeOffIcon from './eye.off.react.svg'; import OrigEyeOffIcon from './eye.off.react.svg';
@ -800,4 +802,12 @@ export const ShareLinkedInIcon = createStyledIcon(
export const KeyIcon = createStyledIcon( export const KeyIcon = createStyledIcon(
OrigKeyIcon, OrigKeyIcon,
'KeyIcon' 'KeyIcon'
);
export const FilterViewSelectorRowIcon = createStyledIcon(
OrigFilterViewSelectorRowIcon,
'FilterViewSelectorRowIcon'
);
export const FilterViewSelectorTileIcon = createStyledIcon(
OrigFilterViewSelectorTileIcon,
'FilterViewSelectorTileIcon'
); );

View File

@ -20,6 +20,14 @@
<Compile Remove="WebZones\IRenderCustomNavigation.cs" /> <Compile Remove="WebZones\IRenderCustomNavigation.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="PublicResources\webstudio_patterns.xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="PublicResources\webstudio_patterns.xml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\common\ASC.Common\ASC.Common.csproj" /> <ProjectReference Include="..\..\common\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\..\common\ASC.Core.Common\ASC.Core.Common.csproj" /> <ProjectReference Include="..\..\common\ASC.Core.Common\ASC.Core.Common.csproj" />

View File

@ -28,7 +28,7 @@ using ASC.Notify.Model;
namespace ASC.Web.Studio.Core.Notify namespace ASC.Web.Studio.Core.Notify
{ {
static class Actions public static class Actions
{ {
public static readonly INotifyAction AdminNotify = new NotifyAction("admin_notify", "admin notifications"); public static readonly INotifyAction AdminNotify = new NotifyAction("admin_notify", "admin notifications");
public static readonly INotifyAction PeriodicNotify = new NotifyAction("periodic_notify", "periodic notifications"); public static readonly INotifyAction PeriodicNotify = new NotifyAction("periodic_notify", "periodic notifications");
@ -49,8 +49,8 @@ namespace ASC.Web.Studio.Core.Notify
public static readonly INotifyAction RestoreCompleted = new NotifyAction("restore_completed", "restore_completed"); public static readonly INotifyAction RestoreCompleted = new NotifyAction("restore_completed", "restore_completed");
public static readonly INotifyAction PortalDeactivate = new NotifyAction("portal_deactivate", "portal deactivate"); public static readonly INotifyAction PortalDeactivate = new NotifyAction("portal_deactivate", "portal deactivate");
public static readonly INotifyAction PortalDelete = new NotifyAction("portal_delete", "portal delete"); public static readonly INotifyAction PortalDelete = new NotifyAction("portal_delete", "portal delete");
public static readonly INotifyAction PortalDeleteSuccessV10 = new NotifyAction("portal_delete_success_v10"); public static readonly INotifyAction PortalDeleteSuccessV10 = new NotifyAction("portal_delete_success_v10");
public static readonly INotifyAction ProfileDelete = new NotifyAction("profile_delete", "profile_delete"); public static readonly INotifyAction ProfileDelete = new NotifyAction("profile_delete", "profile_delete");
public static readonly INotifyAction ProfileHasDeletedItself = new NotifyAction("profile_has_deleted_itself", "profile_has_deleted_itself"); public static readonly INotifyAction ProfileHasDeletedItself = new NotifyAction("profile_has_deleted_itself", "profile_has_deleted_itself");
public static readonly INotifyAction ReassignsCompleted = new NotifyAction("reassigns_completed", "reassigns_completed"); public static readonly INotifyAction ReassignsCompleted = new NotifyAction("reassigns_completed", "reassigns_completed");

View File

@ -58,7 +58,7 @@ namespace ASC.Web.Studio.Core.Notify
{ {
private readonly StudioNotifyServiceHelper client; private readonly StudioNotifyServiceHelper client;
private static string EMailSenderName { get { return ASC.Core.Configuration.Constants.NotifyEMailSenderSysName; } } public static string EMailSenderName { get { return ASC.Core.Configuration.Constants.NotifyEMailSenderSysName; } }
private UserManager UserManager { get; } private UserManager UserManager { get; }
private StudioNotifyHelper StudioNotifyHelper { get; } private StudioNotifyHelper StudioNotifyHelper { get; }
@ -653,49 +653,6 @@ namespace ASC.Web.Studio.Core.Notify
tagValues.ToArray()); tagValues.ToArray());
} }
#region Backup & Restore
public void SendMsgBackupCompleted(Guid userId, string link)
{
client.SendNoticeToAsync(
Actions.BackupCreated,
new[] { StudioNotifyHelper.ToRecipient(userId) },
new[] { EMailSenderName },
new TagValue(Tags.OwnerName, UserManager.GetUsers(userId).DisplayUserName(DisplayUserSettingsHelper)));
}
public void SendMsgRestoreStarted(Tenant tenant, bool notifyAllUsers)
{
var owner = UserManager.GetUsers(tenant.OwnerId);
var users =
notifyAllUsers
? StudioNotifyHelper.RecipientFromEmail(UserManager.GetUsers(EmployeeStatus.Active).Where(r => r.ActivationStatus == EmployeeActivationStatus.Activated).Select(u => u.Email).ToList(), false)
: owner.ActivationStatus == EmployeeActivationStatus.Activated ? StudioNotifyHelper.RecipientFromEmail(owner.Email, false) : new IDirectRecipient[0];
client.SendNoticeToAsync(
Actions.RestoreStarted,
users,
new[] { EMailSenderName });
}
public void SendMsgRestoreCompleted(Tenant tenant, bool notifyAllUsers)
{
var owner = UserManager.GetUsers(tenant.OwnerId);
var users =
notifyAllUsers
? UserManager.GetUsers(EmployeeStatus.Active).Select(u => StudioNotifyHelper.ToRecipient(u.ID)).ToArray()
: new[] { StudioNotifyHelper.ToRecipient(owner.ID) };
client.SendNoticeToAsync(
Actions.RestoreCompleted,
users,
new[] { EMailSenderName },
new TagValue(Tags.OwnerName, owner.DisplayUserName(DisplayUserSettingsHelper)));
}
#endregion
#region Portal Deactivation & Deletion #region Portal Deactivation & Deletion
public void SendMsgPortalDeactivation(Tenant t, string deactivateUrl, string activateUrl) public void SendMsgPortalDeactivation(Tenant t, string deactivateUrl, string activateUrl)
@ -861,39 +818,6 @@ namespace ASC.Web.Studio.Core.Notify
#region Migration Portal #region Migration Portal
public void MigrationPortalStart(Tenant tenant, string region, bool notify)
{
MigrationNotify(tenant, Actions.MigrationPortalStart, region, string.Empty, notify);
}
public void MigrationPortalSuccess(Tenant tenant, string region, string url, bool notify)
{
MigrationNotify(tenant, Actions.MigrationPortalSuccess, region, url, notify);
}
public void MigrationPortalError(Tenant tenant, string region, string url, bool notify)
{
MigrationNotify(tenant, !string.IsNullOrEmpty(region) ? Actions.MigrationPortalError : Actions.MigrationPortalServerFailure, region, url, notify);
}
private void MigrationNotify(Tenant tenant, INotifyAction action, string region, string url, bool notify)
{
var users = UserManager.GetUsers()
.Where(u => notify ? u.ActivationStatus.HasFlag(EmployeeActivationStatus.Activated) : u.IsOwner(tenant))
.Select(u => StudioNotifyHelper.ToRecipient(u.ID))
.ToArray();
if (users.Any())
{
client.SendNoticeToAsync(
action,
users,
new[] { EMailSenderName },
new TagValue(Tags.RegionName, TransferResourceHelper.GetRegionDescription(region)),
new TagValue(Tags.PortalUrl, url));
}
}
public void PortalRenameNotify(Tenant tenant, string oldVirtualRootPath) public void PortalRenameNotify(Tenant tenant, string oldVirtualRootPath)
{ {
var users = UserManager.GetUsers() var users = UserManager.GetUsers()

View File

@ -25,7 +25,7 @@
namespace ASC.Web.Studio.Core.Notify namespace ASC.Web.Studio.Core.Notify
{ {
static class Tags public static class Tags
{ {
public const string UserName = "UserName"; public const string UserName = "UserName";
public const string UserLastName = "UserLastName"; public const string UserLastName = "UserLastName";

File diff suppressed because it is too large Load Diff