Merge branch 'feature/admin-messages' of github.com:ONLYOFFICE/AppServer into feature/session-lifetime

This commit is contained in:
Viktor Fomin 2022-05-03 21:22:47 +03:00
commit fe4b4d3c2d
467 changed files with 11890 additions and 6413 deletions

View File

@ -6,6 +6,7 @@
"common\\ASC.Common\\ASC.Common.csproj",
"common\\ASC.Core.Common\\ASC.Core.Common.csproj",
"common\\ASC.Data.Backup.Core\\ASC.Data.Backup.Core.csproj",
"common\\ASC.Data.Encryption\\ASC.Data.Encryption.csproj",
"common\\ASC.Data.Reassigns\\ASC.Data.Reassigns.csproj",
"common\\ASC.Data.Storage\\ASC.Data.Storage.csproj",
"common\\ASC.FederatedLogin\\ASC.FederatedLogin.csproj",

View File

@ -1,3 +1,5 @@
@echo off
echo "##########################################################"
echo "######### Start build and deploy Personal ##############"
echo "##########################################################"
@ -6,6 +8,7 @@ echo.
PUSHD %~dp0
call runasadmin.bat "%~dpnx0"
if %errorlevel% == 0 (
call start\stop.bat nopause
@ -18,7 +21,11 @@ call build\build.static.bat nopause personal
echo "BACK-END"
call build\build.backend.bat nopause
start /b call build\start\start.bat nopause
PUSHD %~dp0
call start\start.bat nopause
echo.
pause
)

View File

@ -74,4 +74,6 @@ sed -i "s!\"Threads\".*!\"Threads\": \"${ELK_THREADS}\"!g" ${PATH_TO_CONF}/elast
sed -i "s!\"subfolder\".*!\"subfolder\": \"server\",!g" ${PATH_TO_CONF}/appsettings.services.json
sed -i "s!\"BootstrapServers\".*!\"BootstrapServers\": \"${KAFKA_HOST}\"!g" ${PATH_TO_CONF}/kafka.${APP_DOTNET_ENV}.json
sed -i "s!\"path\".*!\"path\": \"../../ASC.Socket.IO\"!g" ${PATH_TO_CONF}/socket.${APP_DOTNET_ENV}.json
dotnet ${DOTNET_RUN} --urls=${URLS} --ENVIRONMENT=${APP_DOTNET_ENV} --'$STORAGE_ROOT'=${APP_STORAGE_ROOT} --pathToConf=${PATH_TO_CONF} --log:dir=${LOG_DIR} --log:name=${DOTNET_LOG_NAME} ${PARAMETERS}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOCUMENT Type="Advanced Installer" CreateVersion="18.6" version="18.7" Modules="enterprise" RootPath="." Language="ru" Id="{2D2C7C0E-FA78-4158-A222-DA463E882AAC}">
<DOCUMENT Type="Advanced Installer" CreateVersion="18.6" version="18.7" Modules="enterprise" RootPath="." Language="en" Id="{2D2C7C0E-FA78-4158-A222-DA463E882AAC}">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_BITMAP_DISPLAY_MODE" Value="0"/>
<ROW Property="AI_CURRENT_YEAR" Value="2022" ValueLocId="-"/>
@ -11,7 +11,7 @@
<ROW Property="ARPNOREPAIR" Value="1"/>
<ROW Property="ARPSYSTEMCOMPONENT" Value="1"/>
<ROW Property="Manufacturer" Value="Apache"/>
<ROW Property="ProductCode" Value="1049:{79F55836-69B9-46CE-B03D-7679D38C0AD4} " Type="16"/>
<ROW Property="ProductCode" Value="1033:{79F55836-69B9-46CE-B03D-7679D38C0AD4} " Type="16"/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="Apache Kafka"/>
<ROW Property="ProductVersion" Value="1.0.0"/>
@ -63,11 +63,11 @@
<ROW Action="AI_DetectSoftware" Sequence="151"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="publish" PackageFileName="[|ProductName]" Languages="ru" InstallationType="4" UseLargeSchema="true" Unicode="true" UACExecutionLevel="2"/>
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="publish" PackageFileName="[|ProductName]" Languages="en" InstallationType="4" UseLargeSchema="true" Unicode="true" UACExecutionLevel="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;AI_DICTS&gt;ui.ail"/>
<ROW Path="&lt;AI_DICTS&gt;ui_ru.ail"/>
<ROW Path="&lt;AI_DICTS&gt;ui_en.ail"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="CommonUI.aip" Path="&lt;AI_FRAGS&gt;CommonUI.aip"/>
@ -84,12 +84,12 @@
<ROW Fragment="WelcomeDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\WelcomeDlg.aip"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiActionTextComponent">
<ROW Action="AI_ConfigFailActions" Description="Настройка действия при сбое службы" DescriptionLocId="ActionText.Description.AI_ConfigFailActions" Template="Служба: [1]" TemplateLocId="ActionText.Template.AI_ConfigFailActions"/>
<ROW Action="AI_ProcessFailActions" Description="Создание действия для настройки действия при сбое службы" DescriptionLocId="ActionText.Description.AI_ProcessFailActions" Template="Служба: [1]" TemplateLocId="ActionText.Template.AI_ProcessFailActions"/>
<ROW Action="AI_TxtUpdaterCommit" Description="Фиксация изменений текстового файла." DescriptionLocId="ActionText.Description.AI_TxtUpdaterCommit" Template="Фиксация изменений текстового файла." TemplateLocId="ActionText.Template.AI_TxtUpdaterCommit"/>
<ROW Action="AI_TxtUpdaterConfig" Description="Выполнение обновления текстового файла" DescriptionLocId="ActionText.Description.AI_TxtUpdaterConfig" Template="Обновление текстового файла: &quot;[1]&quot;" TemplateLocId="ActionText.Template.AI_TxtUpdaterConfig"/>
<ROW Action="AI_TxtUpdaterInstall" Description="Создание действий для настройки текстовых файлов обновлений" DescriptionLocId="ActionText.Description.AI_TxtUpdaterInstall"/>
<ROW Action="AI_TxtUpdaterRollback" Description="Откат изменений текстового файла." DescriptionLocId="ActionText.Description.AI_TxtUpdaterRollback" Template="Откат изменений текстового файла." TemplateLocId="ActionText.Template.AI_TxtUpdaterRollback"/>
<ROW Action="AI_ConfigFailActions" Description="Configure service failure actions" DescriptionLocId="ActionText.Description.AI_ConfigFailActions" Template="Service: [1]" TemplateLocId="ActionText.Template.AI_ConfigFailActions"/>
<ROW Action="AI_ProcessFailActions" Description="Generating actions to configure service failure actions" DescriptionLocId="ActionText.Description.AI_ProcessFailActions" Template="Service: [1]" TemplateLocId="ActionText.Template.AI_ProcessFailActions"/>
<ROW Action="AI_TxtUpdaterCommit" Description="Commit text file changes." DescriptionLocId="ActionText.Description.AI_TxtUpdaterCommit" Template="Commit text file changes." TemplateLocId="ActionText.Template.AI_TxtUpdaterCommit"/>
<ROW Action="AI_TxtUpdaterConfig" Description="Executing text file updates" DescriptionLocId="ActionText.Description.AI_TxtUpdaterConfig" Template="Updating text file: &quot;[1]&quot;" TemplateLocId="ActionText.Template.AI_TxtUpdaterConfig"/>
<ROW Action="AI_TxtUpdaterInstall" Description="Generating actions to configure text files updates" DescriptionLocId="ActionText.Description.AI_TxtUpdaterInstall"/>
<ROW Action="AI_TxtUpdaterRollback" Description="Rolling back text file changes." DescriptionLocId="ActionText.Description.AI_TxtUpdaterRollback" Template="Rolling back text file changes." TemplateLocId="ActionText.Template.AI_TxtUpdaterRollback"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="PowerShellScriptLauncher.dll" SourcePath="&lt;AI_CUSTACTS&gt;PowerShellScriptLauncher.dll"/>
@ -193,11 +193,11 @@
<ROW Action="AI_RestartElevated" Sequence="51" Builds="DefaultBuild"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
<ROW Condition="((VersionNT &lt;&gt; 501) AND (VersionNT &lt;&gt; 502))" Description="Нельзя установить [ProductName] на [WindowsTypeNT5XDisplay]." DescriptionLocId="AI.LaunchCondition.NoNT5X" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 400)" Description="Нельзя установить [ProductName] на [WindowsTypeNT40Display]." DescriptionLocId="AI.LaunchCondition.NoNT40" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 500)" Description="Нельзя установить [ProductName] на [WindowsTypeNT50Display]." DescriptionLocId="AI.LaunchCondition.NoNT50" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="AI_DETECTED_INTERNET_CONNECTION" Description="Для установки [ProductName] требуется активное подключение к Интернету. Пожалуйста, проверьте настройки прокси-сервера и конфигурации сети." DescriptionLocId="AI.LaunchCondition.Internet" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="VersionNT" Description="[ProductName] не может быть установлен на [WindowsType9XDisplay]." DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="((VersionNT &lt;&gt; 501) AND (VersionNT &lt;&gt; 502))" Description="[ProductName] cannot be installed on [WindowsTypeNT5XDisplay]." DescriptionLocId="AI.LaunchCondition.NoNT5X" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 400)" Description="[ProductName] cannot be installed on [WindowsTypeNT40Display]." DescriptionLocId="AI.LaunchCondition.NoNT40" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 500)" Description="[ProductName] cannot be installed on [WindowsTypeNT50Display]." DescriptionLocId="AI.LaunchCondition.NoNT50" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="AI_DETECTED_INTERNET_CONNECTION" Description="[ProductName] requires an active Internet connection for installation. Please check your network configuration and proxy settings." DescriptionLocId="AI.LaunchCondition.Internet" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsType9XDisplay]." DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="Comments" Root="-1" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName] [ProductVersion]" Name="Comments" Value="[ARPCOMMENTS]" Component_="AI_CustomARPName"/>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOCUMENT Type="Advanced Installer" CreateVersion="18.6.1" version="18.7" Modules="enterprise" RootPath="." Language="ru" Id="{93F1E21E-F074-444A-9756-659088E0DA02}">
<DOCUMENT Type="Advanced Installer" CreateVersion="18.6.1" version="18.7" Modules="enterprise" RootPath="." Language="en" Id="{93F1E21E-F074-444A-9756-659088E0DA02}">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_BITMAP_DISPLAY_MODE" Value="0"/>
<ROW Property="AI_CURRENT_YEAR" Value="2022" ValueLocId="-"/>
@ -10,7 +10,7 @@
<ROW Property="ARPNOREPAIR" Value="1"/>
<ROW Property="ARPSYSTEMCOMPONENT" Value="1"/>
<ROW Property="Manufacturer" Value="Apache"/>
<ROW Property="ProductCode" Value="1049:{DA471CF9-78DC-4D13-AB18-87B4604538FA} " Type="16"/>
<ROW Property="ProductCode" Value="1033:{DA471CF9-78DC-4D13-AB18-87B4604538FA} " Type="16"/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="Apache ZooKeeper"/>
<ROW Property="ProductVersion" Value="1.0.0" Type="32"/>
@ -47,11 +47,11 @@
<ROW BootstrOptKey="GlobalOptions" DownloadFolder="[AppDataFolder][|Manufacturer]\[|ProductName]\prerequisites" Options="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="publish" PackageFileName="[|ProductName]" Languages="ru" InstallationType="4" UseLargeSchema="true" Unicode="true" UACExecutionLevel="2"/>
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="publish" PackageFileName="[|ProductName]" Languages="en" InstallationType="4" UseLargeSchema="true" Unicode="true" UACExecutionLevel="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;AI_DICTS&gt;ui.ail"/>
<ROW Path="&lt;AI_DICTS&gt;ui_ru.ail"/>
<ROW Path="&lt;AI_DICTS&gt;ui_en.ail"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="CommonUI.aip" Path="&lt;AI_FRAGS&gt;CommonUI.aip"/>
@ -147,10 +147,10 @@
<ROW Action="AI_RestartElevated" Sequence="51" Builds="DefaultBuild"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
<ROW Condition="((VersionNT &lt;&gt; 501) AND (VersionNT &lt;&gt; 502))" Description="Нельзя установить [ProductName] на [WindowsTypeNT5XDisplay]." DescriptionLocId="AI.LaunchCondition.NoNT5X" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 400)" Description="Нельзя установить [ProductName] на [WindowsTypeNT40Display]." DescriptionLocId="AI.LaunchCondition.NoNT40" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 500)" Description="Нельзя установить [ProductName] на [WindowsTypeNT50Display]." DescriptionLocId="AI.LaunchCondition.NoNT50" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="VersionNT" Description="[ProductName] не может быть установлен на [WindowsType9XDisplay]." DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="((VersionNT &lt;&gt; 501) AND (VersionNT &lt;&gt; 502))" Description="[ProductName] cannot be installed on [WindowsTypeNT5XDisplay]." DescriptionLocId="AI.LaunchCondition.NoNT5X" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 400)" Description="[ProductName] cannot be installed on [WindowsTypeNT40Display]." DescriptionLocId="AI.LaunchCondition.NoNT40" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 500)" Description="[ProductName] cannot be installed on [WindowsTypeNT50Display]." DescriptionLocId="AI.LaunchCondition.NoNT50" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsType9XDisplay]." DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="Comments" Root="-1" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName] [ProductVersion]" Name="Comments" Value="[ARPCOMMENTS]" Component_="AI_CustomARPName"/>

View File

