Merge branch 'feature/virtual-rooms-1.2' of github.com:ONLYOFFICE/AppServer into feature/confirm-test

# Conflicts:
#	yarn.lock
This commit is contained in:
Viktor Fomin 2022-03-18 22:50:34 +03:00
commit bd7389fe27
1051 changed files with 25307 additions and 10299 deletions

View File

@ -31,14 +31,11 @@ if [ "$DOCUMENT_SERVER_INSTALLED" = "false" ]; then
echo ${package_sysname}-documentserver $DS_COMMON_NAME/ds-port select $DS_PORT | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-pwd select $DS_DB_PWD | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-user $DS_DB_USER | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-name $DS_DB_NAME | sudo debconf-set-selections
echo ${package_sysname}-documentserver-de $DS_COMMON_NAME/jwt-enabled select ${DS_JWT_ENABLED} | sudo debconf-set-selections
echo ${package_sysname}-documentserver-de $DS_COMMON_NAME/jwt-secret select ${DS_JWT_SECRET} | sudo debconf-set-selections
echo ${package_sysname}-documentserver-de $DS_COMMON_NAME/jwt-header select ${DS_JWT_HEADER} | sudo debconf-set-selections
echo ${package_sysname}-documentserver-ee $DS_COMMON_NAME/jwt-enabled select ${DS_JWT_ENABLED} | sudo debconf-set-selections
echo ${package_sysname}-documentserver-ee $DS_COMMON_NAME/jwt-secret select ${DS_JWT_SECRET} | sudo debconf-set-selections
echo ${package_sysname}-documentserver-ee $DS_COMMON_NAME/jwt-header select ${DS_JWT_HEADER} | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-user select $DS_DB_USER | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-name select $DS_DB_NAME | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/jwt-enabled select ${DS_JWT_ENABLED} | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/jwt-secret select ${DS_JWT_SECRET} | sudo debconf-set-selections
echo ${package_sysname}-documentserver $DS_COMMON_NAME/jwt-header select ${DS_JWT_HEADER} | sudo debconf-set-selections
apt-get install -yq ${package_sysname}-documentserver
elif [ "$UPDATE" = "true" ] && [ "$DOCUMENT_SERVER_INSTALLED" = "true" ]; then
@ -65,55 +62,17 @@ else
systemctl reload nginx
fi
APPSERVER_INSTALLED_VERSION=$(apt-cache policy ${product} | awk 'NR==2{print $2}')
APPSERVER_LATEST_VERSION=$(apt-cache policy ${product} | awk 'NR==3{print $2}')
if [ "$APPSERVER_INSTALLED_VERSION" != "$APPSERVER_LATEST_VERSION" ]; then
APPSERVER_NEED_UPDATE="true"
fi
if [ "$APPSERVER_INSTALLED" = "false" ]; then
echo ${product} ${product}/db-pwd select $MYSQL_SERVER_PASS | sudo debconf-set-selections
echo ${product} ${product}/db-user select $MYSQL_SERVER_USER | sudo debconf-set-selections
echo ${product} ${product}/db-name select $MYSQL_SERVER_DB_NAME | sudo debconf-set-selections
apt-get install -y ${product} || true #Fix error 'Failed to fetch'
apt-get install -y ${product}
elif [ "$APPSERVER_NEED_UPDATE" = "true" ]; then
ENVIRONMENT="$(cat /lib/systemd/system/${product}-api.service | grep -oP 'ENVIRONMENT=\K.*')"
USER_CONNECTIONSTRING=$(json -f /etc/onlyoffice/${product}/appsettings.$ENVIRONMENT.json ConnectionStrings.default.connectionString)
MYSQL_SERVER_HOST=$(echo $USER_CONNECTIONSTRING | grep -oP 'Server=\K.*' | grep -o '^[^;]*')
MYSQL_SERVER_DB_NAME=$(echo $USER_CONNECTIONSTRING | grep -oP 'Database=\K.*' | grep -o '^[^;]*')
MYSQL_SERVER_USER=$(echo $USER_CONNECTIONSTRING | grep -oP 'User ID=\K.*' | grep -o '^[^;]*')
MYSQL_SERVER_PORT=$(echo $USER_CONNECTIONSTRING | grep -oP 'Port=\K.*' | grep -o '^[^;]*')
MYSQL_SERVER_PASS=$(echo $USER_CONNECTIONSTRING | grep -oP 'Password=\K.*' | grep -o '^[^;]*')
elif [ "$UPDATE" = "true" ] && [ "$APPSERVER_INSTALLED" = "true" ]; then
apt-get install -o DPkg::options::="--force-confnew" -y --only-upgrade ${product} elasticsearch=${ELASTIC_VERSION}
fi
if [ "${APPSERVER_INSTALLED}" = "false" ] || [ "${APPSERVER_NEED_UPDATE}" = "true" ]; then
expect << EOF
set timeout -1
log_user 1
if { "${UPDATE}" == "true" } {
spawn ${product}-configuration.sh -e ${ENVIRONMENT}
} else {
spawn ${product}-configuration.sh
}
expect -re "Database host:"
send "\025$MYSQL_SERVER_HOST\r"
expect -re "Database name:"
send "\025$MYSQL_SERVER_DB_NAME\r"
expect -re "Database user:"
send "\025$MYSQL_SERVER_USER\r"
expect -re "Database password:"
send "\025$MYSQL_SERVER_PASS\r"
expect eof
EOF
APPSERVER_INSTALLED="true";
fi
echo ""
echo "$RES_INSTALL_SUCCESS"
echo "$RES_QUESTIONS"

View File

@ -65,7 +65,7 @@ if [ "$(ls "$PRODUCT_DIR/services/kafka" 2> /dev/null)" == "" ]; then
KAFKA_ARCHIVE=$(curl https://downloads.apache.org/kafka/$KAFKA_VERSION/ | grep -Eo "kafka_2.[0-9][0-9]-$KAFKA_VERSION.tgz" | tail -1)
curl https://downloads.apache.org/kafka/$KAFKA_VERSION/$KAFKA_ARCHIVE -O
tar xzf $KAFKA_ARCHIVE --strip 1 && rm -rf $KAFKA_ARCHIVE
chown -R kafka ${PRODUCT_DIR}/services/kafka
chown -R kafka ${PRODUCT_DIR}/services/kafka/
cd -
fi
@ -83,6 +83,7 @@ Restart=on-abnormal
[Install]
WantedBy=multi-user.target
END
systemctl start zookeeper
fi
if [ ! -e /lib/systemd/system/kafka.service ]; then
@ -99,6 +100,7 @@ Restart=on-abnormal
[Install]
WantedBy=multi-user.target
END
systemctl start kafka
fi
if ! dpkg -l | grep -q "mysql-server"; then

View File

@ -97,6 +97,13 @@ while [ "$1" != "" ]; do
fi
;;
-ess | --elasticsheme )
if [ "$2" != "" ]; then
ELK_SHEME=$2
shift
fi
;;
-esh | --elastichost )
if [ "$2" != "" ]; then
ELK_HOST=$2
@ -106,7 +113,7 @@ while [ "$1" != "" ]; do
-esp | --elasticport )
if [ "$2" != "" ]; then
ELK_HOST=$2
ELK_PORT=$2
shift
fi
;;
@ -118,6 +125,34 @@ while [ "$1" != "" ]; do
fi
;;
-mysqlh | --mysqlhost )
if [ "$2" != "" ]; then
DB_HOST=$2
shift
fi
;;
-mysqld | --mysqldatabase )
if [ "$2" != "" ]; then
DB_NAME=$2
shift
fi
;;
-mysqlu | --mysqluser )
if [ "$2" != "" ]; then
DB_USER=$2
shift
fi
;;
-mysqlp | --mysqlpassword )
if [ "$2" != "" ]; then
DB_PWD=$2
shift
fi
;;
-? | -h | --help )
echo " Usage: bash ${PRODUCT}-configuration.sh [PARAMETER] [[PARAMETER], ...]"
echo
@ -132,6 +167,10 @@ while [ "$1" != "" ]; do
echo " -zkp, --zookeeperport zookeeper port (default 2181)"
echo " -esh, --elastichost elasticsearch ip"
echo " -esp, --elasticport elasticsearch port (default 9200)"
echo " -mysqlh, --mysqlhost mysql server host"
echo " -mysqld, --mysqldatabase ${PRODUCT} database name"
echo " -mysqlu, --mysqluser ${PRODUCT} database user"
echo " -mysqlp, --mysqlpassword ${PRODUCT} database password"
echo " -e, --environment environment (default 'production')"
echo " -?, -h, --help this help"
echo
@ -203,24 +242,10 @@ input_db_params(){
local def_DB_NAME=$(echo $user_connectionString | grep -oP 'Database=\K.*' | grep -o '^[^;]*')
local def_DB_USER=$(echo $user_connectionString | grep -oP 'User ID=\K.*' | grep -o '^[^;]*')
read -e -p "Database host: " -i "$DB_HOST" DB_HOST
read -e -p "Database name: " -i "$DB_NAME" DB_NAME
read -e -p "Database user: " -i "$DB_USER" DB_USER
read -e -p "Database password: " -s DB_PWD
if [ -z $DB_HOST ]; then
DB_HOST="${def_DB_HOST}";
fi
if [ -z $DB_NAME ]; then
DB_NAME="${def_DB_NAME}";
fi
if [ -z $DB_USER ]; then
DB_USER="${def_DB_USER}";
fi
echo
if [ -z $def_DB_HOST ] && [ -z $DB_HOST ]; then read -e -p "Database host: " -i "$DB_HOST" DB_HOST; fi
if [ -z $def_DB_NAME ] && [ -z $DB_NAME ]; then read -e -p "Database name: " -i "$DB_NAME" DB_NAME; fi
if [ -z $def_DB_USER ] && [ -z $DB_USER ]; then read -e -p "Database user: " -i "$DB_USER" DB_USER; fi
if [ -z $DB_PWD ]; then read -e -p "Database password: " -i "$DB_PWD" DB_PWD; fi
}
establish_mysql_conn(){
@ -575,10 +600,6 @@ elif command -v apt >/dev/null 2>&1; then
DIST="Debian"
PACKAGE_MANAGER="dpkg -l"
MYSQL_PACKAGE="mysql"
mkdir -p /var/log/onlyoffice/appserver/ /etc/onlyoffice/appserver/.private/
chown -R onlyoffice:onlyoffice /var/www/appserver/ /var/log/onlyoffice/appserver/ /etc/onlyoffice/appserver/
chown -R kafka /var/www/appserver/services/kafka/
systemctl restart kafka zookeeper
fi
install_json

View File

@ -0,0 +1,2 @@
/var/log/onlyoffice/appserver
/etc/onlyoffice/appserver/.private

View File

@ -18,3 +18,4 @@ if ! cat /etc/passwd | grep -q "nginx:"; then
fi
usermod -aG onlyoffice,nginx onlyoffice
chown onlyoffice:onlyoffice /var/log/onlyoffice/appserver /var/www/appserver /etc/onlyoffice/appserver

View File

@ -0,0 +1,24 @@
#!/bin/sh -e
set -e
. /usr/share/debconf/confmodule
db_input medium appserver/environment || true
db_input medium appserver/host || true
db_input medium appserver/port || true
db_input medium appserver/kafka-host || true
db_input medium appserver/kafka-port || true
db_input medium appserver/zookeeper-host || true
db_input medium appserver/zookeeper-port || true
db_input medium appserver/elasticsearch-sheme || true
db_input medium appserver/elasticsearch-host || true
db_input medium appserver/elasticsearch-port || true
db_input medium appserver/db-host || true
db_input medium appserver/db-name || true
db_input medium appserver/db-user || true
db_go
db_input critical appserver/db-pwd || true
db_go

View File

@ -1,3 +0,0 @@
#!/bin/sh -e
set -e

View File

@ -0,0 +1,65 @@
#!/bin/sh -e
set -e
. /usr/share/debconf/confmodule
case "$1" in
configure)
db_get appserver/environment || true
ENVIRONMENT="$RET"
db_get appserver/host || true
APP_HOST="$RET"
db_get appserver/port || true
APP_PORT="$RET"
db_get appserver/db-host || true
DB_HOST="$RET"
db_get appserver/db-name || true
DB_NAME="$RET"
db_get appserver/db-user || true
DB_USER="$RET"
db_get appserver/db-pwd || true
DB_PWD="$RET"
db_get appserver/kafka-host || true
KAFKA_HOST="$RET"
db_get appserver/kafka-port || true
KAFKA_PORT="$RET"
db_get appserver/zookeeper-host || true
ZOOKEEPER_HOST="$RET"
db_get appserver/zookeeper-port || true
ZOOKEEPER_PORT="$RET"
db_get appserver/elasticsearch-sheme || true
ELK_SHEME="$RET"
db_get appserver/elasticsearch-host || true
ELK_HOST="$RET"
db_get appserver/elasticsearch-port || true
ELK_PORT="$RET"
db_get onlyoffice/db-host || true
DOCUMENT_SERVER_HOST="$RET"
db_get onlyoffice/ds-port || true
DOCUMENT_SERVER_PORT="$RET"
bash /usr/bin/appserver-configuration.sh -e $ENVIRONMENT -mysqlh $DB_HOST -mysqld $DB_NAME -mysqlu $DB_USER -mysqlp $DB_PWD -ash $APP_HOST -asp $APP_PORT \
-dsh $DOCUMENT_SERVER_HOST -dsp $DOCUMENT_SERVER_PORT -kh $KAFKA_HOST -kp $KAFKA_PORT -zkh $ZOOKEEPER_HOST -zkp $ZOOKEEPER_PORT -ess $ELK_SHEME -esh $ELK_HOST -esp $ELK_PORT
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

View File

