Merge branch 'feature/new-viewer' of github.com:ONLYOFFICE/DocSpace into feature/new-viewer

This commit is contained in:
Akmal Isomadinov 2023-01-18 14:15:48 +05:00
commit af99dffde2
72 changed files with 924 additions and 427 deletions

View File

@ -79,6 +79,7 @@ services_name_backend+=(ASC.Web.Studio)
services_name_backend+=(ASC.Data.Backup.BackgroundTasks)
services_name_backend+=(ASC.ClearEvents)
services_name_backend+=(ASC.ApiSystem)
services_name_backend+=(ASC.Web.HealthChecks.UI)
# Publish backend services
for i in ${!services_name_backend[@]}; do

View File

@ -68,6 +68,7 @@
PROXY_HOST=${CONTAINER_PREFIX}proxy
DOCEDITOR_HOST=${CONTAINER_PREFIX}doceditor
LOGIN_HOST=${CONTAINER_PREFIX}login
HELTHCHECKS_HOST=${CONTAINER_PREFIX}healthchecks
# proxy upstream environment #
SERVICE_API_SYSTEM=${API_SYSTEM_HOST}:${SERVICE_PORT}

View File

@ -296,6 +296,16 @@ COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Web.St
CMD ["ASC.Web.Studio.dll", "ASC.Web.Studio"]
## ASC.Web.HealthChecks.UI ##
FROM dotnetrun AS healthchecks
WORKDIR ${BUILD_PATH}/services/ASC.Web.HealthChecks.UI/service
COPY --chown=onlyoffice:onlyoffice docker-healthchecks-entrypoint.sh ./docker-healthchecks-entrypoint.sh
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Web.HealthChecks.UI/service/ .
ENTRYPOINT ["./docker-healthchecks-entrypoint.sh"]
CMD ["ASC.Web.HealthChecks.UI.dll", "ASC.Web.HealthChecks.UI"]
## ASC.Migration.Runner ##
FROM $DOTNET_RUN AS onlyoffice-migration-runner
ARG BUILD_PATH

View File

@ -133,3 +133,10 @@ services:
dockerfile: "${DOCKERFILE}"
target: onlyoffice-migration-runner
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-migration-runner:${DOCKER_TAG}"
onlyoffice-healthchecks:
build:
context: ./
dockerfile: "${DOCKERFILE}"
target: healthchecks
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-healthchecks:${DOCKER_TAG}"

View File

@ -0,0 +1,46 @@
#!/bin/bash
# read parameters
if [ -n "$1" ]; then
RUN_DLL="${1}";
shift
fi
if [ -n "$1" ]; then
NAME_SERVICE="${1}";
shift
fi
echo "Executing -- ${NAME_SERVICE}"
PRODUCT=${PRODUCT:-"onlyoffice"}
CONTAINER_PREFIX=${PRODUCT}-
SERVICE_PORT=${SERVICE_PORT:-"5050"}
SHEME=${SHEME:-"http"}
URLS=${URLS:-"${SHEME}://0.0.0.0:${SERVICE_PORT}"}
PATH_TO_CONF=${PATH_TO_CONF:-"/var/www/services/ASC.Web.HealthChecks.UI/service"}
API_SYSTEM_HOST=${API_SYSTEM_HOST:-"${CONTAINER_PREFIX}api-system:${SERVICE_PORT}"}
BACKUP_HOST=${BACKUP_HOST:-"${CONTAINER_PREFIX}backup:${SERVICE_PORT}"}
BACKUP_BACKGRUOND_TASKS_HOST=${BACKUP_BACKGRUOND_TASKS_HOST:-"${CONTAINER_PREFIX}backup-background-tasks:${SERVICE_PORT}"}
CLEAR_EVENTS_HOST=${CLEAR_EVENTS_HOST:-"${CONTAINER_PREFIX}clear-events:${SERVICE_PORT}"}
FILES_HOST=${FILES_HOST:-"${CONTAINER_PREFIX}files:${SERVICE_PORT}"}
FILES_SERVICES_HOST=${FILES_SERVICES_HOST:-"${CONTAINER_PREFIX}files-services:${SERVICE_PORT}"}
NOTIFY_HOST=${NOTIFY_HOST:-"${CONTAINER_PREFIX}notify:${SERVICE_PORT}"}
PEOPLE_SERVER_HOST=${PEOPLE_SERVER_HOST:-"${CONTAINER_PREFIX}people-server:${SERVICE_PORT}"}
STUDIO_NOTIFY_HOST=${STUDIO_NOTIFY_HOST:-"${CONTAINER_PREFIX}studio-notify:${SERVICE_PORT}"}
API_HOST=${API_HOST:-"${CONTAINER_PREFIX}api:${SERVICE_PORT}"}
STUDIO_HOST=${STUDIO_HOST:-"${CONTAINER_PREFIX}studio:${SERVICE_PORT}"}
sed -i "s!localhost:5010!${API_SYSTEM_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5012!${BACKUP_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5032!${BACKUP_BACKGRUOND_TASKS_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5027!${CLEAR_EVENTS_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5007!${FILES_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5009!${FILES_SERVICES_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5005!${NOTIFY_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5004!${PEOPLE_SERVER_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5006!${API_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5000!${STUDIO_NOTIFY_HOST}!g" ${PATH_TO_CONF}/appsettings.json
sed -i "s!localhost:5003!${STUDIO_HOST}!g" ${PATH_TO_CONF}/appsettings.json
dotnet ${RUN_DLL} --urls=${URLS}

View File

@ -0,0 +1,29 @@
version: "3.8"
x-service:
&x-service-base
container_name: base
restart: always
expose:
- ${SERVICE_PORT}
volumes:
#- /app/onlyoffice/CommunityServer/data:/app/onlyoffice/data
- app_data:/app/onlyoffice/data
- files_data:/var/www/products/ASC.Files/server/
- people_data:/var/www/products/ASC.People/server/
services:
onlyoffice-health-checks-ui:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-healthchecks:${DOCKER_TAG}"
container_name: ${HELTHCHECKS_HOST}
networks:
default:
external:
name: ${NETWORK_NAME}
volumes:
app_data:
files_data:
people_data:

View File