@ -1,5 +1,5 @@
REM echo ######## Set variables ########
set "msbuild4="C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe""
set "publisher="Ascensio System SIA""
REM echo ######## Extracting and preparing files to build ########
%sevenzip% x build\install\win\nginx-1.21.1.zip -o"build\install\win\Files" -y
@ -29,7 +29,7 @@ del /f /q "build\install\win\kafka-zookeeper\zookeeper\conf\zoo_sample.cfg"
rmdir build\install\win\publish /s /q
REM echo ######## Build Utils ########
%msbuild4% build\install\win\CustomActions\C#\Utils\Utils.csproj
%msbuild% build\install\win\CustomActions\C#\Utils\Utils.csproj
copy build\install\win\CustomActions\C#\Utils\bin\Debug\Utils.CA.dll build\install\win\Utils.CA.dll /y
rmdir build\install\win\CustomActions\C#\Utils\bin /s /q
rmdir build\install\win\CustomActions\C#\Utils\obj /s /q
@ -80,7 +80,7 @@ copy "build\install\win\publish\Apache ZooKeeper.msi" "build\install\win\Apache
copy "build\install\win\publish\Apache Kafka.msi" "build\install\win\Apache Kafka.msi" /y
REM echo ######## Build MySQL Server Installer ########
iscc "build\install\win\MySQL Server Installer Runner.iss"
iscc /Qp /S"byparam="signtool" sign /a /n "%publisher%" /t http://timestamp.digicert.com $f" "build\install\win\MySQL Server Installer Runner.iss"
REM echo ######## Build AppServer package ########
%AdvancedInstaller% /edit build\install\win\AppServer.aip /SetVersion %BUILD_VERSION%.%BUILD_NUMBER%

View File

@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.Elasticsearch" Version="6.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Elasticsearch" Version="5.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Kafka" Version="6.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.MySql" Version="6.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="6.0.1" />

View File

@ -50,24 +50,24 @@ namespace ASC.Api.Core.Core
name: "kafka",
tags: new string[] { "kafka" });
}
var elasticSettings = configuration.GetSection("elastic");
if (elasticSettings != null && elasticSettings.GetChildren().Any())
{
var host = elasticSettings.GetSection("Host").Value ?? "localhost";
var scheme = elasticSettings.GetSection("Scheme").Value ?? "http";
var port = elasticSettings.GetSection("Port").Value ?? "9200";
var elasticSearchUri = $"{scheme}://{host}:{port}";
if (Uri.IsWellFormedUriString(elasticSearchUri, UriKind.Absolute))
{
hcBuilder.AddElasticsearch(elasticSearchUri,
name: "elasticsearch",
tags: new string[] { "elasticsearch" });
}
}
var elasticSettings = configuration.GetSection("elastic");
if (elasticSettings != null && elasticSettings.GetChildren().Any())
{
var host = elasticSettings.GetSection("Host").Value ?? "localhost";
var scheme = elasticSettings.GetSection("Scheme").Value ?? "http";
var port = elasticSettings.GetSection("Port").Value ?? "9200";
var elasticSearchUri = $"{scheme}://{host}:{port}";
if (Uri.IsWellFormedUriString(elasticSearchUri, UriKind.Absolute))
{
hcBuilder.AddElasticsearch(elasticSearchUri,
name: "elasticsearch",
tags: new string[] { "elasticsearch" });
}
}
return services;

View File