@ -38,8 +38,13 @@ override_dh_auto_build:
sed -i "s@var/www@var/www/${PRODUCT}@g" ${SRC_PATH}/config/nginx/*.conf
sed -i "s@var/www@var/www/${PRODUCT}@g" ${SRC_PATH}/config/nginx/includes/*.conf
override_dh_fixperms:
dh_fixperms
override_dh_auto_install:
dh_installinit
dh_systemd_enable
dh_systemd_start --no-start
override_dh_strip:
# dh_strip --exclude=/site-packages/

View File

@ -1,3 +1,18 @@
Template: appserver/environment
Type: string
Default: production
Description: Select environment for AppServer configuration:
Template: appserver/host
Type: string
Default: localhost
Description: AppServer host:
Template: appserver/port
Type: string
Default: 80
Description: AppServer port:
Template: appserver/db-host
Type: string
Default: localhost
@ -17,23 +32,39 @@ Type: string
Default: onlyoffice
Description: MySQL database name:
Template: appserver/remove-db
Type: boolean
Default: false
Description: Remove database?
This operation will remove the database which contain all data. It is recommended to take backup before removing the database.
Template: appserver/ds-jwt-enabled
Type: boolean
Default: false
Description: To enabled Document Server JWT?:
Template: appserver/ds-jwt-secret
Template: appserver/kafka-host
Type: string
Default: {{package_sysname}}
Description: Document Server JWT Secret:
Default: localhost
Description: Kafka host:
Template: appserver/ds-jwt-secret-header
Template: appserver/kafka-port
Type: string
Default: AuthorizationJwt
Description: Document Server Secret Header:
Default: 9092
Description: Kafka port:
Template: appserver/zookeeper-host
Type: string
Default: localhost
Description: Zookeeper host:
Template: appserver/zookeeper-port
Type: string
Default: 2181
Description: Zookeeper port:
Template: appserver/elasticsearch-sheme
Type: select
Choices: http, https
Default: http
Description: Elasticsearch sheme:
Template: appserver/elasticsearch-host
Type: string
Default: localhost
Description: Elasticsearch host:
Template: appserver/elasticsearch-port
Type: string
Default: 9200
Description: Elasticsearch port:

View File

@ -173,6 +173,26 @@ namespace ASC.Web.Api.Models
return lambda;
}
public EmployeeWraperFull GetSimple(UserInfo userInfo)
{
var result = new EmployeeWraperFull
{
FirstName = userInfo.FirstName,
LastName = userInfo.LastName,
};
FillGroups(result, userInfo);
var photoData = UserPhotoManager.GetUserPhotoData(userInfo.ID, UserPhotoManager.BigFotoSize);
if (photoData != null)
{
result.Avatar = "data:image/png;base64," + Convert.ToBase64String(photoData);
}
return result;
}
public EmployeeWraperFull GetFull(UserInfo userInfo)
{
var result = new EmployeeWraperFull
@ -223,23 +243,7 @@ namespace ASC.Web.Api.Models
}
FillConacts(result, userInfo);
if (Context.Check("groups") || Context.Check("department"))
{
var groups = UserManager.GetUserGroups(userInfo.ID)
.Select(x => new GroupWrapperSummary(x, UserManager))
.ToList();
if (groups.Count > 0)
{
result.Groups = groups;
result.Department = string.Join(", ", result.Groups.Select(d => d.Name.HtmlEncode()));
}
else
{
result.Department = "";
}
}
FillGroups(result, userInfo);
var userInfoLM = userInfo.LastModified.GetHashCode();
@ -269,6 +273,28 @@ namespace ASC.Web.Api.Models
return result;
}
private void FillGroups(EmployeeWraperFull result, UserInfo userInfo)
{
if (!Context.Check("groups") && !Context.Check("department"))
{
return;
}
var groups = UserManager.GetUserGroups(userInfo.ID)
.Select(x => new GroupWrapperSummary(x, UserManager))
.ToList();
if (groups.Count > 0)
{
result.Groups = groups;
result.Department = string.Join(", ", result.Groups.Select(d => d.Name.HtmlEncode()));
}
else
{
result.Department = "";
}
}
private void FillConacts(EmployeeWraperFull employeeWraperFull, UserInfo userInfo)
{
if (userInfo.ContactsList == null) return;

View File

@ -89,7 +89,7 @@ namespace ASC.ElasticSearch
private bool IsExist { get; set; }
private Client Client { get; }
private ILog Log { get; }
private TenantManager TenantManager { get; }
protected TenantManager TenantManager { get; }
private BaseIndexerHelper BaseIndexerHelper { get; }
private Settings Settings { get; }
private IServiceProvider ServiceProvider { get; }
@ -116,7 +116,8 @@ namespace ASC.ElasticSearch
internal void Index(T data, bool immediately = true)
{
CreateIfNotExist(data);
if (!BeforeIndex(data)) return;
Client.Instance.Index(data, idx => GetMeta(idx, data, immediately));
}
@ -124,7 +125,7 @@ namespace ASC.ElasticSearch
{
if (data.Count == 0) return;
CreateIfNotExist(data[0]);
if (!CheckExist(data[0])) return;
if (data[0] is ISearchItemDocument)
{
@ -135,7 +136,9 @@ namespace ASC.ElasticSearch
for (var i = 0; i < data.Count; i++)
{
var t = data[i];
var runBulk = i == data.Count - 1;
var runBulk = i == data.Count - 1;
BeforeIndex(t);
if (!(t is ISearchItemDocument wwd) || wwd.Document == null || string.IsNullOrEmpty(wwd.Document.Data))
{
@ -207,14 +210,19 @@ namespace ASC.ElasticSearch
}
}
else
{
{
foreach (var item in data)
{
BeforeIndex(item);
}
Client.Instance.Bulk(r => r.IndexMany(data, GetMeta));
}
}
internal async Task IndexAsync(List<T> data, bool immediately = true)
{
CreateIfNotExist(data[0]);
if (!CheckExist(data[0])) return;
if (data is ISearchItemDocument)
{
@ -227,6 +235,8 @@ namespace ASC.ElasticSearch
var t = data[i];
var runBulk = i == data.Count - 1;
await BeforeIndexAsync(t);
var wwd = t as ISearchItemDocument;
if (wwd == null || wwd.Document == null || string.IsNullOrEmpty(wwd.Document.Data))
@ -298,31 +308,36 @@ namespace ASC.ElasticSearch
}
else
{
foreach (var item in data)
{
await BeforeIndexAsync(item);
}
await Client.Instance.BulkAsync(r => r.IndexMany(data, GetMeta));
}
}
internal void Update(T data, bool immediately = true, params Expression<Func<T, object>>[] fields)
{
CreateIfNotExist(data);
if (!CheckExist(data)) return;
Client.Instance.Update(DocumentPath<T>.Id(data), r => GetMetaForUpdate(r, data, immediately, fields));
}
internal void Update(T data, UpdateAction action, Expression<Func<T, IList>> fields, bool immediately = true)
{
CreateIfNotExist(data);
if (!CheckExist(data)) return;
Client.Instance.Update(DocumentPath<T>.Id(data), r => GetMetaForUpdate(r, data, action, fields, immediately));
}
internal void Update(T data, Expression<Func<Selector<T>, Selector<T>>> expression, int tenantId, bool immediately = true, params Expression<Func<T, object>>[] fields)
{
CreateIfNotExist(data);
if (!CheckExist(data)) return;
Client.Instance.UpdateByQuery(GetDescriptorForUpdate(data, expression, tenantId, immediately, fields));
}
internal void Update(T data, Expression<Func<Selector<T>, Selector<T>>> expression, int tenantId, UpdateAction action, Expression<Func<T, IList>> fields, bool immediately = true)
{
CreateIfNotExist(data);
if (!CheckExist(data)) return;
Client.Instance.UpdateByQuery(GetDescriptorForUpdate(data, expression, tenantId, action, fields, immediately));
}
@ -389,8 +404,7 @@ namespace ASC.ElasticSearch
Log.DebugFormat("Delete {0}", Wrapper.IndexName);
Client.Instance.Indices.Delete(Wrapper.IndexName);
BaseIndexerHelper.Clear(Wrapper);
CreateIfNotExist(Wrapper);
BaseIndexerHelper.Clear(Wrapper);
}
internal IReadOnlyCollection<T> Select(Expression<Func<Selector<T>, Selector<T>>> expression, bool onlyId = false)
@ -411,6 +425,16 @@ namespace ASC.ElasticSearch
return result.Documents;
}
protected virtual bool BeforeIndex(T data)
{
return CheckExist(data);
}
protected virtual Task<bool> BeforeIndexAsync(T data)
{
return Task.FromResult(CheckExist(data));
}
public void CreateIfNotExist(T data)
{
try

View File

@ -41,6 +41,10 @@
"cSpell.words": ["appserver", "browserslist", "debuginfo", "doceditor"]
},
"extensions": {
"recommendations": ["folke.vscode-monorepo-workspace"]
"recommendations": [
"folke.vscode-monorepo-workspace",
"orta.vscode-jest",
"firsttris.vscode-jest-runner"
]
}
}

View File

@ -81,6 +81,7 @@ export const request = function (options) {
switch (error.response?.status) {
case 401:
if (options.skipUnauthorized) return Promise.resolve();
if (options.skipLogout) return Promise.reject(errorText || error);
request({
method: "post",

View File

@ -42,6 +42,23 @@ export function getUser(userName = null) {
return user;
});
}
export function getUserFromConfirm(userId, confirmKey = null) {
const options = {
method: "get",
url: `/people/${userId}.json`,
};
if (confirmKey) options.headers = { confirm: confirmKey };
return request(options).then((user) => {
if (user && user.displayName) {
user.displayName = Encoder.htmlDecode(user.displayName);
}
return user;
});
}
export function getUserPhoto(userId) {
return request({
method: "get",

View File

@ -54,3 +54,11 @@ export function getInvitationLinks() {
}
);
}
export function setPortalRename(alias) {
return request({
method: "put",
url: "/portal/portalrename.json",
data: { alias },
});
}

View File

@ -25,6 +25,82 @@ export function getPortalPasswordSettings(confirmKey = null) {
return request(options);
}
export function setPortalPasswordSettings(
minLength,
upperCase,
digits,
specSymbols
) {
return request({
method: "put",
url: "/settings/security/password.json",
data: { minLength, upperCase, digits, specSymbols },
});
}
export function setMailDomainSettings(data) {
return request({
method: "post",
url: "/settings/maildomainsettings.json",
data,
});
}
export function setDNSSettings(dnsName, enable) {
return request({
method: "post",
url: "/settings/maildomainsettings.json",
data: { dnsName, enable },
});
}
export function setIpRestrictions(data) {
return request({
method: "put",
url: "/settings/iprestrictions.json",
data,
});
}
export function setIpRestrictionsEnable(data) {
return request({
method: "put",
url: "/settings/iprestrictions/settings.json",
data,
});
}
export function setMessageSettings(turnOn) {
return request({
method: "post",
url: "/settings/messagesettings.json",
data: { turnOn },
});
}
export function setCookieSettings(lifeTime) {
return request({
method: "put",
url: "/settings/cookiesettings.json",
data: { lifeTime },
});
}
export function setLifetimeAuditSettings(data) {
return request({
method: "post",
url: "/security/audit/settings/lifetime.json",
data,
});
}
export function getAuditTrailReport() {
return request({
method: "post",
url: "/security/audit/login/report.json",
});
}
export function getPortalTimezones(confirmKey = null) {
const options = {
method: "get",
@ -79,6 +155,24 @@ export function getLogoUrls() {
});
}
export function setWhiteLabelSettings(data) {
const options = {
method: "post",
url: "/settings/whitelabel/save.json",
data,
};
return request(options);
}
export function restoreWhiteLabelSettings(isDefault) {
return request({
method: "put",
url: "/settings/whitelabel/restore.json",
data: { isDefault },
});
}
export function getCustomSchemaList() {
return request({
method: "get",
@ -332,6 +426,14 @@ export function getBuildVersion() {
return request(options);
}
export function getCapabilities() {
const options = {
method: "get",
url: "/capabilities",
};
return request(options);
}
export function getTipsSubscription() {
const options = {
method: "get",

View File

@ -10,6 +10,7 @@ export function login(userName, passwordHash, session) {
return request({
method: "post",
url: "/authentication.json",
skipLogout: true,
data,
});
}

View File

@ -1,15 +1,10 @@
import React from "react";
import PropTypes from "prop-types";
import Selector from "./sub-components/Selector";
import utils from "@appserver/components/utils";
import Backdrop from "@appserver/components/backdrop";
import DropDown from "@appserver/components/drop-down";
import Aside from "@appserver/components/aside";
import throttle from "lodash/throttle";
const { desktop } = utils.device;
const displayTypes = ["dropdown", "aside", "auto"];
const sizes = ["compact", "full"];
class AdvancedSelector extends React.Component {
@ -17,75 +12,15 @@ class AdvancedSelector extends React.Component {
super(props);
this.ref = React.createRef();
this.state = {
displayType: this.getTypeByWidth(),
};
this.throttledResize = throttle(this.resize, 300);
}
componentDidMount() {
if (this.props.isOpen) {
window.addEventListener("resize", this.throttledResize);
}
}
resize = () => {
if (this.props.displayType !== "auto") return;
const type = this.getTypeByWidth();
if (type === this.state.displayType) return;
this.setState({ displayType: type });
};
onClose = (e) => {
//console.log("onClose");
//this.setState({ isOpen: false });
this.props.onCancel && this.props.onCancel(e);
};
componentDidUpdate(prevProps) {
if (this.props.isOpen !== prevProps.isOpen) {
//console.log(`ADSelector componentDidUpdate isOpen=${this.props.isOpen}`);
if (this.props.isOpen) {
this.resize();
window.addEventListener("resize", this.throttledResize);
} else {
this.throttledResize.cancel();
window.removeEventListener("resize", this.throttledResize);
}
}
if (this.props.displayType !== prevProps.displayType) {
//console.log(`ADSelector componentDidUpdate displayType=${this.props.displayType}`);
this.setState({ displayType: this.getTypeByWidth() });
}
}
componentWillUnmount() {
if (this.throttledResize) {
this.throttledResize && this.throttledResize.cancel();
window.removeEventListener("resize", this.throttledResize);
}
}
getTypeByWidth = () => {
const displayType =
this.props.displayType !== "auto"
? this.props.displayType
: window.innerWidth < desktop.match(/\d+/)[0]
? "aside"
: "dropdown";
//console.log("AdvancedSelector2 displayType", displayType);
return displayType;
};
render() {
const { displayType } = this.state;
const {
isOpen,
id,
@ -96,24 +31,10 @@ class AdvancedSelector extends React.Component {
smallSectionWidth,
} = this.props;
//console.log(`AdvancedSelector render() isOpen=${isOpen} displayType=${displayType}`);
return (
<div id={id} className={className} style={style}>
{displayType === "dropdown" ? (
<DropDown
forwardedRef={this.ref}
open={isOpen}
className="selector_dropdown-container"
smallSectionWidth={smallSectionWidth}
isDefaultMode={isDefaultDisplayDropDown}
className="dropdown-container"
clickOutsideAction={this.onClose}
>
<Selector {...this.props} displayType={displayType} />
</DropDown>
) : withoutAside ? (
<Selector {...this.props} displayType={displayType} />
{withoutAside ? (
<Selector {...this.props} />
) : (
<>
<Backdrop
@ -123,7 +44,7 @@ class AdvancedSelector extends React.Component {
isAside={true}
/>
<Aside visible={isOpen} scale={false} className="aside-container">
<Selector {...this.props} displayType={displayType} />
<Selector {...this.props} />
</Aside>
</>
)}
@ -134,7 +55,7 @@ class AdvancedSelector extends React.Component {
AdvancedSelector.propTypes = {
id: PropTypes.string,
className: PropTypes.oneOf([PropTypes.string, PropTypes.array]),
className: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
style: PropTypes.object,
options: PropTypes.array,
selectedOptions: PropTypes.array,
@ -147,7 +68,6 @@ AdvancedSelector.propTypes = {
buttonLabel: PropTypes.string,
size: PropTypes.oneOf(sizes),
displayType: PropTypes.oneOf(displayTypes),
maxHeight: PropTypes.number,
@ -178,7 +98,6 @@ AdvancedSelector.defaultProps = {
selectAllLabel: "Select all",
allowGroupSelection: false,
allowAnyClickClose: true,
displayType: "auto",
options: [],
isDefaultDisplayDropDown: true,
};

View File

@ -8,9 +8,9 @@ class Body extends React.Component {
}
render() {
const { children, displayType, className, style } = this.props;
const { children, className, style } = this.props;
return (
<StyledBody displayType={displayType} className={className} style={style}>
<StyledBody className={className} style={style}>
{children}
</StyledBody>
);
@ -21,7 +21,6 @@ Body.propTypes = {
children: PropTypes.any,
className: PropTypes.string,
style: PropTypes.object,
displayType: PropTypes.oneOf(["dropdown", "aside"]),
};
export default Body;

View File

@ -8,14 +8,9 @@ class Column extends React.Component {
}
render() {
const { children, displayType, className, style, size } = this.props;
const { children, className, style, size } = this.props;
return (
<StyledColumn
displayType={displayType}
className={className}
style={style}
size={size}
>
<StyledColumn className={className} style={style} size={size}>
{children}
</StyledColumn>
);
@ -26,7 +21,6 @@ Column.propTypes = {
children: PropTypes.any,
className: PropTypes.string,
style: PropTypes.object,
displayType: PropTypes.oneOf(["dropdown", "aside"]),
size: PropTypes.oneOf(["compact", "full"]),
};

View File

@ -24,7 +24,7 @@ const Footer = (props) => {
<Button
className="add_members_btn"
primary={true}
size="big"
size="normal"
label={`${selectButtonLabel} ${
selectedLength && showCounter ? `(${selectedLength})` : ""
}`}

View File

@ -8,13 +8,9 @@ class Header extends React.Component {
}
render() {
const { children, displayType, className, style } = this.props;
const { children, className, style } = this.props;
return (
<StyledHeader
displayType={displayType}
className={className}
style={style}
>
<StyledHeader className={className} style={style}>
{children}
</StyledHeader>
);

View File

@ -9,15 +9,15 @@ import InfiniteLoader from "react-window-infinite-loader";
import AutoSizer from "react-virtualized-auto-sizer";
import ReactTooltip from "react-tooltip";
import Avatar from "@appserver/components/avatar";
import Checkbox from "@appserver/components/checkbox";
import Link from "@appserver/components/link";
import ComboBox from "@appserver/components/combobox";
import SearchInput from "@appserver/components/search-input";
import Loader from "@appserver/components/loader";
import Text from "@appserver/components/text";
import Tooltip from "@appserver/components/tooltip";
import Heading from "@appserver/components/heading";
import IconButton from "@appserver/components/icon-button";
import CustomScrollbarsVirtualList from "@appserver/components/scrollbar/custom-scrollbars-virtual-list";
import HelpButton from "@appserver/components/help-button";
import StyledSelector from "./StyledSelector";
@ -45,7 +45,6 @@ const getCurrentGroup = (items) => {
const Selector = (props) => {
const {
displayType,
groups,
selectButtonLabel,
isDisabled,
@ -70,14 +69,11 @@ const Selector = (props) => {
allowGroupSelection,
embeddedComponent,
showCounter,
onArrowClick,
headerLabel,
} = props;
//console.log("options", options);
//console.log("hasNextPage", hasNextPage);
//console.log("isNextPageLoading", isNextPageLoading);
const listOptionsRef = useRef(null);
const listGroupsRef = useRef(null);
useEffect(() => {
Object.keys(currentGroup).length === 0 &&
@ -94,12 +90,16 @@ const Selector = (props) => {
);
const [searchValue, setSearchValue] = useState("");
const [selectedAll, setSelectedAll] = useState(false);
const [currentGroup, setCurrentGroup] = useState(
getCurrentGroup(convertGroups(groups))
);
const [groupHeader, setGroupHeader] = useState(null);
useEffect(() => {
if (groups.length === 1) setGroupHeader(groups[0]);
}, [groups]);
// Every row is loaded except for our loading indicator row.
const isItemLoaded = useCallback(
(index) => {
@ -109,9 +109,9 @@ const Selector = (props) => {
);
const onOptionChange = useCallback(
(e) => {
const option = options[+e.target.value];
const newSelected = e.target.checked
(index, isChecked) => {
const option = options[index];
const newSelected = !isChecked
? [option, ...selectedOptionList]
: selectedOptionList.filter((el) => el.key !== option.key);
setSelectedOptionList(newSelected);
@ -121,7 +121,7 @@ const Selector = (props) => {
const newSelectedGroups = [];
const removedSelectedGroups = [];
if (e.target.checked) {
if (isChecked) {
option.groups.forEach((g) => {
let index = selectedGroupList.findIndex((sg) => sg.key === g);
if (index > -1) {
@ -187,69 +187,12 @@ const Selector = (props) => {
[options, selectedOptionList, groups, selectedGroupList]
);
const onGroupChange = useCallback(
(e) => {
const group = convertGroup(groups[+e.target.value]);
group.selected = e.target.checked ? group.total : 0;
const newSelectedGroups = e.target.checked
? [group, ...selectedGroupList]
: selectedGroupList.filter((el) => el.key !== group.key);
//console.log("onGroupChange", item);
setSelectedGroupList(newSelectedGroups);
onGroupSelect(group);
if (e.target.checked) {
//const newSelectedOptions = [];
//options.forEach(o => o.groups.forEach(gKey => group.))
//setSelectedOptionList()
//TODO: Implement setSelectedOptionList changes
}
},
[groups, selectedGroupList, currentGroup]
);
const resetCache = useCallback(() => {
if (listOptionsRef && listOptionsRef.current) {
listOptionsRef.current.resetloadMoreItemsCache(true);
}
}, [listOptionsRef]);
const onGroupSelect = useCallback(
(group) => {
if (!currentGroup || !group || currentGroup.key === group.key) {
return;
}
setCurrentGroup(group);
onGroupChanged && onGroupChanged(group);
if (displayType === "aside" && isMultiSelect) {
setSelectedAll(isGroupChecked(group));
}
},
[displayType, isMultiSelect, currentGroup]
);
const onSelectAllChange = useCallback(() => {
const checked = !selectedAll;
//console.log("onSelectAllChange", checked);
setSelectedAll(checked);
if (!currentGroup) return;
const group = convertGroup(currentGroup);
if (!group) return;
group.selected = checked ? group.total : 0;
const newSelectedGroups = checked
? [group, ...selectedGroupList]
: selectedGroupList.filter((el) => el.key !== group.key);
setSelectedGroupList(newSelectedGroups);
}, [selectedAll, currentGroup, selectedGroupList]);
const onSearchChange = useCallback((value) => {
setSearchValue(value);
onSearchChanged && onSearchChanged(value);
@ -259,10 +202,6 @@ const Selector = (props) => {
onSearchChanged && onSearchChange("");
});
const onSelectOptions = (items) => {
onSelect && onSelect(items);
};
const isOptionChecked = useCallback(
(option) => {
const checked =
@ -282,12 +221,16 @@ const Selector = (props) => {
},
[selectedOptionList, selectedGroupList]
);
const onSelectOptions = (items) => {
onSelect && onSelect(items);
};
const onAddClick = useCallback(() => {
onSelectOptions(selectedOptionList);
}, [selectedOptionList]);
const onLinkClick = useCallback(
(e) => {
const index = e.target.dataset.index;
if (!index) return;
(index) => {
const option = options[index];
if (!option) return;
@ -297,79 +240,73 @@ const Selector = (props) => {
[options]
);
const onAddClick = useCallback(() => {
onSelectOptions(selectedOptionList);
}, [selectedOptionList]);
const renderOptionItem = useCallback(
(index, style, option, isChecked, tooltipProps) => {
return isMultiSelect ? (
<div style={style} className="row-option" {...tooltipProps}>
<div
style={style}
className="row-option"
value={`${index}`}
name={`selector-row-option-${index}`}
onClick={() => onOptionChange(index, isChecked)}
{...tooltipProps}
>
<div className="option-info">
<Avatar
className="option-avatar"
role="user"
size="min"
source={option.avatarUrl}
userName={option.label}
/>
<Text
className="option-text"
truncate={true}
noSelect={true}
fontSize="14px"
>
{option.label}
</Text>
</div>
<Checkbox
id={option.key}
value={`${index}`}
label={option.label}
isChecked={isChecked}
className="option_checkbox"
truncate={true}
title={option.label}
onChange={onOptionChange}
className="option-checkbox"
/>
{displayType === "aside" && getOptionTooltipContent && (
<HelpButton
id={`info-${option.key}`}
className="option-info"
iconName="/static/images/info.react.svg"
color="#D8D8D8"
getContent={getOptionTooltipContent}
place="top"
offsetLeft={150}
offsetRight={0}
offsetTop={60}
offsetBottom={0}
dataTip={`${index}`}
displayType="dropdown"
/>
)}
</div>
) : (
<Link
<div
key={option.key}
data-index={index}
isTextOverflow={true}
style={style}
className="row-option"
data-index={index}
name={`selector-row-option-${index}`}
onClick={() => onLinkClick(index)}
{...tooltipProps}
onClick={onLinkClick}
noHover
>
{option.label}
{displayType === "aside" && getOptionTooltipContent && (
<HelpButton
id={`info-${option.key}`}
className="option-info"
iconName="/static/images/info.react.svg"
color="#D8D8D8"
getContent={getOptionTooltipContent}
place="top"
offsetLeft={150}
offsetRight={0}
offsetTop={60}
offsetBottom={0}
dataTip={`${index}`}
displayType="dropdown"
<div className="option-info">
{" "}
<Avatar
className="option-avatar"
role="user"
size="min"
source={option.avatarUrl}
userName={option.label}
/>
)}
</Link>
<Text
className="option-text"
truncate={true}
noSelect={true}
fontSize="14px"
>
{option.label}
</Text>
</div>
</div>
);
},
[
isMultiSelect,
onOptionChange,
onLinkClick,
displayType,
getOptionTooltipContent,
]
[isMultiSelect, onOptionChange, onLinkClick]
);
const renderOptionLoader = useCallback(
@ -385,7 +322,9 @@ const Selector = (props) => {
marginRight: "10px",
}}
/>
<Text as="span">{loadingLabel}</Text>
<Text as="span" noSelect={true}>
{loadingLabel}
</Text>
</div>
</div>
);
@ -407,9 +346,6 @@ const Selector = (props) => {
const isChecked = isOptionChecked(option);
let tooltipProps = {};
if (displayType === "dropdown")
tooltipProps = { "data-for": "user", "data-tip": index };
ReactTooltip.rebuild();
return renderOptionItem(index, style, option, isChecked, tooltipProps);
@ -421,7 +357,6 @@ const Selector = (props) => {
loadingLabel,
options,
isOptionChecked,
displayType,
isMultiSelect,
onOptionChange,
onLinkClick,
@ -429,119 +364,6 @@ const Selector = (props) => {
]
);
const isGroupChecked = useCallback(
(group) => {
const selectedGroup = selectedGroupList.find((g) => g.key === group.key);
return !!selectedGroup;
},
[selectedGroupList]
);
const isGroupIndeterminate = useCallback(
(group) => {
const selectedGroup = selectedGroupList.find((g) => g.key === group.key);
return (
selectedGroup &&
selectedGroup.selected > 0 &&
group.total !== selectedGroup.selected
);
},
[selectedGroupList]
);
const getGroupSelected = useCallback(
(group) => {
const selectedGroup = selectedGroupList.find((g) => g.key === group.key);
return isGroupIndeterminate(group)
? selectedGroup.selected
: isGroupChecked(group)
? group.total
: 0;
},
[selectedGroupList]
);
const getGroupLabel = useCallback(
(group) => {
const selected = getGroupSelected(group);
return isMultiSelect && allowGroupSelection
? `${group.label} (${group.total}/${selected})`
: group.label;
},
[isMultiSelect, allowGroupSelection]
);
const getSelectorGroups = useCallback(
(groups) => {
return groups.map((group) => {
return {
...group,
label: getGroupLabel(group),
};
});
},
[groups]
);
const onLinkGroupClick = useCallback(
(e) => {
const index = e.target.dataset.index;
if (!index) return;
const group = groups[index];
if (!group) return;
onGroupSelect(group);
},
[groups, currentGroup]
);
// eslint-disable-next-line react/prop-types
const renderGroup = useCallback(
({ index, style }) => {
const group = groups[index];
const isChecked = isGroupChecked(group);
const isIndeterminate = isGroupIndeterminate(group);
const isSelected = currentGroup.key === group.key;
const label = getGroupLabel(group);
return (
<Link
key={group.key}
data-index={index}
isTextOverflow={true}
onClick={onLinkGroupClick}
title={label}
style={style}
className={`row-group${isSelected ? " selected" : ""}`}
noHover
>
{isMultiSelect && allowGroupSelection && (
<Checkbox
id={group.key}
value={`${index}`}
isChecked={isChecked}
isIndeterminate={isIndeterminate}
className="group_checkbox"
truncate={true}
onChange={onGroupChange}
/>
)}
{label}
</Link>
);
},
[
groups,
currentGroup,
isMultiSelect,
selectedGroupList,
allowGroupSelection,
]
);
const hasSelected = useCallback(() => {
return selectedOptionList.length > 0 || selectedGroupList.length > 0;
}, [selectedOptionList, selectedGroupList]);
@ -561,18 +383,171 @@ const Selector = (props) => {
currentGroup: currentGroup ? currentGroup.key : null,
};
//setLastIndex(startIndex);
//console.log("loadMoreItems", options);
loadNextPage && loadNextPage(options);
},
[isNextPageLoading, searchValue, currentGroup, options]
);
const getGroupSelectedOptions = useCallback(
(group) => {
const selectedGroup = selectedOptionList.filter(
(o) => o.groups && o.groups.indexOf(group) > -1
);
if (group === "all") {
selectedGroup.push(...selectedOptionList);
}
return selectedGroup;
},
[selectedOptionList]
);
const onGroupClick = useCallback(
(index) => {
const group = groups[index];
setGroupHeader({ ...group });
onGroupChanged && onGroupChanged(group);
setCurrentGroup(group);
},
[groups, onGroupChanged]
);
const renderGroup = useCallback(
({ index, style }) => {
const group = groups[index];
const selectedOption = getGroupSelectedOptions(group.id);
const isIndeterminate = selectedOption.length > 0;
let label = group.label;
if (isMultiSelect && selectedOption.length > 0) {
label = `${group.label} (${selectedOption.length})`;
}
return (
<div
style={style}
className="row-option"
name={`selector-row-option-${index}`}
onClick={() => onGroupClick(index)}
>
<div className="option-info">
<Avatar
className="option-avatar"
role="user"
size="min"
source={group.avatarUrl}
userName={group.label}
/>
<Text
className="option-text option-text__group"
truncate={true}
noSelect={true}
fontSize="14px"
>
{label}
</Text>
</div>
{isMultiSelect && (
<Checkbox
value={`${index}`}
isIndeterminate={isIndeterminate}
className="option-checkbox"
/>
)}
</div>
);
},
[
isMultiSelect,
groups,
currentGroup,
selectedGroupList,
selectedOptionList,
getGroupSelectedOptions,
]
);
const renderGroupsList = useCallback(() => {
if (groups.length === 0) return renderOptionLoader();
return (
<AutoSizer>
{({ width, height }) => (
<List
className="options_list"
height={height - 8}
width={width + 8}
itemCount={groups.length}
itemSize={48}
outerElementType={CustomScrollbarsVirtualList}
>
{renderGroup}
</List>
)}
</AutoSizer>
);
}, [isMultiSelect, groups, selectedOptionList, getGroupSelectedOptions]);
const renderGroupHeader = useCallback(() => {
const selectedOption = getGroupSelectedOptions(groupHeader.id);
const isIndeterminate = selectedOption.length > 0;
let label = groupHeader.label;
if (isMultiSelect && selectedOption.length > 0) {
label = `${groupHeader.label} (${selectedOption.length})`;
}
return (
<>
<div className="row-option row-header">
<div className="option-info">
<Avatar
className="option-avatar"
role="user"
size="min"
source={groupHeader.avatarUrl}
userName={groupHeader.label}
/>
<Text
className="option-text option-text__header"
truncate={true}
noSelect={true}
fontSize="14px"
>
{label}
</Text>
</div>
{isMultiSelect && (
<Checkbox
isIndeterminate={isIndeterminate}
className="option-checkbox"
/>
)}
</div>
<div className="option-separator"></div>
</>
);
}, [isMultiSelect, groupHeader, selectedOptionList, getGroupSelectedOptions]);
const onArrowClickAction = useCallback(() => {
if (groupHeader && groups.length !== 1) {
setGroupHeader(null);
onGroupChanged && onGroupChanged([]);
setCurrentGroup([]);
return;
}
onArrowClick && onArrowClick();
}, [groups, groupHeader, onArrowClick, onGroupChanged]);
return (
<StyledSelector
displayType={displayType}
options={options}
groups={groups}
isMultiSelect={isMultiSelect}
@ -580,7 +555,19 @@ const Selector = (props) => {
hasSelected={hasSelected()}
className="selector-wrapper"
>
<Column className="column-options" displayType={displayType} size={size}>
<div className="header">
<IconButton
iconName="/static/images/arrow.path.react.svg"
size="17"
isFill={true}
className="arrow-button"
onClick={onArrowClickAction}
/>
<Heading size="medium" truncate={true}>
{headerLabel.replace("()", "")}
</Heading>
</div>
<Column className="column-options" size={size}>
<Header className="header-options">
<SearchInput
className="options_searcher"
@ -593,70 +580,49 @@ const Selector = (props) => {
onChange={onSearchChange}
onClearSearch={onSearchReset}
/>
{displayType === "aside" && groups && groups.length > 0 && (
<>
<ComboBox
className="options_group_selector"
isDisabled={isDisabled}
options={getSelectorGroups(groups)}
selectedOption={currentGroup}
dropDownMaxHeight={220}
scaled={true}
scaledOptions={true}
size="content"
isDefaultMode={false}
onSelect={onGroupSelect}
/>
{isMultiSelect &&
allowGroupSelection &&
options &&
options.length > 0 && (
<Checkbox
className="options_group_select_all"
label={selectAllLabel}
isChecked={selectedAll}
isIndeterminate={false}
truncate={true}
onChange={onSelectAllChange}
/>
)}
</>
)}
</Header>
<Body className="body-options">
<AutoSizer>
{({ width, height }) => (
<InfiniteLoader
ref={listOptionsRef}
isItemLoaded={isItemLoaded}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
className="options_list"
height={height}
itemCount={itemCount}
itemSize={36}
onItemsRendered={onItemsRendered}
ref={ref}
width={width + 8}
outerElementType={CustomScrollbarsVirtualList}
>
{renderOption}
</List>
)}
</InfiniteLoader>
)}
</AutoSizer>
{!hasNextPage && itemCount === 0 && (
<div className="row-option">
<Text>
{!searchValue ? emptyOptionsLabel : emptySearchOptionsLabel}
</Text>
</div>
{!groupHeader && !searchValue && groups ? (
renderGroupsList()
) : (
<>
{!searchValue && renderGroupHeader()}
{!hasNextPage && itemCount === 0 ? (
<div className="row-option">
<Text>
{!searchValue ? emptyOptionsLabel : emptySearchOptionsLabel}
</Text>
</div>
) : (
<AutoSizer>
{({ width, height }) => (
<InfiniteLoader
ref={listOptionsRef}
isItemLoaded={isItemLoaded}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
className="options_list"
height={height - 25}
itemCount={itemCount}
itemSize={48}
onItemsRendered={onItemsRendered}
ref={ref}
width={width + 8}
outerElementType={CustomScrollbarsVirtualList}
>
{renderOption}
</List>
)}
</InfiniteLoader>
)}
</AutoSizer>
)}
</>
)}
{getOptionTooltipContent && (
<Tooltip
id="user"
@ -666,48 +632,9 @@ const Selector = (props) => {
)}
</Body>
</Column>
{displayType === "dropdown" && groups && groups.length > 0 && (
<>
<div className="splitter"></div>
<Column
className="column-groups"
displayType={displayType}
size={size}
>
<Header className="header-groups">
<Text
as="p"
className="group_header"
fontSize="15px"
fontWeight={600}
>
{groupsHeaderLabel}
</Text>
</Header>
<Body className="body-groups">
<AutoSizer>
{({ height, width }) => (
<List
className="group_list"
height={height}
width={width + 8}
itemSize={32}
itemCount={groups.length}
itemData={groups}
outerElementType={CustomScrollbarsVirtualList}
ref={listGroupsRef}
>
{renderGroup}
</List>
)}
</AutoSizer>
</Body>
</Column>
</>
)}
<Footer
className="footer"
selectButtonLabel={selectButtonLabel}
selectButtonLabel={headerLabel}
showCounter={showCounter}
isDisabled={isDisabled}
isVisible={isMultiSelect && hasSelected()}
@ -740,7 +667,6 @@ Selector.propTypes = {
loadingLabel: PropTypes.string,
size: PropTypes.oneOf(["compact", "full"]),
displayType: PropTypes.oneOf(["dropdown", "aside"]),
selectedOptions: PropTypes.array,
selectedGroups: PropTypes.array,

View File

@ -3,7 +3,7 @@ import styled from "styled-components";
/* eslint-disable no-unused-vars */
/* eslint-disable react/prop-types */
const Container = ({ displayType, ...props }) => <div {...props} />;
const Container = ({ ...props }) => <div {...props} />;
/* eslint-enable react/prop-types */
/* eslint-enable no-unused-vars */

