Merge branch 'develop' into feature/room-share-optimization

This commit is contained in:
Nikita Gopienko 2023-09-18 12:48:04 +03:00
commit 32f1c003de
107 changed files with 15390 additions and 1159 deletions

View File

@ -405,6 +405,19 @@
}
]
},
"logocolors":[
{"r":255, "g":102, "b":128},
{"r":255, "g":143, "b":64},
{"r":242, "g":210, "b":48},
{"r":97, "g":192, "b":89},
{"r":112, "g":224, "b":150},
{"r":31, "g":206, "b":203},
{"r":92, "g":195, "b":247},
{"r":97, "g":145, "b":242},
{"r":119, "g":87, "b":217},
{"r":182, "g":121, "b":242},
{"r":255, "g":127, "b":212}
],
"radicale": {
"admin": "",
"path": ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ASC.Migrations.MySql.SaaS.Migrations
{
/// <inheritdoc />
public partial class MigrationContext_Upgrade4 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "color",
table: "files_folder",
type: "char(6)",
nullable: true,
collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "color",
table: "files_folder");
}
}
}

View File

@ -6258,6 +6258,12 @@ namespace ASC.Migrations.MySql.SaaS.Migrations
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Color")
.HasColumnType("char(6)")
.HasColumnName("color")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("CreateBy")
.IsRequired()
.HasColumnType("char(38)")

View File

@ -9,7 +9,8 @@
"clean": "shx rm -rf dist",
"deploy": "shx --silent mkdir -p ../../build/deploy/client && shx cp -r dist/* ../../build/deploy/client",
"start": "yarn build:translations && NODE_OPTIONS=--openssl-legacy-provider webpack-cli serve",
"start-prod": "serve dist -s -p 5001"
"start-prod": "serve dist -s -p 5001",
"analyze": "webpack --profile --json --env mode=analyze && webpack-bundle-analyzer bundle/output/path/stats.json"
},
"old-scripts": {
"build:test": "webpack --env minimize=false --mode production",
@ -84,6 +85,7 @@
"typescript": "^4.9.5",
"use-resize-observer": "^9.1.0",
"webpack": "5.76.3",
"webpack-bundle-analyzer": "^4.9.1",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.13.1"
},

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Avtomatik yedəkləməni aktivləşdirin.",
"EnableAutomaticBackupDescription": "Sahə məlumatlarını yedəkləmək üçün bu seçimdən istifadə edin.",
"EnterTitle": "Başlığı daxil edin",
"EveryDay": "Hər gün",
"EveryMonth": "Hər ay",
"EveryWeek": "Hər həftə",
"ForcePathStyle": "Məcburi Keçid Üslubu",
"IntegrationRequest": "ONLYOFFICE DocSpace-də faydalı inteqrasiya və ya komponent yoxdur? Komandamıza bir sorğu buraxın və biz buna baxacağıq.",
"IPSecurity": "IP Təhlükəsizliyi",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Активиране на автоматично архивиране.",
"EnableAutomaticBackupDescription": "Използвайте тази опция, за да архивирате данните на портала.",
"EnterTitle": "Въведете заглавие",
"EveryDay": "Всеки ден",
"EveryMonth": "Всеки месец",
"EveryWeek": "Всяка седмица",
"ForcePathStyle": "Стил на Force Path",
"IntegrationRequest": "Липсва Ви полезна интеграция или компонент в ONLYOFFICE DocSpace? Направете заявка към нашия екип и ние ще я разгледаме.",
"IPSecurity": "IP сигурност",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Povolit automatické zálohování.",
"EnableAutomaticBackupDescription": "Pomocí této možnosti zálohujte data prostoru.",
"EnterTitle": "Zadejte titulek",
"EveryDay": "Každý den",
"EveryMonth": "Každý měsíc",
"EveryWeek": "Každý týden",
"ForcePathStyle": "Vynutit styl cesty",
"IntegrationRequest": "Chybí vám v ONLYOFFICE DocSpace užitečná integrace nebo komponenta? Zanechte požadavek našemu týmu a my se na něj podíváme.",
"IPSecurity": "Zabezpečení IP",

View File

@ -104,9 +104,6 @@
"EnterTime": "Zeit eingeben",
"EnterTitle": "Titel eingeben",
"ErrorMessageBruteForceProtection": "Das angegebene Argument lag außerhalb des gültigen Wertebereichs.",
"EveryDay": "Jeden Tag",
"EveryMonth": "Jeden Monat",
"EveryWeek": "Jede Woche",
"ForcePathStyle": "Erzwinge Pfad-Stil",
"IntegrationRequest": "Brauchen Sie eine nützliche Integration oder Komponente in ONLYOFFICE DocSpace? Hinterlassen Sie eine Anfrage an unser Team und wir werden uns darum kümmern.",
"IPSecurity": "IP-Sicherheit",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Ενεργοποίηση αυτόματης δημιουργίας αντιγράφων ασφαλείας.",
"EnableAutomaticBackupDescription": "Χρησιμοποιήστε αυτήν την επιλογή για να δημιουργήσετε αντίγραφα ασφαλείας των δεδομένων της DocSpace.",
"EnterTitle": "Εισαγωγή τίτλου",
"EveryDay": "Κάθε μέρα",
"EveryMonth": "Κάθε μήνα",
"EveryWeek": "Κάθε εβδομάδα",
"ForcePathStyle": "Στυλ διαδρομής δύναμης",
"IntegrationRequest": "Σας λείπει μια χρήσιμη ενσωμάτωση ή ένα στοιχείο στο DocSpace του ONLYOFFICE; Αφήστε ένα αίτημα στην ομάδα μας και θα το εξετάσουμε.",
"IPSecurity": "Ασφάλεια IP",

View File

@ -111,9 +111,6 @@
"EnterTime": "Enter time",
"EnterTitle": "Enter title",
"ErrorMessageBruteForceProtection": "Specified argument was out of the range of valid values.",
"EveryDay": "Every day",
"EveryMonth": "Every month",
"EveryWeek": "Every week",
"ForcePathStyle": "Force Path Style",
"IntegrationRequest": "Missing a useful integration or component in ONLYOFFICE DocSpace? Leave a request to our team and we will look into that.",
"IPSecurity": "IP Security",

View File

@ -96,9 +96,6 @@
"EnableAutomaticBackup": "Active copia de seguridad automática.",
"EnableAutomaticBackupDescription": "Utilice esta opción para hacer una copia de seguridad de los datos del espacio.",
"EnterTitle": "Introduzca título",
"EveryDay": "Cada día",
"EveryMonth": "Cada mes",
"EveryWeek": "Cada semana",
"ForcePathStyle": "Estilo de Force Path",
"IntegrationRequest": "¿Falta una integración o componente útil en ONLYOFFICE DocSpace? Envíe una solicitud a nuestro equipo y la analizaremos.",
"IPSecurity": "Seguridad IP",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Ota automaattinen varmuuskopiointi käyttöön.",
"EnableAutomaticBackupDescription": "Tämän vaihtoehdon avulla voit varmuuskopioida tilan tiedot.",
"EnterTitle": "Syötä otsikko",
"EveryDay": "Joka päivä",
"EveryMonth": "Kuukausittain",
"EveryWeek": "Joka viikko",
"ForcePathStyle": "Pakota polku -tyyli",
"IntegrationRequest": "Puuttuuko ONLYOFFICE DocSpacesta käytännöllinen integraatio tai komponentti? Jätä pyyntö tiimillemme, niin tutkimme asiaa.",
"IPSecurity": "IP-suojaus",

View File

@ -96,9 +96,6 @@
"EnableAutomaticBackup": "Activer la sauvegarde automatique.",
"EnableAutomaticBackupDescription": "Utilisez cette option pour sauvegarder les données de lespace.",
"EnterTitle": "Saisir un titre",
"EveryDay": "Chaque jour",
"EveryMonth": "Chaque mois",
"EveryWeek": "Chaque semaine",
"ForcePathStyle": "Force le style de chemin",
"IntegrationRequest": "Il manque une intégration ou un composant utile dans ONLYOFFICE DocSpace ? Laissez une demande à notre équipe et nous nous en occuperons.",
"IPSecurity": "Sécurité IP",

View File

@ -96,9 +96,6 @@
"EnableAutomaticBackup": "Միացնել ավտոմատ կրկնօրինակումը:",
"EnableAutomaticBackupDescription": "Օգտագործեք այս տարբերակը՝ կայքէջի տվյալները պահուստավորելու համար:",
"EnterTitle": "Մուտքագրեք անվանումը",
"EveryDay": "Ամեն օր",
"EveryMonth": "Ամեն ամիս",
"EveryWeek": "Ամեն շաբաթ",
"ForcePathStyle": "Պարտադրել ուղու ոճ",
"IntegrationRequest": "Բացակայո՞ւմ եք օգտակար ինտեգրում կամ բաղադրիչ ONLYOFFICE DocSpace-ում: Խնդրանք թողեք մեր թիմին, և մենք կուսումնասիրենք դա:",
"IPSecurity": "IP անվտանգություն",

View File

@ -104,9 +104,6 @@
"EnterTime": "Inserisci l'ora",
"EnterTitle": "Inserisci il titolo",
"ErrorMessageBruteForceProtection": "L'argomento specificato non rientra nell'intervallo di valori validi.",
"EveryDay": "Ogni giorno",
"EveryMonth": "Ogni mese",
"EveryWeek": "Ogni settimana",
"ForcePathStyle": "Forza percorso stile",
"IntegrationRequest": "Ti manca un'integrazione o un componente utile in ONLYOFFICE DocSpace? Lascia la tua richiesta al nostro team e lo studieremo.",
"IPSecurity": "Sicurezza IP",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "自動バックアップを有効にする",
"EnableAutomaticBackupDescription": "スペースのデータをバックアップする場合は、このオプションを使用してください。",
"EnterTitle": "タイトルを入力",
"EveryDay": "毎日",
"EveryMonth": "毎月",
"EveryWeek": "毎週",
"ForcePathStyle": "強制パススタイル",
"IntegrationRequest": "ONLYOFFICE DocSpaceの便利な統合やコンポーネントをお探しですか私たちのチームにリクエストをしてください。",
"IPSecurity": "IPセキュリティ",

View File

@ -94,9 +94,6 @@
"EnableAutomaticBackup": "자동 백업을 활성화하세요.",
"EnableAutomaticBackupDescription": "스페이스 데이터를 백업하기 위해 이 옵션을 사용하세요.",
"EnterTitle": "제목을 입력하세요",
"EveryDay": "매일",
"EveryMonth": "매월",
"EveryWeek": "매주",
"ForcePathStyle": "경로 스타일 강제",
"IntegrationRequest": "ONLYOFFICE DocSpace에 유용한 통합 또는 구성 요소가 부족한가요? 저희 팀이 검토할 수 있도록 요청을 남겨주세요.",
"IPSecurity": "IP 보안",

View File

@ -94,9 +94,6 @@
"EnableAutomaticBackup": "ເປີດໃຊ້ງານ ອັດຕະໂນມັດ ສຳຮອງ .",
"EnableAutomaticBackupDescription": "ໃຊ້ຕົວເລືອກນີ້ເພື່ອສຳຮອງຂໍ້ມູນປະຕູ.",
"EnterTitle": "ໃສ່ຊື່",
"EveryDay": "ທຸກໆ ມື້",
"EveryMonth": "ທຸກໆ ເດືອນ",
"EveryWeek": "ທຸກໆ ອາທິດ",
"ForcePathStyle": "ບັງຄັບ ເສັ້ນທາງ ແບບ",
"IntegrationRequest": "ຂາດການເຊື່ອມໂຍງ ຫຼືອົງປະກອບທີ່ເປັນປະໂຫຍດໃນ ONLYOFFICE DocSpace ບໍ? ອອກຈາກການຮ້ອງຂໍໃຫ້ທີມງານຂອງພວກເຮົາແລະພວກເຮົາຈະກວດເບິ່ງວ່າ.",
"IPSecurity": "ຄວາມປອດໄພ IP",

View File

@ -94,9 +94,6 @@
"EnableAutomaticBackup": "Iespējojiet automātisko dublēšanu.",
"EnableAutomaticBackupDescription": "Izmantojiet šo opciju, lai dublētu portāla datus.",
"EnterTitle": "Ievadiet nosaukums",
"EveryDay": "Katru dienu",
"EveryMonth": "Katru mēnesi",
"EveryWeek": "Katru nedēļu",
"ForcePathStyle": "Uzspiest ceļa stilu",
"IntegrationRequest": "Vai ONLYOFFICE DocSpace trūkst noderīgas integrācijas vai komponenta? Atstājiet pieprasījumu mūsu komandai, un mēs to izskatīsim.",
"IPSecurity": "IP drošība",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Automatische back-up inschakelen.",
"EnableAutomaticBackupDescription": "Gebruik deze optie om een back-up te maken van de ruimtegegevens.",
"EnterTitle": "Voer titel in",
"EveryDay": "Elke dag",
"EveryMonth": "Elke maand",
"EveryWeek": "Elke week",
"ForcePathStyle": "Forceer Path Style",
"IntegrationRequest": "Mist u een handige integratie of component in ONLYOFFICE DocSpace? Laat een verzoek achter bij ons team en wij zullen ernaar kijken.",
"IPSecurity": "IP Beveiliging",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Włącz automatyczne tworzenie kopii zapasowych.",
"EnableAutomaticBackupDescription": "Użyj tej opcji do tworzenia kopii zapasowych danych portalu.",
"EnterTitle": "Wpisz tytuł",
"EveryDay": "Codziennie",
"EveryMonth": "Każdego miesiąca",
"EveryWeek": "Każdy tydzień",
"ForcePathStyle": "Wymuś styl ścieżki",
"IntegrationRequest": "Brakuje Ci przydatnej integracji lub innego ważnego komponentu w ONLYOFFICE DocSpace? Zostaw nam wiadomość, a niezwłocznie przyjrzymy się sprawie.",
"IPSecurity": "IP Security",