@ -98,7 +98,9 @@ namespace ASC.Web.Api.Models
public bool IsSSO { get; set; }
public new static EmployeeWraperFull GetSample()
public DarkThemeSettingsEnum? Theme { get; set; }
public static new EmployeeWraperFull GetSample()
{
return new EmployeeWraperFull
{

View File

@ -175,7 +175,7 @@ namespace ASC.Core
public UserInfo GetUserBySid(string sid)
{
return GetUsersInternal()
.FirstOrDefault(u => u.Sid != null && string.Equals(u.Sid , sid, StringComparison.CurrentCultureIgnoreCase)) ?? Constants.LostUser;
.FirstOrDefault(u => u.Sid != null && string.Equals(u.Sid, sid, StringComparison.CurrentCultureIgnoreCase)) ?? Constants.LostUser;
}
public UserInfo GetSsoUserByNameId(string nameId)
@ -518,6 +518,11 @@ namespace ASC.Core
{
var group = UserService.GetGroup(Tenant.TenantId, groupID);
if (group == null)
{
group = ToGroup(Constants.BuildinGroups.FirstOrDefault(r => r.ID == groupID));
}
return new GroupInfo
{
ID = group.Id,

View File

@ -321,11 +321,11 @@ namespace ASC.Core.Notify.Signalr
}
}
public void StopEdit<T>(T fileId, string room, string data)
public void StopEdit<T>(T fileId, string room)
{
try
{
MakeRequest("stop-edit", new { room, fileId, data });
MakeRequest("stop-edit", new { room, fileId });
}
catch (Exception error)
{

View File

@ -275,9 +275,14 @@ namespace ASC.Security.Cryptography
case ConfirmType.PhoneAuth:
case ConfirmType.TfaActivation:
case ConfirmType.TfaAuth:
case ConfirmType.Auth:
checkKeyResult = Provider.ValidateEmailKey(email + type, key, Provider.ValidAuthKeyInterval);
break;
case ConfirmType.PortalContinue:
checkKeyResult = Provider.ValidateEmailKey(email + type, key);
break;
default:
checkKeyResult = Provider.ValidateEmailKey(email + type, key, Provider.ValidEmailKeyInterval);
break;

View File

@ -0,0 +1,61 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System;
using System.Text.Json.Serialization;
using ASC.Core.Common.Settings;
namespace ASC.Web.Core.Users;
[Serializable]
public class DarkThemeSettings : ISettings
{
[JsonIgnore]
public Guid ID
{
get { return new Guid("{38362061-066D-4C57-A23E-8953CF34EFC3}"); }
}
public DarkThemeSettingsEnum Theme { get; set; }
public ISettings GetDefault(IServiceProvider serviceProvider)
{
return new DarkThemeSettings
{
Theme = DarkThemeSettingsEnum.Base,
};
}
}
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum DarkThemeSettingsEnum
{
Base,
Dark,
System
}

View File

@ -43,7 +43,7 @@ namespace ASC.IPSecurity
public IPRestrictionsServiceCache(ICacheNotify<IPRestrictionItem> notify, ICache cache)
{
Cache = cache;
notify.Subscribe((r) => Cache.Remove(GetCacheKey(r.TenantId)), CacheNotifyAction.Any);
notify.Subscribe((r) => Cache.Remove(GetCacheKey(r.TenantId)), CacheNotifyAction.InsertOrUpdate);
Notify = notify;
}

View File

@ -101,9 +101,9 @@
filesIO.to(room).emit("s:start-edit-file", fileId);
}
function stopEdit({ fileId, room, data } = {}) {
function stopEdit({ fileId, room } = {}) {
logger.info(`stop edit file ${fileId} in room ${room}`);
filesIO.to(room).emit("s:stop-edit-file", fileId, data);
filesIO.to(room).emit("s:stop-edit-file", fileId);
}
function modifyFolder(room, cmd, id, type, data) {

View File

@ -554,7 +554,8 @@ namespace ASC.ElasticSearch
while (!string.IsNullOrEmpty(name = TryGetName(expression, out var member)))
{
sourceExprText = "." + name + sourceExprText;
sourceExprText = "." + name + sourceExprText;
expression = member.Expression;
}
if (isList)

View File

@ -53,18 +53,18 @@
},
"files": {
"thirdparty": {
"enable": [ "box", "dropboxv2", "docusign", "google", "onedrive", "sharepoint", "nextcloud", "owncloud", "webdav", "kdrive", "yandex" ]
"enable": [ "box", "dropboxv2", "docusign", "google", "onedrive", "sharepoint", "nextcloud", "owncloud", "webdav", "kdrive", "yandex" ]
},
"docservice": {
"coauthor-docs": [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".docxf", ".oform", ".txt" ],
"commented-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ],
"convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf" ],
"edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ],
"encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx", ".oform" ],
"formfilling-docs": [ ".oform" ],
"customfilter-docs": [ ".xlsx" ],
"reviewed-docs": [ ".docx", ".docxf" ],
"viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ],
"coauthor-docs": [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".docxf", ".oform", ".txt" ],
"commented-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ],
"convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf" ],
"edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ],
"encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx", ".oform" ],
"formfilling-docs": [ ".oform" ],
"customfilter-docs": [ ".xlsx" ],
"reviewed-docs": [ ".docx", ".docxf" ],
"viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ],
"secret": {
"value": "",
"header": ""
@ -77,15 +77,20 @@
},
"ffmpeg": {
"value": "",
"exts": [ "avi", "mpeg", "mpg", "wmv" ]
"exts": [ "avi", "mpeg", "mpg", "wmv" ]
},
"uploader": {
"chunk-size": 10485760,
"url": "products/files/"
},
"viewed-images": [ ".bmp", ".gif", ".jpeg", ".jpg", ".png", ".ico", ".tif", ".tiff", ".webp" ],
"viewed-media": [ ".aac", ".flac", ".m4a", ".mp3", ".oga", ".ogg", ".wav", ".f4v", ".m4v", ".mov", ".mp4", ".ogv", ".webm" ],
"index": [ ".pptx", ".xlsx", ".docx" ]
"viewed-images": [ ".bmp", ".gif", ".jpeg", ".jpg", ".png", ".ico", ".tif", ".tiff", ".webp" ],
"viewed-media": [ ".aac", ".flac", ".m4a", ".mp3", ".oga", ".ogg", ".wav", ".f4v", ".m4v", ".mov", ".mp4", ".ogv", ".webm" ],
"index": [ ".pptx", ".xlsx", ".docx" ],
"oform": {
"url": "https://oforms.onlyoffice.com/data/reqdata.json",
"period": 60,
"ext": ".oform"
}
},
"web": {
"api": "api/2.0",

View File

@ -1,5 +1,4 @@
{
"kafka": {
"BootstrapServers": "localhost:9092"
}
"kafka": {
}
}

View File

@ -1,5 +1,5 @@
{
"version": "1.1.1",
"version": "1.2.0",
"npmClient": "yarn",
"packages": [
"packages/asc-web-components",

View File

@ -14,7 +14,7 @@
],
"scripts": {
"build": "lerna run build --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc",
"build:personal": "lerna run build --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor}",
"build:personal": "lerna run build:personal --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor}",
"build:test": "lerna run build:test --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info",
"build:test.translation": "lerna run build:test.translation --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info",
"bump": "lerna version --no-push --no-git-tag-version",
@ -23,7 +23,7 @@
"deploy:personal": "shx rm -rf build/deploy/products && shx rm -rf build/deploy/public && shx rm -rf build/deploy/studio && lerna run deploy --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor} && shx cp -r public build/deploy",
"serve": "lerna run serve --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc",
"start": "lerna run start --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc",
"start:personal": "lerna run start --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor}",
"start:personal": "lerna run start:personal --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor}",
"start-prod": "lerna run start-prod --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc",
"storybook": "yarn workspace @appserver/components storybook",
"storybook-build": "yarn workspace @appserver/components run storybook-build",

View File

@ -3,6 +3,7 @@ import axios from "axios";
import FilesFilter from "./filter";
import { FolderType } from "../../constants";
import find from "lodash/find";
import { getFolderOptions } from "../../utils";
export function openEdit(fileId, version, doc, view) {
const params = []; // doc ? `?doc=${doc}` : "";
@ -48,19 +49,7 @@ export function getFolderPath(folderId) {
}
export function getFolder(folderId, filter) {
if (folderId && typeof folderId === "string") {
folderId = encodeURIComponent(folderId.replace(/\\\\/g, "\\"));
}
const params =
filter && filter instanceof FilesFilter
? `${folderId}?${filter.toApiUrlParams()}`
: folderId;
const options = {
method: "get",
url: `/files/${params}`,
};
const options = getFolderOptions(folderId, filter);
return request(options);
}
@ -181,6 +170,38 @@ export function getFoldersTree() {
);
}
export function getCommonFoldersTree() {
const index = 1;
return request({ method: "get", url: "/files/@common" }).then(
(commonFolders) => {
return [
{
id: commonFolders.current.id,
key: `0-${index}`,
parentId: commonFolders.current.parentId,
title: commonFolders.current.title,
rootFolderType: +commonFolders.current.rootFolderType,
rootFolderName: "@common",
pathParts: commonFolders.pathParts,
foldersCount: commonFolders.current.foldersCount,
newItems: commonFolders.new,
},
];
}
);
}
export function getThirdPartyCommonFolderTree() {
return request({ method: "get", url: "/files/thirdparty/common" }).then(
(commonThirdPartyArray) => {
commonThirdPartyArray.map((currentValue, index) => {
commonThirdPartyArray[index].key = `0-${index}`;
});
return commonThirdPartyArray;
}
);
}
export function getMyFolderList(filter = FilesFilter.getDefault()) {
const options = {
method: "get",
@ -277,8 +298,8 @@ export function deleteFolder(folderId, deleteAfter, immediately) {
return request(options);
}
export function createFile(folderId, title, templateId) {
const data = { title, templateId };
export function createFile(folderId, title, templateId, formId) {
const data = { title, templateId, formId };
const options = {
method: "post",
url: `/files/${folderId}/file`,

View File

@ -303,3 +303,13 @@ export function getSelectorUserList() {
url: "/people/filter.json?fields=id,displayName,groups",
});
}
export function changeTheme(key) {
const data = { Theme: key };
return request({
method: "put",
url: `/people/theme.json`,
data,
});
}

View File

@ -1,4 +1,5 @@
import { request } from "../client";
import axios from "axios";
export function getSettings() {
return request({
@ -436,13 +437,6 @@ export function validateTfaCode(code) {
});
}
export function getCommonThirdPartyList() {
const options = {
method: "get",
url: "/files/thirdparty/common",
};
return request(options);
}
export function getBackupStorage() {
const options = {
method: "get",
@ -482,3 +476,7 @@ export function toggleTipsSubscription() {
};
return request(options);
}
export function getOforms(url) {
return axios.get(url);
}

View File

@ -12,8 +12,8 @@ import Backdrop from "@appserver/components/backdrop";
const mobileView = css`
top: 64px;
width: 100vw !important;
height: calc(100vh - 64px) !important;
width: 100% !important;
height: calc(100% - 64px) !important;
`;
const StyledBlock = styled.div`
@ -22,8 +22,8 @@ const StyledBlock = styled.div`
right: 0;
width: 480px;
max-width: 100vw;
height: 100vh;
max-width: 100%;
height: 100%;
z-index: 400;

View File

@ -136,7 +136,11 @@ const Selector = (props) => {
const newGroupList = groupList;
newGroupList.find((group) => group.key === groupHeader.key).total = total;
if (newGroupList.length > 0) {
newGroupList.find(
(group) => group.key === groupHeader.key
).total = total;
}
setGroupList(newGroupList);
}
@ -334,7 +338,7 @@ const Selector = (props) => {
isDisabled={isDisabled}
placeholder={searchPlaceHolderLabel}
value={searchValue}
onChange={onSearchChange}
onSearchChange={onSearchChange}
onClearSearch={onSearchReset}
/>
<div style={{ width: "100%", height: "100%" }} className="body-options">

View File

@ -7,6 +7,9 @@ const StyledFooter = styled.div`
padding: 16px;
height: 69px;
display: flex;
align-items: center;
${(props) =>
props.withEmbeddedComponent &&
css`
@ -18,6 +21,16 @@ const StyledFooter = styled.div`
css`
display: none;
`}
button {
min-height: 40px;
}
.embedded_combo-box {
.combo-button {
min-height: 42px;
}
}
`;
StyledFooter.defaultProps = { theme: Base };

View File

@ -31,7 +31,7 @@ const Article = ({
toggleShowText,
toggleArticleOpen,
setIsMobileArticle,
isLoading,
isLoadedPage,
children,
...rest
}) => {
@ -107,7 +107,12 @@ const Article = ({
return (
<>
<StyledArticle showText={showText} articleOpen={articleOpen} {...rest}>
<StyledArticle
id={"article-container"}
showText={showText}
articleOpen={articleOpen}
{...rest}
>
<Resizable
defaultSize={{
width: 256,
@ -117,7 +122,7 @@ const Article = ({
handleWrapperClass="resizable-border not-selectable"
>
<SubArticleHeader
isLoading={isLoading}
isLoadedPage={isLoadedPage}
showText={showText}
onClick={toggleShowText}
>
@ -128,7 +133,7 @@ const Article = ({
{articleMainButtonContent.props.children}
</SubArticleMainButton>
) : null}
<SubArticleBody isLoading={isLoading} showText={showText}>
<SubArticleBody showText={showText}>
{articleBodyContent ? articleBodyContent.props.children : null}
</SubArticleBody>
</Resizable>

View File

@ -36,36 +36,31 @@ const StyledArticle = styled.article`
@media ${mobile} {
display: ${(props) => (props.articleOpen ? "flex" : "none")};
min-width: 100vw;
width: 100vw;
height: calc(100vh - 64px) !important;
min-width: 100%;
width: 100%;
position: fixed;
height: calc(100% - 64px) !important;
margin: 0;
margin-top: 16px;
padding: 0;
padding-bottom: 0px;
}
${isMobileOnly &&
css`
display: ${(props) => (props.articleOpen ? "flex" : "none")} !important;
min-width: 100vw !important;
width: 100vw;
min-width: 100% !important;
width: 100%;
position: fixed;
margin-top: 64px !important;
height: calc(100vh - 64px) !important;
height: calc(100% - 64px) !important;
margin: 0;
padding: 0;
padding-bottom: 0px;
`}
@media ${mobile} {
position: fixed;
margin-top: 16px;
height: calc(100vh - 64px) !important;
z-index: 400;
}
z-index: ${(props) =>
props.showText && (isMobileOnly || isMobileUtils()) ? "205" : "100"};
props.showText && (isMobileOnly || isMobileUtils()) ? "230" : "100"};
.resizable-block {
overflow: hidden;
@ -97,12 +92,11 @@ const StyledArticle = styled.article`
@media ${mobile} {
display: ${(props) => (props.articleOpen ? "flex" : "none")};
min-width: 100vw;
width: 100vw;
height: calc(100vh - 64px) !important;
min-width: 100%;
width: 100%;
margin: 0;
padding: 0;
padding-bottom: 0px;
}
${isMobile &&
@ -117,18 +111,25 @@ const StyledArticle = styled.article`
${isMobileOnly &&
css`
display: ${(props) => (props.articleOpen ? "flex" : "none")};
min-width: 100vw !important;
width: 100vw;
height: calc(100vh - 64px) !important;
min-width: 100% !important;
width: 100%;
margin: 0;
padding: 0;
padding-bottom: 0px;
`}
}
.article-body__scrollbar {
.scroll-body {
padding-right: 0px !important;
@media ${mobile} {
padding-bottom: 20px;
}
${isMobileOnly &&
css`
padding-bottom: 20px;
`}
}
}
`;

View File

@ -11,7 +11,12 @@ const ArticleBackdrop = ({ onClick, ...rest }) => {
<StyledControlContainer onClick={onClick} {...rest}>
<StyledCrossIcon />
</StyledControlContainer>
<Backdrop visible={true} zIndex={201} withBackground={true} />
<Backdrop
onClick={onClick}
visible={true}
zIndex={210}
withBackground={true}
/>
</>
);
};

View File

@ -1,10 +1,8 @@
import React from "react";
import Scrollbar from "@appserver/components/scrollbar";
import LoaderArticleBody from "./article-body-loader";
const ArticleBody = ({ children, isLoading = false }) => {
return isLoading ? (
<LoaderArticleBody />
) : (
const ArticleBody = ({ children }) => {
return (
<Scrollbar className="article-body__scrollbar" stype="mediumBlack">
{children}
</Scrollbar>

View File

@ -1,9 +1,10 @@
import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import { useLocation } from "react-router";
import Loaders from "@appserver/common/components/Loaders";
import { isTablet as isTabletUtils } from "@appserver/components/utils/device";
import { isTablet } from "react-device-detect";
import { inject, observer } from "mobx-react";
import {
StyledArticleHeader,
StyledHeading,
@ -15,12 +16,29 @@ const ArticleHeader = ({
showText,
children,
onClick,
isLoading = false,
isLoadedPage,
isLoaded,
tReady,
setIsLoadedArticleHeader,
...rest
}) => {
const location = useLocation();
const isLoadedSetting = isLoaded;
const commonSettings =
location.pathname.includes("common/customization") ||
location.pathname === "/settings";
useEffect(() => {
if (isLoadedSetting) setIsLoadedArticleHeader(isLoadedSetting);
}, [isLoadedSetting]);
const heightLoader = isTabletUtils() || isTablet ? "20px" : "32px";
return isLoading ? (
const showLoader = commonSettings ? !isLoadedPage : false;
return showLoader ? (
<StyledArticleHeader>
<Loaders.ArticleHeader height={heightLoader} className="loader" />
</StyledArticleHeader>
@ -45,4 +63,11 @@ ArticleHeader.propTypes = {
ArticleHeader.displayName = "Header";
export default React.memo(ArticleHeader);
export default inject(({ common }) => {
const { isLoaded, setIsLoadedArticleHeader } = common;
return {
isLoaded,
setIsLoadedArticleHeader,
};
})(observer(ArticleHeader));

View File

@ -80,7 +80,8 @@ const FilterInput = React.memo(
!isMobile &&
viewSelectorVisible &&
!isMobileUtils() &&
!isTabletUtils() ? (
!isTabletUtils() &&
viewAs !== "row" ? (
<ViewSelector
style={{ marginLeft: "8px" }}
onChangeView={onChangeViewAs}
@ -89,7 +90,10 @@ const FilterInput = React.memo(
/>
) : (
<>
{(isMobile || isTabletUtils() || isMobileUtils()) && (
{(isMobile ||
isTabletUtils() ||
isMobileUtils() ||
viewAs === "row") && (
<SortButton
t={t}
selectedFilterData={selectedFilterData}

View File

@ -18,6 +18,7 @@ import {
StyledCrossIcon,
} from "./StyledFilterBlock";
import { withTranslation } from "react-i18next";
import Scrollbar from "@appserver/components/scrollbar";
//TODO: fix translate
const FilterBlock = ({
@ -213,21 +214,25 @@ const FilterBlock = ({
size={17}
/>
</StyledFilterBlockHeader>
{filterData.map((item) => {
return (
<FilterBlockItem
key={item.key}
label={item.label}
keyProp={item.key}
group={item.group}
groupItem={item.groupItem}
isLast={item.isLast}
withoutHeader={item.withoutHeader}
changeFilterValue={changeFilterValue}
showSelector={changeShowSelector}
/>
);
})}
<div className="filter-body">
<Scrollbar className="filter-body__scrollbar" stype="mediumBlack">
{filterData.map((item) => {
return (
<FilterBlockItem
key={item.key}
label={item.label}
keyProp={item.key}
group={item.group}
groupItem={item.groupItem}
isLast={item.isLast}
withoutHeader={item.withoutHeader}
changeFilterValue={changeFilterValue}
showSelector={changeShowSelector}
/>
);
})}
</Scrollbar>
</div>
<StyledFilterBlockFooter>
<Button
size="normal"

View File

@ -13,6 +13,7 @@ import { mobile } from "@appserver/components/utils/device";
import { Base } from "@appserver/components/themes";
import SortDesc from "../../../../../public/images/sort.desc.react.svg";
import Backdrop from "@appserver/components/backdrop";
const selectedViewIcon = css`
svg {
@ -31,13 +32,18 @@ const notSelectedViewIcon = css`
`;
const mobileView = css`
position: fixed;
top: auto;
left: 0;
bottom: 0;
width: 100vw;
${(props) =>
!props.isRecentFolder &&
!props.isFavoritesFolder &&
css`
position: fixed;
top: auto;
left: 0;
bottom: 0;
width: 100vw;
z-index: 999;
z-index: 999;
`}
`;
const StyledSortButton = styled.div`
@ -62,12 +68,10 @@ const StyledSortButton = styled.div`
min-width: 200px;
margin-top: 3px;
@media ${mobile} {
@media (max-width: 428px) {
${mobileView}
}
${isMobileOnly && mobileView}
.view-selector-item {
display: flex;
align-items: center;
@ -203,14 +207,14 @@ const SortButton = ({
const onOptionClick = React.useCallback(
(e) => {
const sortDirection =
currentSelectedFilterData.sortDirection === "desc" &&
e.target.closest(".option-item__icon")
? "asc"
: "desc";
const key = e.target.closest(".option-item").dataset.value;
let sortDirection = currentSelectedFilterData.sortDirection;
if (key === currentSelectedFilterData.sortId) {
sortDirection = sortDirection === "desc" ? "asc" : "desc";
}
setCurrentSelectedFilterData({
sortId: key,
sortDirection: sortDirection,
@ -287,30 +291,39 @@ const SortButton = ({
]);
return (
<StyledSortButton
viewAs={viewAs}
isDesc={currentSelectedFilterData.sortDirection === "desc"}
onClick={toggleCombobox}
>
<ComboBox
opened={isOpen}
toggleAction={toggleCombobox}
className={"sort-combo-box"}
options={[]}
selectedOption={{}}
directionX={"right"}
directionY={"both"}
scaled={true}
size={"content"}
advancedOptions={getAdvancedOptions()}
disableIconClick={false}
disableItemClick={true}
isDefaultMode={false}
manualY={"102%"}
<>
<Backdrop
visible={isOpen}
withBackground={true}
onClick={toggleCombobox}
/>
<StyledSortButton
viewAs={viewAs}
isDesc={currentSelectedFilterData.sortDirection === "desc"}
onClick={toggleCombobox}
isRecentFolder={isRecentFolder}
isFavoritesFolder={isFavoritesFolder}
>
<IconButton iconName="/static/images/sort.react.svg" size={16} />
</ComboBox>
</StyledSortButton>
<ComboBox
opened={isOpen}
toggleAction={toggleCombobox}
className={"sort-combo-box"}
options={[]}
selectedOption={{}}
directionX={"right"}
directionY={"both"}
scaled={true}
size={"content"}
advancedOptions={getAdvancedOptions()}
disableIconClick={false}
disableItemClick={true}
isDefaultMode={false}
manualY={"102%"}
>
<IconButton iconName="/static/images/sort.react.svg" size={16} />
</ComboBox>
</StyledSortButton>
</>
);
};

View File

@ -10,8 +10,8 @@ import CrossIcon from "@appserver/components/public/static/images/cross.react.sv
const mobileView = css`
top: 64px;
width: 100vw !important;
height: calc(100vh - 64px) !important;
width: 100% !important;
height: calc(100% - 64px) !important;
`;
const StyledFilterBlock = styled.div`
@ -20,7 +20,7 @@ const StyledFilterBlock = styled.div`
right: 0;
width: 480px;
height: 100vh;
height: 100%;
z-index: 400;
@ -44,6 +44,10 @@ const StyledFilterBlock = styled.div`
width: 100%;
}
}
.filter-body {
height: calc(100% - 125px);
}
`;
StyledFilterBlock.defaultProps = { theme: Base };
@ -210,7 +214,9 @@ const StyledFilterBlockItemToggleButton = styled(ToggleButton)`
const StyledFilterBlockItemSeparator = styled.div`
height: 1px;
width: 100%;
width: calc(100% + 16px);
margin-right: 16px;
background: ${(props) => props.theme.filterInput.filter.separatorColor};
@ -242,12 +248,12 @@ const StyledFilterBlockFooter = styled.div`
justify-content: center;
@media ${mobile} {
width: 100vw;
width: 100%;
}
${isMobileOnly &&
css`
width: 100vw;
width: 100%;
`}
`;

View File

@ -2,6 +2,8 @@ import Base from "@appserver/components/themes/base";
import styled, { keyframes, css } from "styled-components";
const StyledCircleWrap = styled.div`
position: relative;
z-index: 500;
width: 48px;
height: 48px;
background: ${(props) =>

View File

@ -9,10 +9,10 @@ const StyledFilter = styled.div`
grid-template-rows: 1fr;
grid-column-gap: 8px;
${isMobile &&
/* ${isMobile &&
css`
margin-top: -22px;
`}
`} */
@media ${mobile} {
grid-template-columns: 1fr 50px;

View File

@ -3,7 +3,14 @@ import PropTypes from "prop-types";
import { StyledRow } from "./StyledListLoader";
import RectangleLoader from "../RectangleLoader";
const ListItemLoader = ({ id, className, style, isRectangle, ...rest }) => {
const ListItemLoader = ({
id,
className,
style,
withoutFirstRectangle,
withoutLastRectangle,
...rest
}) => {
const {
title,
borderRadius,
@ -16,8 +23,14 @@ const ListItemLoader = ({ id, className, style, isRectangle, ...rest }) => {
} = rest;
return (
<StyledRow id={id} className={className} style={style}>
{isRectangle && (
<StyledRow
id={id}
className={className}
style={style}
withoutFirstRectangle={withoutFirstRectangle}
withoutLastRectangle={withoutLastRectangle}
>
{!withoutFirstRectangle && (
<RectangleLoader
title={title}
width="16"
@ -60,18 +73,20 @@ const ListItemLoader = ({ id, className, style, isRectangle, ...rest }) => {
animate={animate}
/>
<RectangleLoader
title={title}
width="16"
height="16"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate={animate}
/>
{!withoutLastRectangle && (
<RectangleLoader
title={title}
width="16"
height="16"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate={animate}
/>
)}
</StyledRow>
);
};
@ -80,14 +95,16 @@ ListItemLoader.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
isRectangle: PropTypes.bool,
withoutFirstRectangle: PropTypes.bool,
withoutLastRectangle: PropTypes.bool,
};
ListItemLoader.defaultProps = {
id: undefined,
className: undefined,
style: undefined,
isRectangle: true,
withoutFirstRectangle: false,
withoutLastRectangle: false,
};
export default ListItemLoader;

View File

@ -9,21 +9,26 @@ const StyledRow = styled.div`
width: 100%;
display: grid;
grid-template-columns: 16px 32px 1fr 16px;
${(props) =>
props.withoutFirstRectangle &&
props.withoutLastRectangle &&
"grid-template-columns: 32px 1fr"};
${(props) =>
props.withoutFirstRectangle &&
!props.withoutLastRectangle &&
"grid-template-columns: 32px 1fr 16px"};
grid-template-rows: 1fr;
grid-column-gap: 8px;
margin-bottom: 16px;
justify-items: center;
align-items: center;
.list-loader_rectangle {
padding-right: 4px;
}
.list-loader_rectangle-content {
width: 32px;
height: 32px;
}
.list-loader_rectangle-row {
margin-right: auto;
max-width: 167px;

View File

@ -0,0 +1,24 @@
import styled from "styled-components";
import { desktop } from "@appserver/components/utils/device";
const StyledTreeFolder = styled.div`
padding-right: 16px;
`;
const StyledLoader = styled.div`
width: 100%;
display: grid;
grid-template-columns: 8px 16px 1fr;
grid-template-rows: 1fr;
grid-column-gap: 6px;
margin-bottom: 8px;
box-sizing: border-box;
.tree-node-loader_additional-rectangle {
padding-top: 4px;
}
${(props) => props.paddingLeft && `padding-left: ${props.paddingLeft}`};
`;
export { StyledLoader, StyledTreeFolder };

View File

@ -0,0 +1,51 @@
import React from "react";
import PropTypes from "prop-types";
import { StyledTreeFolder, StyledLoader } from "./StyledTreeFolder";
import TreeNodeLoader from "../TreeNodeLoader";
const NewTreeFolderLoader = ({ id, className, style, ...rest }) => {
return (
<StyledTreeFolder id={id} className={className} style={style}>
<StyledLoader>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
<StyledLoader paddingLeft={"16px"}>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
<StyledLoader paddingLeft={"32px"}>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
<StyledLoader paddingLeft={"32px"}>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
<StyledLoader paddingLeft={"32px"}>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
<StyledLoader>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
<StyledLoader paddingLeft={"16px"}>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
<StyledLoader paddingLeft={"16px"}>
<TreeNodeLoader {...rest} withRectangle />
</StyledLoader>
</StyledTreeFolder>
);
};
NewTreeFolderLoader.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
};
NewTreeFolderLoader.defaultProps = {
id: undefined,
className: undefined,
style: undefined,
};
export default NewTreeFolderLoader;

View File

@ -0,0 +1,145 @@
import styled, { css } from "styled-components";
import { Base } from "@appserver/components/themes";
const StyledContainer = styled.div`
width: 100%;
display: flex;
flex-direction: column;
`;
const StyledHeader = styled.div`
width: 100%;
padding: ${(props) => (props.isPersonal ? "0px 16px 12px" : "12px 16px")};
${(props) =>
props.isPersonal &&
css`
margin-left: -12px;
margin-right: 12px;
`}
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
border-bottom: ${(props) => props.theme.filesPanels.sharing.borderBottom};
`;
StyledHeader.defaultProps = { theme: Base };
const StyledExternalLink = styled.div`
width: 100%;
display: flex;
flex-direction: column;
padding: ${(props) => (props.isPersonal ? "20px 4px" : "20px 16px")};
box-sizing: border-box;
border-bottom: ${(props) =>
props.isPersonal ? "none" : props.theme.filesPanels.sharing.borderBottom};
.rectangle-loader {
margin-bottom: 16px;
}
`;
StyledExternalLink.defaultProps = { theme: Base };
const StyledInternalLink = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 16px;
box-sizing: border-box;
`;
const StyledOwner = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
box-sizing: border-box;
margin-bottom: 16px;
.owner-info {
display: flex;
align-items: center;
svg:first-child {
margin-right: 12px;
}
}
`;
const StyledBody = styled.div`
width: 100%;
display: flex;
flex-direction: column;
div:nth-child(3) {
margin-bottom: 16px;
}
`;
const StyledItem = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 8px 16px;
.item-info {
display: flex;
align-items: center;
svg:first-child {
margin-right: 12px;
}
}
`;
const StyledButtons = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 4px;
svg:first-child {
margin-right: 8px;
}
`;
export {
StyledContainer,
StyledHeader,
StyledExternalLink,
StyledInternalLink,
StyledOwner,
StyledBody,
StyledItem,
StyledButtons,
};

View File

@ -0,0 +1,138 @@
import React from "react";
import PropTypes from "prop-types";
import {
StyledContainer,
StyledHeader,
StyledExternalLink,
StyledInternalLink,
StyledOwner,
StyledBody,
StyledItem,
} from "./StyledSharingPanel";
import RectangleLoader from "../RectangleLoader/RectangleLoader";
const SharingPanelLoader = ({ id, className, style, ...rest }) => {
return (
<StyledContainer>
<StyledHeader>
<RectangleLoader width={"283px"} height={"29px"} />
<RectangleLoader width={"48px"} height={"29px"} />
</StyledHeader>
<StyledExternalLink>
<RectangleLoader
className="rectangle-loader"
width={"146px"}
height={"22px"}
/>
<RectangleLoader
className="rectangle-loader"
width={"448px"}
height={"32px"}
/>
<RectangleLoader width={"184px"} height={"20px"} />
</StyledExternalLink>
<StyledInternalLink>
<RectangleLoader width={"99px"} height={"22px"} />
<RectangleLoader width={"30px"} height={"22px"} />
</StyledInternalLink>
<StyledOwner>
<div className="owner-info">
<RectangleLoader
width={"32px"}
height={"32px"}
borderRadius={"1000px"}
/>
<RectangleLoader width={"91px"} height={"16px"} />
</div>
<RectangleLoader width={"91px"} height={"16px"} />
</StyledOwner>
<StyledBody>
<StyledItem>
<div className="item-info">
<RectangleLoader
width={"32px"}
height={"32px"}
borderRadius={"1000px"}
/>
<RectangleLoader width={"91px"} height={"16px"} />
</div>
<RectangleLoader width={"45px"} height={"32px"} />
</StyledItem>
<StyledItem>
<div className="item-info">
<RectangleLoader
width={"32px"}
height={"32px"}
borderRadius={"1000px"}
/>
<RectangleLoader width={"91px"} height={"16px"} />
</div>
<RectangleLoader width={"45px"} height={"32px"} />
</StyledItem>
<StyledItem>
<div className="item-info">
<RectangleLoader
width={"32px"}
height={"32px"}
borderRadius={"1000px"}
/>
<RectangleLoader width={"91px"} height={"16px"} />
</div>
<RectangleLoader width={"45px"} height={"32px"} />
</StyledItem>
<StyledItem>
<div className="item-info">
<RectangleLoader
width={"32px"}
height={"32px"}
borderRadius={"1000px"}
/>
<RectangleLoader width={"91px"} height={"16px"} />
</div>
<RectangleLoader width={"45px"} height={"32px"} />
</StyledItem>
<StyledItem>
<div className="item-info">
<RectangleLoader
width={"32px"}
height={"32px"}
borderRadius={"1000px"}
/>
<RectangleLoader width={"91px"} height={"16px"} />
</div>
<RectangleLoader width={"45px"} height={"32px"} />
</StyledItem>
<StyledItem>
<div className="item-info">
<RectangleLoader
width={"32px"}
height={"32px"}
borderRadius={"1000px"}
/>
<RectangleLoader width={"91px"} height={"16px"} />
</div>
<RectangleLoader width={"45px"} height={"32px"} />
</StyledItem>
</StyledBody>
</StyledContainer>
);
};
SharingPanelLoader.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
};
SharingPanelLoader.defaultProps = {
id: undefined,
className: undefined,
style: undefined,
};
export default SharingPanelLoader;

View File

@ -0,0 +1,66 @@
import React from "react";
import PropTypes from "prop-types";
import {
StyledContainer,
StyledHeader,
StyledExternalLink,
StyledInternalLink,
StyledOwner,
StyledBody,
StyledItem,
StyledButtons,
} from "./StyledSharingPanel";
import RectangleLoader from "../RectangleLoader/RectangleLoader";
const SharingPanelLoaderModal = ({
id,
className,
style,
isShared,
...rest
}) => {
return (
<StyledContainer>
<StyledHeader isPersonal={true}>
<RectangleLoader width={"283px"} height={"16px"} />
</StyledHeader>
<StyledExternalLink isPersonal={true}>
<RectangleLoader
className="rectangle-loader"
width={"146px"}
height={"22px"}
/>
{isShared && (
<>
<RectangleLoader
className="rectangle-loader"
width={"368px"}
height={"32px"}
/>
<RectangleLoader width={"184px"} height={"20px"} />
</>
)}
</StyledExternalLink>
<StyledButtons>
<RectangleLoader width={"100%"} height={"40px"} />
<RectangleLoader width={"100%"} height={"40px"} />
</StyledButtons>
</StyledContainer>
);
};
SharingPanelLoaderModal.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
};
SharingPanelLoaderModal.defaultProps = {
id: undefined,
className: undefined,
style: undefined,
};
export default SharingPanelLoaderModal;

View File

@ -34,7 +34,13 @@ const StyledWrapper = styled.div`
}
`;
const TilesLoader = ({ foldersCount, filesCount, sectionWidth, ...rest }) => {
const TilesLoader = ({
foldersCount,
filesCount,
sectionWidth,
withTitle,
...rest
}) => {
const folders = [];
const files = [];
@ -59,15 +65,17 @@ const TilesLoader = ({ foldersCount, filesCount, sectionWidth, ...rest }) => {
) : null}
<StyledTilesLoader>{folders}</StyledTilesLoader>
{filesCount > 0 ? (
<RectangleLoader
height="22px"
width="35px"
className="files"
animate
{...rest}
/>
) : null}
{filesCount > 0
? withTitle && (
<RectangleLoader
height="22px"
width="35px"
className="files"
animate
{...rest}
/>
)
: null}
<StyledTilesLoader>{files}</StyledTilesLoader>
</StyledWrapper>
);
@ -81,6 +89,7 @@ TilesLoader.propTypes = {
TilesLoader.defaultProps = {
foldersCount: 2,
filesCount: 8,
withTitle: true,
};
export default TilesLoader;

View File

@ -11,6 +11,7 @@ const TreeNodeLoader = ({
foregroundOpacity,
speed,
animate,
withRectangle = false,
}) => {
return (
<>
@ -25,6 +26,23 @@ const TreeNodeLoader = ({
speed={speed}
animate={animate}
/>
{withRectangle && (
<RectangleLoader
title={title}
width="16"
height="16"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate={animate}
className="tree-node-loader_additional-rectangle"
/>
)}
<RectangleLoader
title={title}
width="100%"

View File

@ -7,6 +7,7 @@ import ArticleButton from "./ArticleButtonLoader";
import ArticleFolder from "./ArticleFolderLoader";
import ArticleGroup from "./ArticleGroupsLoader";
import TreeFolders from "./TreeFolderLoader";
import NewTreeFolders from "./NewTreeFolderLoader";
import TreeSettingsLoader from "./TreeSettingsLoader";
import Row from "./RowLoader";
import Rows from "./RowsLoader";
@ -21,6 +22,8 @@ import Tiles from "./TilesLoader";
import DialogLoader from "./DialogLoader";
import DialogAsideLoader from "./DialogAsideLoader";
import ListLoader from "./ListLoader";
import SharingPanelLoader from "./SharingPanelLoader";
import SharingPanelLoaderModal from "./SharingPanelLoader/modal";
export default {
Rectangle,
@ -46,4 +49,7 @@ export default {
ArticleFolder,
ArticleGroup,
ListLoader,
NewTreeFolders,
SharingPanelLoader,
SharingPanelLoaderModal,
};

View File

@ -9,9 +9,12 @@ import Text from "./sub-components/text";
import ControlButtons from "./sub-components/control-btn";
import DropBox from "./sub-components/drop-box";
import { isMobileOnly } from "react-device-detect";
import { Consumer } from "@appserver/components/utils/context";
import DomHelpers from "@appserver/components/utils/domHelpers";
import Backdrop from "@appserver/components/backdrop";
const Navigation = ({
tReady,
@ -39,6 +42,7 @@ const Navigation = ({
const [isOpen, setIsOpen] = React.useState(false);
const [firstClick, setFirstClick] = React.useState(true);
const [dropBoxWidth, setDropBoxWidth] = React.useState(0);
const [maxHeight, setMaxHeight] = React.useState(false);
const dropBoxRef = React.useRef(null);
const containerRef = React.useRef(null);
@ -64,21 +68,36 @@ const Navigation = ({
const toggleDropBox = () => {
if (isRootFolder) return setIsOpen(false);
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
setIsOpen((prev) => !prev);
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
const { top } = DomHelpers.getOffset(containerRef.current);
setMaxHeight(`calc(100vh - ${top}px)`);
setFirstClick(true);
};
const onResize = React.useCallback(() => {
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
}, [containerRef.current]);
React.useEffect(() => {
if (isOpen) {
window.addEventListener("click", onMissClick);
window.addEventListener("resize", onResize);
} else {
window.removeEventListener("click", onMissClick);
window.addEventListener("resize", onResize);
setFirstClick(true);
}
return () => window.removeEventListener("click", onMissClick);
}, [isOpen, onMissClick]);
return () => {
window.removeEventListener("click", onMissClick);
window.addEventListener("resize", onResize);
};
}, [isOpen, onResize, onMissClick]);
const onBackToParentFolderAction = React.useCallback(() => {
setIsOpen((val) => !val);
@ -90,25 +109,36 @@ const Navigation = ({
{(context) => (
<>
{isOpen && (
<DropBox
{...rest}
ref={dropBoxRef}
dropBoxWidth={dropBoxWidth}
sectionHeight={context.sectionHeight}
showText={showText}
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolderAction}
title={title}
personal={personal}
canCreate={canCreate}
navigationItems={navigationItems}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
toggleDropBox={toggleDropBox}
toggleInfoPanel={toggleInfoPanel}
isInfoPanelVisible={isInfoPanelVisible}
onClickAvailable={onClickAvailable}
/>
<>
{isMobileOnly && (
<Backdrop
isAside={true}
visible={isOpen}
withBackground={true}
zIndex={400}
/>
)}
<DropBox
{...rest}
ref={dropBoxRef}
maxHeight={maxHeight}
dropBoxWidth={dropBoxWidth}
sectionHeight={context.sectionHeight}
showText={showText}
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolderAction}
title={title}
personal={personal}
canCreate={canCreate}
navigationItems={navigationItems}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
toggleDropBox={toggleDropBox}
toggleInfoPanel={toggleInfoPanel}
isInfoPanelVisible={isInfoPanelVisible}
onClickAvailable={onClickAvailable}
/>
</>
)}
<StyledContainer
ref={containerRef}

View File

@ -1,6 +1,6 @@
import styled, { css } from "styled-components";
import { isMobile, isMobileOnly } from "react-device-detect";
import { tablet, desktop, mobile } from "@appserver/components/utils/device";
import { isMobileOnly } from "react-device-detect";
import { tablet, mobile } from "@appserver/components/utils/device";
const StyledContainer = styled.div`
width: 100% !important;
@ -16,28 +16,23 @@ const StyledContainer = styled.div`
min-width: 17px;
}
.headline-heading {
display: flex;
height: 32px;
align-items: center;
}
@media ${tablet} {
width: 100%;
grid-template-columns: ${(props) =>
props.isRootFolder ? "auto 1fr" : "29px 1fr auto"};
padding: ${(props) => (props.isDropBox ? "14px 0 5px" : "14px 0 15px")};
}
${isMobile &&
css`
width: 100%;
padding: ${(props) =>
props.isDropBox ? "12px 0 5px" : " 12px 0 13px"} !important;
`}
@media ${mobile} {
padding: ${(props) =>
props.isDropBox ? "10px 0 5px" : "10px 0 11px"} !important;
}
${isMobileOnly &&
css`
width: 100% !important;
padding: ${(props) =>
props.isDropBox ? "10px 0 5px" : "10px 0 11px"} !important;
`}
`;
export default StyledContainer;

View File

@ -13,8 +13,8 @@ const StyledContainer = styled.div`
align-items: center;
.add-button {
margin-right: 12px;
min-width: 17px;
margin-right: 16px;
min-width: 15px;
${(props) =>
!props.isDropBox &&
@ -31,12 +31,13 @@ const StyledContainer = styled.div`
}
.option-button {
margin-right: 15px;
min-width: 17px;
margin-right: 16px;
min-width: 15px;
}
.trash-button {
min-width: 17px;
margin-right: 16px;
min-width: 15px;
}
`;
@ -47,6 +48,15 @@ const StyledInfoPanelToggleWrapper = styled.div`
justify-content: center;
margin-left: auto;
@media ${tablet} {
margin-left: ${(props) => (props.isRootFolder ? "auto" : "0")};
}
${isMobile &&
css`
margin-left: ${(props) => (props.isRootFolder ? "auto" : "0")};
`}
.info-panel-toggle-bg {
height: 32px;
width: 32px;
@ -81,7 +91,14 @@ const ControlButtons = ({
clearTrash,
isInfoPanelVisible,
toggleInfoPanel,
toggleDropBox,
isDesktop,
}) => {
const toggleInfoPanelAction = () => {
toggleInfoPanel && toggleInfoPanel();
toggleDropBox && toggleDropBox();
};
return (
<StyledContainer isDropBox={isDropBox}>
{!isRootFolder && canCreate ? (
@ -90,7 +107,7 @@ const ControlButtons = ({
className="add-button"
directionX="right"
iconName="images/plus.svg"
size={17}
size={15}
isFill
getData={getContextOptionsPlus}
isDisabled={false}
@ -100,48 +117,97 @@ const ControlButtons = ({
className="option-button"
directionX="right"
iconName="images/vertical-dots.react.svg"
size={17}
size={15}
isFill
getData={getContextOptionsFolder}
isDisabled={false}
/>
)}
{!isDesktop && (
<StyledInfoPanelToggleWrapper
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
>
<div className="info-panel-toggle-bg">
<IconButton
className="info-panel-toggle"
iconName="images/panel.react.svg"
size="16"
isFill={true}
onClick={toggleInfoPanelAction}
/>
</div>
</StyledInfoPanelToggleWrapper>
)}
</>
) : canCreate ? (
<ContextMenuButton
className="add-button"
directionX="right"
iconName="images/plus.svg"
size={17}
isFill
getData={getContextOptionsPlus}
isDisabled={false}
/>
) : isRecycleBinFolder && !isEmptyFilesList ? (
<IconButton
iconName="images/clear.active.react.svg"
size={17}
isFill={true}
onClick={clearTrash}
className="trash-button"
/>
) : (
<></>
)}
<StyledInfoPanelToggleWrapper
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
>
<div className="info-panel-toggle-bg">
<IconButton
className="info-panel-toggle"
iconName="images/panel.react.svg"
size="16"
isFill={true}
onClick={toggleInfoPanel}
<>
<ContextMenuButton
className="add-button"
directionX="right"
iconName="images/plus.svg"
size={15}
isFill
getData={getContextOptionsPlus}
isDisabled={false}
/>
</div>
</StyledInfoPanelToggleWrapper>
<StyledInfoPanelToggleWrapper
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
>
<div className="info-panel-toggle-bg">
<IconButton
className="info-panel-toggle"
iconName="images/panel.react.svg"
size="16"
isFill={true}
onClick={toggleInfoPanelAction}
/>
</div>
</StyledInfoPanelToggleWrapper>
</>
) : isRecycleBinFolder && !isEmptyFilesList ? (
<>
<IconButton
iconName="images/clear.active.react.svg"
size={15}
isFill={true}
onClick={clearTrash}
className="trash-button"
/>
<StyledInfoPanelToggleWrapper
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
>
<div className="info-panel-toggle-bg">
<IconButton
className="info-panel-toggle"
iconName="images/panel.react.svg"
size="16"
isFill={true}
onClick={toggleInfoPanelAction}
/>
</div>
</StyledInfoPanelToggleWrapper>
</>
) : (
<>
<StyledInfoPanelToggleWrapper
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
>
<div className="info-panel-toggle-bg">
<IconButton
className="info-panel-toggle"
iconName="images/panel.react.svg"
size="16"
isFill={true}
onClick={toggleInfoPanelAction}
/>
</div>
</StyledInfoPanelToggleWrapper>
</>
)}
</StyledContainer>
);
};

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from "react";
import React from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
@ -17,6 +17,7 @@ import {
mobile,
isMobile as isMobileUtils,
isTablet as isTabletUtils,
isDesktop as isDesktopUtils,
} from "@appserver/components/utils/device";
import { Base } from "@appserver/components/themes";
@ -28,12 +29,12 @@ const StyledBox = styled.div`
padding: ${isMobile ? "0 16px " : "0 20px"};
width: ${(props) => props.dropBoxWidth}px;
${(props) => !props.isDesktop && `width: ${props.dropBoxWidth}px;`};
height: ${(props) => (props.height ? `${props.height}px` : "fit-content")};
max-height: calc(100vh - 48px);
z-index: 399;
z-index: 401;
display: flex;
flex-direction: column;
@ -51,6 +52,7 @@ const StyledBox = styled.div`
css`
margin-left: 16px;
padding: 0 16px !important;
max-height: ${(props) => props.maxHeight};
`}
`;
@ -88,12 +90,16 @@ const DropBox = React.forwardRef(
toggleInfoPanel,
onClickAvailable,
isInfoPanelVisible,
maxHeight,
isOpen,
},
ref
) => {
const [dropBoxHeight, setDropBoxHeight] = React.useState(0);
const countItems = navigationItems.length;
const isDesktop = !isMobile || isDesktopUtils();
const getItemSize = (index) => {
if (index === countItems - 1) return 51;
return isMobile || isMobileUtils() || isTabletUtils() ? 36 : 30;
@ -124,41 +130,47 @@ const DropBox = React.forwardRef(
}, [sectionHeight]);
return (
<StyledBox
ref={ref}
height={sectionHeight < dropBoxHeight ? sectionHeight : null}
showText={showText}
dropBoxWidth={dropBoxWidth}
>
<StyledContainer canCreate={canCreate} isDropBox={true}>
<ArrowButton
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolder}
/>
<Text title={title} isOpen={true} onClick={toggleDropBox} />
<ControlButtons
personal={personal}
isRootFolder={isRootFolder}
isDropBox={true}
canCreate={canCreate}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
toggleInfoPanel={toggleInfoPanel}
isInfoPanelVisible={isInfoPanelVisible}
/>
</StyledContainer>
<VariableSizeList
height={dropBoxHeight}
width={"auto"}
itemCount={countItems}
itemSize={getItemSize}
itemData={[navigationItems, onClickAvailable]}
outerElementType={CustomScrollbarsVirtualList}
<>
<StyledBox
ref={ref}
maxHeight={maxHeight}
height={sectionHeight < dropBoxHeight ? sectionHeight : null}
showText={showText}
dropBoxWidth={dropBoxWidth}
isDesktop={isDesktop}
>
{Row}
</VariableSizeList>
</StyledBox>
<StyledContainer canCreate={canCreate} isDropBox={true}>
<ArrowButton
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolder}
/>
<Text title={title} isOpen={true} onClick={toggleDropBox} />
<ControlButtons
isDesktop={isDesktop}
personal={personal}
isRootFolder={isRootFolder}
isDropBox={true}
canCreate={canCreate}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
toggleInfoPanel={toggleInfoPanel}
toggleDropBox={toggleDropBox}
isInfoPanelVisible={isInfoPanelVisible}
/>
</StyledContainer>
<VariableSizeList
height={dropBoxHeight}
width={"auto"}
itemCount={countItems}
itemSize={getItemSize}
itemData={[navigationItems, onClickAvailable]}
outerElementType={CustomScrollbarsVirtualList}
>
{Row}
</VariableSizeList>
</StyledBox>
</>
);
}
);

View File

@ -80,7 +80,6 @@ const Item = ({ id, title, isRoot, onClick, ...rest }) => {
<StyledText
isRoot={isRoot}
fontWeight={isRoot ? "600" : "400"}
isRoot={isRoot}
fontSize={"15px"}
truncate={true}
>

View File

@ -26,7 +26,7 @@ const StyledTextContainer = styled.div`
`;
const StyledHeadline = styled(Headline)`
width: calc(100% + 1px);
width: 100%;
font-weight: 700;
font-size: ${isMobile ? "21px !important" : "18px"};
line-height: ${isMobile ? "28px !important" : "24px"};

View File

@ -229,6 +229,8 @@ class Section extends React.Component {
snackbarExist,
showText,
infoPanelIsVisible,
isInfoPanelAvailable,
settingsStudio,
} = this.props;
let sectionHeaderContent = null;
@ -365,6 +367,7 @@ class Section extends React.Component {
autoFocus={isMobile || isTabletView ? false : true}
viewAs={viewAs}
isHomepage={isHomepage}
settingsStudio={settingsStudio}
>
{isMobile && (
<StyledMainBar
@ -394,6 +397,7 @@ class Section extends React.Component {
viewAs={viewAs}
showText={showText}
infoPanelIsVisible={infoPanelIsVisible}
settingsStudio={settingsStudio}
>
{sectionHeaderContent
? sectionHeaderContent.props.children
@ -467,12 +471,16 @@ class Section extends React.Component {
<></>
)}
</SectionContainer>
<InfoPanel>
<SubInfoPanelHeader>
{infoPanelHeaderContent}
</SubInfoPanelHeader>
<SubInfoPanelBody>{infoPanelBodyContent}</SubInfoPanelBody>
</InfoPanel>
{isInfoPanelAvailable && (
<InfoPanel>
<SubInfoPanelHeader>
{infoPanelHeaderContent}
</SubInfoPanelHeader>
<SubInfoPanelBody>
{infoPanelBodyContent}
</SubInfoPanelBody>
</InfoPanel>
)}
</Provider>
)}
</ReactResizeDetector>
@ -539,11 +547,15 @@ Section.propTypes = {
isHeaderVisible: PropTypes.bool,
firstLoad: PropTypes.bool,
isHomepage: PropTypes.bool,
isInfoPanelAvailable: PropTypes.bool,
settingsStudio: PropTypes.bool,
};
Section.defaultProps = {
withBodyScroll: true,
withBodyAutoFocus: false,
isInfoPanelAvailable: true,
settingsStudio: false,
};
Section.InfoPanelHeader = InfoPanelHeader;

View File

@ -1,14 +1,21 @@
import IconButton from "@appserver/components/icon-button";
import { Base } from "@appserver/components/themes";
import { isTablet, mobile, tablet } from "@appserver/components/utils/device";
import {
isTablet,
isMobile as isMobileUtils,
tablet,
} from "@appserver/components/utils/device";
import { inject } from "mobx-react";
import PropTypes from "prop-types";
import React, { useEffect } from "react";
import styled from "styled-components";
import styled, { css } from "styled-components";
import CrossIcon from "@appserver/components/public/static/images/cross.react.svg";
import { isMobile } from "react-device-detect";
const StyledInfoPanelWrapper = styled.div.attrs(({ id }) => ({
id: id,
}))`
user-select: none;
height: auto;
width: auto;
background: rgba(6, 22, 38, 0.2);
@ -16,12 +23,22 @@ const StyledInfoPanelWrapper = styled.div.attrs(({ id }) => ({
@media ${tablet} {
z-index: 309;
position: absolute;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
${isMobile &&
css`
z-index: 309;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
`}
`;
const StyledInfoPanel = styled.div`
@ -32,6 +49,10 @@ const StyledInfoPanel = styled.div`
display: flex;
flex-direction: column;
.scroll-body {
padding-bottom: 20px;
}
@media ${tablet} {
position: absolute;
border: none;
@ -40,46 +61,75 @@ const StyledInfoPanel = styled.div`
max-width: calc(100vw - 69px);
}
@media ${mobile} {
${isMobile &&
css`
position: absolute;
border: none;
right: 0;
width: 480px;
max-width: calc(100vw - 69px);
`}
@media (max-width: 428px) {
bottom: 0;
height: 80%;
height: calc(100% - 64px);
width: 100vw;
max-width: 100vw;
}
`;
const StyledCloseButtonWrapper = styled.div`
position: absolute;
const StyledControlContainer = styled.div`
display: none;
background-color: ${(props) => props.theme.infoPanel.closeButtonBg};
padding: ${(props) => props.theme.infoPanel.closeButtonWrapperPadding};
border-radius: 50%;
.info-panel-button {
svg {
width: ${(props) => props.theme.infoPanel.closeButtonSize};
height: ${(props) => props.theme.infoPanel.closeButtonSize};
}
path {
fill: ${(props) => props.theme.infoPanel.closeButtonIcon};
}
}
width: 24px;
height: 24px;
position: absolute;
border-radius: 100px;
cursor: pointer;
align-items: center;
justify-content: center;
z-index: 450;
/* background: ${(props) => props.theme.catalog.control.background}; */
@media ${tablet} {
display: block;
top: 0;
left: 0;
margin-top: 18px;
margin-left: -34px;
display: flex;
top: 16px;
left: -34px;
}
@media ${mobile} {
right: 0;
left: auto;
margin-top: -34px;
margin-right: 10px;
${isMobile &&
css`
display: flex !important;
top: 18px;
left: -34px;
`}
@media (max-width: 428px) {
display: flex;
top: -34px;
right: 10px;
left: unset;
}
`;
StyledControlContainer.defaultProps = { theme: Base };
const StyledCrossIcon = styled(CrossIcon)`
width: 17px;
height: 17px;
z-index: 455;
path {
fill: ${(props) => props.theme.catalog.control.fill};
}
`;
StyledCrossIcon.defaultProps = { theme: Base };
const InfoPanel = ({ children, isVisible, setIsVisible }) => {
if (!isVisible) return null;
@ -90,20 +140,19 @@ const InfoPanel = ({ children, isVisible, setIsVisible }) => {
if (e.target.id === "InfoPanelWrapper") closeInfoPanel();
};
if (isTablet()) document.addEventListener("mousedown", onMouseDown);
if (isTablet() || isMobile || isMobileUtils()) {
document.addEventListener("mousedown", onMouseDown);
}
return () => document.removeEventListener("mousedown", onMouseDown);
}, []);
return (
<StyledInfoPanelWrapper className="info-panel" id="InfoPanelWrapper">
<StyledInfoPanel>
<StyledCloseButtonWrapper>
<IconButton
onClick={closeInfoPanel}
iconName="/static/images/cross.react.svg"
className="info-panel-button"
/>
</StyledCloseButtonWrapper>
<StyledControlContainer onClick={closeInfoPanel}>
<StyledCrossIcon />
</StyledControlContainer>
{children}
</StyledInfoPanel>
</StyledInfoPanelWrapper>
@ -120,7 +169,6 @@ InfoPanel.propTypes = {
};
StyledInfoPanelWrapper.defaultProps = { theme: Base };
StyledCloseButtonWrapper.defaultProps = { theme: Base };
StyledInfoPanel.defaultProps = { theme: Base };
InfoPanel.defaultProps = { theme: Base };

View File

@ -8,25 +8,43 @@ import { inject, observer } from "mobx-react";
import Scrollbar from "@appserver/components/scrollbar";
import DragAndDrop from "@appserver/components/drag-and-drop";
import { tablet, mobile, desktop } from "@appserver/components/utils/device";
import {
tablet,
mobile,
desktop,
smallTablet,
} from "@appserver/components/utils/device";
const paddingStyles = css`
padding: 19px 7px 16px 20px;
padding: ${(props) =>
props.settingsStudio ? "0 7px 16px 24px" : "19px 7px 16px 20px"};
@media ${tablet} {
padding: 19px 0 16px 24px;
padding: ${(props) =>
props.settingsStudio ? "0 0 16px 24px" : "19px 0 16px 24px"};
}
@media ${smallTablet} {
padding: ${(props) =>
props.settingsStudio ? "8px 0 16px 24px" : "19px 0 16px 24px"};
}
@media ${mobile} {
padding: 19px 0 16px 24px;
padding: ${(props) =>
props.settingsStudio ? "8px 0 16px 24px" : "19px 0 16px 24px"};
}
${isMobile &&
css`
padding: 0 0 16px 24px !important;
`};
${isMobileOnly &&
css`
padding: 0px 0 16px 24px !important;
`};
`;
const commonStyles = css`
flex-grow: 1;
@ -40,7 +58,7 @@ const commonStyles = css`
.section-wrapper {
${(props) =>
!props.withScroll &&
`display: flex; height: 100%; box-sizing:border-box`};
`display: flex; flex-direction: column; height: 100%; box-sizing:border-box`};
${(props) => !props.withScroll && paddingStyles}
}
@ -169,6 +187,7 @@ class SectionBody extends React.Component {
isLoaded,
isDesktop,
isHomepage,
settingsStudio,
} = this.props;
const focusProps = autoFocus
@ -187,6 +206,7 @@ class SectionBody extends React.Component {
pinned={pinned}
isLoaded={isLoaded}
isDesktop={isDesktop}
settingsStudio={settingsStudio}
className="section-body"
>
{withScroll ? (
@ -221,6 +241,7 @@ class SectionBody extends React.Component {
pinned={pinned}
isLoaded={isLoaded}
isDesktop={isDesktop}
settingsStudio={settingsStudio}
>
{withScroll ? (
!isMobileOnly ? (
@ -268,6 +289,7 @@ SectionBody.propTypes = {
viewAs: PropTypes.string,
isLoaded: PropTypes.bool,
isHomepage: PropTypes.bool,
settingsStudio: PropTypes.bool,
};
SectionBody.defaultProps = {
@ -276,6 +298,7 @@ SectionBody.defaultProps = {
uploadFiles: false,
withScroll: true,
isHomepage: false,
settingsStudio: false,
};
export default inject(({ auth }) => {

View File

@ -38,10 +38,8 @@ const StyledSectionContainer = styled.section`
display: flex;
flex-direction: column;
width: ${(props) =>
props.infoPanelIsVisible ? "calc(100% - 677px)" : "100%"};
max-width: ${(props) =>
props.infoPanelIsVisible ? "calc(100vw - 677px)" : "100vw"};
width: 100%;
max-width: 100%;
@media ${tablet} {
width: 100%;
@ -52,7 +50,7 @@ const StyledSectionContainer = styled.section`
${isMobile &&
css`
width: 100% !important;
max-width: 100vw !important
max-width: 100vw !important;
padding: 0 0 0 16px;
${tabletProps};
min-width: 100px;

View File

@ -47,18 +47,22 @@ const StyledSectionHeader = styled.div`
padding-right: 16px !important;
margin-top: -2px !important;
margin-bottom: ${(props) =>
props.settingsStudio ? "8px !important" : "0"};
`}
`;
StyledSectionHeader.defaultProps = { theme: Base };
const SectionHeader = (props) => {
const { viewAs, className, ...rest } = props;
const { viewAs, settingsStudio = false, className, ...rest } = props;
return (
<StyledSectionHeader
className={`section-header ${className}`}
viewAs={viewAs}
settingsStudio={settingsStudio}
{...rest}
/>
);
@ -69,5 +73,6 @@ SectionHeader.displayName = "SectionHeader";
SectionHeader.propTypes = {
isArticlePinned: PropTypes.bool,
isHeaderVisible: PropTypes.bool,
settingsStudio: PropTypes.bool,
};
export default SectionHeader;

View File

@ -199,3 +199,16 @@ export const FileStatus = Object.freeze({
export const TenantStatus = Object.freeze({
PortalRestore: 4,
});
/**
* Enum for theme keys.
* @readonly
*/
export const ThemeKeys = Object.freeze({
Base: "0",
BaseStr: "Base",
Dark: "1",
DarkStr: "Dark",
System: "2",
SystemStr: "System",
});

View File

@ -1,6 +1,6 @@
{
"name": "@appserver/common",
"version": "1.1.1",
"version": "1.2.0",
"private": true,
"scripts": {
"build": "echo 'skip it'",

View File

@ -323,6 +323,12 @@ class AuthStore {
setProviders = (providers) => {
this.providers = providers;
};
getOforms = () => {
const culture =
this.userStore.user.cultureName || this.settingsStore.culture;
return api.settings.getOforms(`${this.settingsStore.urlOforms}${culture}`);
};
}
export default new AuthStore();

View File

@ -3,7 +3,7 @@ import api from "../api";
import { LANGUAGE, TenantStatus } from "../constants";
import { combineUrl } from "../utils";
import FirebaseHelper from "../utils/firebase";
import { AppServerConfig } from "../constants";
import { AppServerConfig, ThemeKeys } from "../constants";
import { version } from "../package.json";
import SocketIOHelper from "../utils/socket";
@ -26,9 +26,7 @@ class SettingsStore {
currentProductId = "";
culture = "en";
cultures = [];
theme = !!localStorage.getItem("theme")
? themes[localStorage.getItem("theme")]
: Base;
theme = Base;
trustedDomains = [];
trustedDomainsType = 0;
ipRestrictionEnable = false;
@ -36,6 +34,7 @@ class SettingsStore {
sessionLifetime = "1440";
timezone = "UTC";
timezones = [];
tenantAlias = "";
utcOffset = "00:00:00";
utcHoursOffset = 0;
defaultPage = "/";
@ -54,6 +53,8 @@ class SettingsStore {
enabledJoin = false;
urlLicense = "https://gnu.org/licenses/gpl-3.0.html";
urlSupport = "https://helpdesk.onlyoffice.com/";
urlOforms = "https://cmsoforms.onlyoffice.com/api/oforms?populate=*&locale=";
logoUrl = combineUrl(proxyURL, "/static/images/nav.logo.opened.react.svg");
customNames = {
id: "Common",
@ -224,6 +225,10 @@ class SettingsStore {
) {
this.getCurrentCustomSchema(origSettings.nameSchemaId);
}
if (origSettings.tenantAlias) {
this.setTenantAlias(origSettings.tenantAlias);
}
};
init = async () => {
@ -253,8 +258,13 @@ class SettingsStore {
this.isLoaded = isLoaded;
};
setCultures = (cultures) => {
this.cultures = cultures;
};
getPortalCultures = async () => {
this.cultures = await api.settings.getPortalCultures();
const cultures = await api.settings.getPortalCultures();
this.setCultures(cultures);
};
setIsEncryptionSupport = (isEncryptionSupport) => {
@ -431,19 +441,28 @@ class SettingsStore {
this.buildVersionInfo.documentServer = "6.4.1";
};
changeTheme = () => {
const currentTheme =
JSON.stringify(this.theme) === JSON.stringify(Base) ? Dark : Base;
localStorage.setItem(
"theme",
JSON.stringify(this.theme) === JSON.stringify(Base) ? "Dark" : "Base"
);
this.theme = currentTheme;
};
setTheme = (key) => {
let theme = null;
switch (key) {
case ThemeKeys.Base:
case ThemeKeys.BaseStr:
theme = ThemeKeys.BaseStr;
break;
case ThemeKeys.Dark:
case ThemeKeys.DarkStr:
theme = ThemeKeys.DarkStr;
break;
case ThemeKeys.System:
case ThemeKeys.SystemStr:
default:
theme =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
? ThemeKeys.DarkStr
: ThemeKeys.BaseStr;
}
setTheme = (theme) => {
this.theme = themes[theme];
localStorage.setItem("theme", theme);
};
setMailDomainSettings = async (data) => {
@ -453,6 +472,10 @@ class SettingsStore {
return res;
};
setTenantAlias = (tenantAlias) => {
this.tenantAlias = tenantAlias;
};
getIpRestrictions = async () => {
const res = await api.settings.getIpRestrictions();
this.ipRestrictions = res?.map((el) => el.ip);

View File

@ -42,10 +42,8 @@ class TfaStore {
this.backupCodes = codes;
};
getTfaConfirmLink = async (res) => {
if (res) {
return await api.settings.getTfaConfirmLink();
}
getTfaConfirmLink = async () => {
return await api.settings.getTfaConfirmLink();
};
getSecretKeyAndQR = async (confirmKey) => {

View File

@ -48,6 +48,18 @@ class UserStore {
this.setIsLoading(false);
};
changeTheme = async (key) => {
this.setIsLoading(true);
const { theme } = await api.people.changeTheme(key);
this.user.theme = theme;
this.setIsLoading(false);
return theme;
};
setUserIsUpdate = (isUpdate) => {
//console.log("setUserIsUpdate");
this.userIsUpdate = isUpdate;

View File

@ -324,3 +324,26 @@ export function convertLanguage(key) {
return key;
}
import FilesFilter from "../api/files/filter";
export function getFolderOptions(folderId, filter) {
if (folderId && typeof folderId === "string") {
folderId = encodeURIComponent(folderId.replace(/\\\\/g, "\\"));
}
const params =
filter && filter instanceof FilesFilter
? `${folderId}?${filter.toApiUrlParams()}`
: folderId;
const options = {
method: "get",
url: `/files/${params}`,
};
return options;
}
export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@ -13,21 +13,33 @@ import {
StyledAccessRightItemContent,
StyledAccessRightItemTitleAndBadge,
} from "./styled-accessright.js";
import { ReactSVG } from "react-svg";
const AccessRightSelect = ({ options, onSelect, selectedOption, ...props }) => {
const AccessRightSelect = ({
options,
onSelect,
advancedOptions,
selectedOption,
...props
}) => {
const [currentItem, setCurrentItem] = useState(selectedOption);
function onSelectCurrentItem(e) {
const key = e.currentTarget.dataset.key;
const key = +e.currentTarget.dataset.key;
const item = options.find((el) => {
return el.key === key;
});
if (item) {
setCurrentItem(item);
onSelect && onSelect(item);
}
}
React.useEffect(() => {
setCurrentItem(selectedOption);
}, [selectedOption]);
const formatToAccessRightItem = (data) => {
return (
<>
@ -67,12 +79,15 @@ const AccessRightSelect = ({ options, onSelect, selectedOption, ...props }) => {
return (
<StyledAccessRightWrapper>
<StyledAccessRightIcon src={currentItem?.icon} />
<ReactSVG className="access-right__icon" src={currentItem?.icon} />
<ComboBox
advancedOptions={formatToAccessRightItem(options)}
advancedOptions={
!!advancedOptions ? advancedOptions : formatToAccessRightItem(options)
}
onSelect={onSelectCurrentItem}
directionX="left"
directionY="bottom"
opened
opened={false}
noBorder
options={[]}
scaled={false}
@ -92,8 +107,10 @@ AccessRightSelect.propTypes = {
options: PropTypes.arrayOf(PropTypes.object).isRequired,
/** Will be triggered whenever an AccessRightSelect is selected option */
onSelect: PropTypes.func,
/** List of advanced options */
advancedOptions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
/** The option that is selected by default */
selectedOption: PropTypes.object,
};
export default AccessRightSelect;
export default React.memo(AccessRightSelect);

View File

@ -3,14 +3,28 @@ import Base from "../themes/base";
const StyledAccessRightWrapper = styled.div`
display: flex;
align-items: center;
.access-right__icon {
display: flex;
align-items: center;
path {
fill: ${(props) => props.theme.dropDownItem.icon.color};
}
}
.combo-button {
padding-left: 4px;
}
`;
StyledAccessRightWrapper.defaultProps = { theme: Base };
const StyledAccessRightIcon = styled.img`
margin-right: 4.18px;
margin-right: 4px;
`;
const StyledAccessRightItem = styled.div`
width: 424px;
width: auto;
display: flex;
align-items: flex-start;

View File

@ -81,8 +81,10 @@ class AvatarEditor extends React.Component {
};
onClose = () => {
this.setState({ visible: false });
this.props.onClose();
if (this.state.visible) {
this.setState({ visible: false });
this.props.onClose();
}
};
componentDidUpdate(prevProps) {
@ -94,6 +96,20 @@ class AvatarEditor extends React.Component {
}
}
keyPress = (e) => {
if (e.keyCode === 13) {
this.onSaveButtonClick();
}
};
componentDidMount() {
addEventListener("keydown", this.keyPress, false);
}
componentWillUnmount() {
removeEventListener("keydown", this.keyPress, false);
}
render() {
const {
displayType,

View File

@ -56,7 +56,7 @@ class Backdrop extends React.Component {
modifyClassName = () => {
const { className } = this.props;
let modifiedClass = "backdrop-active";
let modifiedClass = "backdrop-active not-selectable";
if (className) {
if (typeof className !== "string") {

View File

@ -37,7 +37,14 @@ Button.propTypes = {
/** Size of button.
The normal size equals 36px and 40px in height on the Desktop and Touchcreen devices. */
size: PropTypes.oneOf(["extraSmall", "small", "normal", "medium"]),
size: PropTypes.oneOf([
"extraSmall",
"small",
"normal",
"medium",
"normalDesktop",
"normalTouchscreen",
]),
/** Scale width of button to 100% */
scale: PropTypes.bool,
/** Icon node element */

View File

@ -156,8 +156,7 @@ const StyledButton = styled(ButtonWrapper).attrs((props) => ({
: props.theme.button.border.base};
${(props) => props.scale && `width: 100%;`};
min-width: ${(props) =>
props.minwidth ? props.minwidth : props.theme.button.minWidth[props.size]};
min-width: ${(props) => props.minwidth && props.minwidth};
padding: ${(props) => `${props.theme.button.padding[props.size]}`};
@ -214,6 +213,8 @@ const StyledButton = styled(ButtonWrapper).attrs((props) => ({
}
.button-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
@ -222,7 +223,6 @@ const StyledButton = styled(ButtonWrapper).attrs((props) => ({
.icon {
width: auto;
height: 100%;
display: flex;
align-items: center;
}

View File

@ -83,7 +83,9 @@ const CatalogItem = (props) => {
)}
</StyledCatalogItemImg>
{showText && <StyledCatalogItemText>{text}</StyledCatalogItemText>}
{showText && (
<StyledCatalogItemText noSelect={true}>{text}</StyledCatalogItemText>
)}
{showBadge && showText && (
<StyledCatalogItemBadgeWrapper

View File

@ -250,6 +250,10 @@ const StyledCatalogItemSibling = styled.div`
css`
min-height: ${(props) => props.theme.catalogItem.container.tablet.height};
max-height: ${(props) => props.theme.catalogItem.container.tablet.height};
&:hover {
background-color: transparent;
}
`}
${(props) => props.isDragging && draggingSiblingCss}

View File

@ -62,6 +62,10 @@ class Checkbox extends React.Component {
this.props.onChange && this.props.onChange(e);
}
onClick(e) {
return e.preventDefault();
}
render() {
//console.log("Checkbox render");
const {
@ -75,39 +79,49 @@ class Checkbox extends React.Component {
title,
truncate,
name,
helpButton,
} = this.props;
return (
<StyledLabel
id={id}
style={style}
isDisabled={isDisabled}
isIndeterminate={isIndeterminate}
className={className}
title={title}
>
<HiddenInput
name={name}
type="checkbox"
checked={this.state.checked}
<>
<StyledLabel
id={id}
style={style}
isDisabled={isDisabled}
ref={this.ref}
value={value}
onChange={this.onInputChange}
/>
<RenderCheckboxIcon {...this.props} />
{this.props.label && (
<Text
as="span"
title={title}
isIndeterminate={isIndeterminate}
className={className}
title={title}
>
<HiddenInput
name={name}
type="checkbox"
checked={this.state.checked}
isDisabled={isDisabled}
truncate={truncate}
className="checkbox-text"
>
{label}
</Text>
)}
</StyledLabel>
ref={this.ref}
value={value}
onChange={this.onInputChange}
/>
<RenderCheckboxIcon {...this.props} />
<div className="wrapper">
{this.props.label && (
<Text
as="span"
title={title}
isDisabled={isDisabled}
truncate={truncate}
className="checkbox-text"
>
{label}
</Text>
)}
{helpButton && (
<span className="help-button" onClick={this.onClick}>
{helpButton}
</span>
)}
</div>
</StyledLabel>
</>
);
}
}
@ -137,11 +151,16 @@ Checkbox.propTypes = {
title: PropTypes.string,
/** Disables word wrapping */
truncate: PropTypes.bool,
/** Help button render */
helpButton: PropTypes.any,
isLogin: PropTypes.bool,
};
Checkbox.defaultProps = {
isChecked: false,
truncate: false,
isLogin: false,
};
export default React.memo(Checkbox);

View File

@ -3,7 +3,14 @@ import Base from "../themes/base";
const StyledLabel = styled.label`
display: flex;
align-items: center;
${(props) =>
!props.isLogin &&
css`
align-items: "center";
`};
justify-content: ${(props) => props.isLogin && "center"};
position: relative;
margin: 0;
@ -107,11 +114,21 @@ const StyledLabel = styled.label`
`}
}
.wrapper {
display: inline-block;
}
.checkbox-text {
color: ${(props) =>
props.isDisabled
? props.theme.text.disableColor
: props.theme.text.color};
margin-top: -2px;
}
.help-button {
display: inline-block;
margin-left: 4px;
}
`;
StyledLabel.defaultProps = { theme: Base };

View File

@ -83,10 +83,12 @@ class ContextMenu extends Component {
hide = (e) => {
this.currentEvent = e;
this.setState({ visible: false, reshow: false, changeView: false }, () => {
if (this.props.onHide) {
this.props.onHide(this.currentEvent);
}
this.props.onHide && this.props.onHide(e);
this.setState({
visible: false,
reshow: false,
changeView: false,
});
};

View File

@ -21,6 +21,7 @@ const styledMobileView = css`
max-height: ${(props) => props.theme.newContextMenu.devices.maxHeight};
left: ${(props) => props.theme.newContextMenu.devices.left};
bottom: ${(props) => props.theme.newContextMenu.devices.bottom};
border-radius: ${(props) => props.theme.newContextMenu.mobileBorderRadius};
`;
const StyledContextMenu = styled.div`

View File

@ -62,7 +62,7 @@ class DropDown extends React.PureComponent {
componentDidUpdate(prevProps) {
if (this.props.open !== prevProps.open) {
if (this.props.open) {
this.props.enableOnClickOutside();
!isMobile && this.props.enableOnClickOutside(); //fixed main-button-mobile click, remove !isMobile if have dd problem
this.bindDocumentResizeListener();
if (this.props.isDefaultMode) {
return this.checkPositionPortal();
@ -114,13 +114,14 @@ class DropDown extends React.PureComponent {
const rects = this.dropDownRef.current.getBoundingClientRect();
const parentRects = forwardedRef?.current?.getBoundingClientRect();
const container = DomHelpers.getViewport();
const dimensions = parentRects
? {
toTopCorner: parentRects.top,
parentHeight: parentRects.height,
containerHeight: parentRects.top,
containerHeight: !parentRects.top,
}
: {
toTopCorner: rects.top,
@ -352,6 +353,7 @@ DropDownContainer.propTypes = {
isDefaultMode: PropTypes.bool,
/** Needed to open correctly people and group selector when the section width is small */
smallSectionWidth: PropTypes.bool,
/** It is necessary when we explicitly set the direction, disables check position */
fixedDirection: PropTypes.bool,
};

View File

@ -58,8 +58,7 @@ const EmptyContentBody = styled.div`
}
}
@media ${mobile} {
min-width: 343px;
@media (max-width: 428px) {
grid-template-areas:
"img img img"
"headerText headerText headerText"

View File

@ -29,6 +29,8 @@ class FieldContainer extends React.Component {
errorMessage,
errorColor,
errorMessageWidth,
offsetRight,
tooltipMaxWidth,
} = this.props;
return (
@ -53,7 +55,9 @@ class FieldContainer extends React.Component {
<HelpButton
tooltipContent={tooltipContent}
place={place}
offsetRight={offsetRight}
helpButtonHeaderContent={helpButtonHeaderContent}
tooltipMaxWidth={tooltipMaxWidth}
/>
)}
</div>
@ -109,6 +113,8 @@ FieldContainer.propTypes = {
id: PropTypes.string,
/** Accepts css style */
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
offsetRight: PropTypes.number,
tooltipMaxWidth: PropTypes.string,
};
FieldContainer.defaultProps = {
@ -116,6 +122,7 @@ FieldContainer.defaultProps = {
labelVisible: true,
maxLabelWidth: "110px",
errorMessageWidth: "293px",
offsetRight: 0,
};
export default FieldContainer;

View File

@ -106,6 +106,7 @@ class HelpButton extends React.Component {
offsetLeft={offsetLeft}
afterShow={this.afterShow}
afterHide={this.afterHide}
maxWidth={tooltipMaxWidth}
>
{tooltipContent}
</Tooltip>

View File

@ -84,7 +84,8 @@ const StyledLinkWithDropdown = styled(SimpleLinkWithDropdown)`
text-decoration: none;
user-select: none;
position: relative;
display: inline-grid;
display: flex;
align-items: center;
padding-right: ${(props) => props.theme.linkWithDropdown.paddingRight};

View File

@ -16,7 +16,7 @@ import IconButton from "../icon-button";
import Button from "../button";
import Text from "../text";
import Scrollbar from "@appserver/components/scrollbar";
import { isMobile, isTablet } from "react-device-detect";
import { isMobile } from "react-device-detect";
import Backdrop from "../backdrop";
const ProgressBarMobile = ({
@ -98,7 +98,7 @@ const MainButtonMobile = (props) => {
const [isOpen, setIsOpen] = useState(opened);
const [isUploading, setIsUploading] = useState(false);
const [height, setHeight] = useState("90vh");
const [height, setHeight] = useState("calc(100% - 48px)");
const divRef = useRef();
@ -111,7 +111,10 @@ const MainButtonMobile = (props) => {
useEffect(() => {
let height =
divRef?.current?.getBoundingClientRect()?.height || window.innerHeight;
height >= window.innerHeight ? setHeight("90vh") : setHeight(height + "px");
height >= window.innerHeight
? setHeight("calc(100% - 48px)")
: setHeight(height + "px");
}, [isOpen, isOpenButton, window.innerHeight, isUploading]);
const ref = useRef();
@ -127,7 +130,6 @@ const MainButtonMobile = (props) => {
};
const onMainButtonClick = (e) => {
if (isOpen && ref.current.contains(e.target)) return;
toggle(!isOpen);
};
@ -191,8 +193,8 @@ const MainButtonMobile = (props) => {
/>
))}
</StyledProgressContainer>
<StyledButtonOptions isOpenButton={isOpenButton}>
{isOpenButton && buttonOptions
<StyledButtonOptions>
{buttonOptions
? buttonOptions.map((option) =>
option.isSeparator ? (
<div key={option.key} className="separator-wrapper">
@ -213,20 +215,6 @@ const MainButtonMobile = (props) => {
)
: ""}
</StyledButtonOptions>
{withButton && (
<StyledButtonWrapper
isUploading={isUploading}
isOpenButton={isOpenButton}
>
<Button
label={title}
className="action-mobile-button"
primary
size="medium"
onClick={onUploadClick}
/>
</StyledButtonWrapper>
)}
</div>
);
};
@ -249,13 +237,15 @@ const MainButtonMobile = (props) => {
manualWidth={manualWidth || "400px"}
directionY="top"
directionX="right"
isMobile={isMobile || isTablet}
isMobile={isMobile}
fixedDirection={true}
heightProp={height}
sectionWidth={sectionWidth}
isDefaultMode={false}
>
{isMobile || isTablet ? (
{isMobile ? (
<Scrollbar
style={{ position: "absolute" }}
scrollclass="section-scroll"
stype="mediumBlack"
ref={dropDownRef}

View File

@ -12,6 +12,8 @@ const StyledFloatingButton = styled(FloatingButton)`
z-index: 1010;
background: ${(props) => props.theme.mainButtonMobile.buttonColor} !important;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
.circle__mask + div {
display: flex;
align-items: center;
@ -56,7 +58,9 @@ const StyledFloatingButton = styled(FloatingButton)`
StyledFloatingButton.defaultProps = { theme: Base };
const mobileDropDown = css`
width: ${(props) => props.theme.mainButtonMobile.dropDown.mobile.width};
@media (max-width: 428px) {
width: ${(props) => props.theme.mainButtonMobile.dropDown.mobile.width};
}
right: ${(props) => props.theme.mainButtonMobile.dropDown.mobile.right};
bottom: ${(props) => props.theme.mainButtonMobile.dropDown.mobile.bottom};
@ -65,7 +69,7 @@ const mobileDropDown = css`
const StyledDropDown = styled(DropDown)`
position: ${(props) => props.theme.mainButtonMobile.dropDown.position};
width: ${(props) => props.theme.mainButtonMobile.dropDown.width};
max-width: calc(100vw - 64px);
max-width: calc(100vw - 48px);
right: ${(props) => props.theme.mainButtonMobile.dropDown.right};
bottom: ${(props) => props.theme.mainButtonMobile.dropDown.bottom};
@ -134,7 +138,6 @@ const StyledDropDownItem = styled(DropDownItem)`
`;
const StyledButtonOptions = styled.div`
display: ${(props) => !props.isOpenButton && "none"};
padding: 16px 0;
background-color: ${(props) =>
props.theme.mainButtonMobile.buttonOptions.backgroundColor};

View File

@ -81,6 +81,10 @@ class ModalDialog extends React.Component {
componentDidMount() {
window.addEventListener("resize", this.throttledResize);
window.addEventListener("keyup", this.onKeyPress);
window.onpopstate = () => {
this.props.onClose();
};
}
componentWillUnmount() {
@ -163,21 +167,29 @@ class ModalDialog extends React.Component {
<Loaders.DialogLoader bodyHeight={modalLoaderBodyHeight} />
) : (
<>
<StyledHeader>
<Heading className="heading" size="medium" truncate={true}>
{header ? header.props.children : null}
</Heading>
{!withoutCloseButton && (
<CloseButton
className="modal-dialog-button_close"
onClick={onClose}
></CloseButton>
)}
</StyledHeader>
{header && (
<StyledHeader>
<Heading
className="heading"
size="medium"
truncate={true}
>
{header ? header.props.children : null}
</Heading>
{!withoutCloseButton && (
<CloseButton
className="modal-dialog-button_close"
onClick={onClose}
></CloseButton>
)}
</StyledHeader>
)}
<BodyBox paddingProp={modalBodyPadding}>
{body ? body.props.children : null}
</BodyBox>
<Box>{footer ? footer.props.children : null}</Box>
<Box className="modal-dialog-modal-footer">
{footer ? footer.props.children : null}
</Box>
</>
)}
</Content>

View File

@ -1,6 +1,6 @@
{
"name": "@appserver/components",
"version": "1.1.1",
"version": "1.2.0",
"private": true,
"scripts": {
"build": "echo 'skip it'",

View File

@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_20597_65864)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.84646 0.979492L5.22299 14.5025L7.15485 15.0201L10.7783 1.49713L8.84646 0.979492ZM2.41419 7.99916L5.20702 5.20614L3.79276 3.79197L0.292868 7.2921C-0.0976332 7.68263 -0.0976212 8.31578 0.292895 8.7063L3.79278 12.2062L5.20699 10.7919L2.41419 7.99916ZM13.5857 8.00004L10.7928 5.20714L12.207 3.79292L15.707 7.29293C15.8945 7.48047 15.9999 7.73482 15.9999 8.00004C15.9999 8.26526 15.8945 8.51961 15.707 8.70715L12.2065 12.2076L10.7923 10.7934L13.5857 8.00004Z" fill="#333333"/>
</g>
<defs>
<clipPath id="clip0_20597_65864">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 777 B

View File

@ -1,3 +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="M9.85498 16L1.15188 16C0.518827 16 0.00382324 15.485 0.00378789 14.8519L0.00378713 6.14776C0.00378708 5.51468 0.518826 4.99964 1.15187 4.99964L4.01036 4.99964L4.01036 9.96112C4.01036 11.1721 4.75801 11.9963 5.96898 11.9963L11.0031 11.9963L11.0031 14.8519C11.0031 15.485 10.4881 16 9.85498 16ZM14.8703 11.0013L6.12976 11.0013C5.50682 11.0013 4.99997 10.4945 5 9.87155L5 1.12969C5 0.506788 5.50679 9.17346e-07 6.12973 8.62887e-07L14.8703 9.87639e-08C15.4932 4.43048e-08 16 0.506787 16 1.12973L16 9.87155C16 10.4945 15.4932 11.0013 14.8703 11.0013Z" fill="#A3A9AE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.14502 -1.07443e-06L14.8481 -2.59613e-06C15.4812 -2.70681e-06 15.9962 0.515037 15.9962 1.14808L15.9962 9.85224C15.9962 10.4853 15.4812 11.0004 14.8481 11.0004L11.9896 11.0004L11.9896 6.03888C11.9896 4.82791 11.242 4.00369 10.031 4.00369L4.99694 4.00369L4.99694 1.14809C4.99693 0.515038 5.51194 -9.63738e-07 6.14502 -1.07443e-06ZM1.12973 4.99872L9.87024 4.99872C10.4932 4.99872 11 5.50551 11 6.12845L11 14.8703C11 15.4932 10.4932 16 9.87028 16L1.12973 16C0.50679 16 3.70837e-05 15.4932 2.6e-06 14.8703L1.07154e-06 6.12845C9.62618e-07 5.50551 0.506823 4.99872 1.12973 4.99872Z" fill="#A3A9AE"/>
</svg>

Before

Width:  |  Height:  |  Size: 716 B

After

Width:  |  Height:  |  Size: 747 B

View File

@ -108,7 +108,6 @@ RadioButtonGroup.defaultProps = {
selected: undefined,
spacing: "15px",
orientation: "horizontal",
width: "100%",
};
export default RadioButtonGroup;

View File

@ -17,7 +17,7 @@ const StyledDiv = styled(ClearDiv)`
`) ||
(props.orientation === "vertical" &&
css`
display: block;
display: inline-block;
`)};
width: ${(props) => props.width};

View File

@ -47,6 +47,7 @@ class SaveCancelButtons extends React.Component {
isFirstWelcomePageSettings,
className,
id,
isSaving,
} = this.props;
const cancelButtonDisabled =
@ -69,11 +70,12 @@ class SaveCancelButtons extends React.Component {
onClick={onSaveClick}
label={saveButtonLabel}
minwidth={displaySettings && "auto"}
isLoading={isSaving}
/>
<Button
className="cancel-button"
size="normal"
isDisabled={cancelButtonDisabled}
isDisabled={cancelButtonDisabled || isSaving}
onClick={onCancelClick}
label={cancelButtonLabel}
minwidth={displaySettings && "auto"}
@ -109,6 +111,7 @@ SaveCancelButtons.propTypes = {
hasScroll: PropTypes.bool,
minwidth: PropTypes.string,
isFirstWelcomePageSettings: PropTypes.string,
isSaving: PropTypes.bool,
};
SaveCancelButtons.defaultProps = {

View File

@ -39,7 +39,7 @@ export class CustomScrollbars extends React.Component {
}
CustomScrollbars.defaultProps = {
stype: "smallBlack",
stype: "mediumBlack",
};
const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => (

View File

@ -114,7 +114,7 @@ Scrollbar.propTypes = {
};
Scrollbar.defaultProps = {
stype: "smallBlack",
stype: "mediumBlack",
};
export default Scrollbar;

View File

@ -5,7 +5,7 @@ import StyledButton from "./styled-selector-add-button";
import IconButton from "../icon-button";
const SelectorAddButton = (props) => {
const { isDisabled, title, className, id, style } = props;
const { isDisabled, title, className, id, style, iconName } = props;
const onClick = (e) => {
!isDisabled && props.onClick && props.onClick(e);
@ -22,7 +22,7 @@ const SelectorAddButton = (props) => {
>
<IconButton
size={14}
iconName="/static/images/actions.header.touch.react.svg"
iconName={iconName}
isFill={true}
isDisabled={isDisabled}
isClickable={!isDisabled}
@ -44,10 +44,13 @@ SelectorAddButton.propTypes = {
id: PropTypes.string,
/** Accepts css style */
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
/** Specifies the icon name */
iconName: PropTypes.string,
};
SelectorAddButton.defaultProps = {
isDisabled: false,
iconName: "/static/images/actions.header.touch.react.svg",
};
export default SelectorAddButton;

View File

@ -12,8 +12,9 @@ import {
StyledSubmenuItems,
StyledSubmenuItemText,
} from "./styled-submenu";
import LoaderSubmenu from "./loader";
const Submenu = ({ data, startSelect = 0, onSelect, ...rest }) => {
const Submenu = ({ data, startSelect = 0, onSelect, isLoading, ...rest }) => {
if (!data) return null;
const [currentItem, setCurrentItem] = useState(
@ -74,29 +75,43 @@ const Submenu = ({ data, startSelect = 0, onSelect, ...rest }) => {
return (
<StyledSubmenu {...rest}>
<StyledSubmenuItems ref={submenuItemsRef} role="list">
{data.map((d) => {
const isActive = d.id === currentItem.id;
return (
<StyledSubmenuItem key={d.id} id={d.id} onClick={selectSubmenuItem}>
<StyledSubmenuItemText>
<Text
color={isActive ? "#316DAA" : "#657077"}
fontSize="13px"
fontWeight="600"
truncate={false}
>
{d.name}
</Text>
</StyledSubmenuItemText>
<StyledSubmenuItemLabel color={isActive ? "#316DAA" : "none"} />
</StyledSubmenuItem>
);
})}
</StyledSubmenuItems>
<StyledSubmenuBottomLine />
{isLoading ? (
<LoaderSubmenu />
) : (
<>
<div className="sticky">
<StyledSubmenuItems ref={submenuItemsRef} role="list">
{data.map((d) => {
const isActive = d.id === currentItem.id;
return (
<StyledSubmenuItem
key={d.id}
id={d.id}
onClick={selectSubmenuItem}
>
<StyledSubmenuItemText>
<Text
color={isActive ? "#316DAA" : "#657077"}
fontSize="13px"
fontWeight="600"
truncate={false}
>
{d.name}
</Text>
</StyledSubmenuItemText>
<StyledSubmenuItemLabel
color={isActive ? "#316DAA" : "none"}
/>
</StyledSubmenuItem>
);
})}
</StyledSubmenuItems>
<StyledSubmenuBottomLine />
</div>
<div className="sticky-indent"></div>
</>
)}
<StyledSubmenuContentWrapper>
{currentItem.content}
</StyledSubmenuContentWrapper>
@ -108,6 +123,7 @@ Submenu.propTypes = {
data: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired,
startSelect: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
onSelect: PropTypes.func,
isLoading: PropTypes.bool,
};
export default Submenu;

View File

@ -1,5 +1,5 @@
import React from "react";
import styled from "styled-components";
import styled, { css } from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
import { isTablet } from "react-device-detect";

View File

@ -16,12 +16,23 @@ export const StyledSubmenu = styled.div`
display: inline-block;
position: absolute;
}
.sticky {
position: sticky;
top: 0;
background: ${(props) => props.theme.submenu.backgroundColor};
z-index: 1;
}
.sticky-indent {
height: 15px;
}
`;
export const StyledSubmenuBottomLine = styled.div`
height: 1px;
width: 100%;
margin: -1px 0 15px 0;
margin-top: -1px;
background: ${(props) => props.theme.submenu.lineColor};
`;

View File

@ -77,13 +77,21 @@ StyledButton.defaultProps = { theme: Base };
const GroupMenuItem = ({ item }) => {
const { label, disabled, onClick, iconUrl, title } = item;
return (
<StyledButton
label={label}
title={title || label}
isDisabled={disabled}
onClick={onClick}
icon={<ReactSVG src={iconUrl} className="combo-button_selected-icon" />}
/>
<>
{disabled ? (
<></>
) : (
<StyledButton
label={label}
title={title || label}
isDisabled={disabled}
onClick={onClick}
icon={
<ReactSVG src={iconUrl} className="combo-button_selected-icon" />
}
/>
)}
</>
);
};

View File

@ -41,11 +41,18 @@ const StyledTableContainer = styled.div`
props.theme.tableContainer.header.borderImageSource};
border-top: 0;
}
.lengthen-header {
border-bottom: ${(props) => props.theme.tableContainer.header.borderBottom};
border-image-source: none;
}
.hotkeys-lengthen-header {
border-bottom: ${(props) =>
props.theme.tableContainer.header.hotkeyBorderBottom};
border-image-source: none;
}
.content-container {
overflow: hidden;
}
@ -79,7 +86,7 @@ const StyledTableGroupMenu = styled.div`
align-items: center;
width: 100%;
z-index: 199;
height: 53px;
height: 52px;
box-shadow: ${(props) => props.theme.tableContainer.groupMenu.boxShadow};
border-radius: 0px 0px 6px 6px;
margin: 0;

View File

@ -168,7 +168,7 @@ class TabContainer extends Component {
selected={activeTab === index}
isDisabled={isDisabled}
>
<Text className="title_style" fontSize="13px">
<Text fontWeight={600} className="title_style" fontSize="13px">
{item.title}
</Text>
</Label>

Some files were not shown because too many files have changed in this diff Show More