View File

@ -3,29 +3,13 @@ import styled, { css } from "styled-components";
/* eslint-disable no-unused-vars */
/* eslint-disable react/prop-types */
const Container = ({ displayType, ...props }) => <div {...props} />;
const Container = ({ ...props }) => <div {...props} />;
/* eslint-enable react/prop-types */
/* eslint-enable no-unused-vars */
const StyledColumn = styled(Container)`
${(props) =>
props.displayType === "dropdown"
? css`
${(props) =>
props.size === "compact"
? css`
width: 379px;
height: 267px;
`
: css`
width: 345px;
height: 544px;
`}
`
: css`
width: 320px;
height: 100%;
`}
width: 320px;
height: 100%;
`;
export default StyledColumn;

View File

@ -1,8 +1,9 @@
import styled, { css } from "styled-components";
import Base from "../../../../asc-web-components/themes/base";
const StyledFooter = styled.div`
box-sizing: border-box;
border-top: 1px solid #eceef1;
border-top: ${(props) => props.theme.advancedSelector.footerBorder};
padding: 16px;
height: 69px;
@ -19,4 +20,6 @@ const StyledFooter = styled.div`
`}
`;
StyledFooter.defaultProps = { theme: Base };
export default StyledFooter;

View File

@ -3,7 +3,7 @@ import styled from "styled-components";
/* eslint-disable no-unused-vars */
/* eslint-disable react/prop-types */
const Container = ({ displayType, ...props }) => <div {...props} />;
const Container = ({ ...props }) => <div {...props} />;
/* eslint-enable react/prop-types */
/* eslint-enable no-unused-vars */

View File

