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));
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,11 +24,13 @@
*/
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;
@ -37,6 +39,7 @@ 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; }
@ -44,12 +47,14 @@ namespace ASC.Data.Backup.Service
public BackupServiceNotifier BackupServiceNotifier { get; }
public BackupServiceLauncher(
IServiceProvider serviceProvider,
BackupCleanerService cleanerService,
BackupSchedulerService schedulerService,
BackupWorker backupWorker,
IConfiguration configuration,
BackupServiceNotifier backupServiceNotifier)
{
ServiceProvider = serviceProvider;
CleanerService = cleanerService;
SchedulerService = schedulerService;
BackupWorker = backupWorker;
@ -59,6 +64,8 @@ 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,69 +25,128 @@
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(IOptionsMonitor<ILog> options, NotifyService notifyService)
public NotifyHelper(IServiceProvider serviceProvider)
{
this.notifyService = notifyService;
log = options.CurrentValue;
}
public void SendAboutTransferStart(int tenantId, string targetRegion, bool notifyUsers)
{
SendNotification(MethodTransferStart, tenantId, targetRegion, notifyUsers);
ServiceProvider = serviceProvider;
}
public void SendAboutTransferComplete(int tenantId, string targetRegion, string targetAddress, bool notifyOnlyOwner)
public void SendAboutTransferStart(Tenant tenant, string targetRegion, bool notifyUsers)
{
SendNotification(MethodTransferCompleted, tenantId, targetRegion, targetAddress, !notifyOnlyOwner);
MigrationNotify(tenant, Actions.MigrationPortalStart, targetRegion, string.Empty, notifyUsers);
}
public void SendAboutTransferError(int tenantId, string targetRegion, string resultAddress, bool notifyOnlyOwner)
public void SendAboutTransferComplete(Tenant tenant, string targetRegion, string targetAddress, bool notifyOnlyOwner)
{
SendNotification(MethodTransferError, tenantId, targetRegion, resultAddress, !notifyOnlyOwner);
MigrationNotify(tenant, Actions.MigrationPortalSuccess, targetRegion, targetAddress, !notifyOnlyOwner);
}
public void SendAboutBackupCompleted(int tenantId, Guid userId, string link)
public void SendAboutTransferError(Tenant tenant, string targetRegion, string resultAddress, bool notifyOnlyOwner)
{
SendNotification(MethodBackupCompleted, tenantId, userId, link);
MigrationNotify(tenant, !string.IsNullOrEmpty(targetRegion) ? Actions.MigrationPortalError : Actions.MigrationPortalServerFailure, targetRegion, resultAddress, !notifyOnlyOwner);
}
public void SendAboutRestoreStarted(int tenantId, bool notifyAllUsers)
public void SendAboutBackupCompleted(Guid userId)
{
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);
client.SendNoticeToAsync(
Actions.BackupCreated,
new[] { studioNotifyHelper.ToRecipient(userId) },
new[] { StudioNotifyService.EMailSenderName },
new TagValue(Tags.OwnerName, userManager.GetUsers(userId).DisplayUserName(displayUserSettingsHelper)));
}
public void SendAboutRestoreCompleted(int tenantId, bool notifyAllUsers)
public void SendAboutRestoreStarted(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>();
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 });
}
private void SendNotification(string method, int tenantId, params object[] args)
public void SendAboutRestoreCompleted(Tenant tenant, bool notifyAllUsers)
{
try
{
notifyService.InvokeSendMethod(NotifyService, method, tenantId, args);
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)));
}
catch (Exception error)
private void MigrationNotify(Tenant tenant, INotifyAction action, string region, string url, bool notify)
{
log.Warn("Error while sending notification", error);
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));
}
}
}
@ -95,9 +154,14 @@ namespace ASC.Data.Backup
{
public static DIHelper AddNotifyHelperService(this DIHelper services)
{
services.TryAddScoped<NotifyHelper>();
services.TryAddSingleton<NotifyHelper>();
return services
.AddNotifyService();
.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;
@ -274,7 +284,7 @@ class SectionFilterContent extends React.Component {
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% - 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

@ -29,6 +29,12 @@ class SortComboBox extends React.Component {
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;
@ -56,10 +62,17 @@ class SortComboBox extends React.Component {
const { options, directionAscLabel, directionDescLabel, isDisabled,
selectedOption } = this.props;
const { sortDirection } = this.state;
let sortArray = options.map(function (item) {
let settingsArray = options.filter(item => {
item.value = item.key
return item;
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 }
@ -92,6 +105,23 @@ class SortComboBox extends React.Component {
spacing='0px'
/>
</DropDownItem>
{settingsArray.length !== 0 &&
<>
<DropDownItem isSeparator />
<DropDownItem noHover >
<RadioButtonGroup
fontWeight={600}
isDisabled={isDisabled}
name={'view'}
onClick={this.onChangeView}
options={settingsArray}
orientation='vertical'
selected={selectedOption.key}
spacing='0px'
/>
</DropDownItem>
</>
}
</>
);
return (
@ -130,6 +160,7 @@ SortComboBox.propTypes = {
onButtonClick: PropTypes.func,
onChangeSortDirection: PropTypes.func,
onChangeSortId: PropTypes.func,
onChangeView: PropTypes.func,
sortDirection: PropTypes.number,
}

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';
@ -801,3 +803,11 @@ 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");

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";

View File

@ -105,15 +105,6 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to CRM task was created at.
/// </summary>
internal static string ActionCreateCrmTask {
get {
return ResourceManager.GetString("ActionCreateCrmTask", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CRM opportunity was created at.
/// </summary>
@ -411,15 +402,6 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to Move Right Now.
/// </summary>
internal static string ButtonMoveRightNow {
get {
return ResourceManager.GetString("ButtonMoveRightNow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove profile.
/// </summary>
@ -492,87 +474,6 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to Add your email account to ${LetterLogoText} Mail and manage all correspondence in one place.
/// </summary>
internal static string ItemAddEmailAccount {
get {
return ResourceManager.GetString("ItemAddEmailAccount", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connect cloud storages you use to ONLYOFFICE to create a single workspace for all your docs..
/// </summary>
internal static string ItemAddFilesCreatWorkspace {
get {
return ResourceManager.GetString("ItemAddFilesCreatWorkspace", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add your email account and manage all correspondence in one place.
/// </summary>
internal static string ItemAddTeamlabMail {
get {
return ResourceManager.GetString("ItemAddTeamlabMail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Explore different ways of co-authoring documents - real-time co-editing, chat, comments, review..
/// </summary>
internal static string ItemCoAuthoringDocuments {
get {
return ResourceManager.GetString("ItemCoAuthoringDocuments", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Create a single workspace for all your docs.
/// </summary>
internal static string ItemCreateWorkspaceDocs {
get {
return ResourceManager.GetString("ItemCreateWorkspaceDocs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Import projects.
/// </summary>
internal static string ItemImportProjects {
get {
return ResourceManager.GetString("ItemImportProjects", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Import projects from Basecamp.
/// </summary>
internal static string ItemImportProjectsBasecamp {
get {
return ResourceManager.GetString("ItemImportProjectsBasecamp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Integrate documents.
/// </summary>
internal static string ItemIntegrateDocs {
get {
return ResourceManager.GetString("ItemIntegrateDocs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Integrate your IM client with ONLYOFFICE talk and stay tuned.
/// </summary>
internal static string ItemIntegrateIM {
get {
return ResourceManager.GetString("ItemIntegrateIM", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &lt;b&gt;Tip #1.&lt;/b&gt; Use the &lt;b&gt;Real-time Co-editing&lt;/b&gt; feature to speed up the interaction process when specifying details or proofreading a document. Choose between two co-editing modes - &lt;b&gt;Fast&lt;/b&gt; when you see changes as your co-author is typing or &lt;b&gt;Strict&lt;/b&gt; when the changes are shown only after saving the document..
/// </summary>
@ -636,51 +537,6 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to Share documents with your teammates..
/// </summary>
internal static string ItemShareDocuments {
get {
return ResourceManager.GetString("ItemShareDocuments", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Try full-featured online document editing in your browser.
/// </summary>
internal static string ItemTryOnlineDocEditor {
get {
return ResourceManager.GetString("ItemTryOnlineDocEditor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Upload CRM contacts.
/// </summary>
internal static string ItemUploadCrm {
get {
return ResourceManager.GetString("ItemUploadCrm", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Upload CRM contacts to ONLYOFFICE to access them everywhere.
/// </summary>
internal static string ItemUploadCrmContacts {
get {
return ResourceManager.GetString("ItemUploadCrmContacts", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Upload CRM contacts from a csv. file.
/// </summary>
internal static string ItemUploadCrmContactsCsv {
get {
return ResourceManager.GetString("ItemUploadCrmContactsCsv", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Learn More &gt;&gt;.
/// </summary>
@ -693,7 +549,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to h1.Activate your email for &quot;${__VirtualRootPath}&quot;:&quot;$InviteLink&quot; portal.
///
///Hello $UserDisplayName,
///Hello!
///
///To start participating in the &quot;${__VirtualRootPath}&quot;:&quot;$InviteLink&quot; portal life you need to activate your email address. This is done for security reasons.
///
@ -707,26 +563,6 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to h1.Message from the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; VoIP disabled.
/// </summary>
internal static string pattern_admin_voip_blocked {
get {
return ResourceManager.GetString("pattern_admin_voip_blocked", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to h1.Message from the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal
///
///VoIP Balance: $Body.
/// </summary>
internal static string pattern_admin_voip_warning {
get {
return ResourceManager.GetString("pattern_admin_voip_warning", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to h1.&quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal backup created
///
@ -734,12 +570,12 @@ namespace ASC.Web.Core.PublicResources {
///
///A backup file containing data from your &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal has been created.
///
///To learn more on the backup procedure please refer to our &quot;Data backup&quot;:&quot;${__HelpLink}/tipstricks/data-backup-restore.aspx&quot; user guide.
///To learn more on the backup procedure please refer to our &quot;Data backup&quot;:&quot;http://helpcenter.onlyoffice.com/tipstricks/data-backup-restore.aspx&quot; user guide.
///
///
///If you have any questions or need assistance please feel free to contact us at &quot;support.onlyoffice.com&quot;:&quot;http://support.onlyoffice.com&quot;
///
///Best regards,
///ONLYOFFICE™ [rest of string was truncated]&quot;;.
///Best [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_backup_created {
get {
@ -760,7 +596,7 @@ namespace ASC.Web.Core.PublicResources {
///
///You might need your old email address to access the portal when you follow the link. If you do not remember the old email address, please contact your portal administrator.
///
///If you do not want [rest of string was truncated]&quot;;.
///If you do not want to change yo [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_change_email {
get {
@ -779,7 +615,7 @@ namespace ASC.Web.Core.PublicResources {
///
///*Note*: this link is valid for 7 days only. Please complete the password change process within that period.
///
///If you do not want to change your password or received this email by mistake, please ignore it or contact your &quot;$ [rest of string was truncated]&quot;;.
///If you do not want to change your password or received this email by mistake, please ignore it or contact your &quot;${__Virtual [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_change_password {
get {
@ -831,9 +667,7 @@ namespace ASC.Web.Core.PublicResources {
///
///$GreenButton
///
///*Note*: this link is valid for 7 days only. Please complete the portal owner change process within that period.
///
///If you have any questions or need assistance please feel free to contact us at &quot;support@onlyoffice.com&quot;:&quot;mailto:s [rest of string was truncated]&quot;;.
///*Note*: this link is valid for 7 days only. Please complete the portal owner change process within that period..
/// </summary>
internal static string pattern_confirm_owner_change {
get {
@ -852,9 +686,7 @@ namespace ASC.Web.Core.PublicResources {
///
///$GreenButton
///
///*Note*: this link is valid for 7 days only. Please complete the portal address change process within that period.
///
///If you have any questions or need assistance please feel free to contact u [rest of string was truncated]&quot;;.
///*Note*: this link is valid for 7 days only. Please complete the portal address change process within that period..
/// </summary>
internal static string pattern_dns_change {
get {
@ -1117,7 +949,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Hello, $UserName!
///
///We hope you enjoy using ONLYOFFICE. How everything is going with your trial? We would really appreciate you feedback!
///We hope you enjoy using ONLYOFFICE. How everything is going with your trial? We would really appreciate your feedback!
///
///Please, dont hesitate to contact us at &quot;support.onlyoffice.com&quot;:&quot;https://support.onlyoffice.com&quot; whenever you have questions or ideas..
/// </summary>
@ -1438,7 +1270,9 @@ namespace ASC.Web.Core.PublicResources {
///
///Best regards,
///ONLYOFFICE™ Support Team
///&quot;www.onlyoffice.com&quot;:&quot;http://onlyoffice.com/&quot;.
///&quot;www.onlyoffice.com&quot;:&quot;http://onlyoffice.com/&quot;
///
///^You receive this email because you are a registered user of the &quot; [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_migration_error {
get {
@ -1456,7 +1290,9 @@ namespace ASC.Web.Core.PublicResources {
///
///Best regards,
///ONLYOFFICE™ Support Team
///&quot;www.onlyoffice.com&quot;:&quot;http://onlyoffice.com/&quot;.
///&quot;www.onlyoffice.com&quot;:&quot;http://onlyoffice.com/&quot;
///
///^You receive this email because you are a registered user of the &quot;${__Virt [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_migration_server_failure {
get {
@ -1511,7 +1347,9 @@ namespace ASC.Web.Core.PublicResources {
///
///Please, complete your email activation within a week as the link is valid for 7 days only.
///
///Note: we do not send out confidential information (like your password) in emails for safety reasons. You may change your email or password in your &quot;Profile page&quot;:&quot;$MyStaffLink&quot; [rest of string was truncated]&quot;;.
///Note: we do not send out confidential information (like your password) in emails for safety reasons. You may change your email or password in your &quot;Profile page&quot;:&quot;$MyStaffLink&quot;.
///
///To se [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_opensource_admin_activation {
get {
@ -1581,7 +1419,7 @@ namespace ASC.Web.Core.PublicResources {
///
///ONLYOFFICE is compatible with Microsoft Office™ document formats and guarantees no loss of formatting or quality of created objects. We promise to relieve you from formatting fidelity headache and give full professional editing toolset in your hands.
///
///h3.For a quic [rest of string was truncated]&quot;;.
///h3.For a quick start, [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_personal_after_registration1 {
get {
@ -1657,7 +1495,7 @@ namespace ASC.Web.Core.PublicResources {
///
///It has been a week since you created your cloud office, so we believe it&apos;s time to unveil some beneficial features you might have missed.
///
///Connect *Dropbox*, *Google Drive*, *Box*, *OneDrive*, *Nextcloud*, *ownCloud* or *Yandex.Disk* to ONLYOFFICE and create a single document management space for all your documents. You&apos;ll be able to edit external files in ONLYOFFICE and save them to the storage you keep documents [rest of string was truncated]&quot;;.
///Connect *Dropbox*, *Google Drive*, *Box*, *OneDrive*, *Nextcloud*, *ownCloud* or *Yandex.Disk* to ONLYOFFICE and create a single document management space for all your documents. You&apos;ll be able to edit external files in ONLYOFFICE and save them to the storage you keep documents in. [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_personal_after_registration7 {
get {
@ -1708,7 +1546,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Hello,
///
///you&apos;ve just registered an account at the ONLYOFFICE solution for personal use. Click &quot;here&quot;:&quot;$InviteLink&quot; to confirm the registration and create a password.
///You&apos;ve just registered an account at the ONLYOFFICE solution for personal use. Click &quot;here&quot;:&quot;$InviteLink&quot; to confirm the registration and create a password.
///
///If you can&apos;t open the link, please copy the following &quot;$InviteLink&quot;:&quot;$InviteLink&quot; and paste it into your browser address bar.
///
@ -1728,7 +1566,7 @@ namespace ASC.Web.Core.PublicResources {
///
///*Note*: After the deletion, your account and all data associated with it will be erased permanently in accordance with our &quot;Privacy statement&quot;:&quot;https://help.onlyoffice.com/products/files/doceditor.aspx?fileid=5048502&amp;doc=SXhWMEVzSEYxNlVVaXJJeUVtS0kyYk14YWdXTEFUQmRWL250NllHNUFGbz0_IjUwNDg1MDIi0&quot;.
///
///Ignore this email if you do not wa [rest of string was truncated]&quot;;.
///Ignore this email if you do not want to [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_personal_profile_delete {
get {
@ -1748,7 +1586,7 @@ namespace ASC.Web.Core.PublicResources {
///*Note*: this link is valid for 7 days only. Please complete the portal deactivation process within that period.
///
///You can reactivate your portal any time by clicking the following link:
///p=. &quot;Reactivate Portal&quot;:&quot;$ActivateUrl&quot; (this [rest of string was truncated]&quot;;.
///p=. &quot;Reactivate Portal&quot;:&quot;$ActivateUrl&quot; (this link has no [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_portal_deactivate {
get {
@ -1766,7 +1604,7 @@ namespace ASC.Web.Core.PublicResources {
///*Important! All the data stored on your portal, as well as your registration details will be lost and cannot be recovered.*
///
///#if($AutoRenew == &quot;True&quot;)
///Before you delete the portal, please make sure that automatic billing is turned off. You may check the status of automatic billing in your &quot;Avangate account&quot;:&quot;h [rest of string was truncated]&quot;;.
///Before you delete the portal, please make sure that automatic billing is turned off. You may check the status of automatic billing in your &quot;Avangate account&quot;:&quot;https://se [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_portal_delete {
get {
@ -1800,7 +1638,7 @@ namespace ASC.Web.Core.PublicResources {
///
///Your $PortalUrl portal address was changed to the new &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; address.
///
///*Note*: All the shared documents links are inaccessible now, as well as DNS settings and single sign-on options stop working until you change them. The third party iCal links added to your calendar will also stop updating until you reload them..
///*Note*: All the shared documents links are inaccessible now, as well as DNS settings and single sign-on options stop working until you change them. The third-party iCal links added to your calendar will also stop updating until you reload them..
/// </summary>
internal static string pattern_portal_rename {
get {
@ -1817,9 +1655,7 @@ namespace ASC.Web.Core.PublicResources {
///
///$GreenButton
///
///*Note*: this link is valid for 7 days only. Please complete the profile deletion process within that period.
///
///If you have any questions or need assistance please feel free to contact u [rest of string was truncated]&quot;;.
///*Note*: this link is valid for 7 days only. Please complete the profile deletion process within that period..
/// </summary>
internal static string pattern_profile_delete {
get {
@ -1827,6 +1663,19 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to User &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; has deleted his/her profile, and this profile is now blocked. All users files are still assigned to him/her and occupy disk space.
///
///You can reassign the users documents shared with others to another active user or remove them to free up the portal disk space.
///
///Please go to the user profile using &quot;this link&quot;:&quot;$FromUserLink&quot; to reassign documents to another user or remove the data..
/// </summary>
internal static string pattern_profile_has_deleted_itself {
get {
return ResourceManager.GetString("pattern_profile_has_deleted_itself", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to h1.Your profile at &quot;$__VirtualRootPath&quot;:&quot;$__VirtualRootPath&quot; has been changed
///
@ -1837,8 +1686,8 @@ namespace ASC.Web.Core.PublicResources {
///To view your profile follow the link below:
///&quot;$UserName&quot;:&quot;$MyStaffLink&quot;
///
///^You receive this email because you are a registered user of the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal.^
///.
///
///^You receive this email because you are a registered user of the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal.^.
/// </summary>
internal static string pattern_profile_updated {
get {
@ -1849,7 +1698,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Dear $UserName,
///
///The process of reassign data from user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; to user &quot;$ToUserName&quot;:&quot;$ToUserLink&quot; has been successfully completed.
///The process of data reassignment from user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; to user &quot;$ToUserName&quot;:&quot;$ToUserLink&quot; has been successfully completed.
///
///^You receive this email because you are a registered user of the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal.^.
/// </summary>
@ -1862,7 +1711,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Dear $UserName,
///
///The process of reassign data from user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; to user &quot;$ToUserName&quot;:&quot;$ToUserLink&quot; has been failed.
///The process of data reassignment from user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; to user &quot;$ToUserName&quot;:&quot;$ToUserLink&quot; failed.
///
///Exception message: $Message
///
@ -1877,7 +1726,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Dear $UserName,
///
///The process of removing data from user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; has been successfully completed.
///The process of data removal from user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; has been successfully completed.
///
///The deletion of personal data allowed to free:
///
@ -1897,7 +1746,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Dear $UserName,
///
///The process of removing data from user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; has been failed.
///The process of data removal for user &quot;$FromUserName&quot;:&quot;$FromUserLink&quot; failed.
///
///Exception message: $Message
///
@ -1986,7 +1835,9 @@ namespace ASC.Web.Core.PublicResources {
///
///Best regards,
///ONLYOFFICE™ Support Team
///&quot;www.onlyoffice.com&quot;:&quot;http://onlyoffice.com/&quot;.
///&quot;www.onlyoffice.com&quot;:&quot;http://onlyoffice.com/&quot;
///
///^You receive this email because you are a registered user of the &quot;${__VirtualRoot [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_restore_started {
get {
@ -2104,7 +1955,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Hello, $UserName!
///
///You havent added any teammates, so maybe ONLYOFFICE Personal suits you more? It free and you will be able to:
///You havent added any teammates, so maybe ONLYOFFICE Personal suits you more? It is free and you will be able to:
///
///# Work with text documents, spreadsheets, and presentations.
///# Share docs with your friends and collaborate on them in real-time.
@ -2115,7 +1966,7 @@ namespace ASC.Web.Core.PublicResources {
///
///$GreenButton
///
///You can also install this [rest of string was truncated]&quot;;.
///You can also install this &quot;Chrome e [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_saas_admin_trial_warning_after30_v10 {
get {
@ -2450,7 +2301,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Hello, $UserName!
///
///We hope you enjoy using ONLYOFFICE. How everything is going with your trial? We would really appreciate you feedback!
///We hope you enjoy using ONLYOFFICE. How everything is going with your trial? We would really appreciate your feedback!
///
///Please, dont hesitate to contact us at &quot;support@onlyoffice.com&quot;:&quot;mailto:support@onlyoffice.com&quot; whenever you have questions or ideas.
///
@ -2467,7 +2318,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Hello!
///
///$__AuthorName has invited you as a guest user to &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot;. Accept the invitation by clicking the link:
///You are invited to join &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; as a guest user. Accept the invitation by clicking the link:
///
///$GreenButton
///
@ -2484,14 +2335,14 @@ namespace ASC.Web.Core.PublicResources {
}
/// <summary>
/// Looks up a localized string similar to Hello $UserName!
/// Looks up a localized string similar to Hello!
///
///Your guest profile has been successfully added to &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot;. Now you can:
///
///# Edit your &quot;profile&quot;:&quot;$MyStaffLink&quot;.
///# View and comment the content available in the &quot;Community&quot;:&quot;${__VirtualRootPath}/products/community/&quot; and &quot;Projects&quot;:&quot;${__VirtualRootPath}/products/projects/&quot;.
///# Add and download files available for you in the &quot;Documents&quot;:&quot;${__VirtualRootPath}/products/files/&quot;.
///# Organize your schedule with the built-in &quot;Calendar&quot;:&quot;${__VirtualRootPath [rest of string was truncated]&quot;;.
///# Organize your schedule with the built-in &quot;Calendar&quot;:&quot;${__VirtualRootPath}/addons/calendar [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_saas_guest_welcome_v10 {
get {
@ -2502,7 +2353,7 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to Hello!
///
///$__AuthorName has invited you to join ONLYOFFICE at &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot;. Accept the invitation by clicking the link:
///You are invited to join ONLYOFFICE at &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot;. Accept the invitation by clicking the link:
///
///$GreenButton
///
@ -2519,13 +2370,13 @@ namespace ASC.Web.Core.PublicResources {
}
/// <summary>
/// Looks up a localized string similar to Hello, $UserName!
/// Looks up a localized string similar to Hello!
///
///Welcome to ONLYOFFICE! Your user profile has been successfully added to the portal at &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot;. Now you can:
///
///# Create and edit &quot;Documents&quot;:&quot;${__VirtualRootPath}/products/files/&quot;, share them with teammates, and collaborate in real time.
///# Add your email accounts and manage all correspondence in one place with &quot;Mail&quot;:&quot;${__VirtualRootPath}/addons/mail/&quot;.
///# Manage your workflow with &quot;Projects&quot;:&quot;${__VirtualRootPath}/products/projects/&quot; and your custo [rest of string was truncated]&quot;;.
///# Manage your workflow with &quot;Projects&quot;:&quot;${__VirtualRootPath}/products/projects/&quot; and your customer relationships [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_saas_user_welcome_v10 {
get {
@ -2536,7 +2387,10 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to h1.&quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal profile change notification
///
///&quot;$__AuthorName&quot;:&quot;$__AuthorUrl&quot; has changed his/her profile details at the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal..
///&quot;$__AuthorName&quot;:&quot;$__AuthorUrl&quot; has changed his/her profile details at the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal.
///
///
///^You receive this email because you are an administrator of the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal. If you do not want to receive the notifications about profile updates, please manage your &quot;subscription settings&quot;:&quot;$RecipientSubscriptionConfigURL&quot;.^.
/// </summary>
internal static string pattern_self_profile_updated {
get {
@ -2559,7 +2413,10 @@ namespace ASC.Web.Core.PublicResources {
///
///#end
///
///#end.
///#end
///
///
///^You receive this email because you are a regi [rest of string was truncated]&quot;;.
/// </summary>
internal static string pattern_send_whats_new {
get {
@ -2585,7 +2442,10 @@ namespace ASC.Web.Core.PublicResources {
/// <summary>
/// Looks up a localized string similar to h1.New user added to &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal
///
///&quot;$__AuthorName&quot;:&quot;$__AuthorUrl&quot; has joined your portal at &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot;..
///&quot;$__AuthorName&quot;:&quot;$__AuthorUrl&quot; has joined your portal at &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot;.
///
///
///^You receive this email because you are an administrator user of the &quot;${__VirtualRootPath}&quot;:&quot;${__VirtualRootPath}&quot; portal. If you do not want to receive the notifications about new users, please manage your &quot;subscription settings&quot;:&quot;$RecipientSubscriptionConfigURL&quot;.^.
/// </summary>
internal static string pattern_user_has_join {
get {
@ -2619,24 +2479,6 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to VoIP disabled.
/// </summary>
internal static string subject_admin_voip_blocked {
get {
return ResourceManager.GetString("subject_admin_voip_blocked", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Notification to administrators about VoIP balance.
/// </summary>
internal static string subject_admin_voip_warning {
get {
return ResourceManager.GetString("subject_admin_voip_warning", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ${LetterLogoText}. ${__VirtualRootPath} portal backup created.
/// </summary>
@ -3141,6 +2983,15 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to User has deleted his/her profile.
/// </summary>
internal static string subject_profile_has_deleted_itself {
get {
return ResourceManager.GetString("subject_profile_has_deleted_itself", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your profile at ${__VirtualRootPath} has been changed.
/// </summary>
@ -3151,7 +3002,7 @@ namespace ASC.Web.Core.PublicResources {
}
/// <summary>
/// Looks up a localized string similar to ${LetterLogoText}. Reassigns user data is completed.
/// Looks up a localized string similar to ${LetterLogoText}. User data reassignment is completed.
/// </summary>
internal static string subject_reassigns_completed {
get {
@ -3160,7 +3011,7 @@ namespace ASC.Web.Core.PublicResources {
}
/// <summary>
/// Looks up a localized string similar to ${LetterLogoText}. Reassigns user data is failed.
/// Looks up a localized string similar to ${LetterLogoText}. User data reassignment failed.
/// </summary>
internal static string subject_reassigns_failed {
get {
@ -3169,7 +3020,7 @@ namespace ASC.Web.Core.PublicResources {
}
/// <summary>
/// Looks up a localized string similar to ${LetterLogoText}. Remove user data is completed.
/// Looks up a localized string similar to ${LetterLogoText}. User data removal completed.
/// </summary>
internal static string subject_remove_user_data_completed {
get {
@ -3178,7 +3029,7 @@ namespace ASC.Web.Core.PublicResources {
}
/// <summary>
/// Looks up a localized string similar to ${LetterLogoText}. Remove user data is failed.
/// Looks up a localized string similar to ${LetterLogoText}. User data removal failed.
/// </summary>
internal static string subject_remove_user_data_failed {
get {