View File

@ -96,9 +96,6 @@
"EnableAutomaticBackup": "Ativar backup automático.",
"EnableAutomaticBackupDescription": "Use esta opção para fazer backup dos dados do espaço.",
"EnterTitle": "Insira o título",
"EveryDay": "Todos os dias",
"EveryMonth": "Todos os meses",
"EveryWeek": "Todas as semanas",
"ForcePathStyle": "Estilo de Caminho de Força",
"IntegrationRequest": "Falta uma integração ou componente útil no ONLYOFFICE DocSpace? Deixe uma solicitação para nossa equipe e analisaremos isso.",
"IPSecurity": "Segurança IP",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Ativar Criação Automática de Cópias de Segurança.",
"EnableAutomaticBackupDescription": "Utilize esta opção para criar cópias de segurança dos dados do espaço.",
"EnterTitle": "Introduza o título",
"EveryDay": "Todos os dias",
"EveryMonth": "Todos os meses",
"EveryWeek": "Todas as semanas",
"ForcePathStyle": "Forçar a Formatação do Destino",
"IntegrationRequest": "Está a faltar uma integração ou componente útil no ONLYOFFICE DocSpace? Deixe um pedido à nossa equipa e analisaremos o assunto.",
"IPSecurity": "Segurança IP",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Activează copiere de rezervă automată.",
"EnableAutomaticBackupDescription": "Utilizați această opțiune pentru a crea o copie de rezervă a datelor din portal.",
"EnterTitle": "Introduceți titlul",
"EveryDay": "Zilnic",
"EveryMonth": "Lunar",
"EveryWeek": "Saptamanal",
"ForcePathStyle": "Tip cale forțat",
"IntegrationRequest": "Vă lipsește o soluție de integrare sau o componentă utilă în spațiul DocSpace? Trimiteți o solicitare către echipa noastră și vom analiza propunerea dvs.",
"IPSecurity": "Securitate IP",

View File

@ -104,9 +104,6 @@
"EnterTime": "Ввести время",
"EnterTitle": "Укажите название",
"ErrorMessageBruteForceProtection": "Указанный аргумент находился вне диапазона допустимых значений.",
"EveryDay": "Каждый день",
"EveryMonth": "Каждый месяц",
"EveryWeek": "Каждую неделю",
"ForcePathStyle": "Принудительно использовать стиль пути",
"IntegrationRequest": "В ONLYOFFICE DocSpace отсутствует полезная интеграция или компонент? Отправьте запрос нашей команде, и мы его рассмотрим.",
"IPSecurity": "IP-безопасность",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Povoliť automatické zálohovanie údajov.",
"EnableAutomaticBackupDescription": "Použiť túto voľbu na zálohovanie údajov priestoru.",
"EnterTitle": "Zadajte názov",
"EveryDay": "Každý deň",
"EveryMonth": "Každý mesiac",
"EveryWeek": "Každý týždeň",
"ForcePathStyle": "Zapnúť Path-Style",
"IntegrationRequest": "Chýba vám v ONLYOFFICE DocSpace užitočná integrácia alebo komponent? Napíšte nášmu tímu svoju požiadavku a my sa na ňu pozrieme.",
"IPSecurity": "IP Bezpečnosť",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Omogoči samodejno varnostno kopiranje.",
"EnableAutomaticBackupDescription": "Uporabite to možnost za varnostno kopiranje podatkov prostora.",
"EnterTitle": "Vnesite naslov",
"EveryDay": "Vsak dan",
"EveryMonth": "Vsak mesec",
"EveryWeek": "Vsak teden",
"ForcePathStyle": "Stil vsiljene poti",
"IntegrationRequest": "Pogrešate uporabno integracijo ali komponento v ONLYOFFICE DocSpace? Zaupajte zahtevo naši ekipi in preučili jo bomo.",
"IPSecurity": "IP varnost",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Otomatik yedeklemeyi etkinleştir.",
"EnableAutomaticBackupDescription": "Alan verilerini yedeklemek için bu seçeneği kullanın.",
"EnterTitle": "Başlık girin",
"EveryDay": "Her gün",
"EveryMonth": "Her ay",
"EveryWeek": "Her hafta",
"ForcePathStyle": "Zorla Yol Stili",
"IntegrationRequest": "ONLYOFFICE DocSpace'te yararlı bir entegrasyon veya bileşen mi eksik? Ekibimize bir talep bırakın, bununla ilgilenelim.",
"IPSecurity": "IP Güvenliği",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Увімкнути автоматичне резервне копіювання.",
"EnableAutomaticBackupDescription": "Використовуйте цей параметр для резервного копіювання даних порталу.",
"EnterTitle": "Введіть заголовок",
"EveryDay": "Кожного дня",
"EveryMonth": "Кожного місяця",
"EveryWeek": "Кожного тижня",
"ForcePathStyle": "Примусово використати стиль шляху",
"IntegrationRequest": "Не вистачає корисної інтеграції чи компонента в ONLYOFFICE DocSpace? Залиште заявку нашій команді, і ми її розглянемо.",
"IPSecurity": "IP-безпека",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "Bật sao lưu tự động.",
"EnableAutomaticBackupDescription": "Sử dụng tùy chọn này để sao lưu dữ liệu không gian.",
"EnterTitle": "Nhập tiêu đề",
"EveryDay": "Hàng ngày",
"EveryMonth": "Hàng tháng",
"EveryWeek": "Hàng tuần",
"ForcePathStyle": "Kiểu đường dẫn bắt buộc",
"IntegrationRequest": "Bạn thấy thiếu một tích hợp hoặc thành phần hữu ích trong ONLYOFFICE DocSpace? Xin hãy gửi yêu cầu cho nhóm của chúng tôi và chúng tôi sẽ xem xét điều đó.",
"IPSecurity": "Bảo mật IP",

View File

@ -95,9 +95,6 @@
"EnableAutomaticBackup": "启用自动备份",
"EnableAutomaticBackupDescription": "使用该选项备份协作空间的数据。",
"EnterTitle": "请输入标题",
"EveryDay": "每天",
"EveryMonth": "每个月",
"EveryWeek": "每周",
"ForcePathStyle": "力路风格",
"IntegrationRequest": "在 ONLYOFFICE 协作空间中缺少有用的集成或组件?请向我们的团队提出申请,我们将对此进行研究。",
"IPSecurity": "IP 安全性",

View File

