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.Threading;
using System.Threading.Tasks;
using ASC.Common.Logging;
using ASC.Common.Notify.Patterns;
using ASC.Notify.Channels;
@ -36,6 +37,7 @@ using ASC.Notify.Cron;
using ASC.Notify.Messages;
using ASC.Notify.Patterns;
using ASC.Notify.Recipients;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -360,9 +362,10 @@ namespace ASC.Notify.Engine
PrepareRequestFillPatterns(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)

View File

@ -26,10 +26,10 @@
</ItemGroup>
<ItemGroup>
<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.Core.Common\ASC.Core.Common.csproj" />
<ProjectReference Include="..\..\ASC.Data.Storage\ASC.Data.Storage.csproj" />
<ProjectReference Include="..\ASC.Notify\ASC.Notify.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\BackupProgress.proto" />

View File

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

View File

@ -25,79 +25,143 @@
using System;
using System.Linq;
using ASC.Common;
using ASC.Common.Logging;
using Microsoft.Extensions.Options;
using ASC.Notify;
using ASC.Core;
using ASC.Core.Tenants;
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
{
public class NotifyHelper
{
private const string NotifyService = "ASC.Web.Studio.Core.Notify.StudioNotifyService, ASC.Web.Studio";
private const string MethodTransferStart = "MigrationPortalStart";
private const string MethodTransferCompleted = "MigrationPortalSuccess";
private const string MethodTransferError = "MigrationPortalError";
private const string MethodBackupCompleted = "SendMsgBackupCompleted";
private const string MethodRestoreStarted = "SendMsgRestoreStarted";
private const string MethodRestoreCompleted = "SendMsgRestoreCompleted";
private readonly ILog log;
private readonly NotifyService notifyService;
{
public IServiceProvider ServiceProvider { get; }
public NotifyHelper(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public NotifyHelper(IOptionsMonitor<ILog> options, NotifyService notifyService)
{
this.notifyService = notifyService;
log = options.CurrentValue;
}
public void SendAboutTransferStart(int tenantId, string targetRegion, bool notifyUsers)
{
SendNotification(MethodTransferStart, tenantId, targetRegion, notifyUsers);
public void SendAboutTransferStart(Tenant tenant, string targetRegion, bool notifyUsers)
{
MigrationNotify(tenant, Actions.MigrationPortalStart, targetRegion, string.Empty, notifyUsers);
}
public void SendAboutTransferComplete(int tenantId, string targetRegion, string targetAddress, bool notifyOnlyOwner)
{
SendNotification(MethodTransferCompleted, tenantId, targetRegion, targetAddress, !notifyOnlyOwner);
public void SendAboutTransferComplete(Tenant tenant, string targetRegion, string targetAddress, bool notifyOnlyOwner)
{
MigrationNotify(tenant, Actions.MigrationPortalSuccess, targetRegion, targetAddress, !notifyOnlyOwner);
}
public void SendAboutTransferError(int tenantId, string targetRegion, string resultAddress, bool notifyOnlyOwner)
{
SendNotification(MethodTransferError, tenantId, targetRegion, resultAddress, !notifyOnlyOwner);
public void SendAboutTransferError(Tenant tenant, string targetRegion, string resultAddress, bool notifyOnlyOwner)
{
MigrationNotify(tenant, !string.IsNullOrEmpty(targetRegion) ? Actions.MigrationPortalError : Actions.MigrationPortalServerFailure, targetRegion, resultAddress, !notifyOnlyOwner);
}
public void SendAboutBackupCompleted(int tenantId, Guid userId, string link)
{
SendNotification(MethodBackupCompleted, tenantId, userId, link);
public void SendAboutBackupCompleted(Guid userId)
{
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)
{
SendNotification(MethodRestoreStarted, tenantId, notifyAllUsers);
public void SendAboutRestoreStarted(Tenant tenant, bool 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)
{
SendNotification(MethodRestoreCompleted, tenantId, notifyAllUsers);
}
private void SendNotification(string method, int tenantId, params object[] args)
{
try
{
notifyService.InvokeSendMethod(NotifyService, method, tenantId, args);
}
catch (Exception error)
{
log.Warn("Error while sending notification", error);
}
public void SendAboutRestoreCompleted(Tenant tenant, bool 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
? 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 DIHelper AddNotifyHelperService(this DIHelper services)
{
services.TryAddScoped<NotifyHelper>();
return services
.AddNotifyService();
services.TryAddSingleton<NotifyHelper>();
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 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 storagePath = tempFile;
try
@ -424,10 +425,11 @@ namespace ASC.Data.Backup.Service
if (UserId != Guid.Empty && !IsScheduled)
{
notifyHelper.SendAboutBackupCompleted(TenantId, UserId, Link);
notifyHelper.SendAboutBackupCompleted(UserId);
}
IsCompleted = true;
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
@ -437,6 +439,15 @@ namespace ASC.Data.Backup.Service
}
finally
{
try
{
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
Log.Error("publish", error);
}
try
{
if (!(storagePath == tempFile && StorageType == BackupStorageType.Local))
@ -503,13 +514,13 @@ namespace ASC.Data.Backup.Service
var tempFile = PathHelper.GetTempFileName(TempFolder);
try
{
notifyHelper.SendAboutRestoreStarted(TenantId, Notify);
tenant = tenantManager.GetTenant(TenantId);
notifyHelper.SendAboutRestoreStarted(tenant, Notify);
var storage = backupStorageFactory.GetBackupStorage(StorageType, TenantId, StorageParams);
storage.Download(StoragePath, tempFile);
Percentage = 10;
tenant = tenantManager.GetTenant(TenantId);
tenant.SetStatus(TenantStatus.Restoring);
tenantManager.SaveTenant(tenant);
@ -536,7 +547,7 @@ namespace ASC.Data.Backup.Service
var tenants = tenantManager.GetTenants();
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
Thread.Sleep(TimeSpan.FromMinutes(2));
notifyHelper.SendAboutRestoreCompleted(restoredTenant.TenantId, Notify);
notifyHelper.SendAboutRestoreCompleted(restoredTenant, Notify);
}
Percentage = 75;
backupWorker.PublishProgress(this);
File.Delete(tempFile);
Percentage = 100;
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
@ -579,6 +593,15 @@ namespace ASC.Data.Backup.Service
}
finally
{
try
{
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
Log.Error("publish", error);
}
if (File.Exists(tempFile))
{
File.Delete(tempFile);
@ -649,11 +672,12 @@ namespace ASC.Data.Backup.Service
var backupWorker = scope.ServiceProvider.GetService<BackupWorker>();
var tempFile = PathHelper.GetTempFileName(TempFolder);
var alias = tenantManager.GetTenant(TenantId).TenantAlias;
var tenant = tenantManager.GetTenant(TenantId);
var alias = tenant.TenantAlias;
try
{
notifyHelper.SendAboutTransferStart(TenantId, TargetRegion, Notify);
notifyHelper.SendAboutTransferStart(tenant, TargetRegion, Notify);
var transferProgressItem = scope.ServiceProvider.GetService<TransferPortalTask>();
transferProgressItem.Init(TenantId, ConfigPaths[CurrentRegion], ConfigPaths[TargetRegion], Limit, TempFolder);
transferProgressItem.ProgressChanged += (sender, args) =>
@ -668,7 +692,8 @@ namespace ASC.Data.Backup.Service
transferProgressItem.RunJob();
Link = GetLink(alias, false);
notifyHelper.SendAboutTransferComplete(TenantId, TargetRegion, Link, !Notify);
notifyHelper.SendAboutTransferComplete(tenant, TargetRegion, Link, !Notify);
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
@ -676,10 +701,19 @@ namespace ASC.Data.Backup.Service
Error = error;
Link = GetLink(alias, true);
notifyHelper.SendAboutTransferError(TenantId, TargetRegion, Link, !Notify);
notifyHelper.SendAboutTransferError(tenant, TargetRegion, Link, !Notify);
}
finally
{
try
{
backupWorker.PublishProgress(this);
}
catch (Exception error)
{
Log.Error("publish", error);
}
if (File.Exists(tempFile))
{
File.Delete(tempFile);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ import {
StyledFooter
} from "../StyledPanels";
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";
const { changeLanguage } = commonUtils;
@ -36,6 +36,20 @@ class NewFilesPanelComponent extends React.Component {
super(props);
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) => {
@ -57,16 +71,29 @@ class NewFilesPanelComponent extends React.Component {
};
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
.markAsRead([folderId], [])
.then(() => setNewFilesCount(folderId, 0))
.markAsRead(folderIds, fileIds)
.then(() => this.setNewFilesCount(folderId, markAsReadFiles))
.catch(err => toastr.error(err))
.finally(() => onClose());
};
onNewFilesClick = item => {
const { setNewFilesCount, onClose, /*onLoading,*/ folderId } = this.props;
const { onClose, /*onLoading,*/ folderId } = this.props;
const folderIds = [];
const fileId = [];
const isFile = item.fileExst;
@ -75,9 +102,10 @@ class NewFilesPanelComponent extends React.Component {
//onLoading(true);
//api.files.markAsRead([], [])
api.files.markAsRead(folderIds, fileId)
.then(() => {
setNewFilesCount(folderId);
this.setNewFilesCount(folderId, false, item);
this.onFilesClick(item);
})
.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() {
//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;
return (
@ -192,8 +264,8 @@ const NewFilesPanel = (props) => (
);
const mapStateToProps = (state) => {
const { filter } = state.files
return { filter };
const { filter, files, folders, treeFolders, selectedFolder } = state.files
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 SORT_BY = "sortby";
export const SORT_ORDER = "sortorder";
export const VIEW_AS = "viewas";
export const PAGE = "page";
export const PAGE_COUNT = "pagecount";
export const FOLDER = "folder";

View File

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

View File

@ -188,7 +188,7 @@ export function fetchFiles(folderId, filter, dispatch) {
dispatch(setFolders(data.folders));
dispatch(setFiles(data.files));
//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) {
return (dispatch, getState) => {
const { people } = getState();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,10 @@ const StyledFilterInput = styled.div`
.styled-search-input {
display: block;
float: left;
width: calc(100% - 140px);
width: calc(100% - 212px);
@media (max-width: 460px) {
width: calc(100% - 140px);
}
@media ${mobile} {
width: calc(100% - 58px);
}
@ -121,8 +124,50 @@ const StyledFilterInput = styled.div`
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`
@ -228,4 +273,11 @@ export const StyledIconButton = styled.div`
transform: ${state => !state.sortDirection ? 'scale(1, -1)' : 'scale(1)'};
`;
export const StyledIconWrapper = styled.div`
display: inline-flex;
width: 32px;
height: 100%;
`;
export default StyledFilterInput;

View File

@ -5,137 +5,168 @@ import PropTypes from 'prop-types';
import { StyledIconButton } from '../StyledFilterInput';
class SortComboBox extends React.Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
const { sortDirection } = props;
const { sortDirection } = props;
this.state = {
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
});
this.state = {
sortDirection
}
onChangeSortId = (e) => {
const { onChangeSortId } = this.props;
typeof onChangeSortId === 'function' && onChangeSortId(e.target.value);
}
onChangeSortDirection = (e) => {
const sortDirection = +e.target.value;
const { onChangeSortDirection } = this.props;
this.setState({ sortDirection });
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 }
];
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
});
}
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>
</>
);
return (
<ComboBox
advancedOptions={advancedOptions}
className='styled-sort-combobox'
directionX="right"
onChangeSortId = (e) => {
const { onChangeSortId } = this.props;
typeof onChangeSortId === 'function' && onChangeSortId(e.target.value);
}
onChangeView = (e) => {
const { onChangeView } = this.props;
typeof onChangeView === 'function' && onChangeView(e.target.value);
}
onChangeSortDirection = (e) => {
const sortDirection = +e.target.value;
const { onChangeSortDirection } = this.props;
this.setState({ sortDirection });
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 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}
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>
);
}
name={'view'}
onClick={this.onChangeView}
options={settingsArray}
orientation='vertical'
selected={selectedOption.key}
spacing='0px'
/>
</DropDownItem>
</>
}
</>
);
return (
<ComboBox
advancedOptions={advancedOptions}
className='styled-sort-combobox'
directionX="right"
isDisabled={isDisabled}
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 = {
directionAscLabel: PropTypes.string,
directionDescLabel: PropTypes.string,
isDisabled: PropTypes.bool,
onButtonClick: PropTypes.func,
onChangeSortDirection: PropTypes.func,
onChangeSortId: PropTypes.func,
sortDirection: PropTypes.number,
directionAscLabel: PropTypes.string,
directionDescLabel: PropTypes.string,
isDisabled: PropTypes.bool,
onButtonClick: PropTypes.func,
onChangeSortDirection: PropTypes.func,
onChangeSortId: PropTypes.func,
onChangeView: PropTypes.func,
sortDirection: PropTypes.number,
}
SortComboBox.defaultProps = {
isDisabled: false,
sortDirection: 0
isDisabled: false,
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 OrigCheckboxCheckedIcon from './checkbox.checked.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 OrigEyeOffIcon from './eye.off.react.svg';
@ -800,4 +802,12 @@ export const ShareLinkedInIcon = createStyledIcon(
export const KeyIcon = createStyledIcon(
OrigKeyIcon,
'KeyIcon'
);
export const FilterViewSelectorRowIcon = createStyledIcon(
OrigFilterViewSelectorRowIcon,
'FilterViewSelectorRowIcon'
);
export const FilterViewSelectorTileIcon = createStyledIcon(
OrigFilterViewSelectorTileIcon,
'FilterViewSelectorTileIcon'
);

View File

@ -20,6 +20,14 @@
<Compile Remove="WebZones\IRenderCustomNavigation.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="PublicResources\webstudio_patterns.xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="PublicResources\webstudio_patterns.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\ASC.Common\ASC.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
{
static class Actions
public static class Actions
{
public static readonly INotifyAction AdminNotify = new NotifyAction("admin_notify", "admin 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 PortalDeactivate = new NotifyAction("portal_deactivate", "portal deactivate");
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 ProfileHasDeletedItself = new NotifyAction("profile_has_deleted_itself", "profile_has_deleted_itself");
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 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 StudioNotifyHelper StudioNotifyHelper { get; }
@ -653,49 +653,6 @@ namespace ASC.Web.Studio.Core.Notify
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
public void SendMsgPortalDeactivation(Tenant t, string deactivateUrl, string activateUrl)
@ -861,39 +818,6 @@ namespace ASC.Web.Studio.Core.Notify
#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)
{
var users = UserManager.GetUsers()

View File

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

File diff suppressed because it is too large Load Diff