@ -42,8 +42,10 @@ public class MigrationCreator
private string _pathToSave;
private string _userName;
private string _mail;
private string _toRegion;
private int _tenant;
private string _toRegion;
private string _toAlias;
private string _fromAlias;
private int _fromTenantId;
private readonly object _locker = new object();
private readonly int _limit = 1000;
private readonly List<ModuleName> _namesModules = new List<ModuleName>()
@ -55,6 +57,13 @@ public class MigrationCreator
ModuleName.WebStudio
};
private readonly List<ModuleName> _namesModulesForAlreadyExistPortal = new List<ModuleName>()
{
ModuleName.Core,
ModuleName.Files,
ModuleName.Files2,
};
public string NewAlias { get; private set; }
public MigrationCreator(
@ -73,9 +82,9 @@ public class MigrationCreator
_moduleProvider = moduleProvider;
}
public string Create(int tenant, string userName, string mail, string toRegion)
public string Create(string fromAlias, string userName, string mail, string toRegion, string toAlias)
{
Init(tenant, userName, mail, toRegion);
Init(fromAlias, userName, mail, toRegion, toAlias);
var id = GetUserId();
var fileName = _userName + ".tar.gz";
@ -88,49 +97,92 @@ public class MigrationCreator
return fileName;
}
private void Init(int tenant, string userName, string mail, string toRegion)
private void Init(string fromAlias, string userName, string mail, string toRegion, string toAlias)
{
_modules = _moduleProvider.AllModules.Where(m => _namesModules.Contains(m.ModuleName)).ToList();
_pathToSave = "";
_toRegion = toRegion;
_userName = userName;
_mail = mail;
_tenant = tenant;
_fromAlias = fromAlias;
_toAlias = toAlias;
using var dbContextTenant = _dbFactory.CreateDbContext<TenantDbContext>();
var tenant = dbContextTenant.Tenants.SingleOrDefault(q => q.Alias == _fromAlias);
if (tenant == null)
{
throw new ArgumentException("tenant was not found");
}
_fromTenantId = tenant.Id;
_modules = string.IsNullOrEmpty(_toAlias)
? _moduleProvider.AllModules.Where(m => _namesModules.Contains(m.ModuleName)).ToList()
: _moduleProvider.AllModules.Where(m => _namesModulesForAlreadyExistPortal.Contains(m.ModuleName)).ToList();
}
private Guid GetUserId()
{
try
{
var userDbContext = _dbFactory.CreateDbContext<UserDbContext>();
using var userDbContext = _dbFactory.CreateDbContext<UserDbContext>();
User user = null;
if (string.IsNullOrEmpty(_userName) || string.IsNullOrEmpty(_mail))
{
if (string.IsNullOrEmpty(_userName))
{
user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _tenant && q.Status == EmployeeStatus.Active && q.Email == _mail);
user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _fromTenantId && q.Status == EmployeeStatus.Active && q.Email == _mail);
_userName = user.UserName;
}
else
{
user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _tenant && q.Status == EmployeeStatus.Active && q.UserName == _userName);
user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _fromTenantId && q.Status == EmployeeStatus.Active && q.UserName == _userName);
}
}
else
{
user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _tenant && q.Status == EmployeeStatus.Active && q.UserName == _userName && q.Email == _mail);
user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _fromTenantId && q.Status == EmployeeStatus.Active && q.UserName == _userName && q.Email == _mail);
}
if (!string.IsNullOrEmpty(_toAlias))
{
using var dbContextTenant = _dbFactory.CreateDbContext<TenantDbContext>(_toRegion);
using var userDbContextToregion = _dbFactory.CreateDbContext<UserDbContext>(_toRegion);
var tenant = dbContextTenant.Tenants.SingleOrDefault(t => t.Alias == _toAlias);
if (tenant == null)
{
throw new ArgumentException("tenant was not found");
}
else
{
if (userDbContextToregion.Users.Any(q => q.Tenant == tenant.Id && q.UserName == _userName || q.Email == _mail))
{
throw new ArgumentException("username already exist in the portal");
}
}
}
return user.Id;
}
catch (ArgumentException e)
{
throw e;
}
catch (Exception)
{
throw new Exception("username was not found");
throw new ArgumentException("username was not found");
}
}
private void DoMigrationDb(Guid id, IDataWriteOperator writer)
{
{
if (!string.IsNullOrEmpty(_toAlias))
{
using (var connection = _dbFactory.OpenConnection())
{
var tenantsModule = _moduleProvider.AllModules.Single(q => q.ModuleName == ModuleName.Tenants);
var coreUserTable = tenantsModule.Tables.Single(q => q.Name == "core_user");
ArhiveTable(coreUserTable, writer, tenantsModule, connection, id);
}
}
foreach (var module in _modules)
{
var tablesToProcess = module.Tables.Where(t => t.InsertMethod != InsertMethod.None).ToList();
@ -143,55 +195,63 @@ public class MigrationCreator
{
continue;
}
Console.WriteLine($"backup table {table.Name}");
using (var data = new DataTable(table.Name))
{
ActionInvoker.Try(
state =>
{
data.Clear();
int counts;
var offset = 0;
do
{
var t = (TableInfo)state;
var dataAdapter = _dbFactory.CreateDataAdapter();
dataAdapter.SelectCommand = module.CreateSelectCommand(connection.Fix(), _tenant, t, _limit, offset, id).WithTimeout(600);
counts = ((DbDataAdapter)dataAdapter).Fill(data);
offset += _limit;
} while (counts == _limit);
},
table,
maxAttempts: 5,
onFailure: error => { throw ThrowHelper.CantBackupTable(table.Name, error); });
foreach (var col in data.Columns.Cast<DataColumn>().Where(col => col.DataType == typeof(DateTime)))
{
col.DateTimeMode = DataSetDateTime.Unspecified;
}
module.PrepareData(data);
if (data.TableName == "tenants_tenants")
{
ChangeAlias(data);
ChangeName(data);
}
using (var file = _tempStream.Create())
{
data.WriteXml(file, XmlWriteMode.WriteSchema);
data.Clear();
writer.WriteEntry(KeyHelper.GetTableZipKey(module, data.TableName), file);
}
}
ArhiveTable(table, writer, module, connection, id);
}
}
}
}
private void ArhiveTable(TableInfo table, IDataWriteOperator writer, IModuleSpecifics module, DbConnection connection, Guid id)
{
Console.WriteLine($"backup table {table.Name}");
using (var data = new DataTable(table.Name))
{
ActionInvoker.Try(
state =>
{
data.Clear();
int counts;
var offset = 0;
do
{
var t = (TableInfo)state;
var dataAdapter = _dbFactory.CreateDataAdapter();
dataAdapter.SelectCommand = module.CreateSelectCommand(connection.Fix(), _fromTenantId, t, _limit, offset, id).WithTimeout(600);
counts = ((DbDataAdapter)dataAdapter).Fill(data);
offset += _limit;
} while (counts == _limit);
},
table,
maxAttempts: 5,
onFailure: error => { throw ThrowHelper.CantBackupTable(table.Name, error); });
foreach (var col in data.Columns.Cast<DataColumn>().Where(col => col.DataType == typeof(DateTime)))
{
col.DateTimeMode = DataSetDateTime.Unspecified;
}
module.PrepareData(data);
if (data.TableName == "tenants_tenants")
{
ChangeAlias(data);
ChangeName(data);
}
WriteEnrty(data, writer, module);
}
}
private void WriteEnrty(DataTable data, IDataWriteOperator writer, IModuleSpecifics module)
{
using (var file = _tempStream.Create())
{
data.WriteXml(file, XmlWriteMode.WriteSchema);
data.Clear();
writer.WriteEntry(KeyHelper.GetTableZipKey(module, data.TableName), file);
}
}
private void ChangeAlias(DataTable data)
@ -255,8 +315,8 @@ public class MigrationCreator
private void ChangeName(DataTable data)
{
data.Rows[0]["name"] = "";
}
}
private void DoMigrationStorage(Guid id, IDataWriteOperator writer)
{
Console.WriteLine($"start backup storage");
@ -266,7 +326,7 @@ public class MigrationCreator
Console.WriteLine($"start backup fileGroup: {group.Key}");
foreach (var file in group)
{
var storage = _storageFactory.GetStorage(_tenant, group.Key);
var storage = _storageFactory.GetStorage(_fromTenantId, group.Key);
var file1 = file;
ActionInvoker.Try(state =>
{
@ -307,9 +367,9 @@ public class MigrationCreator
var module = _storageFactoryConfig.GetModuleList().Where(m => m == "files").Single();
var store = _storageFactory.GetStorage(_tenant, module);
var store = _storageFactory.GetStorage(_fromTenantId, module);
var dbFiles = filesDbContext.Files.Where(q => q.CreateBy == id && q.TenantId == _tenant).ToList();
var dbFiles = filesDbContext.Files.Where(q => q.CreateBy == id && q.TenantId == _fromTenantId).ToList();
var tasks = new List<Task>(20);
foreach (var dbFile in dbFiles)
@ -331,7 +391,7 @@ public class MigrationCreator
private async Task FindFiles(List<BackupFileInfo> list, IDataStore store, DbFile dbFile, string module)
{
var files = await store.ListFilesRelativeAsync(string.Empty, $"\\{GetUniqFileDirectory(dbFile.Id)}", "*.*", true)
.Select(path => new BackupFileInfo(string.Empty, module, $"{GetUniqFileDirectory(dbFile.Id)}\\{path}", _tenant))
.Select(path => new BackupFileInfo(string.Empty, module, $"{GetUniqFileDirectory(dbFile.Id)}\\{path}", _fromTenantId))
.ToListAsync();
lock (_locker)

View File

@ -61,13 +61,29 @@ public class MigrationRunner
_logger = logger;
}
public async Task Run(string backupFile, string region)
public async Task Run(string backupFile, string region, string fromAlias, string toAlias)
{
_region = region;
_modules = _moduleProvider.AllModules.Where(m => _namesModules.Contains(m.ModuleName)).ToList();
_backupFile = backupFile;
var columnMapper = new ColumnMapper();
if (!string.IsNullOrEmpty(toAlias))
{
using var dbContextTenant = _dbFactory.CreateDbContext<TenantDbContext>();
var fromTenant = dbContextTenant.Tenants.SingleOrDefault(q => q.Alias == fromAlias);
using var dbContextToTenant = _dbFactory.CreateDbContext<TenantDbContext>(region);
var toTenant = dbContextToTenant.Tenants.SingleOrDefault(q => q.Alias == toAlias);
toTenant.Status = TenantStatus.Restoring;
toTenant.StatusChanged = DateTime.UtcNow;
dbContextTenant.Tenants.Update(toTenant);
dbContextToTenant.SaveChanges();
columnMapper.SetMapping("tenants_tenants", "id", fromTenant.Id, toTenant.Id);
columnMapper.Commit();
}
using (var dataReader = new ZipReadOperator(_backupFile))
{
foreach (var module in _modules)

View File

@ -37,8 +37,7 @@ var param = Parser.Default.ParseArguments<Options>(args).Value;
{
FromRegion = "personal",
ToRegion = "personal",
Tenant = 1,
UserName = "administrator"
FromAlias = "localhost"
};*/
var builder = WebApplication.CreateBuilder(options);
@ -94,11 +93,12 @@ if(string.IsNullOrEmpty(param.UserName) && string.IsNullOrEmpty(param.Mail))
var app = builder.Build();
Console.WriteLine("backup start");
var migrationCreator = app.Services.GetService<MigrationCreator>();
var fileName = migrationCreator.Create(param.Tenant, param.UserName, param.Mail, param.ToRegion);
var fileName = migrationCreator.Create(param.FromAlias, param.UserName, param.Mail, param.ToRegion, param.ToAlias);
Console.WriteLine("backup was success");
Console.WriteLine("restore start");
var migrationRunner = app.Services.GetService<MigrationRunner>();
await migrationRunner.Run(fileName, param.ToRegion);
await migrationRunner.Run(fileName, param.ToRegion, param.FromAlias, param.ToAlias);
Console.WriteLine("restore was success");
Directory.GetFiles(AppContext.BaseDirectory).Where(f => f.Equals(fileName)).ToList().ForEach(File.Delete);
@ -109,12 +109,14 @@ if (Directory.Exists(AppContext.BaseDirectory + "\\temp"))
}
Console.WriteLine("migration was success");
Console.WriteLine($"new alias is - {migrationCreator.NewAlias}");
if (!string.IsNullOrEmpty(migrationCreator.NewAlias))
{
Console.WriteLine($"new alias is - {migrationCreator.NewAlias}");
}
public sealed class Options
{
[Option('t', "tenant", Required = true)]
public int Tenant { get; set; }
[Option('a', "FromAlias", Required = true)]
public string FromAlias { get; set; }
[Option('u', "username", Required = false, HelpText = "enter username or mail for find user")]
public string UserName { get; set; }
@ -122,9 +124,12 @@ public sealed class Options
[Option('m', "mail", Required = false, HelpText = "enter username or mail for find user")]
public string Mail { get; set; }
[Option("toregion", Required = true)]
[Option('t' ,"toregion", Required = true)]
public string ToRegion { get; set; }
[Option("fromregion", Required = false, Default = "personal")]
[Option('f' ,"fromregion", Required = false, Default = "personal")]
public string FromRegion { get; set; }
[Option("ToAlias", Required = false, HelpText = "if you wish migration to already exist portal, enter the alias")]
public string ToAlias { get; set; }
}

View File

@ -42,7 +42,7 @@
"property": [
{
"name": "$STORAGE_ROOT",
"value": "..\\Data\\"
"value": "..\\..\\..\\Data\\"
}
]
}
@ -90,7 +90,7 @@
"property": [
{
"name": "$STORAGE_ROOT",
"value": "..\\Data\\"
"value": "..\\..\\..\\Data\\"
}
]
}

View File

@ -1,120 +0,0 @@
{
"DbProviderFactories": {
"mysql": {
"name": "MySQL Data Provider",
"invariant": "MySql.Data.MySqlClient",
"description": ".Net Framework Data Provider for MySQL",
"type": "MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data"
}
},
"core": {
"machinekey": "1123askdasjklasbnd",
"payment": {
"delay": 10,
"region": "test",
"test": true,
"url": "",
"key": "",
"secret": ""
}
},
"regions": {
"personal": {
"ConnectionStrings": {
"default": {
"name": "default",
"connectionString": "Server=localhost;Database=onlyoffice;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;ConnectionReset=false",
"providerName": "MySql.Data.MySqlClient"
}
},
"storage": {
"appender": [
{
"name": "generic",
"append": "~/"
}
],
"handler": [
{
"name": "disc",
"type": "ASC.Data.Storage.DiscStorage.DiscDataStore, ASC.Data.Storage",
"property": [
{
"name": "$STORAGE_ROOT",
"value": "..\\Data\\"
}
]
}
],
"module": [
{
"name": "files",
"data": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4",
"type": "disc",
"path": "$STORAGE_ROOT\\Products\\Files",
"expires": "0:16:0",
"domain": [
{
"name": "files_temp",
"visible": false,
"data": "00000000-0000-0000-0000-000000000000",
"path": "$STORAGE_ROOT\\Products\\Files\\{0}\\temp",
"virtualpath": "~/products/community/modules/wiki/data/filestemp",
"expires": "0:10:0"
}
]
}
]
}
},
"docSpace": {
"ConnectionStrings": {
"default": {
"name": "default",
"connectionString": "Server=localhost;Database=onlyoffice1;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;ConnectionReset=false",
"providerName": "MySql.Data.MySqlClient"
}
},
"storage": {
"appender": [
{
"name": "generic",
"append": "~/"
}
],
"handler": [
{
"name": "disc",
"type": "ASC.Data.Storage.DiscStorage.DiscDataStore, ASC.Data.Storage",
"property": [
{
"name": "$STORAGE_ROOT",
"value": "..\\Data\\"
}
]
}
],
"module": [
{
"name": "files",
"data": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4",
"type": "disc",
"path": "$STORAGE_ROOT\\Products\\Files1",
"expires": "0:16:0",
"domain": [
{
"name": "files_temp",
"visible": false,
"data": "00000000-0000-0000-0000-000000000000",
"path": "$STORAGE_ROOT\\Products\\Files1\\{0}\\temp",
"virtualpath": "~/products/community/modules/wiki/data/filestemp",
"expires": "0:10:0"
}
]
}
]
}
}
}
}

View File

@ -266,13 +266,7 @@ server {
proxy_pass http://127.0.0.1:5007;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
location /Products {
location ~* /Files {
rewrite ^/Products/Files(.*)$ $1 redirect;
}
}
location /apisystem {
rewrite apisystem/(.*) /$1 break;
proxy_pass http://127.0.0.1:5010;

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<babeledit_project be_version="4.0.2" version="1.3">
<babeledit_project be_version="4.0.3" version="1.3">
<!--
BabelEdit project file
@ -1366,118 +1366,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>AdminSettings</name>
<description/>
<comment/>
<default_text/>
<translations>
<translation>
<language>az-Cyrl-AZ</language>
<approved>false</approved>
</translation>
<translation>
<language>bg-BG</language>
<approved>false</approved>
</translation>
<translation>
<language>cs-CZ</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>el-GR</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fi-FI</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>it-IT</language>
<approved>false</approved>
</translation>
<translation>
<language>ja-JP</language>
<approved>false</approved>
</translation>
<translation>
<language>ko-KR</language>
<approved>false</approved>
</translation>
<translation>
<language>lo-LA</language>
<approved>false</approved>
</translation>
<translation>
<language>lv-LV</language>
<approved>false</approved>
</translation>
<translation>
<language>nl-NL</language>
<approved>false</approved>
</translation>
<translation>
<language>pl-PL</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-BR</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-PT</language>
<approved>false</approved>
</translation>
<translation>
<language>ro-RO</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>sk-SK</language>
<approved>false</approved>
</translation>
<translation>
<language>sl-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
<translation>
<language>uk-UA</language>
<approved>false</approved>
</translation>
<translation>
<language>vi-VN</language>
<approved>false</approved>
</translation>
<translation>
<language>zh-CN</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>AdvancedFilter</name>
<description/>
@ -8870,6 +8758,118 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>DontAskAgain</name>
<description/>
<comment/>
<default_text/>
<translations>
<translation>
<language>az-Cyrl-AZ</language>
<approved>false</approved>
</translation>
<translation>
<language>bg-BG</language>
<approved>false</approved>
</translation>
<translation>
<language>cs-CZ</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>el-GR</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fi-FI</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>it-IT</language>
<approved>false</approved>
</translation>
<translation>
<language>ja-JP</language>
<approved>false</approved>
</translation>
<translation>
<language>ko-KR</language>
<approved>false</approved>
</translation>
<translation>
<language>lo-LA</language>
<approved>false</approved>
</translation>
<translation>
<language>lv-LV</language>
<approved>false</approved>
</translation>
<translation>
<language>nl-NL</language>
<approved>false</approved>
</translation>
<translation>
<language>pl-PL</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-BR</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-PT</language>
<approved>false</approved>
</translation>
<translation>
<language>ro-RO</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>sk-SK</language>
<approved>false</approved>
</translation>
<translation>
<language>sl-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
<translation>
<language>uk-UA</language>
<approved>false</approved>
</translation>
<translation>
<language>vi-VN</language>
<approved>false</approved>
</translation>
<translation>
<language>zh-CN</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>Download</name>
<description/>
@ -21750,6 +21750,342 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>SettingsDocSpace</name>
<description/>
<comment/>
<default_text/>
<translations>
<translation>
<language>az-Cyrl-AZ</language>
<approved>false</approved>
</translation>
<translation>
<language>bg-BG</language>
<approved>false</approved>
</translation>
<translation>
<language>cs-CZ</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>el-GR</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fi-FI</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>it-IT</language>
<approved>false</approved>
</translation>
<translation>
<language>ja-JP</language>
<approved>false</approved>
</translation>
<translation>
<language>ko-KR</language>
<approved>false</approved>
</translation>
<translation>
<language>lo-LA</language>
<approved>false</approved>
</translation>
<translation>
<language>lv-LV</language>
<approved>false</approved>
</translation>
<translation>
<language>nl-NL</language>
<approved>false</approved>
</translation>
<translation>
<language>pl-PL</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-BR</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-PT</language>
<approved>false</approved>
</translation>
<translation>
<language>ro-RO</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>sk-SK</language>
<approved>false</approved>
</translation>
<translation>
<language>sl-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
<translation>
<language>uk-UA</language>
<approved>false</approved>
</translation>
<translation>
<language>vi-VN</language>
<approved>false</approved>
</translation>
<translation>
<language>zh-CN</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>SettingsGenaral</name>
<description/>
<comment/>
<default_text/>
<translations>
<translation>
<language>az-Cyrl-AZ</language>
<approved>false</approved>
</translation>
<translation>
<language>bg-BG</language>
<approved>false</approved>
</translation>
<translation>
<language>cs-CZ</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>el-GR</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fi-FI</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>it-IT</language>
<approved>false</approved>
</translation>
<translation>
<language>ja-JP</language>
<approved>false</approved>
</translation>
<translation>
<language>ko-KR</language>
<approved>false</approved>
</translation>
<translation>
<language>lo-LA</language>
<approved>false</approved>
</translation>
<translation>
<language>lv-LV</language>
<approved>false</approved>
</translation>
<translation>
<language>nl-NL</language>
<approved>false</approved>
</translation>
<translation>
<language>pl-PL</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-BR</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-PT</language>
<approved>false</approved>
</translation>
<translation>
<language>ro-RO</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>sk-SK</language>
<approved>false</approved>
</translation>
<translation>
<language>sl-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
<translation>
<language>uk-UA</language>
<approved>false</approved>
</translation>
<translation>
<language>vi-VN</language>
<approved>false</approved>
</translation>
<translation>
<language>zh-CN</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>SettingsPersonal</name>
<description/>
<comment/>
<default_text/>
<translations>
<translation>
<language>az-Cyrl-AZ</language>
<approved>false</approved>
</translation>
<translation>
<language>bg-BG</language>
<approved>false</approved>
</translation>
<translation>
<language>cs-CZ</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>el-GR</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fi-FI</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>it-IT</language>
<approved>false</approved>
</translation>
<translation>
<language>ja-JP</language>
<approved>false</approved>
</translation>
<translation>
<language>ko-KR</language>
<approved>false</approved>
</translation>
<translation>
<language>lo-LA</language>
<approved>false</approved>
</translation>
<translation>
<language>lv-LV</language>
<approved>false</approved>
</translation>
<translation>
<language>nl-NL</language>
<approved>false</approved>
</translation>
<translation>
<language>pl-PL</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-BR</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-PT</language>
<approved>false</approved>
</translation>
<translation>
<language>ro-RO</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>sk-SK</language>
<approved>false</approved>
</translation>
<translation>
<language>sl-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
<translation>
<language>uk-UA</language>
<approved>false</approved>
</translation>
<translation>
<language>vi-VN</language>
<approved>false</approved>
</translation>
<translation>
<language>zh-CN</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>ShowMore</name>
<description/>

View File

@ -1,5 +1,4 @@
{
"DeleteGroupUsersSuccessMessage": "Benutzer wurden erfolgreich gelöscht.",
"DeleteUsers": "Benutzer löschen",
"DeleteUsersMessage": "Die ausgewählten deaktivierten Benutzer werden aus dem Portal gelöscht. Persönliche Dokumente dieser Benutzer, die für andere verfügbar sind, werden auch gelöscht. Um dies zu vermeiden, müssen Sie den Datenübertragungsprozess starten."
"DeleteUsers": "Benutzer löschen"
}

View File

@ -1,5 +1,5 @@
{
"DeleteGroupUsersSuccessMessage": "Users have been successfully deleted.",
"DeleteUsers": "Delete users",
"DeleteUsersMessage": "The selected disabled users will be deleted from the DocSpace. Personal documents of these users which are available to others will be deleted. To avoid this, you need to start the data transfer process before deleting."
"DeleteUsersMessage": "The selected disabled users will be deleted from the DocSpace. Personal documents of these users which are available to others will be deleted."
}

View File

@ -1,5 +1,4 @@
{
"DeleteGroupUsersSuccessMessage": "Les utilisateurs ont été supprimés avec succès.",
"DeleteUsers": "Supprimer les utilisateurs",
"DeleteUsersMessage": "Les utilisateurs désactivés sélectionnés seront supprimés du portail. Les documents personnels de ces utilisateurs qui sont partagés avec d'autres seront supprimés. Pour éviter cela, vous devez démarrer le processus de transfert de données avant la suppression."
"DeleteUsers": "Supprimer les utilisateurs"
}

View File

@ -1,5 +1,4 @@
{
"DeleteGroupUsersSuccessMessage": "Gli utenti sono stati eliminati con successo.",
"DeleteUsers": "Eliminare utenti",
"DeleteUsersMessage": "Gli utenti disabilitati selezionati verranno eliminati dal portale. I documenti personali di questi utenti disponibili per gli altri verranno eliminati. Per evitarlo, è necessario avviare il processo di trasferimento dei dati prima di eliminarli."
"DeleteUsers": "Eliminare utenti"
}

View File

@ -1,5 +1,4 @@
{
"DeleteGroupUsersSuccessMessage": "ユーザーが正常に削除されました。",
"DeleteUsers": "ユーザーを削除する",
"DeleteUsersMessage": "選択した無効のユーザーは、ポータルサイトから削除されます。これらのユーザーの個人文書で、他の人が利用できるものも削除されます。これを避けるには、削除する前にデータ転送プロセスを開始する必要があります。"
"DeleteUsers": "ユーザーを削除する"
}

View File

@ -1,5 +1,5 @@
{
"DeleteGroupUsersSuccessMessage": "Пользователи были успешно удалены",
"DeleteUsers": "Удалить пользователей",
"DeleteUsersMessage": "Выбранные заблокированные пользователи будут удалены с портала. Будут удалены личные документы этих пользователей, доступные для других. Чтобы избежать этого, нужно перед удалением запустить процесс передачи данных."
"DeleteUsersMessage": "Выбранные заблокированные пользователи будут удалены с портала. Будут удалены личные документы этих пользователей, доступные для других."
}

View File

@ -1,5 +1,4 @@
{
"DeleteGroupUsersSuccessMessage": "用户已成功删除。",
"DeleteUsers": "删除用户",
"DeleteUsersMessage": "选择的禁用用户将从门户网站中删除。这些用户对其他人可用的个人文档将被删除。为了避免这种情况,您必须在删除前启动数据传输过程。"
"DeleteUsers": "删除用户"
}

View File

@ -413,6 +413,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
{!isMobileOnly && <MainBar />}
<div className="main-container">
<Switch>
<Redirect from="/Products/Files/" to="/rooms/shared" />
<PrivateRoute
exact
path={[

View File

@ -26,6 +26,7 @@ const EmptyFolderContainer = ({
tReady,
isLoadedFetchFiles,
viewAs,
setIsLoadedEmptyPage,
}) => {
const onBackToParentFolder = () => {
setIsLoading(true);
@ -35,10 +36,21 @@ const EmptyFolderContainer = ({
: fetchFiles(parentId).finally(() => setIsLoading(false));
};
React.useEffect(() => {
if (isLoadedFetchFiles && tReady) {
setIsLoadedEmptyPage(true);
} else {
setIsLoadedEmptyPage(false);
}
}, [isLoadedFetchFiles, tReady]);
React.useEffect(() => {
setIsEmptyPage(true);
return () => setIsEmptyPage(false);
return () => {
setIsEmptyPage(false);
setIsLoadedEmptyPage(false);
};
}, []);
const onInviteUsersClick = () => {
@ -161,6 +173,7 @@ export default inject(
setIsEmptyPage,
isLoadedFetchFiles,
viewAs,
setIsLoadedEmptyPage,
} = filesStore;
const {
navigationPath,
@ -198,6 +211,7 @@ export default inject(
folderId,
isLoadedFetchFiles,
viewAs,
setIsLoadedEmptyPage,
};
}
)(withTranslation(["Files", "Translations"])(observer(EmptyFolderContainer)));

View File

@ -48,6 +48,7 @@ const RootFolderContainer = (props) => {
setIsEmptyPage,
isVisitor,
sectionWidth,
setIsLoadedEmptyPage,
} = props;
const personalDescription = t("EmptyFolderDecription");
@ -87,8 +88,13 @@ const RootFolderContainer = (props) => {
return () => {
setIsEmptyPage(false);
setIsLoadedEmptyPage(false);
};
}, [isEmptyPage, setIsEmptyPage, rootFolderType]);
}, []);
React.useEffect(() => {
setIsLoadedEmptyPage(!isLoading);
}, [isLoading]);
const onGoToPersonal = () => {
const newFilter = filter.clone();
@ -313,31 +319,17 @@ const RootFolderContainer = (props) => {
const headerText = isPrivacyFolder ? privateRoomHeader : title;
const emptyFolderProps = getEmptyFolderProps();
React.useEffect(() => {
let timeout;
if (isLoading) {
setShowLoader(isLoading);
} else {
timeout = setTimeout(() => setShowLoader(isLoading), 300);
}
return () => clearTimeout(timeout);
}, [isLoading]);
if (isLoading) {
return <Loaders.EmptyContainerLoader viewAs={viewAs} />;
}
return (
<>
{showLoader ? (
<Loaders.EmptyContainerLoader viewAs={viewAs} />
) : (
<EmptyContainer
headerText={headerText}
isEmptyPage={isEmptyPage}
sectionWidth={sectionWidth}
{...emptyFolderProps}
/>
)}
</>
<EmptyContainer
headerText={headerText}
isEmptyPage={isEmptyPage}
sectionWidth={sectionWidth}
{...emptyFolderProps}
/>
);
};
@ -362,6 +354,7 @@ export default inject(
setAlreadyFetchingRooms,
isEmptyPage,
setIsEmptyPage,
setIsLoadedEmptyPage,
} = filesStore;
const { title, rootFolderType } = selectedFolderStore;
const { isPrivacyFolder, myFolderId } = treeFoldersStore;
@ -387,6 +380,7 @@ export default inject(
setAlreadyFetchingRooms,
isEmptyPage,
setIsEmptyPage,
setIsLoadedEmptyPage,
};
}
)(withTranslation(["Files"])(observer(RootFolderContainer)));

View File

@ -218,7 +218,7 @@ export default inject(
? filesList
: isCopy
? selections
: selections.filter((f) => !f.isEditing);
: selections.filter((f) => f && !f?.isEditing);
const provider = selections?.find((x) => x?.providerKey);

View File

@ -169,10 +169,23 @@ const SectionFilterContent = ({
searchTitleOpenLocation,
isLoadedLocationFiles,
setIsLoadedSearchFiles,
isLoadedEmptyPage,
isEmptyPage,
clearSearch,
setClearSearch,
}) => {
const [selectedFilterValues, setSelectedFilterValues] = React.useState(null);
const [isLoadedFilter, setIsLoadedFilter] = React.useState(false);
React.useEffect(() => {
if (isEmptyPage) {
setIsLoadedFilter(isLoadedEmptyPage);
}
if (!isEmptyPage && !isLoadedEmptyPage) {
setIsLoadedFilter(true);
}
}, [isLoadedEmptyPage, isEmptyPage]);
React.useEffect(() => {
if (!(searchTitleOpenLocation && isLoadedLocationFiles)) return;
@ -1300,6 +1313,10 @@ const SectionFilterContent = ({
}
};
if (!isLoadedFilter) {
return <Loaders.Filter />;
}
return (
<FilterInput
t={t}
@ -1353,6 +1370,8 @@ export default inject(
thirdPartyStore,
clearSearch,
setClearSearch,
isLoadedEmptyPage,
isEmptyPage,
} = filesStore;
const { providers } = thirdPartyStore;
@ -1411,6 +1430,9 @@ export default inject(
isLoadedLocationFiles,
setIsLoadedSearchFiles,
isLoadedEmptyPage,
isEmptyPage,
clearSearch,
setClearSearch,
};

View File

@ -656,6 +656,7 @@ class SectionHeaderContent extends React.Component {
isEmptyArchive,
isVisitor,
isRoom,
isGroupMenuBlocked,
} = this.props;
const menuItems = this.getMenuItems();
@ -676,6 +677,7 @@ class SectionHeaderContent extends React.Component {
isInfoPanelVisible={isInfoPanelVisible}
toggleInfoPanel={this.onToggleInfoPanel}
isMobileView={isMobileOnly}
isBlocked={isGroupMenuBlocked}
/>
) : (
<div className="header-container">
@ -795,6 +797,7 @@ export default inject(
downloadAction,
getHeaderMenu,
backToParentFolder,
isGroupMenuBlocked,
} = filesActionsStore;
const { setIsVisible, isVisible } = auth.infoPanelStore;
@ -912,6 +915,7 @@ export default inject(
isEmptyArchive,
canRestoreAll,
canDeleteAll,
isGroupMenuBlocked,
};
}
)(

View File

@ -492,6 +492,7 @@ class PureHome extends React.Component {
frameConfig,
withPaging,
isEmptyPage,
isLoadedEmptyPage,
} = this.props;
if (window.parent && !frameConfig) {
@ -538,7 +539,7 @@ class PureHome extends React.Component {
</Section.SectionHeader>
)}
{!isEmptyPage && !isErrorRoomNotAvailable && (
{!isLoadedEmptyPage && !isErrorRoomNotAvailable && (
<Section.SectionFilter>
{isFrame ? (
showFilter && <SectionFilterContent />
@ -548,6 +549,12 @@ class PureHome extends React.Component {
</Section.SectionFilter>
)}
{isLoadedEmptyPage && (
<Section.SectionFilter>
<div style={{ height: "32px" }} />
</Section.SectionFilter>
)}
<Section.SectionBody>
<Consumer>
{(context) => (
@ -622,7 +629,7 @@ export default inject(
refreshFiles,
setViewAs,
isEmptyPage,
isLoadedEmptyPage,
disableDrag,
isErrorRoomNotAvailable,
} = filesStore;
@ -773,6 +780,7 @@ export default inject(
setViewAs,
withPaging,
isEmptyPage,
isLoadedEmptyPage,
};
}
)(withRouter(observer(Home)));

View File

@ -5,7 +5,7 @@ import Heading from "@docspace/components/heading";
import Box from "@docspace/components/box";
import StyledSettings from "./StyledSettings";
const AdminSettings = ({
const GeneralSettings = ({
storeForceSave,
setStoreForceSave,
enableThirdParty,
@ -62,4 +62,4 @@ export default inject(({ settingsStore }) => {
enableThirdParty,
setEnableThirdParty,
};
})(observer(AdminSettings));
})(observer(GeneralSettings));

View File

@ -6,7 +6,7 @@ import Heading from "@docspace/components/heading";
import Box from "@docspace/components/box";
import StyledSettings from "./StyledSettings";
const CommonSettings = ({
const PersonalSettings = ({
storeOriginalFiles,
confirmDelete,
updateIfExist,
@ -206,4 +206,4 @@ export default inject(
createWithoutDialog,
};
}
)(observer(CommonSettings));
)(observer(PersonalSettings));

View File

@ -7,8 +7,8 @@ import { inject, observer } from "mobx-react";
import { combineUrl } from "@docspace/common/utils";
import config from "PACKAGE_FILE";
import Submenu from "@docspace/components/submenu";
import CommonSettings from "./CommonSettings";
import AdminSettings from "./AdminSettings";
import PersonalSettings from "./CommonSettings";
import GeneralSettings from "./AdminSettings";
import { tablet } from "@docspace/components/utils/device";
import { isMobile } from "react-device-detect";
@ -34,14 +34,14 @@ const SectionBodyContent = ({ isErrorSettings, history, user }) => {
const commonSettings = {
id: "common",
name: t("Common:Common"),
content: <CommonSettings t={t} />,
name: t("Common:SettingsPersonal"),
content: <PersonalSettings t={t} />,
};
const adminSettings = {
id: "admin",
name: t("Common:AdminSettings"),
content: <AdminSettings t={t} />,
name: t("Common:SettingsGenaral"),
content: <GeneralSettings t={t} />,
};
const data = [adminSettings, commonSettings];
@ -70,7 +70,7 @@ const SectionBodyContent = ({ isErrorSettings, history, user }) => {
) : (
<StyledContainer>
{!showAdminSettings ? (
<CommonSettings t={t} showTitle={true} />
<PersonalSettings t={t} showTitle={true} />
) : (
<Submenu
data={data}

View File

@ -45,6 +45,7 @@ class FilesActionStore {
itemOpenLocation = null;
isLoadedLocationFiles = false;
isLoadedSearchFiles = false;
isGroupMenuBlocked = false;
constructor(
authStore,
@ -282,6 +283,7 @@ class FilesActionStore {
try {
this.filesStore.setOperationAction(true);
this.setGroupMenuBlocked(true);
await removeFiles(folderIds, fileIds, deleteAfter, immediately)
.then(async (res) => {
if (res[0]?.error) return Promise.reject(res[0].error);
@ -337,6 +339,7 @@ class FilesActionStore {
return toastr.error(err.message ? err.message : err);
} finally {
this.filesStore.setOperationAction(false);
this.setGroupMenuBlocked(false);
}
}
};
@ -550,7 +553,10 @@ class FilesActionStore {
this.dialogsStore.setIsFolderActions(false);
}
return this.downloadFiles(fileIds, folderIds, label);
this.setGroupMenuBlocked(true);
return this.downloadFiles(fileIds, folderIds, label).finally(() =>
this.setGroupMenuBlocked(false)
);
};
completeAction = async (selectedItem, type, isFolder = false) => {
@ -710,6 +716,7 @@ class FilesActionStore {
const items = Array.isArray(itemId) ? itemId : [itemId];
addActiveItems(null, items);
this.setGroupMenuBlocked(true);
return removeFiles(items, [], true, true)
.then(async (res) => {
if (res[0]?.error) return Promise.reject(res[0].error);
@ -723,7 +730,10 @@ class FilesActionStore {
? translations?.successRemoveRooms
: translations?.successRemoveRoom
)
);
)
.finally(() => {
this.setGroupMenuBlocked(false);
});
} else {
addActiveItems(null, [itemId]);
return deleteFolder(itemId)
@ -1456,6 +1466,7 @@ class FilesActionStore {
});
try {
this.setGroupMenuBlocked(true);
await this.deleteItemOperation(false, itemId, translations, true);
const id = Array.isArray(itemId) ? itemId : [itemId];
@ -1470,6 +1481,8 @@ class FilesActionStore {
});
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
return toastr.error(err.message ? err.message : err);
} finally {
this.setGroupMenuBlocked(false);
}
};
@ -1898,6 +1911,10 @@ class FilesActionStore {
false
).finally(() => setIsLoading(false));
};
setGroupMenuBlocked = (blocked) => {
this.isGroupMenuBlocked = blocked;
};
}
export default FilesActionStore;

View File

@ -113,6 +113,8 @@ class FilesStore {
clearSearch = false;
isLoadedEmptyPage = false;
constructor(
authStore,
selectedFolderStore,
@ -135,7 +137,6 @@ class FilesStore {
this.roomsController = new AbortController();
this.filesController = new AbortController();
const { socketHelper, withPaging } = authStore.settingsStore;
socketHelper.on("s:modify-folder", async (opt) => {
@ -438,6 +439,10 @@ class FilesStore {
this.isEmptyPage = isEmptyPage;
};
setIsLoadedEmptyPage = (isLoadedEmptyPage) => {
this.isLoadedEmptyPage = isLoadedEmptyPage;
};
get tooltipOptions() {
if (!this.dragging) return null;
@ -2958,22 +2963,26 @@ class FilesStore {
(f) => f.id === item.id && f.isFolder === item.isFolder
);
if (fileIndex === -1) {
this.setSelection([item, ...this.selection]);
this.setSelection([...this.selection, item]);
} else {
this.deselectFile(item);
}
};
withShiftSelect = (item) => {
const caretStart = this.hotkeyCaretStart
? this.hotkeyCaretStart
: this.filesList[0];
const caret = this.hotkeyCaret ? this.hotkeyCaret : caretStart;
if (!caret || !caretStart) return;
const startCaretIndex = this.filesList.findIndex(
(f) =>
f.id === this.hotkeyCaretStart.id &&
f.isFolder === this.hotkeyCaretStart.isFolder
(f) => f.id === caretStart.id && f.isFolder === caretStart.isFolder
);
const caretIndex = this.filesList.findIndex(
(f) =>
f.id === this.hotkeyCaret.id && f.isFolder === this.hotkeyCaret.isFolder
(f) => f.id === caret.id && f.isFolder === caret.isFolder
);
const itemIndex = this.filesList.findIndex(
@ -2994,6 +3003,11 @@ class FilesStore {
);
if (selectionIndex === -1) {
newSelection.push(filesItem);
} else {
newSelection = newSelection.filter(
(_, fIndex) => selectionIndex !== fIndex
);
newSelection.push(filesItem);
}
if (isMoveDown) {
@ -3003,13 +3017,18 @@ class FilesStore {
}
}
const lastSelection = this.selection[this.selection.length - 1];
const indexOfLast = this.filesList.findIndex(
(f) => f.id === lastSelection.id && f.isFolder === lastSelection.isFolder
);
newSelection = newSelection.filter((f) => {
const listIndex = this.filesList.findIndex(
(x) => x.id === f.id && x.isFolder === f.isFolder
);
if (isMoveDown) {
const isSelect = itemIndex > startCaretIndex;
const isSelect = listIndex < indexOfLast;
if (isSelect) return true;
if (listIndex >= startCaretIndex) {
@ -3018,7 +3037,7 @@ class FilesStore {
return listIndex >= itemIndex;
}
} else {
const isSelect = itemIndex < startCaretIndex;
const isSelect = listIndex > indexOfLast;
if (isSelect) return true;
if (listIndex <= startCaretIndex) {

View File

@ -136,7 +136,7 @@ class ProfileActionsStore {
? {
key: "user-menu-settings",
icon: "/static/images/catalog.settings.react.svg",
label: t("Common:Settings"),
label: t("Common:SettingsDocSpace"),
onClick: () => this.onSettingsClick(settingsUrl),
}
: null;

View File

@ -320,7 +320,7 @@ class ImageViewer extends React.Component {
key: "delete",
actionType: 103,
render: (
<div className="iconContainer rotateLeft">
<div className="iconContainer viewer-delete">
<MediaDeleteIcon size="scale" />
</div>
),
@ -329,7 +329,7 @@ class ImageViewer extends React.Component {
key: "favorite",
actionType: 104,
render: (
<div className="iconContainer rotateLeft">
<div className="iconContainer viewer-favorite">
<MediaFavoriteIcon size="scale" />
</div>
),

View File

@ -119,7 +119,7 @@ const StyledButton = styled(Button)`
StyledButton.defaultProps = { theme: Base };
const GroupMenuItem = ({ item }) => {
const GroupMenuItem = ({ item, isBlocked }) => {
const buttonRef = React.useRef(null);
const [open, setOpen] = React.useState(false);
@ -129,6 +129,8 @@ const GroupMenuItem = ({ item }) => {
};
const onClickAction = (e) => {
if (isBlocked) return;
onClick && onClick(e);
if (withDropDown) {
@ -156,7 +158,7 @@ const GroupMenuItem = ({ item }) => {
id={id}
label={label}
title={title || label}
isDisabled={disabled}
isDisabled={isBlocked}
onClick={onClickAction}
icon={
<ReactSVG src={iconUrl} className="combo-button_selected-icon" />

View File

@ -23,6 +23,7 @@ const TableGroupMenu = (props) => {
toggleInfoPanel,
withoutInfoPanelToggler,
isMobileView,
isBlocked,
...rest
} = props;
const onCheckboxChange = (e) => {
@ -60,7 +61,7 @@ const TableGroupMenu = (props) => {
<div className="table-container_group-menu-separator" />
<StyledScrollbar>
{headerMenu.map((item, index) => (
<GroupMenuItem key={index} item={item} />
<GroupMenuItem key={index} item={item} isBlocked={isBlocked} />
))}
</StyledScrollbar>
{!withoutInfoPanelToggler && (

View File

@ -62,7 +62,8 @@ export const Viewer = (props) => {
}, []);
React.useEffect(() => {
if (!isPlay || isOpenContextMenu) return clearTimeout(timer);
if ((!isPlay || isOpenContextMenu) && (!isImage || isOpenContextMenu))
return clearTimeout(timer);
document.addEventListener("touchstart", onTouch);
if (!isMobileOnly) {
document.addEventListener("mousemove", resetTimer);
@ -140,13 +141,14 @@ export const Viewer = (props) => {
</StyledMobileDetails>
);
const displayUI = isAudio || panelVisible;
const displayUI = (isMobileOnly && isAudio) || panelVisible;
const viewerPortal = ReactDOM.createPortal(
<StyledViewer
{...props}
displayUI={displayUI}
mobileDetails={mobileDetails}
setIsOpenContextMenu={setIsOpenContextMenu}
container={container}
onMaskClick={onMaskClick}
setPanelVisible={setPanelVisible}

View File

@ -295,7 +295,19 @@ function MobileViewer({
lastTapTimeRef.current = time;
setTimeoutIDTapRef.current = setTimeout(() => {
// onTap
setPanelVisible((visible) => !visible);
setPanelVisible((visible) => {
let display = visible;
const displayVisible =
JSON.parse(localStorage.getItem("displayVisible")) || null;
if (displayVisible !== null) {
display = !displayVisible;
}
localStorage.setItem("displayVisible", display);
return !visible;
});
}, 300);
}
},

View File

@ -609,6 +609,7 @@ const ViewerBase = (props) => {
activeImg = getActiveImage();
}
const displayVisible = JSON.parse(localStorage.getItem("displayVisible"));
return (
<div
className={className}
@ -625,14 +626,14 @@ const ViewerBase = (props) => {
}}
ref={viewerCore}
>
{isMobileOnly && props.displayUI && mobileDetails}
{isMobileOnly && displayVisible !== "true" && mobileDetails}
<div
className={`${prefixCls}-mask`}
style={{
zIndex: zIndex,
backgroundColor: `${
isMobileOnly
? props.displayUI
? !displayVisible
? "rgba(55,55,55,0.6)"
: "#000"
: "rgba(55,55,55,0.6)"
@ -697,6 +698,7 @@ const ViewerBase = (props) => {
percent={state.percent}
attribute={attribute}
isPreviewFile={isPreviewFile}
setIsOpenContextMenu={props.setIsOpenContextMenu}
zoomable={zoomable}
rotatable={rotatable}
onPercentClick={onPercentClick}

View File

@ -382,6 +382,7 @@ export default function ViewerPlayer(props) {
speedToastVisible: false,
isControlTouch: false,
isFirstTap: false,
isSecondTap: false,
};
function reducer(state, action) {
switch (action.type) {
@ -454,6 +455,7 @@ export default function ViewerPlayer(props) {
createAction(ACTION_TYPES.update, {
isFirstTap: false,
isPlaying: false,
isSecondTap: true,
})
);
}
@ -461,6 +463,7 @@ export default function ViewerPlayer(props) {
dispatch(
createAction(ACTION_TYPES.update, {
isFirstTap: true,
isSecondTap: false,
})
);
},
@ -491,7 +494,14 @@ export default function ViewerPlayer(props) {
const togglePlay = (e) => {
e.stopPropagation();
if (e.target === videoRef.current && isMobileOnly) return;
if ((e.target === videoRef.current && isMobileOnly) || state.isSecondTap) {
return dispatch(
createAction(ACTION_TYPES.update, {
isSecondTap: false,
})
);
}
props.setIsPlay(!state.isPlaying);
dispatch(
createAction(ACTION_TYPES.update, {
@ -806,6 +816,8 @@ export default function ViewerPlayer(props) {
setPanelVisible(true);
localStorage.setItem("displayVisible", false);
const [width, height, left, top] = getVideoPosition(video);
dispatch(
createAction(ACTION_TYPES.update, {
@ -826,6 +838,7 @@ export default function ViewerPlayer(props) {
speedToastVisible: false,
isControlTouch: false,
isFirstTap: false,
isSecondTap: false,
})
);
}

View File

@ -23,6 +23,13 @@ const ToolbarItem = styled.li`
-webkit-user-select: none;
}
.zoomOut,
.zoomIn,
.rotateLeft,
.rotateRight {
margin-top: 3px;
}
svg {
width: 16px;
height: 16px;
@ -121,7 +128,10 @@ export default function ViewerToolbar(props) {
noHover={config.noHover}
key={config.key}
className={`${props.prefixCls}-btn`}
onClick={() => props.setIsOpen((open) => !open)}
onClick={() => {
props.setIsOpenContextMenu((open) => !open);
props.setIsOpen((open) => !open);
}}
data-key={config.key}
>
<div className="context" style={{ height: "16px" }}>

View File

@ -435,6 +435,13 @@ public class FileSecurity : IFileSecurity
break;
case FolderType.VirtualRooms:
defaultRecords = null;
if (entry is not Folder<T> || entry is Folder<T> folder && folder.FolderType != FolderType.VirtualRooms)
{
break;
}
defaultRecords = new[]
{
new FileShareRecord
@ -443,7 +450,7 @@ public class FileSecurity : IFileSecurity
EntryId = entry.Id,
EntryType = entry.FileEntryType,
Share = FileShare.Read,
Subject = Constants.GroupAdmin.ID,
Subject = Constants.GroupEveryone.ID,
TenantId = _tenantManager.GetCurrentTenant().Id,
Owner = entry.RootCreateBy
}
@ -612,7 +619,7 @@ public class FileSecurity : IFileSecurity
var isRoom = folder != null && DocSpaceHelper.IsRoom(folder.FolderType);
if ((action == FilesSecurityActions.ReadHistory ||
action == FilesSecurityActions.EditHistory) &&
action == FilesSecurityActions.EditHistory) &&
e.ProviderEntry)
{
return false;
@ -755,8 +762,8 @@ public class FileSecurity : IFileSecurity
return false;
}
if ((action == FilesSecurityActions.Delete ||
action == FilesSecurityActions.Move) &&
if ((action == FilesSecurityActions.Delete ||
action == FilesSecurityActions.Move) &&
!isRoom)
{
return false;

View File

@ -277,6 +277,16 @@ public class FileMarker
var virtualRoomsFolderId = await _globalFolder.GetFolderVirtualRoomsAsync(_daoFactory);
userIDs.ForEach(userID => RemoveFromCahce(virtualRoomsFolderId, userID));
var room = parentFolders.Where(f => DocSpaceHelper.IsRoom(f.FolderType)).FirstOrDefault();
if (room.CreateBy != obj.CurrentAccountId)
{
var roomOwnerEntries = parentFolders.Cast<FileEntry>().Concat(new[] { obj.FileEntry }).ToList();
userEntriesData.Add(room.CreateBy, roomOwnerEntries);
RemoveFromCahce(virtualRoomsFolderId, room.CreateBy);
}
if (obj.FileEntry.ProviderEntry)
{
var virtualRoomsFolder = await _daoFactory.GetFolderDao<int>().GetFolderAsync(virtualRoomsFolderId);

View File

@ -7,7 +7,6 @@
"AddButton": "Əlavə edin",
"AddFilter": "Filtr əlavə edin",
"AddUsers": "İstifadəçilərin əlavə edin",
"AdminSettings": "Admin ayarları",
"Alert": "Xəbərdarlıq",
"Archive": "Arxiv",
"Attention": "Diqqət",

View File

@ -7,7 +7,6 @@
"AddButton": "Добави",
"AddFilter": "Добави филтър",
"AddUsers": "Добави потребители",
"AdminSettings": "Административни настройки",
"Alert": "Тревога",
"Archive": "Архивирай",
"Attention": "Внимание",

View File

@ -7,7 +7,6 @@
"AddButton": "Přidat",
"AddFilter": "Přidat filtr",
"AddUsers": "Přidat uživatele",
"AdminSettings": "Nastavení správce",
"Alert": "Pozor",
"Archive": "Archiv",
"Attention": "Pozornost",

View File

@ -7,7 +7,6 @@
"AddButton": "Hinzufügen",
"AddFilter": "Filter hinzufügen",
"AddUsers": "Benutzer hinzufügen",
"AdminSettings": "Administrative Einstellungen",
"Alert": "Warnung",
"Archive": "Archiv",
"Attention": "Achtung",

View File

@ -7,7 +7,6 @@
"AddButton": "Προσθήκη",
"AddFilter": "Προσθήκη φίλτρου",
"AddUsers": "Προσθήκη χρηστών",
"AdminSettings": "Ρυθμίσεις διαχειριστή",
"Alert": "Ειδοποίηση",
"Archive": "Αρχείο",
"Attention": "Προσοχή",

View File

@ -11,7 +11,6 @@
"AddFilter": "Add filter",
"AddUsers": "Add users",
"Address": "Address",
"AdminSettings": "Admin",
"AdvancedFilter": "Search options",
"Alert": "Alert",
"ApplyButton": "Apply",
@ -77,8 +76,8 @@
"DocSpaceAdmin": "DocSpace admin",
"Documents": "Documents",
"DomainIpAddress": "Domains as IP addresses are not supported",
"DontAskAgain": "Don't ask file name again on creation",
"Done": "Done",
"DontAskAgain": "Don't ask file name again on creation",
"Download": "Download",
"Duplicate": "Create copy",
"EditButton": "Edit",
@ -194,6 +193,9 @@
"Sending": "Sending...",
"Sessions": "Sessions",
"Settings": "Settings",
"SettingsDocSpace": "DocSpace Settings",
"SettingsGenaral": "Genaral",
"SettingsPersonal": "Personal",
"ShowMore": "Show more",
"SignInWithFacebook": "Sign in with Facebook",
"SignInWithGoogle": "Sign in with Google",

View File

@ -7,7 +7,6 @@
"AddButton": "Añadir",
"AddFilter": "Añadir filtro",
"AddUsers": "Añadir usuarios",
"AdminSettings": "Configuración de administración",
"Alert": "Alerta",
"Archive": "Archivo",
"Attention": "¡Atención",

View File

@ -7,7 +7,6 @@
"AddButton": "Lisää",
"AddFilter": "Lisää suodatin",
"AddUsers": "Lisää käyttäjiä",
"AdminSettings": "Järjestelmänvalvojan asetukset",
"Alert": "Varoitus",
"Archive": "Arkisto",
"Attention": "Huomio",

View File

@ -7,7 +7,6 @@
"AddButton": "Ajouter",
"AddFilter": "Ajouter un filtre",
"AddUsers": "Ajouter des utilisateurs",
"AdminSettings": "Administrateurs",
"Alert": "Alerte",
"Archive": "Archive",
"Attention": "Attention",

View File

@ -6,7 +6,6 @@
"AddButton": "Հավելել",
"AddFilter": "Ավելացնել զտիչ",
"AddUsers": "Ավելացնել օգտվողներ",
"AdminSettings": "Ադմինիստրատորի կարգավորումներ",
"Alert": "Զգուշացում",
"Archive": "Արխիվ",
"Attention": "Ուշադրություն",

View File

@ -7,7 +7,6 @@
"AddButton": "Salva",
"AddFilter": "Aggiungi filtro",
"AddUsers": "Aggiungi utenti",
"AdminSettings": "Amministratori",
"Alert": "Allerta",
"Archive": "Archivio",
"Attention": "Attenzione",

View File

@ -7,7 +7,6 @@
"AddButton": "追加",
"AddFilter": "フィルターの追加",
"AddUsers": "ユーザーの追加",
"AdminSettings": "管理者設定",
"Alert": "アラート",
"Archive": "アーカイブ",
"Attention": "注意",

View File

@ -7,7 +7,6 @@
"AddButton": "추가",
"AddFilter": "필터 추가",
"AddUsers": "사용자 추가",
"AdminSettings": "관리자 설정",
"Alert": "알림",
"Archive": "아카이브",
"Attention": "주의",

View File

@ -7,7 +7,6 @@
"AddButton": "ບັນທຶກ",
"AddFilter": "ເພີ່ມການຄົ້ນຫາ",
"AddUsers": "ເພີ່ມຜູ້ໃຊ້",
"AdminSettings": "ຜູ້ດູແລລະບົບ",
"Alert": "ແຈ້ງເຕືອນ",
"Archive": "ເກັບກຳ",
"Attention": "ຮຽນທ່ານ",

View File

@ -7,7 +7,6 @@
"AddButton": "Pievienot",
"AddFilter": "Pievienot filtru",
"AddUsers": "Pievienot lietotājus",
"AdminSettings": "Administratora iestatījumi",
"Alert": "Brīdinājums",
"Archive": "Arhīvs",
"Attention": "Uzmanību",

View File

@ -7,7 +7,6 @@
"AddButton": "Toevoegen",
"AddFilter": "Filter toevoegen",
"AddUsers": "Gebruikers toevoegen",
"AdminSettings": "Beheerdersinstellingen",
"Alert": "Waarschuwing",
"Archive": "Archief",
"Attention": "Attentie",

View File

@ -7,7 +7,6 @@
"AddButton": "Dodaj",
"AddFilter": "Dodaj filtr",
"AddUsers": "Dodaj użytkowników",
"AdminSettings": "Ustawienia administratora",
"Alert": "Alert",
"Archive": "Archiwum",
"Attention": "Uwaga",

View File

@ -7,7 +7,6 @@
"AddButton": "Salvar",
"AddFilter": "Adicionar filtro",
"AddUsers": "Adicionar Usuários",
"AdminSettings": "Administradores",
"Alert": "Alerta",
"Archive": "Arquivo-morto",
"Attention": "Atenção",

View File

@ -7,7 +7,6 @@
"AddButton": "Adicionar",
"AddFilter": "Adicionar filtro",
"AddUsers": "Adicionar utilizadores",
"AdminSettings": "Definições de Administrador",
"Alert": "Alerta",
"Archive": "Arquivo",
"Attention": "Atenção",

View File

@ -7,7 +7,6 @@
"AddButton": "Salvează",
"AddFilter": "Adăugați filtru",
"AddUsers": "Adăugați utilizatorii",
"AdminSettings": "Administratori",
"Alert": "Alertă",
"Archive": "Arhivă",
"Attention": "Atenție",

View File

@ -11,7 +11,6 @@
"AddFilter": "Добавить фильтр",
"AddUsers": "Добавить пользователей",
"Address": "Адрес",
"AdminSettings": "Администратора",
"AdvancedFilter": "Параметры поиска",
"Alert": "Предупреждение",
"ApplyButton": "Применять",
@ -77,8 +76,8 @@
"DocSpaceAdmin": "DocSpace администратор",
"Documents": "Документы",
"DomainIpAddress": "IP адрес в качестве домена не поддерживается",
"DontAskAgain": "Больше не запрашивать имя файла при создани",
"Done": "Успешно",
"DontAskAgain": "Больше не запрашивать имя файла при создании",
"Download": "Скачать",
"Duplicate": "Создать копию",
"EditButton": "Редактировать",
@ -194,6 +193,9 @@
"Sending": "Отправка...",
"Sessions": "Сессии",
"Settings": "Настройки",
"SettingsDocSpace": "Настройки DocSpace",
"SettingsGenaral": "Общие",
"SettingsPersonal": "Персональные",
"ShowMore": "Показать еще",
"SignInWithFacebook": "Вход через Facebook",
"SignInWithGoogle": "Вход через Google",

View File

@ -7,7 +7,6 @@
"AddButton": "Pridať",
"AddFilter": "Pridať filter",
"AddUsers": "Pridať používateľov",
"AdminSettings": "Správcovia",
"Alert": "Upozornenie",
"Archive": "Archív",
"Attention": "Pozor",

View File

@ -7,7 +7,6 @@
"AddButton": "Dodaj",
"AddFilter": "Dodaj filter",
"AddUsers": "Dodaj uporabnike",
"AdminSettings": "Administratorske nastavitve",
"Alert": "Pozor",
"Archive": "Arhiv",
"Attention": "Pozornost",

View File

@ -7,7 +7,6 @@
"AddButton": "Ekle",
"AddFilter": "Filtre Ekle",
"AddUsers": "Kullanıcılar ekle",
"AdminSettings": "Yönetici ayarları",
"Alert": "Alarm",
"Archive": "Arşiv",
"Attention": "Dikkat",

View File

@ -7,7 +7,6 @@
"AddButton": "Активний",
"AddFilter": "Додати фільтр",
"AddUsers": "Додати користувачів",
"AdminSettings": "Параметри адміністратора",
"Alert": "Оповіщення",
"Archive": "Архів",
"Attention": "Увагу",

View File

@ -7,7 +7,6 @@
"AddButton": "Thêm vào",
"AddFilter": "Thêm bộ lọc",
"AddUsers": "Thêm người dùng",
"AdminSettings": "Thiết lập quản trị",
"Alert": "Cảnh báo",
"Archive": "Lưu trữ",
"Attention": "Chú ý",

View File

@ -7,7 +7,6 @@
"AddButton": "添加",
"AddFilter": "添加过滤条件",
"AddUsers": "添加用户",
"AdminSettings": "管理员设置",
"Alert": "警报",
"Archive": "归档",
"Attention": "注意",