@ -1,11 +1,11 @@
import React from "react";
import styled, { css } from "styled-components";
import { tablet } from "@appserver/components/utils/device";
import Base from "@appserver/components/themes/base";
/* eslint-disable no-unused-vars */
/* eslint-disable react/prop-types */
const Container = ({
displayType,
options,
groups,
isMultiSelect,
@ -16,189 +16,93 @@ const Container = ({
/* eslint-enable react/prop-types */
/* eslint-enable no-unused-vars */
const dropdownStyles = css`
grid-auto-rows: max-content;
const StyledSelector = styled(Container)`
display: grid;
${(props) =>
props.groups && props.groups.length > 0
? css`
grid-template-areas: "column-options splitter column-groups" "footer footer footer";
`
: css`
grid-template-areas: "column-options column-groups" "footer footer";
`};
.column-groups {
box-sizing: border-box;
grid-area: column-groups;
display: grid;
/* background-color: gold; */
padding: 16px 16px 0 16px;
grid-row-gap: 2px;
grid-template-columns: 1fr;
grid-template-rows: 30px 1fr;
grid-template-areas: "header-groups" "body-groups";
.header-groups {
grid-area: header-groups;
.group_header {
line-height: 30px;
}
/* background-color: white; */
}
.body-groups {
grid-area: body-groups;
margin-left: -8px;
/* background-color: white; */
.row-group:first-child {
font-weight: 700;
}
.row-group {
box-sizing: border-box;
height: 32px;
cursor: pointer;
padding-top: 8px;
padding-left: 8px;
.group_checkbox {
display: inline-block;
}
&:hover {
background-color: #eceef1;
border-radius: 3px;
}
}
.row-group.selected {
background-color: #eceef1;
border-radius: 3px;
}
}
}
${(props) =>
props.groups &&
props.groups.length > 0 &&
css`
.splitter {
grid-area: splitter;
border-left: 1px solid #eceef1;
margin-top: 16px;
}
`}
`;
const asideStyles = css`
height: 100%;
grid-template-columns: 1fr;
${(props) =>
props.isMultiSelect && props.hasSelected
? css`
grid-template-rows: 1fr 69px;
grid-template-areas: "column-options" "footer";
grid-template-rows: 53px 1fr 69px;
grid-template-areas: "header" "column-options" "footer";
`
: css`
grid-template-rows: 1fr;
grid-template-areas: "column-options";
grid-template-rows: 53px 1fr;
grid-template-areas: "header" "column-options";
`}
`;
const StyledSelector = styled(Container)`
display: grid;
.header {
grid-area: header;
${(props) =>
props.displayType === "dropdown" ? dropdownStyles : asideStyles}
height: 53px;
min-height: 53px;
padding: 0 16px;
margin: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: start;
.arrow-button {
margin-right: 12px;
}
svg {
cursor: pointer;
}
}
.column-options {
grid-area: column-options;
box-sizing: border-box;
display: grid;
/* background-color: red; */
padding: 16px 16px 0 16px;
padding: 0;
grid-row-gap: 2px;
overflow: hidden;
grid-template-columns: 1fr;
grid-template-rows: ${(props) =>
props.displayType === "aside"
? props.isMultiSelect &&
props.allowGroupSelection &&
props.options &&
props.options.length > 0
? props.groups && props.groups.length > 0
? "100px"
: "30px"
: props.groups && props.groups.length > 0
? "75px"
: "30px"
: "30px"} 1fr;
grid-template-rows: 30px 1fr;
grid-template-areas: "header-options" "body-options";
.header-options {
grid-area: header-options;
margin-right: 2px;
/* background-color: white; */
${(props) =>
props.displayType === "aside" &&
css`
display: grid;
grid-row-gap: 17px;
grid-template-columns: 1fr;
grid-template-rows: 30px 30px ${(props) =>
props.isMultiSelect &&
props.options &&
props.options.length > 0 &&
"30px"};
${(props) =>
props.isMultiSelect && props.options && props.options.length > 0
? css`
grid-template-areas: "options_searcher" "options_group_selector" "options_group_select_all";
`
: css`
grid-template-areas: "options_searcher" "options_group_selector";
`}
padding: 0 16px;
margin-right: 0px !important;
.options_searcher {
grid-area: options_searcher;
}
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 30px;
.options_group_selector {
grid-area: options_group_selector;
}
grid-template-areas: "options_searcher";
${(props) =>
props.isMultiSelect &&
props.options &&
props.options.length > 0 &&
css`
.options_group_select_all {
grid-area: options_group_select_all;
}
`}
`}
.options_searcher {
grid-area: options_searcher;
}
.options_searcher {
div:first-child {
:hover {
border-color: #d0d5da;
border-color: ${(props) =>
props.theme.advancedSelector.searcher.hoverBorderColor};
}
:focus,
:focus-within {
border-color: #2da7db;
border-color: ${(props) =>
props.theme.advancedSelector.searcher.focusBorderColor};
}
& > input::placeholder {
color: #a3a9ae;
color: ${(props) =>
props.theme.advancedSelector.searcher.placeholderColor};
}
}
}
@ -206,43 +110,75 @@ const StyledSelector = styled(Container)`
.body-options {
grid-area: body-options;
margin-left: -8px;
margin-top: 2px;
@media ${tablet} {
width: 290px;
}
/* background-color: white; */
margin-top: 8px;
.row-option {
padding-left: 8px;
padding-top: 8px;
box-sizing: border-box;
height: 32px;
margin-top: 16px;
height: 48px;
cursor: pointer;
&:hover {
background-color: #eceef1;
border-radius: 3px;
}
display: flex;
align-items: center;
justify-content: space-between;
.option_checkbox {
width: 265px;
padding: 0 16px;
&:hover {
background-color: ${(props) =>
props.theme.advancedSelector.hoverBackgroundColor};
}
.option-info {
position: absolute;
top: 12px;
right: 10px;
padding: 8px 0 8px 8px;
margin-top: -8px;
width: calc(100% - 32px);
display: flex;
align-items: center;
justify-content: start;
}
/* .__react_component_tooltip {
left: 8px !important;
} */
.option-avatar {
margin-right: 12px;
min-width: 32px;
max-width: 32px;
}
.option-text {
max-width: 100%;
line-height: 16px;
}
.option-text__group {
width: auto;
border-bottom: ${(props) =>
props.theme.toggleContent.hoverBorderBottom};
}
.option-text__header {
font-weight: 600;
}
.option-checkbox {
margin-left: 8px;
margin-right: 8px;
min-width: 16px;
max-width: 16px;
}
}
.option-separator {
height: 1px;
background: #dfe2e3;
margin: 8px 16px;
}
.row-header {
cursor: auto;
.option-checkbox {
margin-right: 0 !important;
}
:hover {
background: none;
}
}
}
}
@ -252,4 +188,6 @@ const StyledSelector = styled(Container)`
}
`;
StyledSelector.defaultProps = { theme: Base };
export default StyledSelector;

View File

@ -1,13 +1,13 @@
import React from "react";
import PageLayout from "../PageLayout";
import Section from "../Section";
import Loader from "@appserver/components/loader";
const AppLoader = () => (
<PageLayout>
<PageLayout.SectionBody>
<Section>
<Section.SectionBody>
<Loader className="pageLoader" type="rombs" size="40px" />
</PageLayout.SectionBody>
</PageLayout>
</Section.SectionBody>
</Section>
);
export default AppLoader;

View File

@ -0,0 +1,176 @@
import React from "react";
import { inject, observer } from "mobx-react";
import PropTypes from "prop-types";
import { isMobile, isMobileOnly } from "react-device-detect";
import { Resizable } from "re-resizable";
import {
isDesktop as isDesktopUtils,
isTablet as isTabletUtils,
isMobile as isMobileUtils,
} from "@appserver/components/utils/device";
import SubArticleBackdrop from "./sub-components/article-backdrop";
import SubArticleHeader from "./sub-components/article-header";
import SubArticleMainButton from "./sub-components/article-main-button";
import SubArticleBody from "./sub-components/article-body";
import { StyledArticle } from "./styled-article";
const enable = {
top: false,
right: !isMobile,
bottom: false,
left: false,
};
const Article = ({
showText,
setShowText,
articleOpen,
toggleShowText,
toggleArticleOpen,
children,
...rest
}) => {
const [articleHeaderContent, setArticleHeaderContent] = React.useState(null);
const [
articleMainButtonContent,
setArticleMainButtonContent,
] = React.useState(null);
const [articleBodyContent, setArticleBodyContent] = React.useState(null);
const refTimer = React.useRef(null);
React.useEffect(() => {
if (isMobileOnly) {
window.addEventListener("popstate", hideText);
return () => window.removeEventListener("popstate", hideText);
}
}, [hideText]);
React.useEffect(() => {
window.addEventListener("resize", sizeChangeHandler);
return () => window.removeEventListener("resize", sizeChangeHandler);
}, []);
React.useEffect(() => {
sizeChangeHandler();
}, []);
React.useEffect(() => {
React.Children.forEach(children, (child) => {
const childType =
child && child.type && (child.type.displayName || child.type.name);
switch (childType) {
case Article.Header.displayName:
setArticleHeaderContent(child);
break;
case Article.MainButton.displayName:
setArticleMainButtonContent(child);
break;
case Article.Body.displayName:
setArticleBodyContent(child);
break;
default:
break;
}
});
}, [children]);
const sizeChangeHandler = React.useCallback(() => {
clearTimeout(refTimer.current);
refTimer.current = setTimeout(() => {
if (isMobileOnly || isMobileUtils() || window.innerWidth === 375)
setShowText(true);
if (
((isTabletUtils() && window.innerWidth !== 375) || isMobile) &&
!isMobileOnly
)
setShowText(false);
if (isDesktopUtils() && !isMobile) setShowText(true);
}, 100);
}, [refTimer.current, setShowText]);
const hideText = React.useCallback((event) => {
event.preventDefault;
setShowText(false);
}, []);
return (
<>
<StyledArticle showText={showText} articleOpen={articleOpen} {...rest}>
<Resizable
defaultSize={{
width: 256,
}}
enable={enable}
className="resizable-block"
handleWrapperClass="resizable-border not-selectable"
>
<SubArticleHeader showText={showText} onClick={toggleShowText}>
{articleHeaderContent ? articleHeaderContent.props.children : null}
</SubArticleHeader>
{articleMainButtonContent ? (
<SubArticleMainButton showText={showText}>
{articleMainButtonContent.props.children}
</SubArticleMainButton>
) : null}
<SubArticleBody showText={showText}>
{articleBodyContent ? articleBodyContent.props.children : null}
</SubArticleBody>
</Resizable>
</StyledArticle>
{articleOpen && (isMobileOnly || window.innerWidth <= 375) && (
<>
<SubArticleBackdrop onClick={toggleArticleOpen} />
</>
)}
</>
);
};
Article.propTypes = {
showText: PropTypes.bool,
setShowText: PropTypes.func,
articleOpen: PropTypes.bool,
toggleArticleOpen: PropTypes.func,
children: PropTypes.any,
};
Article.Header = () => {
return null;
};
Article.Header.displayName = "Header";
Article.MainButton = () => {
return null;
};
Article.MainButton.displayName = "MainButton";
Article.Body = () => {
return null;
};
Article.Body.displayName = "Body";
export default inject(({ auth }) => {
const { settingsStore } = auth;
const {
showText,
setShowText,
articleOpen,
toggleShowText,
toggleArticleOpen,
} = settingsStore;
return {
showText,
setShowText,
articleOpen,
toggleShowText,
toggleArticleOpen,
};
})(observer(Article));

View File

@ -0,0 +1,270 @@
import styled, { css } from "styled-components";
import { isMobile, isMobileOnly, isTablet } from "react-device-detect";
import {
mobile,
tablet,
isMobile as isMobileUtils,
isTablet as isTabletUtils,
isDesktop as isDesktopUtils,
} from "@appserver/components/utils/device";
import Heading from "@appserver/components/heading";
import { Base } from "@appserver/components/themes";
import MenuIcon from "@appserver/components/public/static/images/menu.react.svg";
import CrossIcon from "@appserver/components/public/static/images/cross.react.svg";
const StyledArticle = styled.article`
position: relative;
background: ${(props) => props.theme.catalog.background};
${isMobile &&
css`
margin-top: 48px;
`}
@media ${mobile} {
position: fixed;
margin-top: 16px;
height: calc(100vh - 64px) !important;
z-index: 400;
}
${isMobileOnly &&
css`
position: fixed;
margin-top: 64px !important;
height: calc(100vh - 64px) !important;
`}
z-index: ${(props) =>
props.showText && (isMobileOnly || isMobileUtils()) ? "205" : "100"};
.resizable-block {
display: flex;
flex-direction: column;
min-width: ${(props) => (props.showText ? "256px" : "52px")};
width: ${(props) => (props.showText ? "256px" : "52px")};
height: calc(100% - 44px) !important;
background: ${(props) => props.theme.catalog.background};
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
padding-bottom: 0px;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
.resizable-border {
div {
cursor: ew-resize !important;
}
}
@media ${tablet} {
min-width: ${(props) => (props.showText ? "240px" : "52px")};
max-width: ${(props) => (props.showText ? "240px" : "52px")};
.resizable-border {
display: none;
}
}
@media ${mobile} {
display: ${(props) => (props.articleOpen ? "flex" : "none")};
min-width: 100vw;
width: 100vw;
height: calc(100vh - 64px) !important;
margin: 0;
padding: 0;
padding-bottom: 0px;
}
${isTablet &&
css`
min-width: ${(props) => (props.showText ? "240px" : "52px")};
max-width: ${(props) => (props.showText ? "240px" : "52px")};
.resizable-border {
display: none;
}
`}
${isMobileOnly &&
css`
display: ${(props) => (props.articleOpen ? "flex" : "none")};
min-width: 100vw !important;
width: 100vw;
height: calc(100vh - 64px) !important;
margin: 0;
padding: 0;
padding-bottom: 0px;
`}
}
`;
StyledArticle.defaultProps = { theme: Base };
const StyledArticleHeader = styled.div`
padding: 11px 20px 14px;
margin-left: -1px;
display: flex;
justify-content: flex-start;
align-items: center;
@media ${tablet} {
padding: 16px 16px 17px;
margin: 0;
justify-content: ${(props) => (props.showText ? "flex-start" : "center")};
}
@media ${mobile} {
border-bottom: ${(props) => props.theme.catalog.header.borderBottom};
padding: 12px 16px 12px;
margin-bottom: 16px !important;
}
${isTablet &&
css`
padding: 16px 16px 17px;
justify-content: ${(props) => (props.showText ? "flex-start" : "center")};
margin: 0;
`}
${isMobileOnly &&
css`
border-bottom: ${(props) =>
props.theme.catalog.header.borderBottom} !important;
padding: 12px 16px 12px !important;
margin-bottom: 16px !important;
`}
`;
StyledArticleHeader.defaultProps = { theme: Base };
const StyledHeading = styled(Heading)`
margin: 0;
padding: 0;
font-weight: bold;
line-height: 28px;
@media ${tablet} {
display: ${(props) => (props.showText ? "block" : "none")};
margin-left: ${(props) => props.showText && "12px"};
}
${isTablet &&
css`
display: ${(props) => (props.showText ? "block" : "none")};
margin-left: ${(props) => props.showText && "12px"};
`}
@media ${mobile} {
margin-left: 0;
}
${isMobileOnly &&
css`
margin-left: 0 !important;
`}
`;
const StyledIconBox = styled.div`
display: none;
align-items: center;
height: 28px;
@media ${tablet} {
display: flex;
}
@media ${mobile} {
display: none;
}
${isMobile &&
css`
display: flex !important;
`}
${isMobileOnly &&
css`
display: none !important;
`}
`;
const StyledMenuIcon = styled(MenuIcon)`
display: block;
width: 20px;
height: 20px;
cursor: pointer;
path {
fill: ${(props) => props.theme.catalog.header.iconFill};
}
`;
StyledMenuIcon.defaultProps = { theme: Base };
const StyledArticleMainButton = styled.div`
padding: 0px 20px 16px;
max-width: 216px;
@media ${tablet} {
display: none;
}
@media ${mobile} {
display: none;
}
${isTablet &&
css`
display: none;
`}
${isMobileOnly &&
css`
display: none;
`}
`;
const StyledControlContainer = styled.div`
background: ${(props) => props.theme.catalog.control.background};
width: 24px;
height: 24px;
position: absolute;
top: 30px;
right: 10px;
border-radius: 100px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 290;
`;
StyledControlContainer.defaultProps = { theme: Base };
const StyledCrossIcon = styled(CrossIcon)`
width: 12px;
height: 12px;
path {
fill: ${(props) => props.theme.catalog.control.fill};
}
`;
StyledCrossIcon.defaultProps = { theme: Base };
export {
StyledArticle,
StyledArticleHeader,
StyledHeading,
StyledIconBox,
StyledMenuIcon,
StyledArticleMainButton,
StyledControlContainer,
StyledCrossIcon,
};

View File

@ -0,0 +1,24 @@
import React from "react";
import PropTypes from "prop-types";
import Backdrop from "@appserver/components/backdrop";
import { StyledControlContainer, StyledCrossIcon } from "../styled-article";
const ArticleBackdrop = ({ onClick, ...rest }) => {
return (
<>
<StyledControlContainer onClick={onClick} {...rest}>
<StyledCrossIcon />
</StyledControlContainer>
<Backdrop visible={true} zIndex={201} withBackground={true} />
</>
);
};
ArticleBackdrop.propTypes = {
showText: PropTypes.bool,
onClick: PropTypes.func,
};
export default React.memo(ArticleBackdrop);

View File

@ -0,0 +1,9 @@
import React from "react";
const ArticleBody = ({ children }) => {
return <> {children}</>;
};
ArticleBody.displayName = "Body";
export default React.memo(ArticleBody);

View File

@ -0,0 +1,33 @@
import React from "react";
import PropTypes from "prop-types";
import {
StyledArticleHeader,
StyledHeading,
StyledIconBox,
StyledMenuIcon,
} from "../styled-article";
const ArticleHeader = ({ showText, children, onClick, ...rest }) => {
return (
<StyledArticleHeader showText={showText} {...rest}>
<StyledIconBox name="article-burger">
<StyledMenuIcon onClick={onClick} />
</StyledIconBox>
<StyledHeading showText={showText} size="large">
{children}
</StyledHeading>
</StyledArticleHeader>
);
};
ArticleHeader.propTypes = {
children: PropTypes.any,
showText: PropTypes.bool,
onClick: PropTypes.func,
};
ArticleHeader.displayName = "Header";
export default React.memo(ArticleHeader);

View File

@ -0,0 +1,11 @@
import React from "react";
import { StyledArticleMainButton } from "../styled-article";
const ArticleMainButton = (props) => {
return <StyledArticleMainButton {...props} />;
};
ArticleMainButton.displayName = "MainButton";
export default ArticleMainButton;

View File

@ -5,10 +5,21 @@ import Headline from "../Headline";
import Text from "@appserver/components/text";
import Button from "@appserver/components/button";
import store from "studio/store";
const theme = store.auth.settingsStore.theme;
const ErrorContainer = (props) => {
//console.log("ErrorContainer render");
const { headerText, bodyText, buttonText, buttonUrl, ...rest } = props;
const {
headerText,
bodyText,
buttonText,
buttonUrl,
children,
...rest
} = props;
return (
<StyledErrorContainer {...rest}>
@ -335,7 +346,7 @@ const ErrorContainer = (props) => {
<div id="button-container">
<Button
id="button"
size="big"
size="normal"
scale
primary
label={buttonText}
@ -343,6 +354,7 @@ const ErrorContainer = (props) => {
/>
</div>
)}
{children}
</StyledErrorContainer>
);
};
@ -352,6 +364,7 @@ ErrorContainer.propTypes = {
bodyText: PropTypes.string,
buttonText: PropTypes.string,
buttonUrl: PropTypes.string,
children: PropTypes.any,
};
export default ErrorContainer;

View File

@ -1,9 +1,11 @@
import styled from "styled-components";
import { Base } from "@appserver/components/themes";
const StyledErrorContainer = styled.div`
//background: #ffffff;
background: ${(props) => props.theme.errorContainer.background};
cursor: default;
height: 100%;
height: 100vh;
width: 100%;
min-height: 100%;
overflow-x: hidden;
@ -1036,4 +1038,6 @@ const StyledErrorContainer = styled.div`
}
`;
StyledErrorContainer.defaultProps = { theme: Base };
export default StyledErrorContainer;

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import Loader from "@appserver/components/loader";
import PageLayout from "../PageLayout";
import Section from "../Section";
export class ExternalRedirect extends Component {
constructor(props) {
@ -15,11 +15,11 @@ export class ExternalRedirect extends Component {
render() {
return (
<PageLayout>
<PageLayout.SectionBody>
<Section>
<Section.SectionBody>
<Loader className="pageLoader" type="rombs" size="40px" />
</PageLayout.SectionBody>
</PageLayout>
</Section.SectionBody>
</Section>
);
}
}

View File

@ -1,3 +1,4 @@
import Base from "@appserver/components/themes/base";
import styled, { css } from "styled-components";
const StyledFilterInput = styled.div`
@ -26,26 +27,41 @@ const StyledFilterInput = styled.div`
.styled-filter-block {
display: flex;
.filter-button {
#filter-button {
svg {
height: 25px;
path:not(:first-child) {
stroke: #a3a9ae;
path:first-child {
fill: ${(props) =>
props.theme.filterInput.filterButton.fill} !important;
stroke: ${(props) =>
props.theme.filterInput.filterButton.stroke} !important;
}
path:not(:first-child) {
fill: ${(props) =>
props.theme.filterInput.filterButton.fillSecond} !important;
/* stroke: ${(props) =>
props.theme.filterInput.filterButton.stroke} !important; */
}
/* path:not(:first-child) {
stroke: ${(props) => props.theme.filterInput.filterButton.stroke};
} */
}
stroke: #a3a9ae;
/* stroke: ${(props) => props.theme.filterInput.filterButton.stroke};
div:active {
svg path:first-child {
fill: #eceef1;
stroke: #a3a9ae;
fill: ${(props) => props.theme.filterInput.filterButton.fill};
stroke: ${(props) => props.theme.filterInput.filterButton.stroke};
}
}
div:first-child:hover {
svg path:not(:first-child) {
stroke: #a3a9ae;
stroke: ${(props) => props.theme.filterInput.filterButton.stroke};
}
}
} */
}
}
@ -71,7 +87,7 @@ const StyledFilterInput = styled.div`
padding-left: 4px;
}
.combo-button-label {
color: #333;
color: ${(props) => props.theme.filterInput.comboButtonLabelColor};
}
}
@ -120,7 +136,7 @@ const StyledFilterInput = styled.div`
`}
.combo-button-label {
color: #a3a9ae;
color: ${(props) => props.theme.filterInput.comboButtonLabelColorTwo};
}
}
@ -136,24 +152,38 @@ const StyledFilterInput = styled.div`
}
`;
StyledFilterInput.defaultProps = { theme: Base };
export const StyledViewSelector = styled.div`
border: 1px solid ${(props) => (props.isDisabled ? "#ECEEF1" : "#D0D5DA")};
border: 1px solid
${(props) =>
props.isDisabled
? props.theme.filterInput.viewSelector.disabledBorder
: props.theme.filterInput.viewSelector.border};
border-radius: 3px;
padding: 7px;
${(props) => props.isDisabled && "background-color: #F8F9F9;"}
${(props) =>
props.isDisabled &&
`background-color: ${props.theme.filterInput.viewSelector.disabledBackground}`}
svg {
pointer-events: none;
}
&.active {
background-color: #a3a9ae;
border-color: #a3a9ae;
background-color: ${(props) =>
props.theme.filterInput.viewSelector.activeBackground};
border-color: ${(props) =>
props.theme.filterInput.viewSelector.activeBorder};
}
&:hover {
${(props) => !props.isDisabled && "background-color: #A3A9AE;"}
${(props) => !props.isDisabled && "border-color: #A3A9AE;"}
${(props) =>
!props.isDisabled &&
`background-color: ${props.theme.filterInput.viewSelector.activeBackground};`}
${(props) =>
!props.isDisabled &&
`border-color: ${props.theme.filterInput.viewSelector.activeBorder};`}
}
&:first-child {
@ -169,49 +199,56 @@ export const StyledViewSelector = styled.div`
}
`;
StyledViewSelector.defaultProps = { theme: Base };
export const StyledFilterItem = styled.div`
display: ${(props) => (props.block ? "flex" : "inline-block")};
margin-bottom: ${(props) => (props.block ? "8px" : "0")};
position: relative;
height: 25px;
margin-right: 2px;
border: 1px solid #eceef1;
border: ${(props) => props.theme.filterInput.filterItem.border};
border-radius: 3px;
background-color: #f8f9f9;
background-color: ${(props) =>
props.theme.filterInput.filterItem.backgroundColor};
padding-right: 22px;
font-weight: 600;
font-size: 13px;
line-height: 15px;
box-sizing: border-box;
color: #555f65;
color: ${(props) => props.theme.filterInput.filterItem.color};
&:last-child {
margin-bottom: 0;
}
`;
StyledFilterItem.defaultProps = { theme: Base };
export const StyledFilterItemContent = styled.div`
display: flex;
padding: 4px 4px 2px 7px;
width: max-content;
user-select: none;
color: #333;
color: ${(props) => props.theme.filterInput.content.color};
${(props) =>
props.isOpen &&
!props.isDisabled &&
css`
background: #eceef1;
background: ${props.theme.filterInput.content.background};
`}
${(props) =>
!props.isDisabled &&
css`
&:active {
background: #eceef1;
background: ${props.theme.filterInput.content.background};
}
`}
`;
StyledFilterItemContent.defaultProps = { theme: Base };
export const StyledCloseButtonBlock = styled.div`
display: flex;
cursor: ${(props) =>
@ -220,17 +257,18 @@ export const StyledCloseButtonBlock = styled.div`
position: absolute;
height: 100%;
width: 25px;
border-left: 1px solid #eceef1;
border-left: ${(props) => props.theme.filterInput.closeButton.borderLeft};
right: 0;
top: 0;
background-color: #f8f9f9;
background-color: ${(props) =>
props.theme.filterInput.closeButton.background};
${(props) =>
!props.isDisabled &&
css`
&:active {
background: #eceef1;
background: ${props.theme.filterInput.closeButton.activeBackground};
svg path:first-child {
fill: #a3a9ae;
fill: ${props.theme.filterInput.closeButton.activeFill};
}
}
@ -238,7 +276,7 @@ export const StyledCloseButtonBlock = styled.div`
.styled-close-button {
svg {
path {
fill: #555f65;
fill: ${props.theme.filterInput.closeButton.hoverFill};
}
}
}
@ -246,6 +284,8 @@ export const StyledCloseButtonBlock = styled.div`
`}
`;
StyledCloseButtonBlock.defaultProps = { theme: Base };
export const Caret = styled.div`
width: 7px;
position: absolute;
@ -262,9 +302,9 @@ export const StyledHideFilterButton = styled.div`
font-weight: 600;
font-size: 16px;
height: 25px;
border: 1px solid #eceef1;
border: ${(props) => props.theme.filterInput.hideButton.border};
border-radius: 3px;
background-color: #f8f9f9;
background-color: ${(props) => props.theme.filterInput.hideButton.background};
padding: 0 20px 0 9px;
margin-right: 2px;
cursor: ${(props) => (props.isDisabled ? "default" : "pointer")};
@ -272,13 +312,21 @@ export const StyledHideFilterButton = styled.div`
font-style: normal;
:hover {
border-color: ${(props) => (props.isDisabled ? "#ECEEF1" : "#A3A9AE")};
border-color: ${(props) =>
props.isDisabled
? props.theme.filterInput.hideButton.disabledHoverBorder
: props.theme.filterInput.hideButton.hoverBorder};
}
:active {
background-color: ${(props) => (props.isDisabled ? "#F8F9F9" : "#ECEEF1")};
background-color: ${(props) =>
props.isDisabled
? props.theme.filterInput.hideButton.disabledActiveBackground
: props.theme.filterInput.hideButton.activeBackground};
}
`;
StyledHideFilterButton.defaultProps = { theme: Base };
export const StyledIconButton = styled.div`
transform: ${(state) => (!state.sortDirection ? "scale(1, -1)" : "scale(1)")};
`;

View File

@ -10,8 +10,6 @@ const CloseButton = (props) => {
<div className={`styled-close-button ${className}`}>
<IconButton
className="close-button"
color={"#A3A9AE"}
clickColor={"#A3A9AE"}
size={10}
iconName="/static/images/cross.react.svg"
isFill={true}

View File

@ -7,13 +7,17 @@ import DropDown from "@appserver/components/drop-down";
import ExpanderDownIcon from "../../../../../public/images/expander-down.react.svg";
import { Caret, StyledHideFilterButton } from "../StyledFilterInput";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import { Base } from "@appserver/components/themes";
const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
${commonIconsStyles}
path {
fill: "#A3A9AE";
fill: ${(props) => props.theme.filterInput.hideButton.expanderFill};
}
`;
StyledExpanderDownIcon.defaultProps = { theme: Base };
class HideFilter extends React.Component {
constructor(props) {
super(props);

View File

@ -171,7 +171,6 @@ class SortComboBox extends React.Component {
>
<StyledIconButton sortDirection={!!sortDirection}>
<IconButton
color={"#A3A9AE"}
iconName={selectorIcon}
isDisabled={isDisabled}
isFill={true}

View File

@ -28,6 +28,7 @@ const FloatingButton = ({ id, className, style, ...rest }) => {
return (
<StyledCircleWrap
color={color}
id={id}
className={`${className} not-selectable`}
style={style}
@ -42,7 +43,7 @@ const FloatingButton = ({ id, className, style, ...rest }) => {
<div className="circle__fill"></div>
</div>
<StyledFloatingButton color={color}>
<StyledFloatingButton className="circle__background" color={color}>
<IconBox>
{icon == "upload" ? (
<ButtonUploadIcon />

View File

@ -1,14 +1,14 @@
import Base from "@appserver/components/themes/base";
import styled, { keyframes, css } from "styled-components";
const backgroundColor = "none";
const color = "#2DA7DB";
const StyledCircleWrap = styled.div`
width: 54px;
height: 54px;
background: ${backgroundColor};
width: 48px;
height: 48px;
background: ${(props) =>
props.color ? props.color : props.theme.floatingButton.backgroundColor};
border-radius: 50%;
cursor: pointer;
box-shadow: ${(props) => props.theme.floatingButton.boxShadow};
`;
const rotate360 = keyframes`
@ -20,20 +20,31 @@ const rotate360 = keyframes`
}
`;
StyledCircleWrap.defaultProps = { theme: Base };
const StyledCircle = styled.div`
.circle__mask,
.circle__fill {
width: 54px;
height: 54px;
width: 42px;
height: 42px;
position: absolute;
border-radius: 50%;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
}
${(props) =>
props.percent > 0
? css`
.circle__mask {
clip: rect(0px, 54px, 54px, 27px);
clip: rect(0px, 42px, 42px, 21px);
}
.circle__fill {
@ -49,8 +60,8 @@ const StyledCircle = styled.div`
`}
.circle__mask .circle__fill {
clip: rect(0px, 27px, 54px, 0px);
background-color: ${color};
clip: rect(0px, 21px, 42px, 0px);
background-color: ${(props) => props.theme.floatingButton.color};
}
.circle__mask.circle__full {
@ -69,26 +80,36 @@ const StyledCircle = styled.div`
`;
const StyledFloatingButton = styled.div`
width: 48px;
height: 48px;
width: 38px;
height: 38px;
border-radius: 50%;
background: ${(props) => (props.color ? props.color : "#fff")};
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.13);
background: ${(props) =>
props.color ? props.color : props.theme.floatingButton.backgroundColor};
text-align: center;
margin: 3px;
margin: 5px;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
`;
StyledFloatingButton.defaultProps = { theme: Base };
const IconBox = styled.div`
padding-top: 12px;
// padding-top: 12px;
display: flex;
align-items: center;
justify-content: center;
`;
IconBox.defaultProps = { theme: Base };
const StyledAlertIcon = styled.div`
position: absolute;
width: 12px;
height: 12px;
left: 26px;
top: -10px;
left: 19px;
top: 0px;
`;
export {

View File

@ -3,7 +3,7 @@ import PropTypes from "prop-types";
import StyledContainer from "./StyledMainButton";
import RectangleLoader from "../RectangleLoader";
const MainButtonLoader = ({ id, className, style, ...rest }) => {
const ArticleButtonLoader = ({ id, className, style, ...rest }) => {
const {
title,
width,
@ -34,16 +34,16 @@ const MainButtonLoader = ({ id, className, style, ...rest }) => {
);
};
MainButtonLoader.propTypes = {
ArticleButtonLoader.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
};
MainButtonLoader.defaultProps = {
ArticleButtonLoader.defaultProps = {
id: undefined,
className: undefined,
style: undefined,
};
export default MainButtonLoader;
export default ArticleButtonLoader;

View File

@ -9,7 +9,7 @@ import Loaders from "@appserver/common/components/Loaders";
```
```jsx
<Loaders.MainButton />
<Loaders.ArticleButton />
```
### Properties

View File

@ -0,0 +1,16 @@
import { tablet } from "@appserver/components/utils/device";
import { isTablet } from "react-device-detect";
import styled from "styled-components";
const StyledContainer = styled.div`
width: 216px;
margin: -9px 0 0;
@media ${tablet} {
display: none;
}
${isTablet && "display: none"}
`;
export default StyledContainer;

View File

@ -0,0 +1 @@
export default from "./ArticleButtonLoader";

View File

@ -0,0 +1,53 @@
import React from "react";
import PropTypes from "prop-types";
import {
StyledContainer,
StyledBlock,
StyledRectangleLoader,
} from "./StyledArticleFolderLoader";
import { inject, observer } from "mobx-react";
const ArticleFolderLoader = ({ id, className, style, showText, ...rest }) => {
return (
<StyledContainer
id={id}
className={className}
style={style}
showText={showText}
>
<StyledBlock>
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
</StyledBlock>
<StyledBlock>
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
</StyledBlock>
<StyledBlock>
<StyledRectangleLoader {...rest} />
</StyledBlock>
<StyledBlock>
<StyledRectangleLoader {...rest} />
</StyledBlock>
</StyledContainer>
);
};
ArticleFolderLoader.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
showText: PropTypes.bool,
};
ArticleFolderLoader.defaultProps = {
id: undefined,
className: undefined,
style: undefined,
};
export default inject(({ auth }) => ({
showText: auth.settingsStore.showText,
}))(observer(ArticleFolderLoader));

View File

@ -0,0 +1,47 @@
import styled from "styled-components";
import RectangleLoader from "../RectangleLoader";
import { tablet, mobile } from "@appserver/components/utils/device";
const StyledContainer = styled.div`
margin: 0;
max-width: 216px;
padding: 0 20px;
@media ${tablet} {
width: ${(props) => (props.showText ? "240px" : "52px")};
padding: ${(props) => (props.showText ? "0 16px" : "10px 16px")};
}
@media ${mobile} {
width: 100%;
padding: 0 16px;
}
`;
const StyledBlock = styled.div`
margin: 0;
width: 100%;
height: auto;
display: flex;
flex-direction: column;
margin-bottom: 20px;
@media ${tablet} {
margin-bottom: 24px;
}
`;
const StyledRectangleLoader = styled(RectangleLoader)`
height: 20px;
width: 216px;
padding: 0 0 16px;
@media ${tablet} {
height: 20px;
width: 20px;
padding: 0 0 24px;
}
`;
export { StyledBlock, StyledContainer, StyledRectangleLoader };

View File

@ -0,0 +1 @@
export default from "./ArticleFolderLoader";

View File

@ -0,0 +1,44 @@
import React from "react";
import PropTypes from "prop-types";
import {
StyledContainer,
StyledRectangleLoader,
} from "./StyledArticleGroupsLoader";
import { inject, observer } from "mobx-react";
const ArticleGroupsLoader = ({ id, className, style, showText, ...rest }) => {
return (
<StyledContainer
id={id}
className={className}
style={style}
showText={showText}
>
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
<StyledRectangleLoader {...rest} />
</StyledContainer>
);
};
ArticleGroupsLoader.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
showText: PropTypes.bool,
};
ArticleGroupsLoader.defaultProps = {
id: undefined,
className: undefined,
style: undefined,
};
export default inject(({ auth }) => ({
showText: auth.settingsStore.showText,
}))(observer(ArticleGroupsLoader));

View File

@ -0,0 +1,36 @@
import styled from "styled-components";
import RectangleLoader from "../RectangleLoader";
import { tablet, mobile } from "@appserver/components/utils/device";
const StyledContainer = styled.div`
margin: 0;
max-width: 216px;
padding: 0 20px;
display: flex;
flex-direction: column;
@media ${tablet} {
width: ${(props) => (props.showText ? "240px" : "52px")};
padding: 0 16px;
}
@media ${mobile} {
width: 100%;
}
`;
const StyledRectangleLoader = styled(RectangleLoader)`
height: 20px;
width: 216px;
padding: 0 0 16px;
@media ${tablet} {
height: 20px;
width: 20px;
padding: 0 0 24px;
}
`;
export { StyledContainer, StyledRectangleLoader };

View File

@ -0,0 +1 @@
export default from "./ArticleGroupsLoader";

View File

@ -1,8 +1,17 @@
import { isMobile } from "react-device-detect";
import styled from "styled-components";
import { tablet } from "@appserver/components/utils/device";
const StyledContainer = styled.div`
padding-top: 13px;
padding-bottom: 10px;
max-width: 216px;
margin-left: 1px;
@media ${tablet} {
margin-left: 0;
}
${isMobile} {
margin-left: 0;
}
`;
export default StyledContainer;

View File

@ -1,8 +1,9 @@
import { Base } from "@appserver/components/themes";
import styled from "styled-components";
const StyledDialogLoader = styled.div`
.dialog-loader-header {
border-bottom: 1px solid rgb(222, 226, 230);
border-bottom: ${(props) => props.theme.dialogLoader.borderBottom};
display: flex;
padding: 12px 0;
}
@ -24,4 +25,6 @@ const StyledDialogLoader = styled.div`
}
`;
StyledDialogLoader.defaultProps = { theme: Base };
export default StyledDialogLoader;

View File

@ -37,10 +37,10 @@ const HeaderLoader = ({ id, className, style, ...rest }) => {
radius="18"
width="36"
height="36"
backgroundColor="#fff"
foregroundColor="#fff"
backgroundOpacity={0.25}
foregroundOpacity={0.2}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
/>
</StyledHeader>
);

View File

@ -1,12 +0,0 @@
import styled from "styled-components";
import { desktop } from "@appserver/components/utils/device";
const StyledContainer = styled.div`
width: 209px;
@media ${desktop} {
width: 225px;
}
`;
export default StyledContainer;

View File

@ -1 +0,0 @@
export default from "./MainButtonLoader";

View File

@ -1,5 +1,6 @@
import styled, { css } from "styled-components";
import { smallTablet, tablet, size } from "@appserver/components/utils/device";
import Base from "@appserver/components/themes/base";
const StyledTile = styled.div`
position: relative;
@ -28,6 +29,8 @@ const StyledTile = styled.div`
}
`;
StyledTile.defaultProps = { theme: Base };
const StyledMainContent = styled.div`
height: 172px;
`;

View File

@ -3,6 +3,9 @@ import Circle from "./CircleLoader";
import Header from "./HeaderLoader";
import SectionHeader from "./SectionHeaderLoader";
import ArticleHeader from "./ArticleHeaderLoader";
import ArticleButton from "./ArticleButtonLoader";
import ArticleFolder from "./ArticleFolderLoader";
import ArticleGroup from "./ArticleGroupsLoader";
import TreeFolders from "./TreeFolderLoader";
import TreeSettingsLoader from "./TreeSettingsLoader";
import Row from "./RowLoader";
@ -17,7 +20,6 @@ import Tile from "./TileLoader";
import Tiles from "./TilesLoader";
import DialogLoader from "./DialogLoader";
import DialogAsideLoader from "./DialogAsideLoader";
import MainButton from "./MainButtonLoader";
export default {
Rectangle,
@ -39,5 +41,7 @@ export default {
Tiles,
DialogLoader,
DialogAsideLoader,
MainButton,
ArticleButton,
ArticleFolder,
ArticleGroup,
};

View File

@ -513,7 +513,7 @@ class MediaViewer extends React.Component {
<div>
<div className="details" ref={this.detailsContainer}>
<Text isBold fontSize="14px" color="#fff" className="title">
<Text isBold fontSize="14px" className="title">
{title}
</Text>
<ControlBtn
@ -521,7 +521,6 @@ class MediaViewer extends React.Component {
className="mediaPlayerClose"
>
<IconButton
color="#fff"
iconName="/static/images/cross.react.svg"
size={25}
isClickable

View File

@ -1,7 +1,8 @@
import { Base } from "@appserver/components/themes";
import styled from "styled-components";
const StyledMediaViewer = styled.div`
color: #d1d1d1;
color: ${(props) => props.theme.mediaViewer.color};
display: ${(props) => (props.visible ? "block" : "none")};
overflow: hidden;
.videoViewerOverlay {
@ -20,7 +21,7 @@ const StyledMediaViewer = styled.div`
padding-bottom: 14px;
height: 20px;
width: 100%;
background-color: rgba(11, 11, 11, 0.7);
background-color: ${(props) => props.theme.mediaViewer.backgroundColor};
position: fixed;
bottom: 0;
left: 0;
@ -43,7 +44,7 @@ const StyledMediaViewer = styled.div`
svg {
path {
fill: #fff;
fill: ${(props) => props.theme.mediaViewer.fill};
}
}
}
@ -53,7 +54,7 @@ const StyledMediaViewer = styled.div`
padding-bottom: 14px;
height: 20px;
width: 100%;
background: rgba(17, 17, 17, 0.867);
background: ${(props) => props.theme.mediaViewer.background};
position: fixed;
top: 0;
left: 0;
@ -66,6 +67,7 @@ const StyledMediaViewer = styled.div`
width: calc(100% - 50px);
padding-left: 16px;
box-sizing: border-box;
color: ${(props) => props.theme.mediaViewer.titleColor};
}
}
@ -75,7 +77,15 @@ const StyledMediaViewer = styled.div`
right: 10px;
height: 25px;
width: 25px;
svg {
path {
fill: ${(props) => props.theme.mediaViewer.iconColor};
}
}
}
`;
StyledMediaViewer.defaultProps = { theme: Base };
export default StyledMediaViewer;

View File

@ -1,6 +1,7 @@
import React from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import { Base } from "@appserver/components/themes";
const StyledVideoControlBtn = styled.div`
display: inline-block;
@ -13,9 +14,13 @@ const StyledVideoControlBtn = styled.div`
text-align: center;
&:hover {
background-color: rgba(200, 200, 200, 0.2);
background-color: ${(props) =>
props.theme.mediaViewer.controlBtn.backgroundColor};
}
`;
StyledVideoControlBtn.defaultProps = { theme: Base };
const ControlBtn = (props) => {
return (
<StyledVideoControlBtn {...props}>{props.children}</StyledVideoControlBtn>

View File

@ -14,6 +14,7 @@ import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import MediaScrollButton from "./scroll-button";
import ControlBtn from "./control-btn";
import equal from "fast-deep-equal/react";
import { Base } from "@appserver/components/themes";
const StyledMediaZoomInIcon = styled(MediaZoomInIcon)`
${commonIconsStyles}
@ -69,7 +70,8 @@ const StyledViewer = styled(Viewer)`
.react-viewer-btn {
background-color: transparent;
&:hover {
background-color: rgba(200, 200, 200, 0.2);
background-color: ${(props) =>
props.theme.mediaViewer.imageViewer.backgroundColor};
}
}
li[data-key="prev"] {
@ -119,7 +121,7 @@ const StyledViewer = styled(Viewer)`
path,
rect {
fill: #fff;
fill: ${(props) => props.theme.mediaViewer.imageViewer.fill};
}
}
@ -132,7 +134,7 @@ const StyledViewer = styled(Viewer)`
path,
rect {
fill: #fff;
fill: ${(props) => props.theme.mediaViewer.imageViewer.fill};
}
}
.scrollBtn {
@ -140,11 +142,15 @@ const StyledViewer = styled(Viewer)`
opacity: ${(props) => (props.inactive ? "0.2" : "1")};
&:hover {
background-color: ${(props) =>
!props.inactive ? "rgba(200, 200, 200, 0.2)" : "rgba(11,11,11,0.7)"};
!props.inactive
? props.theme.mediaViewer.imageViewer.backgroundColor
: props.theme.mediaViewer.imageViewer.inactiveBackgroundColor};
}
}
`;
StyledViewer.defaultProps = { theme: Base };
var customToolbar = [
{
key: "zoomIn",

View File

@ -1,6 +1,7 @@
import React from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import { Base } from "@appserver/components/themes";
const StyledProgress = styled.div`
display: inline-block;
@ -11,7 +12,8 @@ const StyledProgress = styled.div`
position: relative;
width: ${(props) => props.width}px;
height: 6px;
background: rgba(200, 200, 200, 0.2);
background: ${(props) =>
props.theme.mediaViewer.progressBar.backgroundColor};
margin: 15px 0;
vertical-align: middle;
}
@ -22,7 +24,7 @@ const StyledProgress = styled.div`
top: calc(50% - 3px);
height: 6px;
background: #d1d1d1;
background: ${(props) => props.theme.mediaViewer.progressBar.background};
border-radius: 2px;
}
input[type="range"] {
@ -104,6 +106,8 @@ const StyledProgress = styled.div`
pointer-events: none;
}
`;
StyledProgress.defaultProps = { theme: Base };
const Progress = (props) => {
return (
<StyledProgress {...props}>

View File

@ -1,3 +1,4 @@
import { Base } from "@appserver/components/themes";
import React from "react";
import styled from "styled-components";
@ -14,12 +15,13 @@ const ScrollButton = styled.div`
width: 40px;
height: 40px;
background-color: rgba(11, 11, 11, 0.7);
background-color: ${(props) =>
props.theme.mediaViewer.scrollButton.backgroundColor};
border-radius: 50%;
&:hover {
background-color: ${(props) =>
!props.inactive && "rgba(200, 200, 200, 0.2)"};
!props.inactive && props.theme.mediaViewer.scrollButton.background};
}
&:before {
@ -27,7 +29,7 @@ const ScrollButton = styled.div`
top: 12px;
left: ${(props) => (props.orientation == "left" ? "9px;" : "15px;")};
position: absolute;
border: solid #fff;
border: ${(props) => props.theme.mediaViewer.scrollButton.border};
border-width: 0 2px 2px 0;
display: inline-block;
padding: 7px;
@ -38,6 +40,8 @@ const ScrollButton = styled.div`
}
`;
ScrollButton.defaultProps = { theme: Base };
const MediaScrollButton = (props) => {
return <ScrollButton {...props} />;
};

View File

@ -13,12 +13,13 @@ import MediaFullScreenIcon from "../../../../../public/images/media.fullscreen.v
import MediaMuteIcon from "../../../../../public/images/media.mute.react.svg";
import MediaMuteOffIcon from "../../../../../public/images/media.muteoff.react.svg";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import { Base } from "@appserver/components/themes";
const iconsStyles = css`
path,
stroke,
rect {
fill: #fff;
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
@ -28,10 +29,14 @@ const StyledControls = styled.div`
display: block;
position: fixed;
z-index: 301;
${(props) => !props.isVideo && "background-color: rgba(11,11,11,0.7);"}
${(props) =>
!props.isVideo &&
`background-color: ${props.theme.mediaViewer.videoViewer.backgroundColor};`}
top: calc(50% + ${(props) => props.top}px);
left: ${(props) => props.left}px;
`;
StyledControls.defaultProps = { theme: Base };
const StyledVideoControlBtn = styled.div`
display: inline-block;
height: 26px;
@ -43,7 +48,8 @@ const StyledVideoControlBtn = styled.div`
text-align: center;
vertical-align: top;
&:hover {
background-color: rgba(200, 200, 200, 0.2);
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.background};
}
.playBtnContainer {
@ -74,36 +80,43 @@ const StyledVideoControlBtn = styled.div`
line-height: 19px;
}
`;
StyledVideoControlBtn.defaultProps = { theme: Base };
const StyledMediaPauseIcon = styled(MediaPauseIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaPauseIcon.defaultProps = { theme: Base };
const StyledMediaPlayIcon = styled(MediaPlayIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaPlayIcon.defaultProps = { theme: Base };
const StyledMediaFullScreenIcon = styled(MediaFullScreenIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaFullScreenIcon.defaultProps = { theme: Base };
const StyledMediaMuteIcon = styled(MediaMuteIcon)`
${commonIconsStyles}
path:first-child {
stroke: #fff;
stroke: ${(props) => props.theme.mediaViewer.videoViewer.stroke};
}
path:last-child {
fill: #fff;
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
const StyledMediaMuteOffIcon = styled(MediaMuteOffIcon)`
${commonIconsStyles}
path, rect {
fill: #fff;
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
StyledMediaMuteIcon.defaultProps = { theme: Base };
const VideoControlBtn = (props) => {
return (
<StyledVideoControlBtn {...props}>{props.children}</StyledVideoControlBtn>
@ -189,11 +202,14 @@ const StyledDuration = styled.div`
cursor: pointer;
&:hover {
background-color: rgba(200, 200, 200, 0.2);
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.background};
}
`;
StyledValumeContainer.defaultProps = { theme: Base };
const StyledVideoViewer = styled.div`
color: #d1d1d1;
color: ${(props) => props.theme.mediaViewer.videoViewer.color};
.playerWrapper {
display: ${(props) => (props.isVideo ? "block" : "none")};
@ -204,7 +220,8 @@ const StyledVideoViewer = styled.div`
z-index: 301;
position: fixed;
padding-bottom: 40px;
background-color: rgba(11, 11, 11, 0.7);
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.backgroundColor};
video {
z-index: 300;
@ -212,19 +229,24 @@ const StyledVideoViewer = styled.div`
}
`;
StyledVideoViewer.defaultProps = { theme: Base };
const ErrorContainer = styled.div`
z-index: 301;
display: block;
position: fixed;
left: calc(50% - 110px);
top: calc(50% - 40px);
background-color: #000;
color: #fff;
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.backgroundColorError};
color: ${(props) => props.theme.mediaViewer.videoViewer.colorError};
border-radius: 10px;
padding: 20px;
text-align: center;
`;
ErrorContainer.defaultProps = { theme: Base };
class ValumeBtn extends Component {
constructor(props) {
super(props);

View File

@ -0,0 +1,160 @@
import React from "react";
import PropTypes from "prop-types";
import Loaders from "@appserver/common/components/Loaders";
import StyledContainer from "./StyledNavigation";
import ArrowButton from "./sub-components/arrow-btn";
import Text from "./sub-components/text";
import ControlButtons from "./sub-components/control-btn";
import DropBox from "./sub-components/drop-box";
import { Consumer } from "@appserver/components/utils/context";
import DomHelpers from "@appserver/components/utils/domHelpers";
const Navigation = ({
tReady,
showText,
isRootFolder,
title,
canCreate,
isDesktop,
isTabletView,
personal,
onClickFolder,
navigationItems,
getContextOptionsPlus,
getContextOptionsFolder,
onBackToParentFolder,
isRecycleBinFolder,
isEmptyFilesList,
clearTrash,
...rest
}) => {
const [isOpen, setIsOpen] = React.useState(false);
const [firstClick, setFirstClick] = React.useState(true);
const [dropBoxWidth, setDropBoxWidth] = React.useState(0);
const dropBoxRef = React.useRef(null);
const containerRef = React.useRef(null);
const onMissClick = (e) => {
e.preventDefault;
const path = e.path || (e.composedPath && e.composedPath());
if (!firstClick) {
!path.includes(dropBoxRef.current) ? toggleDropBox() : null;
} else {
setFirstClick((prev) => !prev);
}
};
const onClickAvailable = React.useCallback(
(id) => {
onClickFolder && onClickFolder(id);
toggleDropBox();
},
[onClickFolder, toggleDropBox]
);
const toggleDropBox = () => {
if (isRootFolder) return setIsOpen(false);
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
setIsOpen((prev) => !prev);
setFirstClick(true);
};
React.useEffect(() => {
if (isOpen) {
window.addEventListener("click", onMissClick);
} else {
window.removeEventListener("click", onMissClick);
setFirstClick(true);
}
return () => window.removeEventListener("click", onMissClick);
}, [isOpen, onMissClick]);
const onBackToParentFolderAction = React.useCallback(() => {
setIsOpen((val) => !val);
onBackToParentFolder && onBackToParentFolder();
}, [onBackToParentFolder]);
return (
<Consumer>
{(context) => (
<>
{isOpen && (
<DropBox
{...rest}
ref={dropBoxRef}
dropBoxWidth={dropBoxWidth}
sectionHeight={context.sectionHeight}
showText={showText}
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolderAction}
title={title}
personal={personal}
isRootFolder={isRootFolder}
canCreate={canCreate}
navigationItems={navigationItems}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
toggleDropBox={toggleDropBox}
onClickAvailable={onClickAvailable}
/>
)}
<StyledContainer
ref={containerRef}
width={context.sectionWidth}
isRootFolder={isRootFolder}
canCreate={canCreate}
title={title}
isDesktop={isDesktop}
isTabletView={isTabletView}
isRecycleBinFolder={isRecycleBinFolder}
>
<ArrowButton
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolder}
/>
<Text
title={title}
isOpen={false}
isRootFolder={isRootFolder}
onClick={toggleDropBox}
/>
<ControlButtons
personal={personal}
isRootFolder={isRootFolder}
canCreate={canCreate}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
isRecycleBinFolder={isRecycleBinFolder}
isEmptyFilesList={isEmptyFilesList}
clearTrash={clearTrash}
/>
</StyledContainer>
</>
)}
</Consumer>
);
};
Navigation.propTypes = {
tReady: PropTypes.bool,
isRootFolder: PropTypes.bool,
title: PropTypes.string,
canCreate: PropTypes.bool,
isDesktop: PropTypes.bool,
isTabletView: PropTypes.bool,
personal: PropTypes.bool,
onClickFolder: PropTypes.func,
navigationItems: PropTypes.arrayOf(PropTypes.object),
getContextOptionsPlus: PropTypes.func,
getContextOptionsFolder: PropTypes.func,
onBackToParentFolder: PropTypes.func,
};
export default React.memo(Navigation);

View File

@ -0,0 +1,47 @@
import styled, { css } from "styled-components";
import { isMobile, isMobileOnly } from "react-device-detect";
import { tablet, desktop, mobile } from "@appserver/components/utils/device";
const StyledContainer = styled.div`
padding: ${(props) => (props.isDropBox ? "14px 0 3px" : "14px 0 0px")};
width: fit-content;
display: grid;
grid-template-columns: ${(props) =>
props.isRootFolder ? "1fr auto" : "29px 1fr auto"};
align-items: center;
.arrow-button {
width: 17px;
min-width: 17px;
}
@media ${tablet} {
width: 100%;
padding: ${(props) => (props.isDropBox ? "16px 0 5px" : "16px 0 0px")};
}
${isMobile &&
css`
width: 100% !important;
padding: ${(props) =>
props.isDropBox ? "16px 0 5px" : " 16px 0 0px"} !important;
`}
@media ${mobile} {
width: 100%;
padding: ${(props) => (props.isDropBox ? "12px 0 5px" : "12px 0 0")};
}
${isMobileOnly &&
css`
width: 100% !important;
padding: ${(props) =>
props.isDropBox ? "12px 0 5px" : "12px 0 0"} !important;
`}
`;
export default StyledContainer;

View File

@ -0,0 +1 @@
export default from "./Navigation";

View File

@ -0,0 +1,22 @@
import React from "react";
import IconButton from "@appserver/components/icon-button";
const ArrowButton = ({ isRootFolder, onBackToParentFolder }) => {
return (
<>
{!isRootFolder ? (
<IconButton
iconName="/static/images/arrow.path.react.svg"
size="17"
isFill={true}
onClick={onBackToParentFolder}
className="arrow-button"
/>
) : (
<></>
)}
</>
);
};
export default React.memo(ArrowButton);

View File

@ -0,0 +1,111 @@
import React from "react";
import styled, { css } from "styled-components";
import PropTypes from "prop-types";
import ContextMenuButton from "@appserver/components/context-menu-button";
import IconButton from "@appserver/components/icon-button";
import { isMobile } from "react-device-detect";
import { tablet } from "@appserver/components/utils/device";
const StyledContainer = styled.div`
margin-left: 20px;
display: flex;
align-items: center;
.add-button {
margin-right: 12px;
min-width: 17px;
${(props) =>
!props.isDropBox &&
css`
@media ${tablet} {
display: none;
}
`}
${isMobile &&
css`
${(props) => !props.isDropBox && "display: none"};
`}
}
.option-button {
margin-right: 8px;
min-width: 17px;
}
.trash-button {
min-width: 17px;
}
`;
const ControlButtons = ({
personal,
isDropBox,
isRootFolder,
canCreate,
getContextOptionsFolder,
getContextOptionsPlus,
isRecycleBinFolder,
isEmptyFilesList,
clearTrash,
}) => {
return (
<StyledContainer isDropBox={isDropBox}>
{!isRootFolder && canCreate ? (
<>
<ContextMenuButton
className="add-button"
directionX="right"
iconName="images/plus.svg"
size={17}
isFill
getData={getContextOptionsPlus}
isDisabled={false}
/>
{!personal && (
<ContextMenuButton
className="option-button"
directionX="right"
iconName="images/vertical-dots.react.svg"
size={17}
isFill
getData={getContextOptionsFolder}
isDisabled={false}
/>
)}
</>
) : 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"
/>
) : (
<></>
)}
</StyledContainer>
);
};
ControlButtons.propTypes = {
personal: PropTypes.bool,
isRootFolder: PropTypes.bool,
canCreate: PropTypes.bool,
getContextOptionsFolder: PropTypes.func,
getContextOptionsPlus: PropTypes.func,
};
export default React.memo(ControlButtons);

View File

@ -0,0 +1,171 @@
import React, { useCallback, useEffect } from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import { VariableSizeList } from "react-window";
import CustomScrollbarsVirtualList from "@appserver/components/scrollbar/custom-scrollbars-virtual-list";
import ArrowButton from "./arrow-btn";
import Text from "./text";
import ControlButtons from "./control-btn";
import Item from "./item";
import StyledContainer from "../StyledNavigation";
import { isMobile, isMobileOnly } from "react-device-detect";
import {
tablet,
mobile,
isMobile as isMobileUtils,
isTablet as isTabletUtils,
} from "@appserver/components/utils/device";
import { Base } from "@appserver/components/themes";
const StyledBox = styled.div`
position: absolute;
top: 0px;
left: ${isMobile ? "-16px" : "-20px"};
padding: ${isMobile ? "0 16px" : "0 20px"};
width: ${(props) => props.dropBoxWidth}px;
height: ${(props) => (props.height ? `${props.height}px` : "fit-content")};
max-height: calc(100vh - 48px);
z-index: 399;
display: flex;
flex-direction: column;
background: ${(props) => props.theme.navigation.background};
filter: drop-shadow(0px 12px 40px rgba(4, 15, 27, 0.12));
border-radius: 0px 0px 6px 6px;
@media ${tablet} {
left: -16px;
padding: 0 16px;
}
`;
StyledBox.defaultProps = { theme: Base };
const Row = React.memo(({ data, index, style }) => {
const isRoot = index === data[0].length - 1;
return (
<Item
key={data[0][index].id}
id={data[0][index].id}
title={data[0][index].title}
isRoot={isRoot}
onClick={data[1]}
style={{ ...style }}
/>
);
});
const DropBox = React.forwardRef(
(
{
sectionHeight,
showText,
dropBoxWidth,
isRootFolder,
onBackToParentFolder,
title,
personal,
canCreate,
navigationItems,
getContextOptionsFolder,
getContextOptionsPlus,
toggleDropBox,
onClickAvailable,
},
ref
) => {
const [dropBoxHeight, setDropBoxHeight] = React.useState(0);
const countItems = navigationItems.length;
const getItemSize = (index) => {
if (index === countItems - 1) return 51;
return isMobile || isMobileUtils() || isTabletUtils() ? 36 : 30;
};
React.useEffect(() => {
const itemsHeight = navigationItems.map((item, index) =>
getItemSize(index)
);
const currentHeight = itemsHeight.reduce((a, b) => a + b);
let navHeight = 41;
if (isMobile || isTabletUtils()) {
navHeight = 49;
}
if (isMobileOnly || isMobileUtils()) {
navHeight = 45;
}
setDropBoxHeight(
currentHeight + navHeight > sectionHeight
? sectionHeight - navHeight
: currentHeight
);
}, [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}
/>
</StyledContainer>
<VariableSizeList
height={dropBoxHeight}
width={"auto"}
itemCount={countItems}
itemSize={getItemSize}
itemData={[navigationItems, onClickAvailable]}
outerElementType={CustomScrollbarsVirtualList}
>
{Row}
</VariableSizeList>
</StyledBox>
);
}
);
DropBox.propTypes = {
width: PropTypes.number,
changeWidth: PropTypes.bool,
isRootFolder: PropTypes.bool,
onBackToParentFolder: PropTypes.func,
title: PropTypes.string,
personal: PropTypes.bool,
canCreate: PropTypes.bool,
navigationItems: PropTypes.arrayOf(PropTypes.object),
getContextOptionsFolder: PropTypes.func,
getContextOptionsPlus: PropTypes.func,
toggleDropBox: PropTypes.func,
onClickAvailable: PropTypes.func,
};
export default React.memo(DropBox);

View File

@ -0,0 +1,100 @@
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import Text from "@appserver/components/text";
import DefaultIcon from "../svg/default.react.svg";
import RootIcon from "../svg/root.react.svg";
import DefaultTabletIcon from "../svg/default.tablet.react.svg";
import RootTabletIcon from "../svg/root.tablet.react.svg";
import { isMobile } from "react-device-detect";
import {
tablet,
isTablet,
isMobile as IsMobileUtils,
} from "@appserver/components/utils/device";
import { Base } from "@appserver/components/themes";
const StyledItem = styled.div`
height: auto;
width: auto !important;
position: relative;
display: grid;
align-items: ${(props) => (props.isRoot ? "baseline" : "end")};
grid-template-columns: 17px auto;
cursor: pointer;
`;
const StyledIconWrapper = styled.div`
width: 17px;
display: flex;
align-items: ${(props) => (props.isRoot ? "center" : "flex-end")};
justify-content: center;
svg {
path {
fill: ${(props) => props.theme.navigation.icon.fill};
}
circle {
stroke: ${(props) => props.theme.navigation.icon.fill};
}
path:first-child {
fill: none !important;
stroke: ${(props) => props.theme.navigation.icon.stroke};
}
}
`;
StyledIconWrapper.defaultProps = { theme: Base };
const StyledText = styled(Text)`
margin-left: 10px;
position: relative;
bottom: ${(props) => (props.isRoot ? "2px" : "-1px")};
`;
const Item = ({ id, title, isRoot, onClick, ...rest }) => {
const onClickAvailable = () => {
onClick && onClick(id);
};
return (
<StyledItem id={id} isRoot={isRoot} onClick={onClickAvailable} {...rest}>
<StyledIconWrapper isRoot={isRoot}>
{isMobile || isTablet() || IsMobileUtils() ? (
isRoot ? (
<RootTabletIcon />
) : (
<DefaultTabletIcon />
)
) : isRoot ? (
<RootIcon />
) : (
<DefaultIcon />
)}
</StyledIconWrapper>
<StyledText
isRoot={isRoot}
fontWeight={isRoot ? "600" : "400"}
isRoot={isRoot}
fontSize={"15px"}
truncate={true}
>
{title}
</StyledText>
</StyledItem>
);
};
Item.propTypes = {
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
title: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
isRoot: PropTypes.bool,
onClick: PropTypes.func,
};
export default React.memo(Item);

View File

@ -0,0 +1,99 @@
import React from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import ExpanderDownIcon from "@appserver/components/public/static/images/expander-down.react.svg";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import Headline from "@appserver/common/components/Headline";
import { tablet } from "@appserver/components/utils/device";
import { isMobile } from "react-device-detect";
import { Base } from "@appserver/components/themes";
const StyledTextContainer = styled.div`
width: fit-content;
position: relative;
display: grid;
grid-template-columns: ${(props) =>
props.isRootFolder ? "auto" : "auto 12px"};
align-items: center;
${(props) => !props.isRootFolder && "cursor: pointer"};
`;
const StyledHeadline = styled(Headline)`
width: calc(100% + 1px);
font-weight: 700;
font-size: ${isMobile ? "21px !important" : "18px"};
line-height: ${isMobile ? "28px !important" : "24px"};
@media ${tablet} {
font-size: 21px;
line-height: 28px;
}
`;
const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
min-width: 8px !important;
width: 8px !important;
min-height: 18px !important;
padding: 0 2px 0 4px;
path {
fill: ${(props) => props.theme.navigation.expanderColor};
}
${commonIconsStyles};
`;
StyledExpanderDownIcon.defaultProps = { theme: Base };
const StyledExpanderDownIconRotate = styled(ExpanderDownIcon)`
min-width: 8px !important;
width: 8px !important;
min-height: 18px !important;
padding: 0 4px 0 1px;
transform: rotate(-180deg);
path {
fill: ${(props) => props.theme.navigation.expanderColor};
}
${commonIconsStyles};
`;
StyledExpanderDownIconRotate.defaultProps = { theme: Base };
const Text = ({ title, isRootFolder, isOpen, onClick, ...rest }) => {
return (
<StyledTextContainer
isRootFolder={isRootFolder}
onClick={onClick}
{...rest}
>
<StyledHeadline type="content" truncate={true}>
{title}
</StyledHeadline>
{!isRootFolder ? (
isOpen ? (
<StyledExpanderDownIconRotate />
) : (
<StyledExpanderDownIcon />
)
) : (
<></>
)}
</StyledTextContainer>
);
};
Text.propTypes = {
title: PropTypes.string,
isOpen: PropTypes.bool,
isRootFolder: PropTypes.bool,
onCLick: PropTypes.func,
};
export default React.memo(Text);

View File

@ -0,0 +1,4 @@
<svg width="19" height="30" viewBox="0 0 19 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 1V16" stroke="#DFE2E3" stroke-miterlimit="16" stroke-dasharray="2 2"/>
<circle cx="9.5" cy="23.5" r="2.5" stroke="#316DAA" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 259 B

View File

@ -0,0 +1,4 @@
<svg width="19" height="36" viewBox="0 0 19 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 1V22" stroke="#DFE2E3" stroke-miterlimit="16" stroke-dasharray="2 2"/>
<circle cx="9.5" cy="29.5" r="2.5" stroke="#316DAA" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 259 B

View File

@ -0,0 +1,4 @@
<svg width="19" height="31" viewBox="0 0 19 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 1L9.5 15" stroke="#DFE2E3" stroke-miterlimit="16" stroke-dasharray="2 2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5 19L14 22.7386V27.5461C14 28.3491 13.3284 29 12.5 29H6.5C5.67157 29 5 28.3491 5 27.5461V22.7386L9.5 19ZM7 23.6302V27.0615H12V23.6302L9.5 21.5532L7 23.6302Z" fill="#316DAA"/>
</svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@ -0,0 +1,4 @@
<svg width="19" height="36" viewBox="0 0 19 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 1L9.5 20" stroke="#DFE2E3" stroke-miterlimit="16" stroke-dasharray="2 2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5 24L14 27.7386V32.5461C14 33.3491 13.3284 34 12.5 34H6.5C5.67157 34 5 33.3491 5 32.5461V27.7386L9.5 24ZM7 28.6302V32.0615H12V28.6302L9.5 26.5532L7 28.6302Z" fill="#316DAA"/>
</svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@ -0,0 +1,22 @@
import styled, { css } from "styled-components";
import SearchInput from "@appserver/components/search-input";
const StyledFilterInput = styled.div`
width: 100%;
max-width: ${(props) => props.sectionWidth}px;
height: 32px;
display: flex;
align-items: center;
justify-content: start;
margin: 0;
padding: 0;
`;
const StyledSearchInput = styled(SearchInput)`
width: 100%;
`;
export { StyledFilterInput, StyledSearchInput };

View File

@ -0,0 +1,115 @@
import React from "react";
import { isMobile, isMobileOnly } from "react-device-detect";
import {
isMobile as isMobileUtils,
isTablet as isTabletUtils,
} from "@appserver/components/utils/device";
import ViewSelector from "@appserver/components/view-selector";
import FilterButton from "./sub-components/FilterButton";
import SortButton from "./sub-components/SortButton";
import { StyledFilterInput, StyledSearchInput } from "./StyledFilterInput";
const FilterInput = ({
t,
sectionWidth,
getFilterData,
getSortData,
getViewSettingsData,
getSelectedFilterData,
onFilter,
onSearch,
onSort,
onChangeViewAs,
viewAs,
placeholder,
contextMenuHeader,
headerLabel,
viewSelectorVisible,
isRecentFolder,
isFavoritesFolder,
...props
}) => {
const [viewSettings, setViewSettings] = React.useState([]);
const [selectedFilterData, setSelectedFilterData] = React.useState([]);
const [inputValue, setInputValue] = React.useState("");
const getSelectedFilterDataAction = React.useCallback(async () => {
const data = await getSelectedFilterData();
setSelectedFilterData(data);
setInputValue(!!data.inputValue ? data.inputValue : "");
}, [getSelectedFilterData]);
React.useEffect(() => {
getSelectedFilterDataAction();
}, [getSelectedFilterData]);
React.useEffect(() => {
getViewSettingsData && setViewSettings(getViewSettingsData());
}, [getViewSettingsData]);
const onClearSearch = () => {
onSearch && onSearch();
};
return (
<StyledFilterInput {...props} sectionWidth={sectionWidth}>
<StyledSearchInput
placeholder={placeholder}
value={inputValue}
onChange={onSearch}
onClearSearch={onClearSearch}
/>
<FilterButton
t={t}
selectedFilterData={selectedFilterData}
contextMenuHeader={contextMenuHeader}
getFilterData={getFilterData}
onFilter={onFilter}
headerLabel={headerLabel}
/>
{viewSettings &&
!isMobile &&
viewSelectorVisible &&
!isMobileUtils() &&
!isTabletUtils() ? (
<ViewSelector
style={{ marginLeft: "8px" }}
onChangeView={onChangeViewAs}
viewAs={viewAs === "table" ? "row" : viewAs}
viewSettings={viewSettings}
/>
) : (
<>
{(isMobile || isTabletUtils() || isMobileUtils()) && (
<SortButton
t={t}
selectedFilterData={selectedFilterData}
getSortData={getSortData}
onChangeViewAs={onChangeViewAs}
viewAs={viewAs === "table" ? "row" : viewAs}
viewSettings={viewSettings}
onSort={onSort}
viewSelectorVisible={viewSelectorVisible}
isRecentFolder={isRecentFolder}
isFavoritesFolder={isFavoritesFolder}
/>
)}
</>
)}
</StyledFilterInput>
);
};
FilterInput.defaultProps = {
viewSelectorVisible: false,
};
export default React.memo(FilterInput);

View File

@ -0,0 +1,256 @@
import React from "react";
import Backdrop from "@appserver/components/backdrop";
import Button from "@appserver/components/button";
import Heading from "@appserver/components/heading";
import IconButton from "@appserver/components/icon-button";
import FilterBlockItem from "./FilterBlockItem";
import PeopleSelector from "people/PeopleSelector";
import GroupSelector from "people/GroupSelector";
import {
StyledFilterBlock,
StyledFilterBlockHeader,
StyledFilterBlockFooter,
StyledControlContainer,
StyledCrossIcon,
} from "./StyledFilterBlock";
import { withTranslation } from "react-i18next";
//TODO: fix translate
const FilterBlock = ({
t,
selectedFilterData,
contextMenuHeader,
getFilterData,
hideFilterBlock,
onFilter,
headerLabel,
}) => {
const [showSelector, setShowSelector] = React.useState({
show: false,
isAuthor: false,
group: "",
});
const [filterData, setFilterData] = React.useState([]);
const [filterValues, setFilterValues] = React.useState([]);
const changeShowSelector = (isAuthor, group) => {
setShowSelector((val) => {
return {
show: !val.show,
isAuthor: isAuthor,
group: group,
};
});
};
const changeSelectedItems = (filter) => {
const items = filterData.slice();
items.forEach((item) => {
if (filter.find((value) => value.group === item.group)) {
const currentFilter = filter.filter(
(value) => value.group === item.group
)[0];
item.groupItem.forEach((groupItem) => {
groupItem.isSelected = false;
if (groupItem.key === currentFilter.key) {
groupItem.isSelected = true;
}
if (groupItem.isSelector) {
groupItem.isSelected = true;
groupItem.selectedKey = currentFilter.key;
groupItem.selectedLabel = currentFilter.label;
}
});
} else {
item.groupItem.forEach((groupItem) => {
groupItem.isSelected = false;
if (groupItem.isSelector) {
groupItem.selectedKey = null;
groupItem.selectedLabel = null;
}
});
}
});
setFilterData(items);
};
const clearFilter = () => {
changeSelectedItems([]);
setFilterValues([]);
};
const changeFilterValue = (group, key, isSelected, label) => {
let value = filterValues.concat();
if (isSelected) {
value = filterValues.filter((item) => item.group !== group);
setFilterValues(value);
changeSelectedItems(value);
return;
}
if (value.find((item) => item.group === group)) {
value.forEach((item) => {
if (item.group === group) {
item.key = key;
if (label) {
item.label = label;
}
}
});
} else {
if (label) {
value.push({ group, key, label });
} else {
value.push({ group, key });
}
}
setFilterValues(value);
changeSelectedItems(value);
};
React.useEffect(() => {
const data = getFilterData();
const items = data.filter((item) => item.isHeader === true);
items.forEach((item) => {
const groupItem = data.filter(
(val) => val.group === item.group && val.isHeader !== true
);
groupItem.forEach((item) => (item.isSelected = false));
item.groupItem = groupItem;
});
if (selectedFilterData.filterValues) {
selectedFilterData.filterValues.forEach((value) => {
items.forEach((item) => {
if (item.group === value.group) {
item.groupItem.forEach((groupItem) => {
if (groupItem.key === value.key || groupItem.isSelector) {
groupItem.isSelected = true;
if (groupItem.isSelector) {
groupItem.selectedLabel = value.label;
groupItem.selectedKey = value.key;
}
}
});
}
});
});
}
setFilterData(items);
setFilterValues(selectedFilterData.filterValues);
}, [selectedFilterData, getFilterData]);
const onFilterAction = () => {
onFilter && onFilter(filterValues);
hideFilterBlock();
};
const onArrowClick = () => {
setShowSelector((val) => ({ ...val, show: false }));
};
const selectOption = (items) => {
setShowSelector((val) => ({
...val,
show: false,
}));
changeFilterValue(showSelector.group, items[0].key, false, items[0].label);
};
return (
<>
{showSelector.show ? (
<>
<StyledFilterBlock>
{showSelector.isAuthor ? (
<PeopleSelector
className="people-selector"
isOpen={showSelector.show}
withoutAside={true}
isMultiSelect={false}
onSelect={selectOption}
onArrowClick={onArrowClick}
headerLabel={headerLabel}
/>
) : (
<GroupSelector
className="people-selector"
isOpen={showSelector.show}
withoutAside={true}
isMultiSelect={false}
onSelect={selectOption}
onArrowClick={onArrowClick}
headerLabel={headerLabel}
/>
)}
</StyledFilterBlock>
</>
) : (
<StyledFilterBlock>
<StyledFilterBlockHeader>
<Heading size="medium">{contextMenuHeader}</Heading>
<IconButton
iconName="/static/images/clear.react.svg"
isFill={true}
onClick={clearFilter}
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}
/>
);
})}
<StyledFilterBlockFooter>
<Button
size="normal"
primary={true}
label={t("AddFilter")}
scale={true}
onClick={onFilterAction}
/>
</StyledFilterBlockFooter>
</StyledFilterBlock>
)}
<Backdrop
visible={true}
withBackground={true}
onClick={hideFilterBlock}
/>
<StyledControlContainer onClick={hideFilterBlock}>
<StyledCrossIcon />
</StyledControlContainer>
</>
);
};
export default React.memo(withTranslation("Common")(FilterBlock));

View File

@ -0,0 +1,153 @@
import React from "react";
import SelectorAddButton from "@appserver/components/selector-add-button";
import Heading from "@appserver/components/heading";
import {
StyledFilterBlockItem,
StyledFilterBlockItemHeader,
StyledFilterBlockItemContent,
StyledFilterBlockItemSelector,
StyledFilterBlockItemSelectorText,
StyledFilterBlockItemTag,
StyledFilterBlockItemTagText,
StyledFilterBlockItemTagIcon,
StyledFilterBlockItemToggle,
StyledFilterBlockItemToggleText,
StyledFilterBlockItemToggleButton,
StyledFilterBlockItemSeparator,
} from "./StyledFilterBlock";
import TickIcon from "../svg/tick.react.svg";
import XIcon from "../svg/x.react.svg";
const FilterBlockItem = ({
group,
label,
groupItem,
isLast,
withoutHeader,
changeFilterValue,
showSelector,
}) => {
const changeFilterValueAction = (key, isSelected) => {
changeFilterValue && changeFilterValue(group, key, isSelected);
};
const showSelectorAction = (event, isAuthor, group, ref) => {
let target = event.target;
while (!!target.parentNode) {
target = target.parentNode;
if (target === ref) {
changeFilterValue && changeFilterValue(group, [], true);
return;
}
}
showSelector && showSelector(isAuthor, group);
};
const getSelectorItem = (item) => {
const clearSelectorRef = React.useRef(null);
const isAuthor = item.key === "user";
return !item.isSelected ? (
<StyledFilterBlockItemSelector key={item.key}>
<SelectorAddButton
onClick={(event) =>
showSelectorAction(event, isAuthor, item.group, [])
}
/>
<StyledFilterBlockItemSelectorText noSelect={true}>
{item.label}
</StyledFilterBlockItemSelectorText>
</StyledFilterBlockItemSelector>
) : (
<StyledFilterBlockItemTag
key={item.key}
isSelected={item.isSelected}
onClick={(event) =>
showSelectorAction(
event,
isAuthor,
item.group,
clearSelectorRef.current
)
}
>
<StyledFilterBlockItemTagText
noSelect={true}
isSelected={item.isSelected}
>
{item.selectedLabel.toLowerCase()}
</StyledFilterBlockItemTagText>
{item.isSelected && (
<StyledFilterBlockItemTagIcon ref={clearSelectorRef}>
<XIcon style={{ marginTop: "2px" }} />
</StyledFilterBlockItemTagIcon>
)}
</StyledFilterBlockItemTag>
);
};
const getToggleItem = (item) => {
return (
<StyledFilterBlockItemToggle key={item.key}>
<StyledFilterBlockItemToggleText noSelect={true}>
{item.label}
</StyledFilterBlockItemToggleText>
<StyledFilterBlockItemToggleButton
isChecked={item.isSelected}
onChange={() => changeFilterValueAction(item.key, item.isSelected)}
/>
</StyledFilterBlockItemToggle>
);
};
const getTagItem = (item) => {
return (
<StyledFilterBlockItemTag
key={item.key}
isSelected={item.isSelected}
name={`${item.label.toLowerCase()}-${item.key}`}
onClick={() => changeFilterValueAction(item.key, item.isSelected)}
>
<StyledFilterBlockItemTagText
noSelect={true}
isSelected={item.isSelected}
>
{item.label.toLowerCase()}
</StyledFilterBlockItemTagText>
{item.isSelected && (
<StyledFilterBlockItemTagIcon>
<TickIcon />
</StyledFilterBlockItemTagIcon>
)}
</StyledFilterBlockItemTag>
);
};
return (
<StyledFilterBlockItem withoutHeader={withoutHeader}>
{!withoutHeader && (
<StyledFilterBlockItemHeader>
<Heading size="xsmall">{label}</Heading>
</StyledFilterBlockItemHeader>
)}
<StyledFilterBlockItemContent withoutHeader={withoutHeader}>
{groupItem.map((item) => {
if (item.isSelector === true) return getSelectorItem(item);
if (item.isToggle === true) return getToggleItem(item);
return getTagItem(item);
})}
</StyledFilterBlockItemContent>
{!isLast && <StyledFilterBlockItemSeparator />}
</StyledFilterBlockItem>
);
};
export default React.memo(FilterBlockItem);

View File

@ -0,0 +1,66 @@
import React from "react";
import styled from "styled-components";
import IconButton from "@appserver/components/icon-button";
import { Base } from "@appserver/components/themes";
import FilterBlock from "./FilterBlock";
import StyledButton from "./StyledButton";
const Indicator = styled.div`
border-radius: 50%;
width: 8px;
height: 8px;
background: ${(props) => props.theme.newFilterInput.filter.indicatorColor};
position: absolute;
top: 25px;
left: 25px;
z-index: 3;
`;
Indicator.defaultProps = { theme: Base };
const FilterButton = ({
t,
selectedFilterData,
contextMenuHeader,
getFilterData,
onFilter,
headerLabel,
}) => {
const [showFilterBlock, setShowFilterBlock] = React.useState(false);
const changeShowFilterBlock = React.useCallback(() => {
setShowFilterBlock((value) => !value);
}, [setShowFilterBlock]);
// console.log(selectedFilterData.filterValues);
return (
<>
<StyledButton onClick={changeShowFilterBlock}>
<IconButton iconName="/static/images/filter.react.svg" size={16} />
{selectedFilterData.filterValues &&
selectedFilterData.filterValues.length > 0 && <Indicator />}
</StyledButton>
{showFilterBlock && (
<FilterBlock
t={t}
contextMenuHeader={contextMenuHeader}
selectedFilterData={selectedFilterData}
hideFilterBlock={changeShowFilterBlock}
getFilterData={getFilterData}
onFilter={onFilter}
headerLabel={headerLabel}
/>
)}
</>
);
};
export default React.memo(FilterButton);

View File

@ -0,0 +1,318 @@
import React from "react";
import styled, { css } from "styled-components";
import { isMobileOnly } from "react-device-detect";
import { withTranslation } from "react-i18next";
import ComboBox from "@appserver/components/combobox";
import DropDownItem from "@appserver/components/drop-down-item";
import IconButton from "@appserver/components/icon-button";
import ViewSelector from "@appserver/components/view-selector";
import Text from "@appserver/components/text";
import { mobile } from "@appserver/components/utils/device";
import { Base } from "@appserver/components/themes";
import SortDesc from "../../../../../public/images/sort.desc.react.svg";
const selectedViewIcon = css`
svg {
path {
fill: ${(props) => props.theme.newFilterInput.sort.selectedViewIcon};
}
}
`;
const notSelectedViewIcon = css`
svg {
path {
fill: ${(props) => props.theme.newFilterInput.sort.viewIcon};
}
}
`;
const mobileView = css`
position: fixed;
top: auto;
left: 0;
bottom: 0;
width: 100vw;
z-index: 999;
`;
const StyledSortButton = styled.div`
.combo-button {
background: ${(props) =>
props.theme.newFilterInput.sort.background} !important;
.icon-button_svg {
cursor: pointer;
}
}
.sort-combo-box {
width: 32px;
height: 32px;
margin-left: 8px;
.dropdown-container {
top: 102%;
bottom: auto;
min-width: 200px;
margin-top: 3px;
@media ${mobile} {
${mobileView}
}
${isMobileOnly && mobileView}
.view-selector-item {
display: flex;
align-items: center;
justify-content: space-between;
cursor: auto;
.view-selector {
width: 44px;
display: flex;
align-items: center;
justify-content: space-between;
cursor: auto;
.view-selector-icon {
border: none;
background: transparent;
padding: 0;
div {
display: flex;
align-items: center;
justify-content: center;
}
}
.view-selector-icon:nth-child(1) {
${(props) =>
props.viewAs === "row" ? selectedViewIcon : notSelectedViewIcon};
}
.view-selector-icon:nth-child(2) {
${(props) =>
props.viewAs !== "row" ? selectedViewIcon : notSelectedViewIcon};
}
}
}
.option-item {
display: flex;
align-items: center;
justify-content: space-between;
min-width: 200px;
svg {
width: 16px;
height: 16px;
}
.option-item__icon {
display: none;
cursor: pointer;
${(props) =>
props.isDesc &&
css`
transform: rotate(180deg);
`}
path {
fill: ${(props) => props.theme.newFilterInput.sort.sortFill};
}
}
:hover {
.option-item__icon {
display: flex;
}
}
}
.selected-option-item {
background: ${(props) =>
props.theme.newFilterInput.sort.hoverBackground};
cursor: auto;
.selected-option-item__icon {
display: flex;
}
}
}
.optionalBlock {
display: flex;
align-items: center;
justify-content: center;
margin-right: 0;
}
.combo-buttons_arrow-icon {
display: none;
}
.backdrop-active {
display: none;
}
}
`;
StyledSortButton.defaultProps = { theme: Base };
const SortButton = ({
t,
selectedFilterData,
getSortData,
onChangeViewAs,
viewAs,
viewSettings,
onSort,
viewSelectorVisible,
isRecentFolder,
isFavoritesFolder,
}) => {
const [isOpen, setIsOpen] = React.useState(false);
const [
currentSelectedFilterData,
setCurrentSelectedFilterData,
] = React.useState({});
React.useEffect(() => {
setCurrentSelectedFilterData({
sortDirection: selectedFilterData.sortDirection,
sortId: selectedFilterData.sortId,
});
}, [selectedFilterData]);
const toggleCombobox = React.useCallback(() => {
setIsOpen((val) => !val);
}, []);
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;
setCurrentSelectedFilterData({
sortId: key,
sortDirection: sortDirection,
});
toggleCombobox();
onSort && onSort(key, sortDirection);
},
[onSort, toggleCombobox, currentSelectedFilterData]
);
const getSortOption = () => {};
const getAdvancedOptions = React.useCallback(() => {
const data = getSortData();
data.forEach((item) => {
item.className = "option-item";
item.isSelected = false;
if (currentSelectedFilterData.sortId === item.key) {
item.className = item.className + " selected-option-item";
item.isSelected = true;
}
});
return (
<>
{viewSelectorVisible && (
<>
<DropDownItem noHover className="view-selector-item">
<Text fontWeight={600}>{t("View")}</Text>
<ViewSelector
className="view-selector"
onChangeView={onChangeViewAs}
viewAs={viewAs}
viewSettings={viewSettings}
/>
</DropDownItem>
{!isFavoritesFolder && !isRecentFolder && (
<DropDownItem isSeparator={true}></DropDownItem>
)}
</>
)}
{!isFavoritesFolder && !isRecentFolder && (
<>
{data.map((item, index) => (
<DropDownItem
onClick={onOptionClick}
className={item.className}
key={item.key}
data-value={item.key}
>
<Text fontWeight={600}>{item.label}</Text>
<SortDesc
className={`option-item__icon ${
item.isSelected ? "selected-option-item__icon" : ""
}`}
/>
</DropDownItem>
))}
</>
)}
</>
);
}, [
currentSelectedFilterData,
onOptionClick,
onChangeViewAs,
viewAs,
viewSettings,
getSortData,
isFavoritesFolder,
isRecentFolder,
]);
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%"}
>
<IconButton iconName="/static/images/sort.react.svg" size={16} />
</ComboBox>
</StyledSortButton>
);
};
export default React.memo(withTranslation("Common")(SortButton));

View File

@ -0,0 +1,58 @@
import { Base } from "@appserver/components/themes";
import styled, { css } from "styled-components";
const StyledButton = styled.div`
width: 32px;
min-width: 32px;
height: 32px;
position: relative;
border: ${(props) => props.theme.newFilterInput.button.border};
border-radius: 3px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
margin-left: 8px;
cursor: pointer;
&:hover {
border: ${(props) => props.theme.newFilterInput.button.hoverBorder};
}
div {
cursor: pointer;
}
${(props) =>
props.isOpen &&
css`
background: ${(props) =>
props.theme.newFilterInput.button.openBackground};
pointer-events: none;
svg {
path {
fill: ${(props) => props.theme.newFilterInput.button.openFill};
}
}
.dropdown-container {
margin-top: 5px;
min-width: 200px;
width: 200px;
}
`}
`;
StyledButton.defaultProps = { theme: Base };
export default StyledButton;

View File

@ -0,0 +1,322 @@
import Text from "@appserver/components/text";
import styled, { css } from "styled-components";
import { isMobileOnly } from "react-device-detect";
import ToggleButton from "@appserver/components/toggle-button";
import { mobile } from "@appserver/components/utils/device";
import { Base } from "@appserver/components/themes";
import CrossIcon from "@appserver/components/public/static/images/cross.react.svg";
const mobileView = css`
top: 64px;
width: 100vw !important;
height: calc(100vh - 64px) !important;
`;
const StyledFilterBlock = styled.div`
position: fixed;
top: 0;
right: 0;
width: 480px;
height: 100vh;
z-index: 400;
display: flex;
flex-direction: column;
background: ${(props) => props.theme.newFilterInput.filter.background};
@media ${mobile} {
${mobileView}
}
${isMobileOnly && mobileView}
.people-selector {
height: 100%;
width: 100%;
.selector-wrapper,
.column-options {
width: 100%;
}
}
`;
StyledFilterBlock.defaultProps = { theme: Base };
const StyledFilterBlockHeader = styled.div`
height: 53px;
min-height: 53px;
padding: 0 16px;
margin: 0;
box-sizing: border-box;
border-bottom: ${(props) =>
props.isSelector ? "none" : props.theme.newFilterInput.filter.border};
display: flex;
align-items: center;
justify-content: ${(props) => (props.isSelector ? "start" : "space-between")};
.arrow-button {
margin-right: 12px;
}
svg {
cursor: pointer;
}
`;
StyledFilterBlockHeader.defaultProps = { theme: Base };
const StyledFilterBlockItem = styled.div`
padding: ${(props) =>
!props.withoutHeader ? "12px 16px 0px 16px" : "6px 16px 0px 16px"};
display: flex;
flex-direction: column;
justify-content: start;
`;
const StyledFilterBlockItemHeader = styled.div`
height: 16px;
line-height: 16px;
display: flex;
align-items: center;
`;
const StyledFilterBlockItemContent = styled.div`
margin-top: ${(props) => !props.withoutHeader && "12px"};
height: fit-content;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
`;
const StyledFilterBlockItemSelector = styled.div`
height: 32px;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
margin: 0 0 11px;
`;
const StyledFilterBlockItemSelectorText = styled(Text)`
font-weight: 600;
font-size: 13px;
line-height: 15px;
color: ${(props) => props.theme.newFilterInput.filter.color};
margin-left: 8px;
`;
StyledFilterBlockItemSelectorText.defaultProps = { theme: Base };
const selectedItemTag = css`
background: ${(props) =>
props.theme.newFilterInput.filter.selectedItem.background};
border-color: ${(props) =>
props.theme.newFilterInput.filter.selectedItem.border};
`;
const StyledFilterBlockItemTag = styled.div`
height: 30px;
max-height: 30px;
display: flex;
flex-direction: row;
align-items: center;
border: ${(props) => props.theme.newFilterInput.filter.border};
border-radius: 16px;
box-sizing: border-box;
padding: 4px 15px;
margin: 0 6px 12px 0;
cursor: pointer;
${(props) => props.isSelected && selectedItemTag}
`;
StyledFilterBlockItemTag.defaultProps = { theme: Base };
const selectedItemTagText = css`
color: ${(props) => props.theme.newFilterInput.filter.selectedItem.color};
`;
const StyledFilterBlockItemTagText = styled(Text)`
height: 20px;
font-weight: normal;
font-size: 13px;
line-height: 20px;
${(props) => props.isSelected && selectedItemTagText}
`;
StyledFilterBlockItemTagText.defaultProps = { theme: Base };
const StyledFilterBlockItemTagIcon = styled.div`
margin-left: 8px;
display: flex;
align-items: center;
justify-content: space-between;
svg {
path {
fill: ${(props) => props.theme.newFilterInput.filter.selectedItem.color};
}
}
`;
StyledFilterBlockItemTagIcon.defaultProps = { theme: Base };
const StyledFilterBlockItemToggle = styled.div`
width: 100%;
height: 36px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
`;
const StyledFilterBlockItemToggleText = styled(Text)`
font-weight: 600;
font-size: 13px;
line-height: 36px;
`;
const StyledFilterBlockItemToggleButton = styled(ToggleButton)`
position: static;
`;
const StyledFilterBlockItemSeparator = styled.div`
height: 1px;
width: 100%;
background: ${(props) => props.theme.newFilterInput.filter.separatorColor};
margin: 2px 0 0 0;
`;
StyledFilterBlockItemToggleButton.defaultProps = { theme: Base };
const StyledFilterBlockFooter = styled.div`
position: fixed;
bottom: 0;
right: 0;
z-index: 401;
width: 480px;
height: 72px;
min-height: 72px;
border-top: ${(props) => props.theme.newFilterInput.filter.border};
box-sizing: border-box;
padding: 0 16px;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
@media ${mobile} {
width: 100vw;
}
${isMobileOnly &&
css`
width: 100vw;
`}
`;
StyledFilterBlockFooter.defaultProps = { theme: Base };
const StyledControlContainer = styled.div`
background: ${(props) => props.theme.catalog.control.background};
width: 24px;
height: 24px;
position: fixed;
top: 30px;
right: 10px;
border-radius: 100px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
display: none;
@media ${mobile} {
display: flex;
}
${isMobileOnly &&
css`
display: flex;
`}
`;
StyledControlContainer.defaultProps = { theme: Base };
const StyledCrossIcon = styled(CrossIcon)`
width: 12px;
height: 12px;
path {
fill: ${(props) => props.theme.catalog.control.fill};
}
display: none;
@media ${mobile} {
display: flex;
}
${isMobileOnly &&
css`
display: flex;
`}
`;
StyledCrossIcon.defaultProps = { theme: Base };
export {
StyledFilterBlock,
StyledFilterBlockHeader,
StyledFilterBlockItem,
StyledFilterBlockItemHeader,
StyledFilterBlockItemContent,
StyledFilterBlockItemSelector,
StyledFilterBlockItemSelectorText,
StyledFilterBlockItemTag,
StyledFilterBlockItemTagText,
StyledFilterBlockItemTagIcon,
StyledFilterBlockItemToggle,
StyledFilterBlockItemToggleText,
StyledFilterBlockItemToggleButton,
StyledFilterBlockItemSeparator,
StyledFilterBlockFooter,
StyledControlContainer,
StyledCrossIcon,
};

View File

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.70719 10.7069L11.7072 3.70694L10.293 2.29272L4.00008 8.58561L1.70718 6.29272L0.292969 7.70694L3.29298 10.7069C3.6835 11.0975 4.31666 11.0975 4.70719 10.7069Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 329 B

View File

@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_19272_27968)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.41442 6.00033L10.707 9.29295L9.29284 10.7072L6.00033 7.41465L2.70919 10.7063L1.29486 9.29222L4.58611 6.00044L1.29284 2.70716L2.70705 1.29295L6.00021 4.58611L9.29278 1.29301L10.7071 2.70711L7.41442 6.00033Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_19272_27968">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 527 B

View File

@ -1,43 +0,0 @@
# PageLayout
Default page layout
### Usage
```js
import PageLayout from "@appserver/common/components/PageLayout";
```
```jsx
<PageLayout withBodyScroll={true}>
<PageLayout.ArticleHeader>{articleHeaderContent}</PageLayout.ArticleHeader>
<PageLayout.ArticleMainButton>
{articleMainButtonContent}
</PageLayout.ArticleMainButton>
<PageLayout.ArticleBody>{articleBodyContent}</PageLayout.ArticleBody>
<PageLayout.SectionHeader>{sectionHeaderContent}</PageLayout.SectionHeader>
<PageLayout.SectionFilter>{sectionFilterContent}</PageLayout.SectionFilter>
<PageLayout.SectionBody>{sectionBodyContent}</PageLayout.SectionBody>
<PageLayout.SectionPaging>{sectionPagingContent}</PageLayout.SectionPaging>
</PageLayout>
```
### Properties
| Props | Type | Required | Values | Default | Description |
| -------------------------- | :----: | :------: | :----: | :-----: | ----------------------------------------- |
| `articleHeaderContent` | `bool` | - | - | - | Article header content |
| `articleMainButtonContent` | `bool` | - | - | - | Article main button content |
| `articleBodyContent` | `bool` | - | - | - | Article body content |
| `sectionHeaderContent` | `bool` | - | - | - | Section header content |
| `sectionFilterContent` | `bool` | - | - | - | Section filter content |
| `sectionBodyContent` | `bool` | - | - | - | Section body content |
| `sectionPagingContent` | `bool` | - | - | - | Section paging content |
| `withBodyScroll` | `bool` | - | - | `true` | If you need display scroll inside content |
| `withBodyAutoFocus` | `bool` | - | - | `false` | If you need set focus on content element |

View File

@ -1,88 +0,0 @@
import React from "react";
import { mount } from "enzyme";
import PageLayout from ".";
const baseProps = {
withBodyScroll: true,
withBodyAutoFocus: false,
};
describe("<PageLayout />", () => {
it("renders without error", () => {
const wrapper = mount(<PageLayout {...baseProps} />);
expect(wrapper).toExist();
});
it("componentDidUpdate() test re-render", () => {
const wrapper = mount(<PageLayout {...baseProps} />).instance();
wrapper.componentDidUpdate({ withBodyScroll: false });
expect(wrapper.props).toBe(wrapper.props);
});
it("componentDidUpdate() test no re-render", () => {
const wrapper = mount(
<PageLayout
{...baseProps}
articleHeaderContent={<>1</>}
articleMainButtonContent={<>2</>}
articleBodyContent={<>3</>}
sectionHeaderContent={<>4</>}
sectionFilterContent={<>5</>}
sectionBodyContent={<>6</>}
sectionPagingContent={<>7</>}
withBodyScroll={false}
/>
).instance();
wrapper.componentDidUpdate(wrapper.props);
expect(wrapper.props.withBodyScroll).toBe(false);
wrapper.componentDidUpdate(wrapper.props);
expect(wrapper.props).toBe(wrapper.props);
});
it("call backdropClick()", () => {
const wrapper = mount(<PageLayout {...baseProps} />).instance();
wrapper.backdropClick();
expect(wrapper.state.isBackdropVisible).toBe(false);
expect(wrapper.state.isArticleVisible).toBe(false);
expect(wrapper.state.isArticlePinned).toBe(false);
});
it("call pinArticle()", () => {
const wrapper = mount(<PageLayout {...baseProps} />).instance();
wrapper.pinArticle();
expect(wrapper.state.isBackdropVisible).toBe(false);
expect(wrapper.state.isArticleVisible).toBe(true);
expect(wrapper.state.isArticlePinned).toBe(true);
});
it("call unpinArticle()", () => {
const wrapper = mount(<PageLayout {...baseProps} />).instance();
wrapper.unpinArticle();
expect(wrapper.state.isBackdropVisible).toBe(true);
expect(wrapper.state.isArticleVisible).toBe(true);
expect(wrapper.state.isArticlePinned).toBe(false);
});
it("call showArticle()", () => {
const wrapper = mount(<PageLayout {...baseProps} />).instance();
wrapper.showArticle();
expect(wrapper.state.isBackdropVisible).toBe(true);
expect(wrapper.state.isArticleVisible).toBe(true);
expect(wrapper.state.isArticlePinned).toBe(false);
});
});

View File

@ -1,94 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import equal from "fast-deep-equal/react";
import Scrollbar from "@appserver/components/scrollbar";
import { tablet, smallTablet } from "@appserver/components/utils/device";
import { isMobile } from "react-device-detect";
const StyledArticleBody = styled.div`
${(props) => props.displayBorder && `outline: 1px dotted;`}
flex-grow: 1;
${(props) => (props.isDesktop ? "height:auto" : "height:100%")};
.custom-scrollbar {
width: calc(100% + 24px) !important;
}
@media ${tablet} {
height: ${(props) =>
props.isDesktop ? "calc(100% - 104px)" : "calc(100% - 44px)"};
display: table;
width: calc(100% + 16px);
.custom-scrollbar {
display: table-cell;
}
}
@media ${smallTablet} {
display: flex;
height: 100%;
}
.people-tree-menu {
margin-right: 0;
${(props) => isMobile && props.pinned && `margin-bottom: 56px`}
}
.custom-scrollbar {
.nav-thumb-vertical {
opacity: 0;
transition: opacity 200ms ease;
}
}
:hover {
.custom-scrollbar {
.nav-thumb-vertical {
opacity: 1;
}
}
}
`;
const StyledArticleWrapper = styled.div`
margin: 16px 0;
@media ${tablet} {
margin-bottom: 60px;
}
`;
class ArticleBody extends React.Component {
shouldComponentUpdate(nextProps) {
return !equal(this.props, nextProps);
}
render() {
//console.log("PageLayout ArticleBody render");
const { children, pinned, isDesktop } = this.props;
return (
<StyledArticleBody pinned={pinned} isDesktop={isDesktop}>
<Scrollbar
id="articleScrollBar"
className="custom-scrollbar"
stype="mediumBlack"
>
<StyledArticleWrapper>{children}</StyledArticleWrapper>
</Scrollbar>
</StyledArticleBody>
);
}
}
ArticleBody.displayName = "ArticleBody";
ArticleBody.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
};
export default ArticleBody;

View File

@ -1,35 +0,0 @@
import React from "react";
import styled from "styled-components";
import equal from "fast-deep-equal/react";
import { tablet } from "@appserver/components/utils/device";
const StyledArticleHeader = styled.div`
height: 39px;
@media ${tablet} {
height: 39px;
.headline-heading {
margin-top: -5px;
}
}
@media ${tablet} {
display: none;
}
`;
class ArticleHeader extends React.Component {
shouldComponentUpdate(nextProps) {
return !equal(this.props, nextProps);
}
render() {
//console.log("PageLayout ArticleHeader render");
return <StyledArticleHeader {...this.props} />;
}
}
ArticleHeader.displayName = "ArticleHeader";
export default ArticleHeader;

View File

@ -1,33 +0,0 @@
import React from "react";
import styled from "styled-components";
import equal from "fast-deep-equal/react";
import { tablet } from "@appserver/components/utils/device";
const StyledArticleMainButton = styled.div`
margin: 12px 0 0;
max-width: 216px;
.main-button_drop-down {
line-height: 36px;
}
@media ${tablet} {
margin: 16px 0 0;
.main-button_drop-down {
line-height: 40px;
}
}
`;
class ArticleMainButton extends React.Component {
shouldComponentUpdate(nextProps) {
return !equal(this.props, nextProps);
}
render() {
//console.log("PageLayout ArticleMainButton render");
return <StyledArticleMainButton {...this.props} />;
}
}
ArticleMainButton.displayName = "ArticleMainButton";
export default ArticleMainButton;

View File

@ -1,105 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { withTranslation } from "react-i18next";
import Text from "@appserver/components/text";
import { tablet, smallTablet } from "@appserver/components/utils/device";
import CatalogPinIcon from "../../../../../public/images/catalog.pin.react.svg";
import CatalogUnpinIcon from "../../../../../public/images/catalog.unpin.react.svg";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
const StyledCatalogPinIcon = styled(CatalogPinIcon)`
${commonIconsStyles}
`;
const StyledCatalogUnpinIcon = styled(CatalogUnpinIcon)`
${commonIconsStyles}
`;
const StyledArticlePinPanel = styled.div`
border-top: 1px solid #eceef1;
height: 47px;
min-height: 47px;
display: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
@media ${tablet} {
display: block;
position: fixed;
bottom: 0;
width: 208px;
z-index: 10;
background-color: #f8f9f9;
}
@media ${smallTablet} {
display: none;
}
div {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
height: 100%;
.icon-wrapper {
width: 19px;
height: 16px;
}
svg {
margin-top: -1px;
}
span {
margin-left: 6px;
margin-top: -2px !important;
}
}
`;
const ArticlePinPanel = React.memo((props) => {
//console.log("PageLayout ArticlePinPanel render");
const { pinned, onPin, onUnpin, t } = props;
const textStyles = {
as: "span",
color: "#555F65",
fontSize: "14px",
fontWeight: 600,
};
return (
<StyledArticlePinPanel>
{pinned ? (
<div onClick={onUnpin}>
<div className="icon-wrapper">
<StyledCatalogUnpinIcon size="scale" />
</div>
<Text {...textStyles}>{t("Common:Unpin")}</Text>
</div>
) : (
<div onClick={onPin}>
<div className="icon-wrapper">
<StyledCatalogPinIcon size="scale" />
</div>
<Text {...textStyles}>{t("Common:Pin")}</Text>
</div>
)}
</StyledArticlePinPanel>
);
});
ArticlePinPanel.displayName = "ArticlePinPanel";
ArticlePinPanel.propTypes = {
pinned: PropTypes.bool,
pinText: PropTypes.string,
onPin: PropTypes.func,
unpinText: PropTypes.string,
onUnpin: PropTypes.func,
};
const ArticlePinPanelWrapper = withTranslation("Common")(ArticlePinPanel);
export default ArticlePinPanelWrapper;

View File

@ -1,119 +0,0 @@
import React from "react";
import styled, { css } from "styled-components";
import PropTypes from "prop-types";
import { Resizable } from "re-resizable";
import { isMobile } from "react-device-detect";
import { tablet } from "@appserver/components/utils/device";
const StyledArticle = styled.article`
@media ${tablet} {
${(props) =>
props.visible &&
!props.pinned &&
css`
position: fixed;
z-index: 400;
`}
}
.resizable-block {
padding: 0 20px;
background: #f8f9f9;
min-width: 256px;
height: 100% !important;
max-width: ${(props) =>
props.firstLoad ? "256px" : "calc(100vw - 368px)"};
box-sizing: border-box;
overflow: hidden auto;
display: flex;
flex-direction: column;
.resizable-border {
div {
cursor: ew-resize !important;
}
}
${isMobile &&
css`
margin-top: 48px;
height: calc(100% - 48px) !important;
width: 240px !important;
@media ${tablet} {
margin-top: ${(props) => (props.pinned ? "48px;" : "0;")};
}
`}
@media ${tablet} {
padding: 0 16px;
${(props) =>
props.visible
? props.pinned
? `
min-width: 240px;
max-width: 240px;
.increaseHeight {
position: fixed;
height: 100%;
top: 0;
left: 0;
min-width: 240px;
background: #f8f9f9;
z-index: -1;
}
`
: `
position: fixed !important;
width: 260px !important;
min-width: 260px;
max-width: 260px;
position: fixed;
height: 100% !important;
top: 0;
left: 0;
z-index: 400;
.resizable-border {
display: none;
}
.newItem {
right: -24px;
}
`
: `
display: none;
`}
}
}
`;
class Article extends React.Component {
render() {
//console.log("PageLayout Article render", this.props);
const { children, ...rest } = this.props;
const enable = {
top: false,
right: !isMobile,
bottom: false,
left: false,
};
return (
<StyledArticle {...rest}>
<Resizable
enable={enable}
className="resizable-block"
handleWrapperClass="resizable-border not-selectable"
>
{children}
<div className="increaseHeight"></div>
</Resizable>
</StyledArticle>
);
}
}
Article.propTypes = {
children: PropTypes.any,
};
export default Article;

View File

@ -1,59 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { tablet } from "@appserver/components/utils/device";
import CatalogButtonIcon from "../../../../../public/images/catalog.button.react.svg";
const StyledSectionToggler = styled.div`
height: 64px;
position: fixed;
bottom: 0;
right: 16px;
display: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
@media ${tablet} {
display: ${(props) => (props.visible ? "block" : "none")};
}
div {
width: 48px;
height: 48px;
padding: 14px 12px 14px 16px;
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.13);
border-radius: 48px;
cursor: pointer;
background: #fff;
box-sizing: border-box;
line-height: 14px;
}
`;
const iconStyle = {
width: "20px",
height: "20px",
minWidth: "20px",
minHeight: "20px",
};
const SectionToggler = React.memo((props) => {
//console.log("PageLayout SectionToggler render");
const { visible, onClick } = props;
return (
<StyledSectionToggler className="not-selectable" visible={visible}>
<div onClick={onClick}>
<CatalogButtonIcon style={iconStyle} />
</div>
</StyledSectionToggler>
);
});
SectionToggler.displayName = "SectionToggler";
SectionToggler.propTypes = {
visible: PropTypes.bool,
onClick: PropTypes.func,
};
export default SectionToggler;

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