@ -4,6 +4,7 @@ import { inject, observer } from "mobx-react";
import styled, { css } from "styled-components";
import Base from "@docspace/components/themes/base";
import NoUserSelect from "@docspace/components/utils/commonStyles";
import RoomIcon from "./RoomIcon";
const StyledIcon = styled.img`
${NoUserSelect}
@ -29,9 +30,7 @@ const IconWrapper = styled.div`
right: 0px;
bottom: 0px;
left: 0px;
border: 1px solid
${(props) =>
props.default ? "none" : props.theme.itemIcon.borderColor};
border: 1px solid ${(props) => props.theme.itemIcon.borderColor};
border-radius: 5px;
overflow: hidden;
}
@ -46,7 +45,7 @@ const EncryptedFileIcon = styled.div`
position: absolute;
width: 16px;
margin-top: 14px;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 12px;
@ -56,22 +55,31 @@ const EncryptedFileIcon = styled.div`
`}
`;
const ItemIcon = ({ icon, fileExst, isPrivacy, isRoom, defaultRoomIcon }) => {
const [showDefaultIcon, setShowDefaultIcon] = React.useState(isRoom);
React.useEffect(() => {
if (!isRoom || defaultRoomIcon === icon) return;
setShowDefaultIcon(false);
}, [isRoom, defaultRoomIcon, icon, setShowDefaultIcon]);
const ItemIcon = ({
icon,
fileExst,
isPrivacy,
isRoom,
title,
logo,
color,
isArchive,
}) => {
const isLoadedRoomIcon = !!logo?.medium;
const showDefaultRoomIcon = !isLoadedRoomIcon && isRoom;
return (
<>
<IconWrapper isRoom={isRoom} default={showDefaultIcon}>
<IconWrapper isRoom={isRoom}>
{showDefaultRoomIcon ? (
<RoomIcon color={color} title={title} isArchive={isArchive} />
) : (
<StyledIcon
className={`react-svg-icon`}
isRoom={isRoom}
src={showDefaultIcon ? defaultRoomIcon : icon}
src={isRoom ? logo?.medium : icon}
/>
)}
</IconWrapper>
{isPrivacy && fileExst && <EncryptedFileIcon isEdit={false} />}
</>

View File

@ -0,0 +1,66 @@
import styled, { css } from "styled-components";
import Base from "@docspace/components/themes/base";
import Text from "@docspace/components/text";
const StyledIcon = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: ${(props) => props.size};
width: ${(props) => props.size};
.room-background {
height: ${(props) => props.size};
width: ${(props) => props.size};
border-radius: ${(props) => props.radius};
vertical-align: middle;
background: ${(props) =>
props.isArchive
? props.theme.roomIcon.backgroundArchive
: `#` + props.color};
position: absolute;
opacity: ${(props) => props.theme.roomIcon.opacityBackground};
}
.room-title {
font-size: 14px;
font-weight: 700;
line-height: 16px;
color: #ffffff;
position: relative;
${(props) =>
!props.theme.isBase &&
!props.isArchive &&
css`
color: ${(props) => `#` + props.color};
`};
}
`;
StyledIcon.defaultProps = { theme: Base };
const RoomIcon = ({
title,
isArchive,
color,
size = "32px",
radius = "6px",
}) => {
const titleWithoutSpaces = title.replace(/\s+/g, " ").trim();
const indexAfterLastSpace = titleWithoutSpaces.lastIndexOf(" ");
const secondCharacter =
indexAfterLastSpace === -1
? ""
: titleWithoutSpaces[indexAfterLastSpace + 1];
const roomTitle = (title[0] + secondCharacter).toUpperCase();
return (
<StyledIcon color={color} size={size} radius={radius} isArchive={isArchive}>
<div className="room-background" />
<Text className="room-title">{roomTitle}</Text>
</StyledIcon>
);
};
export default RoomIcon;

View File

@ -86,14 +86,27 @@ export const QuotaBarTypes = Object.freeze({
export const BINDING_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
export const BINDING_REDIRECT =
"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
export const SSO_NAME_ID_FORMAT =
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
export const SSO_NAME_ID_FORMAT = [
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted",
"urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos",
];
export const SSO_GIVEN_NAME = "givenName";
export const SSO_SN = "sn";
export const SSO_EMAIL = "email";
export const SSO_LOCATION = "location";
export const SSO_TITLE = "title";
export const SSO_PHONE = "phone";
export const SSO_SIGNING = "signing";
export const SSO_ENCRYPT = "encrypt";
export const SSO_SIGNING_ENCRYPT = "signing and encrypt";
export const DEFAULT_SELECT_TIMEZONE = {
key: "UTC",

View File

@ -34,6 +34,12 @@ const StyledNoThumbnail = styled.div`
outline: 1px solid
${(props) => props.theme.infoPanel.details.customLogoBorderColor};
}
.room-title {
font-size: 41px;
font-weight: 700;
line-height: 56px;
}
`;
StyledThumbnail.defaultProps = { theme: Base };

View File

@ -8,6 +8,7 @@ import { Text } from "@docspace/components";
import ItemContextOptions from "./ItemContextOptions";
import { StyledTitle } from "../../styles/common";
import RoomIcon from "@docspace/client/src/components/RoomIcon";
const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
const itemTitleRef = useRef();
@ -15,15 +16,25 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
if (isSeveralItems) return <></>;
const icon = selection.icon;
const isLoadedRoomIcon = !!selection.logo?.medium;
const showDefaultRoomIcon = !isLoadedRoomIcon && selection.isRoom;
return (
<StyledTitle ref={itemTitleRef}>
<div className="item-icon">
{showDefaultRoomIcon ? (
<RoomIcon
color={selection.logo.color}
title={selection.title}
isArchive={selection.isArchive}
/>
) : (
<img
className={`icon ${selection.isRoom && "is-room"}`}
src={icon}
alt="thumbnail-icon"
/>
)}
</div>
<Text className="text">{selection.title}</Text>
{selection && (

View File

@ -9,7 +9,7 @@ import Text from "@docspace/components/text";
import DetailsHelper from "../../helpers/DetailsHelper.js";
import { StyledNoThumbnail, StyledThumbnail } from "../../styles/details.js";
import { StyledProperties, StyledSubtitle } from "../../styles/common.js";
import RoomIcon from "@docspace/client/src/components/RoomIcon";
const Details = ({
t,
selection,
@ -67,6 +67,9 @@ const Details = ({
//console.log("InfoPanel->Details render", { selection });
const isLoadedRoomIcon = !!selection.logo?.large;
const showDefaultRoomIcon = !isLoadedRoomIcon && selection.isRoom;
return (
<>
{selection.thumbnailUrl && !isThumbnailError ? (
@ -86,6 +89,15 @@ const Details = ({
</StyledThumbnail>
) : (
<StyledNoThumbnail>
{showDefaultRoomIcon ? (
<RoomIcon
color={selection.logo.color}
title={selection.title}
isArchive={selection.isArchive}
size="96px"
radius="16px"
/>
) : (
<img
className={`no-thumbnail-img ${selection.isRoom && "is-room"} ${
selection.isRoom &&
@ -96,6 +108,7 @@ const Details = ({
src={currentIcon}
alt="thumbnail-icon-big"
/>
)}
</StyledNoThumbnail>
)}

View File

@ -15,7 +15,7 @@ import { tablet } from "@docspace/components/utils/device";
import CursorPalmReactSvgUrl from "PUBLIC_DIR/images/cursor.palm.react.svg?url";
import { classNames } from "@docspace/components/utils/classNames";
const checkedStyle = css`
background: ${props => props.theme.filesSection.rowView.checkedBackground};
background: ${(props) => props.theme.filesSection.rowView.checkedBackground};
${marginStyles}
`;
@ -28,10 +28,10 @@ const StyledWrapper = styled.div`
`;
const StyledSimpleFilesRow = styled(Row)`
${props => (props.checked || props.isActive) && checkedStyle};
${(props) => (props.checked || props.isActive) && checkedStyle};
height: 56px;
${props =>
${(props) =>
!isMobile &&
!props.isDragging &&
css`
@ -39,25 +39,25 @@ const StyledSimpleFilesRow = styled(Row)`
cursor: pointer;
${checkedStyle}
${props =>
${(props) =>
!props.showHotkeyBorder &&
css`
margin-top: -2px;
padding-top: 1px;
padding-bottom: 1px;
border-top: ${props =>
border-top: ${(props) =>
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
border-bottom: ${props =>
border-bottom: ${(props) =>
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
`}
}
`};
position: unset;
cursor: ${props =>
cursor: ${(props) =>
!props.isThirdPartyFolder &&
(props.checked || props.isActive) &&
`url(${CursorPalmReactSvgUrl}), auto`};
${props =>
${(props) =>
props.inProgress &&
css`
pointer-events: none;
@ -66,7 +66,7 @@ const StyledSimpleFilesRow = styled(Row)`
margin-top: 0px;
${props =>
${(props) =>
props.showHotkeyBorder &&
css`
border-top: 1px solid #2da7db !important;
@ -77,7 +77,7 @@ const StyledSimpleFilesRow = styled(Row)`
padding-right: 24px;
`}
${props =>
${(props) =>
props.isHighlight &&
css`
${marginStyles}
@ -85,16 +85,16 @@ const StyledSimpleFilesRow = styled(Row)`
margin-top: -2px;
padding-top: 1px;
padding-bottom: 1px;
border-top: ${props =>
border-top: ${(props) =>
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
border-bottom: ${props =>
border-bottom: ${(props) =>
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
animation: Highlight 2s 1;
@keyframes Highlight {
0% {
background: ${props => props.theme.filesSection.animationColor};
background: ${(props) => props.theme.filesSection.animationColor};
}
100% {
@ -105,7 +105,7 @@ const StyledSimpleFilesRow = styled(Row)`
::after {
${props =>
${(props) =>
props.showHotkeyBorder &&
css`
background: #2da7db;
@ -123,7 +123,7 @@ const StyledSimpleFilesRow = styled(Row)`
`}
}
${props =>
${(props) =>
(!props.contextOptions || props.isEdit) &&
`
& > div:last-child {
@ -136,7 +136,7 @@ const StyledSimpleFilesRow = styled(Row)`
.styled-element {
height: 32px;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 7px;
@ -147,7 +147,7 @@ const StyledSimpleFilesRow = styled(Row)`
}
.row_content {
${props =>
${(props) =>
props.sectionWidth > 500 && `max-width: fit-content;`}//min-width: auto
}
@ -158,7 +158,7 @@ const StyledSimpleFilesRow = styled(Row)`
}
.badge {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 8px;
@ -169,7 +169,7 @@ const StyledSimpleFilesRow = styled(Row)`
}
.badge:last-child {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 0px;
@ -180,7 +180,7 @@ const StyledSimpleFilesRow = styled(Row)`
}
.lock-file {
cursor: ${props => (props.withAccess ? "pointer" : "default")};
cursor: ${(props) => (props.withAccess ? "pointer" : "default")};
svg {
height: 12px;
}
@ -203,11 +203,11 @@ const StyledSimpleFilesRow = styled(Row)`
}
.badges {
margin-top: ${props =>
margin-top: ${(props) =>
props.isSmallContainer ? "1px" : props.isRooms ? "4px" : "2px"};
margin-bottom: 0px;
${props =>
${(props) =>
props.isSmallContainer &&
css`
.tablet-pinned {
@ -221,13 +221,15 @@ const StyledSimpleFilesRow = styled(Row)`
}
.badge {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: ${props => (props.isSmallContainer ? "8px" : "24px")};
margin-left: ${(props) =>
props.isSmallContainer ? "8px" : "24px"};
`
: css`
margin-right: ${props => (props.isSmallContainer ? "8px" : "24px")};
margin-right: ${(props) =>
props.isSmallContainer ? "8px" : "24px"};
`}
}
@ -238,21 +240,21 @@ const StyledSimpleFilesRow = styled(Row)`
}
.expandButton {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: ${props => (!props.folderCategory ? "6px" : "0")};
margin-right: ${(props) => (!props.folderCategory ? "6px" : "0")};
`
: css`
margin-left: ${props => (!props.folderCategory ? "6px" : "0")};
margin-left: ${(props) => (!props.folderCategory ? "6px" : "0")};
`}
padding-top: 0px;
}
.expandButton > div:first-child {
${props =>
${(props) =>
props.folderCategory &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 0 !important;
@ -266,7 +268,7 @@ const StyledSimpleFilesRow = styled(Row)`
StyledSimpleFilesRow.defaultProps = { theme: Base };
const SimpleFilesRow = props => {
const SimpleFilesRow = (props) => {
const {
item,
sectionWidth,
@ -309,11 +311,14 @@ const SimpleFilesRow = props => {
icon={item.icon}
fileExst={item.fileExst}
isRoom={item.isRoom}
defaultRoomIcon={item.defaultRoomIcon}
title={item.title}
logo={item.logo}
color={item.logo?.color}
isArchive={item.isArchive}
/>
);
const onDragOver = dragOver => {
const onDragOver = (dragOver) => {
if (dragOver !== isDragOver) {
setIsDragOver(dragOver);
}
@ -347,7 +352,8 @@ const SimpleFilesRow = props => {
: checkedProps || isActive
? "row-selected"
: ""
}`}>
}`}
>
<DragAndDrop
data-title={item.title}
value={value}
@ -357,7 +363,8 @@ const SimpleFilesRow = props => {
dragging={dragging && isDragging}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
style={dragStyles}>
style={dragStyles}
>
<StyledSimpleFilesRow
key={item.id}
data={item}
@ -390,7 +397,8 @@ const SimpleFilesRow = props => {
isSmallContainer={isSmallContainer}
isRooms={isRooms}
folderCategory={folderCategory}
isHighlight={isHighlight}>
isHighlight={isHighlight}
>
<FilesRowContent
item={item}
sectionWidth={sectionWidth}

View File

@ -49,7 +49,10 @@ const FilesTableRow = (props) => {
icon={item.icon}
fileExst={item.fileExst}
isRoom={item.isRoom}
defaultRoomIcon={item.defaultRoomIcon}
title={item.title}
logo={item.logo}
color={item.logo?.color}
isArchive={item.isArchive}
/>
);

View File

@ -75,9 +75,10 @@ const FileTile = (props) => {
icon={item.icon}
fileExst={item.fileExst}
isRoom={item.isRoom}
defaultRoomIcon={
item.isRoom && item.icon ? item.icon : item.defaultRoomIcon
}
title={item.title}
logo={item.logo}
color={item.logo?.color}
isArchive={item.isArchive}
/>
);

View File

@ -60,7 +60,7 @@ const StyledContainer = styled.div`
min-height: 33px;
.table-container_group-menu {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin: 0 -20px 0 0;
@ -75,7 +75,7 @@ const StyledContainer = styled.div`
@media ${tablet} {
height: 60px;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin: 0 -16px 0 0;
@ -89,7 +89,7 @@ const StyledContainer = styled.div`
${isMobile &&
css`
height: 60px;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin: 0 -16px 0 0;
@ -103,7 +103,7 @@ css`
@media ${mobile} {
height: 52px;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin: 0 -16px 0 0;
@ -117,7 +117,7 @@ css`
${isMobileOnly &&
css`
height: 52px;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin: 0 -16px 0 0;
@ -132,7 +132,7 @@ css`
.header-container {
min-height: 33px;
${props =>
${(props) =>
props.hideContextMenuInsideArchiveRoom &&
`.option-button {
display: none;}`}
@ -143,7 +143,7 @@ css`
}
`;
const SectionHeaderContent = props => {
const SectionHeaderContent = (props) => {
const {
currentFolderId,
setSelectFileDialogVisible,
@ -244,7 +244,7 @@ const SectionHeaderContent = props => {
const isAccountsPage = location.pathname.includes("accounts");
const isSettingsPage = location.pathname.includes("settings");
const onCreate = format => {
const onCreate = (format) => {
const event = new Event(Events.CREATE);
const payload = {
@ -286,7 +286,7 @@ const SectionHeaderContent = props => {
const createFolder = () => onCreate();
// TODO: add privacy room check for files
const onUploadAction = type => {
const onUploadAction = (type) => {
const element =
type === "file"
? document.getElementById("customFileInput")
@ -438,7 +438,7 @@ const SectionHeaderContent = props => {
const pluginOptions = getMainButtonItems();
if (pluginOptions) {
pluginOptions.forEach(option => {
pluginOptions.forEach((option) => {
options.splice(option.value.position, 0, {
key: option.key,
...option.value,
@ -474,7 +474,7 @@ const SectionHeaderContent = props => {
setBufferSelection(currentFolderId);
setIsFolderActions(true);
downloadAction(t("Translations:ArchivingData"), [currentFolderId]).catch(
err => toastr.error(err)
(err) => toastr.error(err)
);
};
@ -496,7 +496,7 @@ const SectionHeaderContent = props => {
setIsFolderActions(true);
if (confirmDelete || isThirdPartySelection) {
getFolderInfo(currentFolderId).then(data => {
getFolderInfo(currentFolderId).then((data) => {
setBufferSelection(data);
setDeleteDialogVisible(true);
});
@ -508,7 +508,7 @@ const SectionHeaderContent = props => {
FolderRemoved: t("Files:FolderRemoved"),
};
deleteAction(translations, [currentFolderId], true).catch(err =>
deleteAction(translations, [currentFolderId], true).catch((err) =>
toastr.error(err)
);
}
@ -614,10 +614,10 @@ const SectionHeaderContent = props => {
const isDisabled = isRecycleBinFolder || isRoom;
const links = externalLinks.filter(l => !l.sharedTo.disabled);
const links = externalLinks.filter((l) => !l.sharedTo.disabled);
const isMultiExternalLink = links.length > 1;
const roomLinks = links.map(link => {
const roomLinks = links.map((link) => {
return {
// id: "option_move-to",
key: `external-link_${link.sharedTo.id}`,
@ -768,7 +768,7 @@ const SectionHeaderContent = props => {
key: "archive-room",
label: t("MoveToArchive"),
icon: RoomArchiveSvgUrl,
onClick: e => onClickArchive(e),
onClick: (e) => onClickArchive(e),
disabled: !isRoom || !security?.Move,
"data-action": "archive",
action: "archive",
@ -822,7 +822,7 @@ const SectionHeaderContent = props => {
];
};
const onSelect = e => {
const onSelect = (e) => {
const key = e.currentTarget.dataset.key;
isAccountsPage ? setAccountsSelected(key) : setSelected(key);
@ -835,7 +835,7 @@ const SectionHeaderContent = props => {
const getMenuItems = () => {
const checkboxOptions = isAccountsPage ? (
<>
{accountsCbMenuItems.map(key => {
{accountsCbMenuItems.map((key) => {
const label = getAccountsCheckboxItemLabel(t, key);
const id = getAccountsMenuItemId(key);
return (
@ -851,7 +851,7 @@ const SectionHeaderContent = props => {
</>
) : (
<>
{cbMenuItems.map(key => {
{cbMenuItems.map((key) => {
const label = getCheckboxItemLabel(t, key);
const id = getCheckboxItemId(key);
return (
@ -870,7 +870,7 @@ const SectionHeaderContent = props => {
return checkboxOptions;
};
const onChange = checked => {
const onChange = (checked) => {
isAccountsPage
? setAccountsSelected(checked ? "all" : "none")
: setSelected(checked ? "all" : "none");
@ -898,7 +898,7 @@ const SectionHeaderContent = props => {
filter.folder = id;
const itemIdx = selectedFolder.navigationPath.findIndex(v => v.id === id);
const itemIdx = selectedFolder.navigationPath.findIndex((v) => v.id === id);
const state = {
title: selectedFolder.navigationPath[itemIdx]?.title || "",
@ -912,7 +912,7 @@ const SectionHeaderContent = props => {
window.DocSpace.navigate(`${path}?${filter.toUrlParams()}`, { state });
};
const onInvite = e => {
const onInvite = (e) => {
const type = e.item["data-type"];
if (isGracePeriod) {
@ -933,7 +933,7 @@ const SectionHeaderContent = props => {
.then(() =>
toastr.success(t("PeopleTranslations:SuccessSentMultipleInvitatios"))
)
.catch(err => toastr.error(err));
.catch((err) => toastr.error(err));
}, [resendInvitesAgain]);
const headerMenu = isAccountsPage
@ -956,7 +956,7 @@ const SectionHeaderContent = props => {
tableGroupMenuVisible =
isAccountsHeaderVisible &&
tableGroupMenuVisible &&
headerMenu.some(x => !x.disabled);
headerMenu.some((x) => !x.disabled);
tableGroupMenuProps.isChecked = isAccountsHeaderChecked;
tableGroupMenuProps.isIndeterminate = isAccountsHeaderIndeterminate;
tableGroupMenuProps.withoutInfoPanelToggler = false;
@ -1002,10 +1002,11 @@ const SectionHeaderContent = props => {
return (
<Consumer key="header">
{context => (
{(context) => (
<StyledContainer
isRecycleBinFolder={isRecycleBinFolder}
hideContextMenuInsideArchiveRoom={hideContextMenuInsideArchiveRoom}>
hideContextMenuInsideArchiveRoom={hideContextMenuInsideArchiveRoom}
>
{tableGroupMenuVisible ? (
<TableGroupMenu {...tableGroupMenuProps} />
) : (
@ -1112,10 +1113,14 @@ export default inject(
categoryType,
} = filesStore;
const { setIsSectionFilterLoading, showHeaderLoader, isLoading } =
clientLoadingStore;
const {
setIsSectionFilterLoading,
showHeaderLoader,
const setIsLoading = param => {
isLoading,
} = clientLoadingStore;
const setIsLoading = (param) => {
setIsSectionFilterLoading(param);
};
@ -1208,7 +1213,7 @@ export default inject(
let folderPath = navigationPath;
if (isFrame && !!pathParts) {
folderPath = navigationPath.filter(item => !item.isRootRoom);
folderPath = navigationPath.filter((item) => !item.isRootRoom);
}
const isRoot = isFrame

View File

@ -248,6 +248,7 @@ const PureHome = (props) => {
primaryProgressDataVisible || secondaryProgressDataStoreVisible;
sectionProps.isEmptyPage = isEmptyPage;
sectionProps.isTrashFolder = isRecycleBinFolder;
}
}

View File

@ -41,6 +41,9 @@ const Certificates = (props) => {
spEncryptAlgorithm,
spDecryptAlgorithm,
isLoadingXml,
isDisabledSpSigning,
isDisabledSpEncrypt,
isDisabledIdpSigning,
} = props;
let prefix = "";
@ -139,7 +142,7 @@ const Certificates = (props) => {
{provider === "IdentityProvider" && (
<>
<SsoComboBox
isDisabled={idpCertificates.length === 0}
isDisabled={isDisabledIdpSigning}
labelText={t("idpSigningAlgorithm")}
name="idpVerifyAlgorithm"
options={verifyAlgorithmsOptions}
@ -152,7 +155,7 @@ const Certificates = (props) => {
{provider === "ServiceProvider" && (
<>
<SsoComboBox
isDisabled={spCertificates.length === 0}
isDisabled={isDisabledSpSigning}
labelText={t("spSigningAlgorithm")}
name="spSigningAlgorithm"
options={verifyAlgorithmsOptions}
@ -161,7 +164,7 @@ const Certificates = (props) => {
/>
<SsoComboBox
isDisabled={spCertificates.length === 0}
isDisabled={isDisabledSpEncrypt}
labelText={t("StandardDecryptionAlgorithm")}
name={"spEncryptAlgorithm"}
options={decryptAlgorithmsOptions}
@ -193,6 +196,9 @@ export default inject(({ ssoStore }) => {
spEncryptAlgorithm,
spDecryptAlgorithm,
isLoadingXml,
isDisabledSpSigning,
isDisabledSpEncrypt,
isDisabledIdpSigning,
} = ssoStore;
return {
@ -207,5 +213,8 @@ export default inject(({ ssoStore }) => {
spEncryptAlgorithm,
spDecryptAlgorithm,
isLoadingXml,
isDisabledSpSigning,
isDisabledSpEncrypt,
isDisabledIdpSigning,
};
})(observer(Certificates));

View File

@ -27,8 +27,8 @@ const CertificatesTable = (props) => {
console.log(prefix, index);
const onEdit = () => {
prefix === "sp"
? setSpCertificate(certificate, index)
: setIdpCertificate(certificate);
? setSpCertificate(certificate, index, true)
: setIdpCertificate(certificate, index, true);
};
const onDelete = () => {

View File

@ -44,11 +44,15 @@ const CheckboxSet = (props) => {
spSignLogoutRequests,
spSignLogoutResponses,
spEncryptAssertions,
enableSso,
setCheckbox,
isLoadingXml,
isDisabledSpSigning,
isDisabledSpEncrypt,
isDisabledIdpSigning,
} = props;
const isDisabled =
prefix === "sp" ? isDisabledSpSigning : isDisabledIdpSigning;
return (
<StyledWrapper>
<Checkbox
@ -58,7 +62,7 @@ const CheckboxSet = (props) => {
: "sp-sign-auth-requests"
}
className="checkbox-input"
isDisabled={!enableSso || isLoadingXml}
isDisabled={isDisabled}
onChange={setCheckbox}
label={prefix === "idp" ? t("idpAuthRequest") : t("spAuthRequest")}
name={checkboxesNames[prefix][0]}
@ -74,7 +78,7 @@ const CheckboxSet = (props) => {
: "sp-sign-logout-requests"
}
className="checkbox-input"
isDisabled={!enableSso || isLoadingXml}
isDisabled={isDisabled}
onChange={setCheckbox}
label={
prefix === "idp" ? t("idpSignExitRequest") : t("spSignExitRequest")
@ -92,7 +96,7 @@ const CheckboxSet = (props) => {
: "sp-sign-logout-responses"
}
className="checkbox-input"
isDisabled={!enableSso || isLoadingXml}
isDisabled={isDisabled}
onChange={setCheckbox}
label={
prefix === "idp"
@ -112,7 +116,7 @@ const CheckboxSet = (props) => {
<Checkbox
id="sp-encrypt-assertions"
className="checkbox-input"
isDisabled={!enableSso || isLoadingXml}
isDisabled={isDisabledSpEncrypt}
onChange={setCheckbox}
label={t("spDecryptStatements")}
name={checkboxesNames[prefix][3]}
@ -133,9 +137,10 @@ export default inject(({ ssoStore }) => {
spSignLogoutRequests,
spSignLogoutResponses,
spEncryptAssertions,
enableSso,
setCheckbox,
isLoadingXml,
isDisabledSpSigning,
isDisabledSpEncrypt,
isDisabledIdpSigning,
} = ssoStore;
return {
@ -146,8 +151,9 @@ export default inject(({ ssoStore }) => {
spSignLogoutRequests,
spSignLogoutResponses,
spEncryptAssertions,
enableSso,
setCheckbox,
isLoadingXml,
isDisabledSpSigning,
isDisabledSpEncrypt,
isDisabledIdpSigning,
};
})(observer(CheckboxSet));

View File

@ -66,7 +66,7 @@ class ClientLoadingStore {
return;
}
this.startLoadingTime.header = new Date();
if (withTimer) {
if (withTimer && !this.firstLoad) {
return (this.sectionHeaderTimer = setTimeout(() => {
this.updateIsSectionHeaderLoading(isSectionHeaderLoading);
}, SHOW_LOADER_TIMER));
@ -107,7 +107,7 @@ class ClientLoadingStore {
return;
}
this.startLoadingTime.filter = new Date();
if (withTimer) {
if (withTimer && !this.firstLoad) {
return (this.sectionFilterTimer = setTimeout(() => {
this.updateIsSectionFilterLoading(isSectionFilterLoading);
}, SHOW_LOADER_TIMER));
@ -153,7 +153,7 @@ class ClientLoadingStore {
return;
}
this.startLoadingTime.body = new Date();
if (withTimer) {
if (withTimer && !this.firstLoad) {
return (this.sectionBodyTimer = setTimeout(() => {
this.updateIsSectionBodyLoading(isSectionBodyLoading);
}, SHOW_LOADER_TIMER));

View File

@ -108,7 +108,7 @@ class FilesStore {
mainButtonMobileVisible = true;
filesIsLoading = false;
isEmptyPage = false;
isEmptyPage = true;
isLoadedFetchFiles = false;
tempActionFilesIds = [];
@ -1390,7 +1390,7 @@ class FilesStore {
authorType ||
roomId ||
search ||
!withSubfolders ||
withSubfolders ||
filterType ||
searchInContent;
@ -2805,6 +2805,8 @@ class FilesStore {
if (items.length > 0 && this.isEmptyPage) {
this.setIsEmptyPage(false);
} else if (items.length === 0 && !this.isEmptyPage) {
this.setIsEmptyPage(true);
}
const newItem = items.map((item) => {

View File

@ -19,6 +19,9 @@ import {
SSO_TITLE,
SSO_PHONE,
SSO_NAME_ID_FORMAT,
SSO_SIGNING,
SSO_ENCRYPT,
SSO_SIGNING_ENCRYPT,
} from "../helpers/constants";
import isEqual from "lodash/isEqual";
@ -41,11 +44,11 @@ class SsoFormStore {
sloUrlPost = "";
sloUrlRedirect = "";
sloBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
nameIdFormat = SSO_NAME_ID_FORMAT;
nameIdFormat = SSO_NAME_ID_FORMAT[0];
idpCertificate = "";
idpPrivateKey = null;
idpAction = "signing";
idpAction = SSO_SIGNING;
idpCertificates = [];
// idpCertificateAdvanced
@ -59,7 +62,7 @@ class SsoFormStore {
spCertificate = "";
spPrivateKey = "";
spAction = "signing";
spAction = SSO_SIGNING;
spCertificates = [];
// spCertificateAdvanced
@ -126,6 +129,7 @@ class SsoFormStore {
defaultSettings = null;
editIndex = 0;
isEdit = false;
isInit = false;
@ -191,6 +195,8 @@ class SsoFormStore {
closeIdpModal = () => {
this.idpCertificate = "";
this.idpPrivateKey = "";
this.editIndex = 0;
this.isEdit = false;
this.idpIsModalVisible = false;
};
@ -199,6 +205,7 @@ class SsoFormStore {
this.spPrivateKey = "";
this.spIsModalVisible = false;
this.editIndex = 0;
this.isEdit = false;
};
setComboBoxOption = (option) => {
@ -638,33 +645,77 @@ class SsoFormStore {
return array.filter((item, index, array) => array.indexOf(item) == index);
};
setSpCertificate = (certificate, index) => {
setSpCertificate = (certificate, index, isEdit) => {
this.spCertificate = certificate.crt;
this.spPrivateKey = certificate.key;
this.spAction = certificate.action;
this.editIndex = index;
this.isEdit = isEdit;
this.spIsModalVisible = true;
};
setIdpCertificate = (certificate) => {
setIdpCertificate = (certificate, index, isEdit) => {
this.idpCertificate = certificate.crt;
this.idpPrivateKey = certificate.key;
this.idpAction = certificate.action;
this.editIndex = index;
this.isEdit = isEdit;
this.idpIsModalVisible = true;
};
resetSpCheckboxes = (action) => {
if (action === SSO_SIGNING_ENCRYPT) {
this.spSignAuthRequests = false;
this.spSignLogoutRequests = false;
this.spSignLogoutResponses = false;
this.spEncryptAssertions = false;
}
if (action === SSO_SIGNING) {
this.spSignAuthRequests = false;
this.spSignLogoutRequests = false;
this.spSignLogoutResponses = false;
}
if (action === SSO_ENCRYPT) {
this.spEncryptAssertions = false;
}
};
resetIdpCheckboxes = () => {
this.idpVerifyAuthResponsesSign = false;
this.idpVerifyLogoutRequestsSign = false;
this.idpVerifyLogoutResponsesSign = false;
};
delSpCertificate = (action) => {
this.resetSpCheckboxes(action);
this.spCertificates = this.spCertificates.filter(
(certificate) => certificate.action !== action
);
};
delIdpCertificate = (cert) => {
this.resetIdpCheckboxes();
this.idpCertificates = this.idpCertificates.filter(
(certificate) => certificate.crt !== cert
);
};
checkSpCertificateExist = () => {
if (
this.spAction === SSO_SIGNING_ENCRYPT &&
this.spCertificates.length > 0 &&
!this.isEdit
)
return true;
return this.spCertificates.find(
(item) =>
(item.action === this.spAction ||
item.action === SSO_SIGNING_ENCRYPT) &&
!this.isEdit
);
};
addSpCertificate = async (t) => {
const data = [
{
@ -674,12 +725,7 @@ class SsoFormStore {
},
];
if (
this.spCertificates.find(
(item, index) =>
item.action === this.spAction && this.editIndex !== index
)
) {
if (this.checkSpCertificateExist()) {
toastr.error(t("CertificateExist"));
return;
}
@ -693,10 +739,16 @@ class SsoFormStore {
return;
}
const newCertificates = res.data;
if (this.isEdit) {
this.spCertificates[this.editIndex] = newCertificates[0];
this.checkedSpBoxes(newCertificates[0]);
} else {
newCertificates.map((cert) => {
this.spCertificates = [...this.spCertificates, cert];
this.checkedSpBoxes(cert);
});
}
this.isCertificateLoading = false;
this.closeSpModal();
} catch (err) {
@ -707,14 +759,14 @@ class SsoFormStore {
};
checkedSpBoxes = (cert) => {
if (cert.action === "signing") {
if (cert.action === SSO_SIGNING) {
this.spSignAuthRequests = true;
this.spSignLogoutRequests = true;
}
if (cert.action === "encrypt") {
if (cert.action === SSO_ENCRYPT) {
this.spEncryptAssertions = true;
}
if (cert.action === "signing and encrypt") {
if (cert.action === SSO_SIGNING_ENCRYPT) {
this.spSignAuthRequests = true;
this.spSignLogoutRequests = true;
this.spEncryptAssertions = true;
@ -730,7 +782,11 @@ class SsoFormStore {
},
];
if (this.idpCertificates.find((item) => item.crt === this.idpCertificate)) {
if (
this.idpCertificates.find(
(item) => item.crt === this.idpCertificate && !this.isEdit
)
) {
toastr.error(t("CertificateExist"));
return;
}
@ -744,10 +800,15 @@ class SsoFormStore {
return;
}
const newCertificates = res.data;
if (this.isEdit) {
this.idpCertificates[this.editIndex] = newCertificates[0];
this.checkedIdpBoxes(newCertificates[0]);
} else {
newCertificates.map((cert) => {
this.idpCertificates = [...this.idpCertificates, cert];
this.checkedIdpBoxes(cert);
});
}
this.isCertificateLoading = false;
this.closeIdpModal();
} catch (err) {
@ -824,6 +885,27 @@ class SsoFormStore {
const currentSettings = this.getSettings();
return !isEqual(currentSettings, this.defaultSettings);
}
get isDisabledIdpSigning() {
if (!this.enableSso || this.isLoadingXml) return true;
return this.idpCertificates.length === 0;
}
get isDisabledSpSigning() {
if (!this.enableSso || this.isLoadingXml) return true;
return !this.spCertificates.some(
(cert) =>
cert.action === SSO_SIGNING || cert.action === SSO_SIGNING_ENCRYPT
);
}
get isDisabledSpEncrypt() {
if (!this.enableSso || this.isLoadingXml) return true;
return !this.spCertificates.some(
(cert) =>
cert.action === SSO_ENCRYPT || cert.action === SSO_SIGNING_ENCRYPT
);
}
}
export default SsoFormStore;

View File

@ -4,6 +4,8 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin =
require("webpack").container.ModuleFederationPlugin;
const DefinePlugin = require("webpack").DefinePlugin;
const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const ExternalTemplateRemotesPlugin = require("external-remotes-plugin");
const TerserPlugin = require("terser-webpack-plugin");
@ -362,5 +364,9 @@ module.exports = (env, argv) => {
config.plugins.push(new DefinePlugin(defines));
if (env.mode === "analyze") {
config.plugins.push(new BundleAnalyzerPlugin());
}
return config;
};

View File

@ -57,6 +57,7 @@ const Navigation = ({
burgerLogo,
isPublicRoom,
titleIcon,
...rest
}) => {
const [isOpen, setIsOpen] = React.useState(false);
@ -221,6 +222,7 @@ const Navigation = ({
isDesktopClient={isDesktopClient}
isInfoPanelVisible={isInfoPanelVisible}
withLogo={!!withLogo}
className="navigation-container"
>
{withLogo && (
<NavigationLogo
@ -253,10 +255,14 @@ const Navigation = ({
onPlusClick={onPlusClick}
isFrame={isFrame}
isPublicRoom={isPublicRoom}
isTrashFolder={isTrashFolder}
/>
</StyledContainer>
{isTrashFolder && !isEmptyPage && (
<TrashWarning title={titles.trashWarning} />
<TrashWarning
title={titles.trashWarning}
isTabletView={isTabletView}
/>
)}
{infoPanelIsVisible && !hideInfoPanel && (
<ToggleInfoPanelButton

View File

@ -10,7 +10,7 @@ const ContextButton = (props) => {
const ref = useRef(null);
const menuRef = useRef(null);
const { className, getData, withMenu, ...rest } = props;
const { className, getData, withMenu, isTrashFolder, ...rest } = props;
const toggle = (e, isOpen) => {
isOpen ? menuRef.current.show(e) : menuRef.current.hide(e);
@ -42,7 +42,7 @@ const ContextButton = (props) => {
ref={menuRef}
onHide={onHide}
scaled={false}
leftOffset={isDesktop() ? 150 : 0}
leftOffset={isTrashFolder ? 188 : isDesktop() ? 150 : 0}
/>
</div>
);

View File

@ -164,6 +164,7 @@ const ControlButtons = ({
onPlusClick,
isFrame,
isPublicRoom,
isTrashFolder,
}) => {
const toggleInfoPanelAction = () => {
toggleInfoPanel && toggleInfoPanel();
@ -205,6 +206,7 @@ const ControlButtons = ({
withMenu={withMenu}
//onPlusClick={onPlusClick}
title={titles?.actions}
isTrashFolder={isTrashFolder}
/>
{!isDesktop && (

View File

@ -20,21 +20,6 @@ const StyledTrashWarning = styled.div`
color: ${({ theme }) => theme.section.header.trashErasureLabelText};
background: ${({ theme }) =>
theme.section.header.trashErasureLabelBackground};
${({ isTabletView }) =>
!isTabletView
? css`
@media ${tablet} {
display: none;
}
`
: css`
margin-bottom: 16px;
display: none;
@media ${tablet} {
display: flex;
}
`}
`;
const TrashWarning = ({ title, isTabletView }) => {

View File

@ -49,6 +49,7 @@ const Section = (props) => {
isInfoPanelVisible,
isInfoPanelScrollLocked,
isEmptyPage,
isTrashFolder,
} = props;
const [sectionSize, setSectionSize] = React.useState({
@ -169,6 +170,7 @@ const Section = (props) => {
viewAs={viewAs}
showText={showText}
isEmptyPage={isEmptyPage}
isTrashFolder={isTrashFolder}
>
{sectionHeaderContent
? sectionHeaderContent.props.children
@ -206,6 +208,7 @@ const Section = (props) => {
showText={showText}
settingsStudio={settingsStudio}
isEmptyPage={isEmptyPage}
isTrashFolder={isTrashFolder}
>
{sectionHeaderContent
? sectionHeaderContent.props.children

View File

@ -13,13 +13,14 @@ import {
desktop,
smallTablet,
mobile,
hugeMobile,
} from "@docspace/components/utils/device";
const settingsStudioStyles = css`
${({ settingsStudio }) =>
settingsStudio &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 20px 16px 7px;
@ -29,7 +30,7 @@ const settingsStudioStyles = css`
`}
@media ${tablet} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 24px 16px 0;
@ -40,7 +41,7 @@ const settingsStudioStyles = css`
}
@media ${smallTablet} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 8px 24px 16px 0;
@ -51,7 +52,7 @@ const settingsStudioStyles = css`
}
@media ${mobile} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 24px 16px 0;
@ -64,7 +65,7 @@ const settingsStudioStyles = css`
`;
const paddingStyles = css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 19px 20px 16px 3px;
@ -76,9 +77,20 @@ const paddingStyles = css`
${settingsStudioStyles};
@media ${tablet} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 19px 24px 16px 0;
`
: css`
padding: 19px 0 16px 24px;
`}
}
${isMobile &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 23px 16px 0 !important;
@ -88,9 +100,20 @@ const paddingStyles = css`
`}
`};
@media ${hugeMobile} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0px 24px 16px 0;
`
: css`
padding: 0px 0 16px 24px;
`}
}
${isMobileOnly &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0px 24px 16px 0 !important;
@ -104,16 +127,16 @@ const paddingStyles = css`
const commonStyles = css`
flex-grow: 1;
${props => (props.isDesktop ? "height: auto" : "height: 100%")};
${(props) => (props.isDesktop ? "height: auto" : "height: 100%")};
${props => !props.withScroll && `height: 100%;`}
${(props) => !props.withScroll && `height: 100%;`}
border-left: none;
border-right: none;
border-top: none;
.section-wrapper {
height: 100%;
${props =>
${(props) =>
!props.withScroll &&
css`
display: flex;
@ -121,17 +144,17 @@ const commonStyles = css`
height: 100%;
box-sizing: border-box;
`};
${props => !props.withScroll && paddingStyles}
${(props) => !props.withScroll && paddingStyles}
}
.section-wrapper-content {
${paddingStyles}
flex: 1 0 auto;
outline: none;
${props =>
${(props) =>
props.viewAs == "tile" &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 20px;
@ -141,7 +164,7 @@ const commonStyles = css`
`}
`}
${props =>
${(props) =>
(props.viewAs == "settings" || props.viewAs == "profile") &&
css`
padding-top: 0;
@ -171,7 +194,7 @@ const commonStyles = css`
`}
@media ${desktop} {
${props =>
${(props) =>
props.viewAs === "row" &&
css`
margin-top: -15px;
@ -187,10 +210,10 @@ const StyledSectionBody = styled.div`
${commonStyles};
${props =>
${(props) =>
props.withScroll &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -20px;
@ -200,7 +223,7 @@ const StyledSectionBody = styled.div`
`}
@media ${tablet} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -24px;
@ -213,7 +236,7 @@ const StyledSectionBody = styled.div`
${isMobile &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -24px;
@ -242,10 +265,10 @@ const StyledDropZoneBody = styled(DragAndDrop)`
height: 100%;
}
${props =>
${(props) =>
props.withScroll &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -20px;
@ -255,7 +278,7 @@ const StyledDropZoneBody = styled(DragAndDrop)`
`}
@media ${tablet} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -24px;
@ -267,7 +290,7 @@ const StyledDropZoneBody = styled(DragAndDrop)`
${isMobile &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -24px;
@ -339,13 +362,15 @@ class SectionBody extends React.Component {
isLoaded={isLoaded}
isDesktop={isDesktop}
settingsStudio={settingsStudio}
className="section-body">
className="section-body"
>
{withScroll ? (
!isMobileOnly ? (
<Scrollbar
id="sectionScroll"
scrollclass="section-scroll"
stype="mediumBlack">
stype="mediumBlack"
>
<div className="section-wrapper">
<div className="section-wrapper-content" {...focusProps}>
{children}
@ -374,13 +399,15 @@ class SectionBody extends React.Component {
withScroll={withScroll}
isLoaded={isLoaded}
isDesktop={isDesktop}
settingsStudio={settingsStudio}>
settingsStudio={settingsStudio}
>
{withScroll ? (
!isMobileOnly ? (
<Scrollbar
id="sectionScroll"
scrollclass="section-scroll"
stype="mediumBlack">
stype="mediumBlack"
>
<div className="section-wrapper">
<div className="section-wrapper-content" {...focusProps}>
{children}

View File

@ -18,6 +18,30 @@ const StyledSectionHeader = styled.div`
height: 61px;
min-height: 61px;
${({ isTrashFolder, isEmptyPage }) =>
isTrashFolder &&
!isEmptyPage &&
css`
height: 109px;
min-height: 109px;
.header-container {
flex-direction: column;
height: 109px !important;
min-height: 109px !important;
.navigation-container {
height: calc(100% - 32px);
}
.trash-warning {
min-height: 32px;
height: 32px;
margin-bottom: 15px;
}
}
`}
.header-container {
margin-bottom: 1px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
@ -28,29 +52,91 @@ const StyledSectionHeader = styled.div`
css`
height: 61px;
min-height: 61px;
${({ isTrashFolder, isEmptyPage }) =>
isTrashFolder &&
!isEmptyPage &&
css`
height: 109px;
min-height: 109px;
.header-container {
flex-direction: column;
height: 109px !important;
min-height: 109px !important;
.navigation-container {
height: calc(100% - 32px);
}
.trash-warning {
min-height: 32px;
height: 32px;
margin-bottom: 15px;
}
}
`}
`}
@media ${mobile} {
height: 53px;
min-height: 53px;
${({ isTrashFolder, isEmptyPage }) =>
isTrashFolder &&
!isEmptyPage &&
css`
height: 101px;
min-height: 101px;
.header-container {
flex-direction: column;
height: 101px !important;
min-height: 101px !important;
.navigation-container {
height: calc(100% - 32px);
}
.trash-warning {
min-height: 32px;
height: 32px;
margin-bottom: 15px;
}
}
`}
}
${isMobileOnly &&
css`
height: 53px;
min-height: 53px;
`}
${({ isTrashFolder, isEmptyPage }) =>
isTrashFolder &&
!isEmptyPage &&
css`
@media ${tablet} {
height: 109px;
min-height: 109px;
height: 101px;
min-height: 101px;
.header-container {
flex-direction: column;
height: 101px !important;
min-height: 101px !important;
.navigation-container {
height: calc(100% - 32px);
}
.trash-warning {
min-height: 32px;
height: 32px;
margin-bottom: 15px;
}
}
`}
${props =>
`}
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-left: 20px;
@ -74,7 +160,7 @@ const StyledSectionHeader = styled.div`
}
@media ${tablet} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-left: 16px;
@ -88,7 +174,7 @@ const StyledSectionHeader = styled.div`
${isMobile &&
css`
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-left: 0 !important;
@ -101,7 +187,7 @@ const StyledSectionHeader = styled.div`
`}
@media ${mobile} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 0px;
@ -115,7 +201,7 @@ const StyledSectionHeader = styled.div`
css`
width: 100vw !important;
max-width: 100vw !important;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-left: 16px !important;
@ -124,24 +210,23 @@ const StyledSectionHeader = styled.div`
padding-right: 16px !important;
`}
margin-bottom: ${props => (props.settingsStudio ? "8px !important" : "0")};
margin-bottom: ${(props) =>
props.settingsStudio ? "8px !important" : "0"};
`}
`;
StyledSectionHeader.defaultProps = { theme: Base };
const SectionHeader = props => {
const SectionHeader = (props) => {
const {
viewAs,
settingsStudio = false,
className,
isEmptyPage,
isTrashFolder,
...rest
} = props;
const pathname = window.location.pathname.toLowerCase();
const isTrashFolder = pathname.indexOf("trash") !== -1;
return (
<StyledSectionHeader
className={`section-header ${className}`}

View File

@ -0,0 +1,18 @@
import { I18nextProvider } from "react-i18next";
import React, { Suspense } from "react";
import i18n from "../i18n";
const i18nextStoryDecorator = (Story) => {
return (
// here catches the suspense from components not yet ready (still loading translations)
// alternative set useSuspense false on i18next.options.react when initializing i18next
<Suspense fallback={<div>loading translations...</div>}>
<I18nextProvider i18n={i18n}>
<Story />
</I18nextProvider>
</Suspense>
);
};
export default i18nextStoryDecorator;

View File

@ -0,0 +1,31 @@
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";
import i18n from "i18next";
const newInstance = i18n.createInstance();
newInstance
.use(HttpBackend)
.use(initReactI18next)
.init({
load: "currentOnly",
ns: ["Common"],
defaultNS: "Common",
backend: {
backendOptions: [
{
loadPath: "../../client/public/locales/{{lng}}/{{ns}}.json",
},
{
loadPath: "../../../public/locales/{{lng}}/{{ns}}.json",
},
],
},
lng: "en",
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
});
export default newInstance;

View File

@ -136,7 +136,7 @@ class ComboBox extends React.Component {
forceCloseClickOutside,
withoutBackground,
} = this.props;
const { tabIndex, ...props } = this.props;
const { tabIndex, onClickSelectedItem, ...props } = this.props;
const { isOpen, selectedOption } = this.state;
const dropDownMaxHeightProp = dropDownMaxHeight
@ -258,6 +258,7 @@ class ComboBox extends React.Component {
disabled={disabled}
backgroundColor={option.backgroundColor}
onClick={this.optionClick.bind(this, option)}
onClickSelectedItem={() => onClickSelectedItem?.(option)}
fillIcon={fillIcon}
isModern={noBorder}
isActive={isActive}

View File

@ -0,0 +1,7 @@
interface CronProps {
value?: string;
setValue: (value: string) => void;
onError?: (error?: Error) => void;
}
export default CronProps;

View File

@ -0,0 +1,140 @@
import React, { FC, useEffect, useMemo, useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { useTranslation } from "react-i18next";
import i18nextStoryDecorator from "../.storybook/decorators/i18nextStoryDecorator";
import Cron, { getNextSynchronization } from ".";
import TextInput from "../text-input/text-input";
import Button from "../button";
import CronProps from "./Cron.props";
type CronType = FC<{ locale: string } & CronProps>;
type Story = StoryObj<CronType>;
const locales = [
"az",
"ar-SA",
"zh-cn",
"cs",
"nl",
"en",
"fi",
"fr",
"de",
"de-ch",
"el",
"it",
"ja",
"ko",
"lv",
"pl",
"pt",
"pt-br",
"ru",
"sk",
"sl",
"es",
"tr",
"uk",
"vi",
];
const meta: Meta<CronType> = {
title: "Components/Cron",
component: Cron,
argTypes: {
value: {
description: "Cron value",
},
setValue: {
description: "Set the cron value, similar to onChange.",
},
onError: {
description:
"Triggered when the cron component detects an error with the value.",
},
locale: { control: "select", options: locales },
},
decorators: [i18nextStoryDecorator],
};
export default meta;
export const Default: Story = {
args: {
locale: "en",
},
render: ({ value: defaultValue, locale }) => {
const { i18n } = useTranslation();
const [input, setInput] = useState(defaultValue);
const [cron, setCron] = useState(defaultValue);
const [error, setError] = useState<Error>();
useEffect(() => {
i18n.changeLanguage(locale);
}, [locale]);
const onError = (error?: Error) => {
setError(error);
};
const setValue = (cron?: string) => {
setInput(cron);
setCron(cron);
};
const onClick = () => {
setCron(input);
};
useEffect(() => {
setValue(defaultValue);
}, [defaultValue]);
const date = useMemo(() => cron && getNextSynchronization(cron), [cron]);
return (
<div>
<div
style={{
display: "flex",
gap: "6px",
alignItems: "baseline",
maxWidth: "max-content",
marginBottom: "8px",
}}
>
<TextInput
value={input}
onChange={(e) => setInput(e.target.value)}
hasError={!!error}
scale={false}
/>
{/*@ts-ignore*/}
<Button size="small" primary label={"Set value"} onClick={onClick} />
</div>
<Cron value={cron} setValue={setValue} onError={onError} />
<p>
<strong>Cron string: </strong> {cron}
</p>
<p>
<strong>Error message: </strong> {error?.message ?? "undefined"}
</p>
{date && (
<p>
<strong>Next synchronization: </strong>{" "}
{date
.toUTC()
.setLocale(locale ?? "en")
.toFormat("DDDD tt")}
</p>
)}
</div>
);
},
};

View File

@ -0,0 +1,14 @@
import styled from "styled-components";
export const CronWrapper = styled.div`
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
`;
export const Suffix = styled.span`
font-size: 13px;
line-height: 20px;
font-weight: 400;
`;

View File

@ -0,0 +1,139 @@
import { useTranslation } from "react-i18next";
import React, { useState, useEffect, useRef, useMemo } from "react";
import { MonthDays, Months, Period, WeekDays, Hours, Minutes } from "./Field";
import { getCronStringFromValues, stringToArray } from "./part";
import { defaultCronString, defaultPeriod } from "./constants";
import { getPeriodFromCronParts, getUnits } from "./util";
import { CronWrapper, Suffix } from "./Cron.styled";
import type CronProps from "./Cron.props";
import type { PeriodType } from "./types";
function Cron({ value = defaultCronString, setValue, onError }: CronProps) {
const { t } = useTranslation("Common");
const valueRef = useRef<string>(value);
const [period, setPeriod] = useState<PeriodType>(defaultPeriod);
const [hours, setHours] = useState<number[]>([]);
const [months, setMonths] = useState<number[]>([]);
const [minutes, setMinutes] = useState<number[]>([]);
const [weekDays, setWeekDays] = useState<number[]>([]);
const [monthDays, setMonthDays] = useState<number[]>([]);
useEffect(() => {
onError?.(undefined); // reset error state
if (valueRef.current !== value) init();
}, [value]);
useEffect(() => {
try {
const cornString = getCronStringFromValues(
period,
months,
monthDays,
weekDays,
hours,
minutes
);
setValue(cornString);
valueRef.current = cornString;
onError?.(undefined);
} catch (error) {
onError?.(error);
}
}, [period, hours, months, minutes, weekDays, monthDays]);
useEffect(() => {
init();
}, []);
const init = () => {
try {
const cronParts = stringToArray(value);
const period = getPeriodFromCronParts(cronParts);
const [minutes, hours, monthDays, months, weekDays] = cronParts;
setMinutes(minutes);
setHours(hours);
setMonthDays(monthDays);
setMonths(months);
setWeekDays(weekDays);
setPeriod(period);
} catch (error) {
console.log(error);
onError?.(error);
}
};
const { isYear, isMonth, isWeek, isHour, isMinute } = useMemo(() => {
const isYear = period === "Year";
const isMonth = period === "Month";
const isWeek = period === "Week";
const isHour = period === "Hour";
const isMinute = period == "Minute";
return {
isYear,
isMonth,
isWeek,
isHour,
isMinute,
};
}, [period]);
const units = useMemo(() => getUnits(t), [t]);
return (
<CronWrapper>
<Period t={t} period={period} setPeriod={setPeriod} />
{isYear && (
<Months unit={units[3]} t={t} months={months} setMonths={setMonths} />
)}
{(isYear || isMonth) && (
<MonthDays
t={t}
unit={units[2]}
weekDays={weekDays}
monthDays={monthDays}
setMonthDays={setMonthDays}
/>
)}
{(isYear || isMonth || isWeek) && (
<WeekDays
t={t}
unit={units[4]}
isWeek={isWeek}
period={period}
monthDays={monthDays}
weekDays={weekDays}
setWeekDays={setWeekDays}
/>
)}
{!isHour && !isMinute && (
<Hours unit={units[1]} t={t} hours={hours} setHours={setHours} />
)}
{!isMinute && (
<Minutes
t={t}
unit={units[0]}
period={period}
minutes={minutes}
setMinutes={setMinutes}
/>
)}
<Suffix>UTC</Suffix>
</CronWrapper>
);
}
export default Cron;

View File

@ -0,0 +1,9 @@
import type { Dispatch, SetStateAction } from "react";
import type { FieldProps } from "../../types";
interface HoursProps extends FieldProps {
hours: number[];
setHours: Dispatch<SetStateAction<number[]>>;
}
export default HoursProps;

View File

@ -0,0 +1,19 @@
import React from "react";
import Select from "../../Select";
import type HoursProps from "./Hours.props";
function Hours({ hours, setHours, unit, t }: HoursProps) {
return (
<Select
value={hours}
setValue={setHours}
placeholder={t("EveryHour")}
unit={unit}
prefix={t("At")}
dropDownMaxHeight={300}
/>
);
}
export default Hours;

View File

@ -0,0 +1,10 @@
import type { Dispatch, SetStateAction } from "react";
import type { PeriodType, FieldProps } from "../../types";
interface MinutesProps extends FieldProps {
minutes: number[];
setMinutes: Dispatch<SetStateAction<number[]>>;
period: PeriodType;
}
export default MinutesProps;

View File

@ -0,0 +1,22 @@
import React from "react";
import Select from "../../Select";
import type MinutesProps from "./Minutes.props";
function Minutes({ minutes, setMinutes, period, t, unit }: MinutesProps) {
const isHour = period === "Hour";
const prefix = isHour ? t("At") : ":";
return (
<Select
value={minutes}
setValue={setMinutes}
placeholder={t("EveryMinute")}
unit={unit}
prefix={prefix}
dropDownMaxHeight={300}
/>
);
}
export default Minutes;

View File

@ -0,0 +1,10 @@
import type { Dispatch, SetStateAction } from "react";
import type { FieldProps } from "../../types";
interface MonthDaysProps extends FieldProps {
monthDays: number[];
weekDays: number[];
setMonthDays: Dispatch<SetStateAction<number[]>>;
}
export default MonthDaysProps;

View File

@ -0,0 +1,30 @@
import React, { useMemo } from "react";
import Select from "../../Select";
import type MonthDaysProps from "./MonthDays.props";
function MonthDays({
weekDays,
monthDays,
unit,
setMonthDays,
t,
}: MonthDaysProps) {
const placeholder = useMemo(() => {
const isEmpty = weekDays.length === 0;
return isEmpty ? t("EveryDayOfTheMonth") : t("DayOfTheMonth");
}, [weekDays.length]);
return (
<Select
value={monthDays}
setValue={setMonthDays}
placeholder={placeholder}
unit={unit}
prefix={t("On")}
dropDownMaxHeight={300}
/>
);
}
export default MonthDays;

View File

@ -0,0 +1,9 @@
import type { Dispatch, SetStateAction } from "react";
import type { FieldProps } from "../../types";
interface MonthsProps extends FieldProps {
months: number[];
setMonths: Dispatch<SetStateAction<number[]>>;
}
export default MonthsProps;

View File

@ -0,0 +1,20 @@
import React from "react";
import Select from "../../Select";
import type MonthsProps from "./Months.props";
function Months({ months, unit, setMonths, t }: MonthsProps) {
return (
<Select
value={months}
setValue={setMonths}
placeholder={t("EveryMonth")}
unit={unit}
prefix={t("In")}
dropDownMaxHeight={300}
/>
);
}
export default Months;

View File

@ -0,0 +1,42 @@
import type { PeriodOptionType } from "./Period.props";
import type { PeriodType, TFunction } from "../../types";
export const getOptions = (t: TFunction): PeriodOptionType[] => [
{
key: "Year",
label: getLabel("Year", t),
},
{
key: "Month",
label: getLabel("Month", t),
},
{
key: "Week",
label: getLabel("Week", t),
},
{
key: "Day",
label: getLabel("Day", t),
},
{
key: "Hour",
label: getLabel("Hour", t),
},
];
export const getLabel = (period: PeriodType, t: TFunction) => {
switch (period) {
case "Year":
return t("EveryYear");
case "Month":
return t("EveryMonth");
case "Week":
return t("EveryWeek");
case "Day":
return t("EveryDay");
case "Hour":
return t("EveryHour");
default:
return "";
}
};

View File

@ -0,0 +1,15 @@
import type { Dispatch, SetStateAction } from "react";
import type { PeriodType, TFunction } from "../../types";
interface PeriodProps {
t: TFunction;
period?: PeriodType;
setPeriod: Dispatch<SetStateAction<PeriodType>>;
}
export type PeriodOptionType = {
key: PeriodType;
label: string;
};
export default PeriodProps;

View File

@ -0,0 +1,33 @@
import React, { useMemo } from "react";
import ComboBox from "../../../combobox";
import { getLabel, getOptions } from "./Period.helper";
import PeriodProps, { type PeriodOptionType } from "./Period.props";
function Period({ period = "Hour", setPeriod, t }: PeriodProps) {
const onSelect = (arg: PeriodOptionType) => {
setPeriod(arg.key);
};
const options = useMemo(() => getOptions(t), [t]);
const selectedOption = useMemo(
() => ({ key: period, label: getLabel(period, t) }),
[period, t]
);
return (
<ComboBox
scaledOptions
size="content"
scaled={false}
noBorder={false}
options={options}
showDisabledItems
onSelect={onSelect}
selectedOption={selectedOption}
/>
);
}
export default Period;

View File

@ -0,0 +1,12 @@
import type { Dispatch, SetStateAction } from "react";
import type { PeriodType, FieldProps } from "../../types";
interface WeekDaysProps extends FieldProps {
isWeek: boolean;
period: PeriodType;
weekDays: number[];
monthDays: number[];
setWeekDays: Dispatch<SetStateAction<number[]>>;
}
export default WeekDaysProps;

View File

@ -0,0 +1,36 @@
import React, { useMemo } from "react";
import Select from "../../Select";
import type WeekDaysProps from "./WeekDays.props";
function WeekDays({
setWeekDays,
unit,
isWeek,
weekDays,
monthDays,
period,
t,
}: WeekDaysProps) {
const prefix = period === "Week" ? t("On") : t("And");
const placeholder = useMemo(() => {
const isEmpty = monthDays.length === 0;
return isEmpty || isWeek ? t("EveryDayOfTheWeek") : t("DayOfTheWeek");
}, [monthDays.length, isWeek]);
return (
<Select
value={weekDays}
setValue={setWeekDays}
placeholder={placeholder}
unit={unit}
prefix={prefix}
dropDownMaxHeight={300}
/>
);
}
export default WeekDays;

View File

@ -0,0 +1,6 @@
export { default as Period } from "./Period/Period";
export { default as Months } from "./Months/Months";
export { default as MonthDays } from "./MonthDay/MonthDays";
export { default as WeekDays } from "./WeekDays/WeekDays";
export { default as Hours } from "./Hours/Hours";
export { default as Minutes } from "./Minutes/Minutes";

View File

@ -0,0 +1,12 @@
import { Dispatch, SetStateAction } from "react";
import { Unit } from "./../types";
interface SelectProps {
unit: Unit;
value: number[];
placeholder: string;
setValue: Dispatch<SetStateAction<number[]>>;
prefix: string;
dropDownMaxHeight?: number;
}
export default SelectProps;

View File

@ -0,0 +1,11 @@
import styled from "styled-components";
export const SelectWrapper = styled.div`
display: flex;
align-items: center;
gap: 8px;
& > span {
font-size: 13px;
}
`;

View File

@ -0,0 +1,83 @@
import React, { useMemo } from "react";
import ComboBox from "../../combobox";
import { Option } from "../types";
import { fixFormatValue } from "../util";
import { SelectWrapper } from "./Select.styled";
import SelectProps from "./Select.props";
function Select({
unit,
value,
placeholder,
setValue,
prefix,
dropDownMaxHeight,
}: SelectProps) {
const options = useMemo(() => {
const { altWithTranslation } = unit;
if (altWithTranslation) {
return altWithTranslation.map((item, index) => {
const number = unit.min === 0 ? index : index + 1;
return {
key: number,
label: item,
};
});
}
return [...Array(unit.total)].map((_, index) => {
const number = unit.min === 0 ? index : index + 1;
return {
key: number,
label: fixFormatValue(number),
};
});
}, []);
const selectedOption = useMemo(() => {
const isEmpty = value.length === 0;
return {
key: isEmpty ? -1 : value[0],
label: isEmpty
? placeholder
: unit.altWithTranslation
? unit.altWithTranslation[value[0] - unit.min]
: fixFormatValue(value[0]),
};
}, [value, placeholder]);
const onSelect = (option: Option<number, string>) => {
setValue([option.key]);
};
const onReset = (option: Option<number, string>) => {
if (option.key === value[0]) {
setValue([]);
}
};
return (
<SelectWrapper>
<span>{prefix}</span>
<ComboBox
scaledOptions
size="content"
scaled={false}
noBorder={false}
showDisabledItems
options={options}
onSelect={onSelect}
onClickSelectedItem={onReset}
selectedOption={selectedOption}
dropDownMaxHeight={dropDownMaxHeight}
/>
</SelectWrapper>
);
}
export default Select;

View File

@ -0,0 +1 @@
export { default } from "./Select";

View File

@ -0,0 +1,11 @@
import type { Options, PeriodType, Unit } from "./types";
export const defaultPeriod: PeriodType = "Hour";
export const defaultOptions: Options = {
outputHashes: false,
outputMonthNames: false,
outputWeekdayNames: false,
};
export const defaultCronString = "* * * * *";

View File

@ -0,0 +1,2 @@
export { default } from "./Cron";
export { getNextSynchronization } from "./part";

View File

@ -0,0 +1,88 @@
import { DateTime } from "luxon";
import { Options, PeriodType } from "./types";
import { defaultOptions } from "./constants";
import {
arrayToStringPart,
assertValidArray,
findDate,
stringToArrayPart,
getUnits,
} from "./util";
const units = getUnits();
export const stringToArray = (str: string, full = false) => {
if (typeof str !== "string") {
throw new Error("Invalid cron string");
}
const parts = str.replace(/\s+/g, " ").trim().split(" ");
if (parts.length !== 5) {
throw new Error("Invalid cron string format");
} else {
return parts.map((str, idx) => stringToArrayPart(str, units[idx], full));
}
};
export function arrayToString(arr: number[][], options?: Partial<Options>) {
assertValidArray(arr);
const parts = arr.map((part, idx) =>
arrayToStringPart(part, units[idx], { ...defaultOptions, ...options })
);
return parts.join(" ");
}
export function getCronStringFromValues(
period: PeriodType,
months: number[] | undefined,
monthDays: number[] | undefined,
weekDays: number[] | undefined,
hours: number[] | undefined,
minutes: number[] | undefined
) {
const newMonths = period === "Year" && months ? months : [];
const newMonthDays =
(period === "Year" || period === "Month") && monthDays ? monthDays : [];
const newWeekDays =
(period === "Year" || period === "Month" || period === "Week") && weekDays
? weekDays
: [];
const newHours =
period !== "Minute" && period !== "Hour" && hours ? hours : [];
const newMinutes = period !== "Minute" && minutes ? minutes : [];
const parsedArray = arrayToString([
newMinutes,
newHours,
newMonthDays,
newMonths,
newWeekDays,
]);
return parsedArray;
}
export const getNextSynchronization = (
cronString: string,
timezone?: string
) => {
try {
const cron = stringToArray(cronString, true);
assertValidArray(cron);
let date = DateTime.now();
if (timezone) date = date.setZone(timezone);
if (!date.isValid) {
throw new Error("Invalid timezone provided");
}
if (date.second > 0) {
// plus a minute to the date to prevent returning dates in the past
date = date.plus({ minute: 1 });
}
return findDate(cron, date);
} catch (error) {
console.log(error);
}
};

View File

@ -0,0 +1,30 @@
import type { TFunction as TranslationFunction } from "react-i18next";
export type PeriodType = "Year" | "Month" | "Week" | "Day" | "Hour" | "Minute";
export type Unit = {
name: "minute" | "hour" | "day" | "month" | "weekday";
min: number;
max: number;
alt?: ReadonlyArray<string>;
fullLabel?: ReadonlyArray<string>;
total: number;
altWithTranslation?: ReadonlyArray<string>;
};
export type Options = {
outputHashes: boolean;
outputWeekdayNames: boolean;
outputMonthNames: boolean;
};
export type Option<K = unknown, L = unknown> = {
key: K;
label: L;
};
export type TFunction = TranslationFunction<"translation", undefined>;
export interface FieldProps {
t: TFunction;
unit: Unit;
}

View File

@ -0,0 +1,497 @@
import { DateTime } from "luxon";
import { Options, PeriodType, TFunction, Unit } from "./types";
export const parseNumber = (value: unknown) => {
if (typeof value === "string") {
const str: string = value.trim();
if (/^\d+$/.test(str)) {
const num = Number(str);
if (!isNaN(num) && isFinite(num)) {
return num;
}
}
} else if (typeof value === "number") {
if (!isNaN(value) && isFinite(value) && value === Math.floor(value)) {
return value;
}
}
return undefined;
};
export const assertValidArray = (arr: unknown): void | never => {
if (
arr === undefined ||
!Array.isArray(arr) ||
arr.length !== 5 ||
arr.some((element) => !Array.isArray(element))
) {
throw new Error("Invalid cron array");
}
};
export const range = (start: number, end: number): number[] => {
const array: number[] = [];
for (let i = start; i <= end; i++) {
array.push(i);
}
return array;
};
export const sort = (array: number[]) => [...array].sort((a, b) => a - b);
export const flatten = (arrays: number[][]) =>
([] as number[]).concat.apply([], arrays);
export const dedup = (array: number[]) => {
const result: number[] = [];
array.forEach((i) => {
if (result.indexOf(i) < 0) {
result.push(i);
}
});
return result;
};
export const getPeriodFromCronParts = (cronParts: number[][]): PeriodType => {
if (cronParts[3].length > 0) {
return "Year";
} else if (cronParts[2].length > 0) {
return "Month";
} else if (cronParts[4].length > 0) {
return "Week";
} else if (cronParts[1].length > 0) {
return "Day";
} else if (cronParts[0].length > 0) {
return "Hour";
}
// return "minute";
return "Hour";
};
export const fixFormatValue = (value: number) => {
let result = value.toString();
if (value < 10) {
result = result.padStart(2, "0");
}
return result;
};
export const arrayToStringPart = (
arr: number[],
unit: Unit,
options: Options
) => {
const values = sort(
dedup(
fixSunday(
arr.map((value) => {
const parsedValue = parseNumber(value);
if (parsedValue === undefined) {
throw getError(`Invalid value "${value}"`, unit);
}
return parsedValue;
}),
unit
)
)
);
assertInRange(values, unit);
return toString(values, unit, options);
};
export const stringToArrayPart = (str: string, unit: Unit, full = false) => {
if ((str === "*" || str === "*/1") && !full) {
return [];
}
const values = sort(
dedup(
fixSunday(
flatten(
replaceAlternatives(str, unit)
.split(",")
.map((value: string) => {
const valueParts = value.split("/");
if (valueParts.length > 2) {
throw getError(`Invalid value "${str}"`, unit);
}
let parsedValues: number[];
const left = valueParts[0];
const right = valueParts[1];
if (left === "*") {
parsedValues = range(unit.min, unit.max);
} else {
parsedValues = parseRange(left, str, unit);
}
const step = parseStep(right, unit);
return applyInterval(parsedValues, step);
})
),
unit
)
)
);
assertInRange(values, unit);
return values;
};
const toRanges = (values: number[]) => {
const retval: number[][] = [];
let startPart: number | undefined = undefined;
values.forEach(function (value, index, self) {
if (value !== self[index + 1] - 1) {
if (startPart !== undefined) {
retval.push([startPart, value]);
startPart = undefined;
} else {
retval.push([value]);
}
} else if (startPart === undefined) {
startPart = value;
}
});
return retval;
};
const toString = (values: number[], unit: Unit, options: Options) => {
let retval = "";
if (isFull(values, unit) || values.length === 0) {
if (options.outputHashes) {
retval = "H";
} else {
retval = "*";
}
} else {
const step = getStep(values);
if (step && isInterval(values, step)) {
if (isFullInterval(values, unit, step)) {
if (options.outputHashes) {
retval = `H/${step}`;
} else {
retval = `*/${step}`;
}
} else {
const min = values[0];
const max = values[values.length - 1];
const range =
formatValue(min, unit, options) +
"-" +
formatValue(max, unit, options);
if (options.outputHashes) {
retval = `H(${range})/${step}`;
} else {
retval = `${range}/${step}`;
}
}
} else {
retval = toRanges(values)
.map((range) => {
if (range.length === 1) {
return formatValue(range[0], unit, options);
} else {
return (
formatValue(range[0], unit, options) +
"-" +
formatValue(range[1], unit, options)
);
}
})
.join(",");
}
}
return retval;
};
const formatValue = (value: number, unit: Unit, options: Options) => {
if (
(options.outputWeekdayNames && unit.name === "weekday") ||
(options.outputMonthNames && unit.name === "month")
) {
if (unit.alt) {
return unit.alt[value - unit.min];
}
}
return value;
};
const getError = (error: string, unit: Unit) =>
new Error(`${error} for ${unit.name}`);
const parseRange = (rangeString: string, context: string, unit: Unit) => {
const subparts = rangeString.split("-");
if (subparts.length === 1) {
const value = parseNumber(subparts[0]);
if (value === undefined) {
throw getError(`Invalid value "${context}"`, unit);
}
return [value];
} else if (subparts.length === 2) {
const minValue = parseNumber(subparts[0]);
const maxValue = parseNumber(subparts[1]);
if (minValue === undefined || maxValue === undefined) {
throw getError(`Invalid value "${context}"`, unit);
}
if (maxValue < minValue) {
throw getError(
`Max range is less than min range in "${rangeString}"`,
unit
);
}
return range(minValue, maxValue);
} else {
throw getError(`Invalid value "${rangeString}"`, unit);
}
};
const parseStep = (step: string, unit: Unit) => {
if (step !== undefined) {
const parsedStep = parseNumber(step);
if (parsedStep === undefined) {
throw getError(`Invalid interval step value "${step}"`, unit);
}
return parsedStep;
}
return 0;
};
const applyInterval = (values: number[], step: number) => {
if (step) {
const minVal = values[0];
values = values.filter(
(value) => value % step === minVal % step || value === minVal
);
}
return values;
};
const fixSunday = (values: number[], unit: Unit) => {
if (unit.name === "weekday") {
values = values.map((value) => {
if (value === 7) {
return 0;
}
return value;
});
}
return values;
};
const replaceAlternatives = (str: string, unit: Unit) => {
if (unit.alt) {
str = str.toUpperCase();
for (let i = 0; i < unit.alt.length; i++) {
str = str.replace(unit.alt[i], String(i + unit.min));
}
}
return str;
};
const assertInRange = (values: number[], unit: Unit) => {
const first = values[0];
const last = values[values.length - 1];
if (first < unit.min) {
throw getError(`Value "${first}" out of range`, unit);
} else if (last > unit.max) {
throw getError(`Value "${last}" out of range`, unit);
}
};
const isInterval = (values: number[], step: number) => {
for (let i = 1; i < values.length; i++) {
const prev = values[i - 1];
const value = values[i];
if (value - prev !== step) {
return false;
}
}
return true;
};
const isFullInterval = (values: number[], unit: Unit, step: number) => {
const min = values[0];
const max = values[values.length - 1];
const haveAllValues = values.length === (max - min) / step + 1;
if (min === unit.min && max + step > unit.max && haveAllValues) {
return true;
}
return false;
};
const getStep = (values: number[]) => {
if (values.length > 2) {
const step = values[1] - values[0];
if (step > 1) {
return step;
}
}
return 0;
};
const isFull = (values: number[], unit: Unit) => {
return values.length === unit.max - unit.min + 1;
};
const shiftMonth = (arr: number[][], date: DateTime) => {
while (arr[3].indexOf(date.month) === -1) {
date = date.plus({ months: 1 }).startOf("month");
}
return date;
};
const shiftDay = (arr: number[][], date: DateTime): [DateTime, boolean] => {
const currentMonth = date.month;
while (
arr[2].indexOf(date.day) === -1 ||
// luxon uses 1-7 for weekdays, but we use 0-6
arr[4].indexOf(date.weekday === 7 ? 0 : date.weekday) === -1
) {
date = date.plus({ days: 1 }).startOf("day");
if (currentMonth !== date.month) {
return [date, true];
}
}
return [date, false];
};
const shiftHour = (arr: number[][], date: DateTime): [DateTime, boolean] => {
const currentDay = date.day;
while (arr[1].indexOf(date.hour) === -1) {
date = date.plus({ hours: 1 }).startOf("hour");
if (currentDay !== date.day) {
return [date, true];
}
}
return [date, false];
};
const shiftMinute = (arr: number[][], date: DateTime): [DateTime, boolean] => {
const currentHour = date.hour;
while (arr[0].indexOf(date.minute) === -1) {
date = date.plus({ minutes: 1 }).startOf("minute");
if (currentHour !== date.hour) {
return [date, true];
}
}
return [date, false];
};
export const findDate = (arr: number[][], date: DateTime) => {
let retry = 24;
let monthChanged: boolean;
let dayChanged: boolean;
let hourChanged: boolean;
while (--retry) {
date = shiftMonth(arr, date);
[date, monthChanged] = shiftDay(arr, date);
if (!monthChanged) {
[date, dayChanged] = shiftHour(arr, date);
if (!dayChanged) {
[date, hourChanged] = shiftMinute(arr, date);
if (!hourChanged) {
break;
}
}
}
}
if (!retry) {
throw new Error("Unable to find execution time for schedule");
}
return date.set({ second: 0, millisecond: 0 });
};
export const getUnits = (t?: TFunction) => {
const units: ReadonlyArray<Unit> = Object.freeze([
{
name: "minute",
min: 0,
max: 59,
total: 60,
},
{
name: "hour",
min: 0,
max: 23,
total: 24,
},
{
name: "day",
min: 1,
max: 31,
total: 31,
},
{
name: "month",
min: 1,
max: 12,
total: 12,
alt: [
"JAN",
"FEB",
"MAR",
"APR",
"MAY",
"JUN",
"JUL",
"AUG",
"SEP",
"OCT",
"NOV",
"DEC",
],
altWithTranslation: t
? [
t("JAN"),
t("FEB"),
t("MAR"),
t("APR"),
t("MAY"),
t("JUN"),
t("JUL"),
t("AUG"),
t("SEP"),
t("OCT"),
t("NOV"),
t("DEC"),
]
: undefined,
fullLabel: [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
],
},
{
name: "weekday",
min: 0,
max: 6,
total: 7,
alt: ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"],
altWithTranslation: t
? [
t("Common:SUN"),
t("Common:MON"),
t("Common:TUE"),
t("Common:WED"),
t("Common:THU"),
t("Common:FRI"),
t("Common:SAT"),
]
: undefined,
},
]);
return units;
};

View File

@ -31,10 +31,11 @@ const DropDownItem = (props) => {
isActiveDescendant,
} = props;
const { withToggle, checked, onClick, ...rest } = props;
const { withToggle, checked, onClick, onClickSelectedItem, ...rest } = props;
const onClickAction = (e) => {
onClick && !disabled && onClick(e);
onClickSelectedItem && isSelected && onClickSelectedItem();
};
const stopPropagation = (event) => {

View File

@ -25,6 +25,7 @@
"framer-motion": "^4.1.17",
"html-to-react": "^1.5.0",
"lodash": "4.17.21",
"luxon": "^3.4.0",
"moment": "^2.29.4",
"prop-types": "^15.8.1",
"punycode": "^2.3.0",
@ -77,6 +78,7 @@
"@svgr/webpack": "^5.5.0",
"@testing-library/react": "^9.5.0",
"@types/jest": "^24.9.1",
"@types/luxon": "^3.3.1",
"@wojtekmaj/enzyme-adapter-react-17": "^0.4.1",
"babel-eslint": "^10.1.0",
"babel-jest": "^24.9.0",
@ -94,6 +96,7 @@
"eslint-plugin-jest": "^26.9.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-storybook": "^0.6.12",
"i18next-http-backend": "^2.2.2",
"jest": "^24.9.0",
"jest-enzyme": "^7.1.2",
"jest-junit": "^10.0.0",

View File

@ -1,4 +1,4 @@
import styled from "styled-components";
import styled, { css } from "styled-components";
import commonInputStyles from "./common-input-styles";
import Input from "./input";
import Base from "../themes/base";
@ -24,6 +24,9 @@ const StyledTextInput = styled(Input).attrs((props) => ({
${commonInputStyles}
-webkit-appearance: ${(props) => props.theme.textInput.appearance};
${(props) =>
!props.isDisabled &&
css`
background-color: ${(props) => props.theme.input.backgroundColor};
-webkit-text-fill-color: ${(props) =>
props?.value.length > 0
@ -33,6 +36,7 @@ const StyledTextInput = styled(Input).attrs((props) => ({
-webkit-background-clip: text !important;
box-shadow: inset 0 0 20px 20px
${(props) => props.theme.input.backgroundColor} !important;
`}
display: ${(props) => props.theme.textInput.display};
font-family: ${(props) => props.theme.fontFamily};

View File

@ -3140,6 +3140,11 @@ const Base = {
headerColor: "#333",
descriptionColor: "#555F65",
},
roomIcon: {
backgroundArchive: "#A3A9AE",
opacityBackground: "1",
},
};
export default Base;

View File

@ -3143,6 +3143,11 @@ const Dark = {
headerColor: "#FFF",
descriptionColor: "#ADADAD",
},
roomIcon: {
backgroundArchive: "#FFFFFF",
opacityBackground: "0.1",
},
};
export default Dark;

View File

@ -108,6 +108,10 @@ declare global {
message: string | undefined;
}
interface ISSOSettings {
hideAuthPage: boolean;
}
interface IInitialState {
portalSettings?: IPortalSettings;
buildInfo?: IBuildInfo;
@ -116,6 +120,7 @@ declare global {
match?: MatchType;
currentColorScheme?: ITheme;
isAuth?: boolean;
ssoSettings?: ISSOSettings;
logoUrls: ILogoUrl[];
error?: IError;
}

View File

@ -59,6 +59,13 @@ app.get("*", async (req: ILoginRequest, res: Response, next) => {
try {
initialState = await getInitialState(query);
const hideAuthPage = initialState?.ssoSettings?.hideAuthPage;
const ssoUrl = initialState?.capabilities?.ssoUrl;
if (hideAuthPage && ssoUrl && query.skipssoredirect !== "true") {
res.redirect(ssoUrl);
return next();
}
if (initialState.isAuth && url !== "/login/error") {
res.redirect("/");

View File

@ -8,6 +8,7 @@ import {
getCapabilities,
getAppearanceTheme,
getLogoUrls,
getCurrentSsoSettings
} from "@docspace/common/api/settings";
import { checkIsAuthenticated } from "@docspace/common/api/user";
import { TenantStatus } from "@docspace/common/constants";
@ -51,8 +52,9 @@ export const getInitialState = async (
providers: ProvidersType,
capabilities: ICapabilities,
availableThemes: IThemes,
isAuth: any,
logoUrls: any;
isAuth: boolean,
logoUrls: ILogoUrl[],
ssoSettings: ISSOSettings;
const baseSettings = [
getSettings(),
@ -65,6 +67,7 @@ export const getInitialState = async (
getAuthProviders(),
getCapabilities(),
checkIsAuthenticated(),
getCurrentSsoSettings(),
];
[portalSettings, buildInfo, availableThemes, logoUrls] = await Promise.all(
@ -72,7 +75,7 @@ export const getInitialState = async (
);
if (portalSettings.tenantStatus !== TenantStatus.PortalRestore)
[providers, capabilities, isAuth] = await Promise.all(settings);
[providers, capabilities, isAuth, ssoSettings] = await Promise.all(settings);
const currentColorScheme = availableThemes.themes.find((theme) => {
return availableThemes.selected === theme.id;
@ -87,6 +90,7 @@ export const getInitialState = async (
currentColorScheme,
isAuth,
logoUrls,
ssoSettings
};
return initialState;

View File

@ -401,6 +401,7 @@ internal class FolderDao : AbstractDao, IFolderDao<int>
toUpdate.ModifiedOn = _tenantUtil.DateTimeToUtc(folder.ModifiedOn);
toUpdate.ModifiedBy = folder.ModifiedBy;
toUpdate.HasLogo = folder.HasLogo;
toUpdate.Color = folder.Color;
await filesDbContext.SaveChangesAsync();
@ -423,6 +424,7 @@ internal class FolderDao : AbstractDao, IFolderDao<int>
ModifiedBy = folder.ModifiedBy,
FolderType = folder.FolderType,
Private = folder.Private,
Color = folder.Color,
TenantId = TenantID
};

View File

@ -45,6 +45,7 @@ public class DbFolder : IDbFile, IDbSearch, ISearchItem
public int FilesCount { get; set; }
public bool Private { get; set; }
public bool HasLogo { get; set; }
public string Color { get; set; }
public DbTenant Tenant { get; set; }
@ -145,6 +146,12 @@ public static class DbFolderExtension
.HasDefaultValueSql("'0'");
entity.Property(e => e.HasLogo).HasColumnName("has_logo");
entity.Property(e => e.Color)
.HasColumnName("color")
.HasColumnType("char(6)")
.HasCharSet("utf8")
.UseCollation("utf8_general_ci");
});
}
public static void PgSqlAddDbFolder(this ModelBuilder modelBuilder)

View File

@ -75,6 +75,7 @@ public class Folder<T> : FileEntry<T>, IFolder
public bool Pinned { get; set; }
public bool Private { get; set; }
public bool HasLogo { get; set; }
public string Color { get; set; }
public override bool IsNew
{
get => Convert.ToBoolean(NewForMe);

View File

@ -29,7 +29,6 @@ namespace ASC.Web.Files.Services.WCFService;
[Scope]
public class FileStorageService //: IFileStorageService
{
private static readonly FileEntrySerializer _serializer = new FileEntrySerializer();
private readonly CompressToArchive _compressToArchive;
private readonly OFormRequestManager _oFormRequestManager;
private readonly ThirdPartySelector _thirdPartySelector;
@ -46,7 +45,6 @@ public class FileStorageService //: IFileStorageService
private readonly FilesLinkUtility _filesLinkUtility;
private readonly BaseCommonLinkUtility _baseCommonLinkUtility;
private readonly CoreBaseSettings _coreBaseSettings;
private readonly CustomNamingPeople _customNamingPeople;
private readonly DisplayUserSettingsHelper _displayUserSettingsHelper;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly PathProvider _pathProvider;
@ -88,6 +86,7 @@ public class FileStorageService //: IFileStorageService
private readonly QuotaSocketManager _quotaSocketManager;
private readonly ExternalShare _externalShare;
private readonly TenantUtil _tenantUtil;
private readonly RoomLogoManager _roomLogoManager;
public FileStorageService(
Global global,
@ -100,7 +99,6 @@ public class FileStorageService //: IFileStorageService
FilesLinkUtility filesLinkUtility,
BaseCommonLinkUtility baseCommonLinkUtility,
CoreBaseSettings coreBaseSettings,
CustomNamingPeople customNamingPeople,
DisplayUserSettingsHelper displayUserSettingsHelper,
IHttpContextAccessor httpContextAccessor,
ILoggerProvider optionMonitor,
@ -147,7 +145,8 @@ public class FileStorageService //: IFileStorageService
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
QuotaSocketManager quotaSocketManager,
ExternalShare externalShare,
TenantUtil tenantUtil)
TenantUtil tenantUtil,
RoomLogoManager roomLogoManager)
{
_global = global;
_globalStore = globalStore;
@ -159,7 +158,6 @@ public class FileStorageService //: IFileStorageService
_filesLinkUtility = filesLinkUtility;
_baseCommonLinkUtility = baseCommonLinkUtility;
_coreBaseSettings = coreBaseSettings;
_customNamingPeople = customNamingPeople;
_displayUserSettingsHelper = displayUserSettingsHelper;
_httpContextAccessor = httpContextAccessor;
_pathProvider = pathProvider;
@ -207,6 +205,7 @@ public class FileStorageService //: IFileStorageService
_quotaSocketManager = quotaSocketManager;
_externalShare = externalShare;
_tenantUtil = tenantUtil;
_roomLogoManager = roomLogoManager;
}
public async Task<Folder<T>> GetFolderAsync<T>(T folderId)
@ -614,7 +613,7 @@ public class FileStorageService //: IFileStorageService
newFolder.ParentId = parent.Id;
newFolder.FolderType = folderType;
newFolder.Private = parent.Private ? parent.Private : privacy;
newFolder.Color = _roomLogoManager.GetRandomColour();
var folderId = await folderDao.SaveFolderAsync(newFolder);
var folder = await folderDao.GetFolderAsync(folderId);
@ -634,6 +633,8 @@ public class FileStorageService //: IFileStorageService
{
throw GenerateException(e);
}
}
public async Task<Folder<T>> FolderRenameAsync<T>(T folderId, string title)

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