добавляю все изменения проекта на текущий момент

This commit is contained in:
vasya
2026-02-27 18:49:27 +03:00
parent e927910fda
commit 9c35f4e35e
303 changed files with 79434 additions and 2558 deletions
+34
View File
@@ -0,0 +1,34 @@
import React from "react";
import { useEffect, useState } from "react";
import { getCsrfToken } from './../services/getCsrfService';
function MagicLogin ()
{
//Состояние с введенным логином
const [userLogin, setUserLogin] = useState<string>('');
return (
<div id="login-container">
<div id="login-container__magic-name">Magic</div>
<div id="login-container__form">
<form action="login" method="POST" autoComplete="on">
<input
type="hidden"
name="_token"
value={getCsrfToken()}
/>
<input name="_auth_login" type="text" onChange={ (e) => setUserLogin(e.target.value) } placeholder="Логин" required/>
<input name="_auth_password" type="password" placeholder="Пароль" required/>
<button type="submit" onClick={ () => {
//При сабмите кладем логин в локалсторадж для того, чтобы при вызове api ендпоинтов передавать его в заголовках
//гаврилов
//ТОЧНО ЛИ ЭТО НУЖНО? МОЖЕТ, ЛУЧШЕ ВСЕ API ЕНДПОИНТЫ ПЕРЕПИСАТ ПОД ВЫЗОВ WEB РОУТОВ?
localStorage.setItem('magic_auth_login', userLogin);
} }>Вход</button>
</form>
</div>
</div>
);
}
export default MagicLogin;
+108
View File
@@ -0,0 +1,108 @@
import React, { useEffect, useState } from "react";
import './../../css/components/magicPopup.css';
import { Button } from '@SharePoint/rencredit_uikit';
/**
* Компонент всплывающего окна
*/
/**
* Варианты типов для всплывающих окон: info - стандартный вид, error - произошла ошибка, attention - обратить внимание, success - успешное событие
*/
type PopupType = 'info' | 'success' | 'error' | 'attention';
//Интерфейс для передаваемых пропсов объекта всплывающего окна
interface MagicPopupProps {
key: number,
id: number, //Уникальный идентификатор, по котором всплывающее окно будет в том числе размонтироваться
message: string, //Текст всплывающего окна
renderIndex: number, //Индекс рендеринга компонента. Так как может отрисовываться сразу несколько popup, чтобы корректно их отрисовывать с анимацией с задержкой по времени, нужно передавать индекс компонента в наборе. Передавать "вручную" не нужно, он сам рассчитывается в компоненте MagicPopupContainer
type: PopupType, //Тип всплывающего окна, определяющий его внешний вид
timeOut?: boolean, //Если передается true, для элемента будет запущен стандартный таймер исчезновения. ВАЖНО! для всплывающих окон типа error таймер не будет активироваться, даже если передается. Пользователь должен гарантированно ознакомиться с сообщением и сам его закрыть
onClose?: (id: number) => void //Колбэк для полного размонтирования компонента со страницы
}
/**
* Компонент всплывающих окон
* @param props
* @returns
*/
export default function MagicPopup (props: MagicPopupProps)
{
//Объект с классами для иконок font-awesome в зависимости от переданного типа всплывающего окна. При изменении, необходимо поменять названия классов в magicPopup.css!
const iconNameObj = {
info: 'fa-circle-info',
success: 'fa-circle-check',
error: 'fa-circle-xmark',
attention: 'fa-circle-exclamation'
}
//С помощью деструктуризации указываем значение типа окна по умолчанию
const {message, timeOut = true, onClose} = props;
//Задержка исчезновения popup
const popupDelay = 300;
//Итоговая длительность таймера перед закрытием конкретного popup, учитывая его положение в наборе передаваемых попапов
const hideTimeOut: number = 3000 + popupDelay;
//Стейт для размонтирования попапа
const [popupVisible, setPopupVisible] = useState<boolean>(true)
//Стейт для плавного сокрытия popup. Класс плавного сокрытия присваивается за пол секунды до полного исчезновения через popupVisible, чтобы сначала плавно сокрыть блок из интерфейса, а затем полностью удалить его из DOM
const [popupHideClass, setPopupHideClass] = useState<string>('popup-visible')
//Стейт плавного появления popup. Каждый следующий popup в передаваемом наборе должен появляться с небольшой задержкой
const [popupShowClass, setPopupFadeinClass] = useState<string>('');
//Стейт запуска анимированного таймера исчезновения popup
const [popupTimer, setPopupTimer] = useState<string>('');
//Функция сокрытия popup. Сначала присваиваем класс, который анимированно скрывает popup, а через небольшую задержку размонтируем компонент
function hidePopupBlock (): void
{
setPopupHideClass('hidePopup');
setTimeout ( () => {
setPopupVisible(false);
}, 300)
//Вызываем метод удаления экземпляра компонента из набора. Так как попапы отрисовываются на основе компонента PopupContainer с набором компонентов MagicPopup, то после их размонтирования, их необходимо и удалить из набора в PopupContainer. Без их удаления, при вызове новых попапов, весь набор с уже просмотренными попапами будет снова отрисовываться.
onClose && onClose(props.id)
};
//При рендеринге сразу же запускается таймер сокрытия popup, если аргумент timeOut = true и тип окна != error
useEffect( () => {
if (timeOut && props.type !== 'error') {
setTimeout( () => {
//Задержка для отображения таймера нужна, так как при вызове рендеринга нескольких окон, каждое последующее появляется с задержкой. Без задержки ниже, таймер будет запускаться для всех popup одновременно и когда появится последний popup, его таймер уже может закончиться
setPopupTimer('timerProgress')
}, popupDelay)
//Через небольшую задержку после сокрытия компонента срабатывает таймер на размонтирование компонента и удаления его из набора
setTimeout( () => {
hidePopupBlock()
}, hideTimeOut)
}
//Появление popup при рендеринге
setTimeout ( () => {
setPopupFadeinClass('show')
}, popupDelay)
}, [])
return (
popupVisible ?
// <div onClick = {hidePopupBlock}>Раз</div>
<div className = {`magic-popup-container ${popupHideClass} ${popupShowClass}`} >
<div className = 'popup__icon-block'>
<i className = {`fa-solid ${iconNameObj[props.type]}`}></i>
</div>
<div className = "popup__content-block">
<div className = 'popup__content__text-block'>
{message}
</div>
<div className = "popup__button-block">
<Button
type = "button"
className = "popup__button-block__button"
onClick = {hidePopupBlock}
text = "OK"
/>
</div>
<div className = "popup__timer-block">
<div className = {`popup__timer-block__timer ${popupTimer}`}></div>
</div>
</div>
</div>
: null
);
};
@@ -0,0 +1,45 @@
import React, { useContext, ComponentProps } from "react";
import MagicPopup from "./MagicPopup";
import './../../css/components/magicPopupContainer.css';
import { PopupContext } from "../contexts/PopupContext";
/**
* Компонент контейнера для всплывающих окон, который вызывается на странице и куда передается массив всплывающих окон
*/
//ГАВРИЛОВ. ЛОГИЧНЕЕ ЭКСПОРТИРОВАТЬ ТИП С ПРОПСАМИ КОМПОНЕНТА ИЗ САМОГО КОМПОНЕНТА MAGICPOPUP?
/**
* Дополнительный экспорт типа MagicPopup из компонента всплывающего окна для возможности его импорта, в свою очередь, из компонента, собирающего страницу, например TaxiPage TaxiPage
*/
export type MagicPopupType = ComponentProps<typeof MagicPopup>;
/**
* Метод рендеринга контейнара для всплывающих окон
* @param {array} magicPopupArr массив объектов типа MagicPopup с информацией о всплывающем окне: текст, таймер и т.д.
* @param {callable} delHiddenPopupFunc колбэк для закрытия конкретного всплывающего окна по его уникальному идентификатору
* @returns ReactNode
*/
export default function MagicPopupContainer ()
{
const popupArr = useContext(PopupContext);
return (
//Родительский контейнер для всех всплывающих окон
<div id = "popup-parent-container">
{/* { magicPopupArr.map( (propsObj: MagicPopupType, index: number) => ( */}
{ popupArr.popupArrTest.map( (propsObj: MagicPopupType, index: number) => (
<MagicPopup
//Подробнее описание пропсов компонента смотри в MagicPopup.tsx
id = {propsObj.id}
key = {propsObj.id}
message = {propsObj.message}
timeOut = {propsObj.timeOut}
type = {propsObj.type}
renderIndex = {index}
//onClose = {delHiddenPopupFunc}
onClose = {popupArr.delPopupTest}
/>
))
}
</div>
)
}
+231
View File
@@ -0,0 +1,231 @@
import { Button } from '@SharePoint/rencredit_uikit';
import React, { useEffect, useState } from "react";
function MenuApp ()
{
//Приложения и процессы Magic для отображения в меню
const [menuApps, setMenuApps] = useState<{
'proccesses': object,
'scripts': object
}>({
'proccesses': {},
'scripts': {}
});
//TODO
//ВНЕДРИ В КОНТРОЛЛЕРЕ ПРОВЕРКУ ДОСТУПОВ, А НЕ ВОЗВРАЩАЙ ВСЕ ПОДРЯД ПРИЛОЖЕНИЯ
//Избранные приложения пользователя
const [favApps, setFavApps] = useState<string[]>([]);
//Массив избраннных процессов, в приложениях которых есть хотя бы одно избранное
const [favProcs, setFavProcs] = useState<string[]>([]);
//Состояния видимости скрипта
const [hideScriptClass, setHideScriptClass] = useState('');
//const [sanctumToken, setSanctumToken] = useState('');
const sanctumToken = () => {
const metaTag = document.getElementById('sanctum_token_block') as HTMLMetaElement;
return metaTag.dataset.token;
}
//Получаем при рендере страницы все приложения Magic и все избранные приложения пользователя
useEffect( () => {
Promise.all(
[
fetch('api/magic_apps').then(menuAppsRes => menuAppsRes.json()),
//TODO
//СЮДА ПОТЯГИВАЙ ЛОГИН ПОЛЬЗОВАТЕЛЯ, А НЕ DGAVRILOV
fetch('api/user_fav_app/dgavrilov').then(favAppsRes => favAppsRes.json())
]
).then(
([
responseMenuApp,
responseFavApp
]) => {
setMenuApps(responseMenuApp);
setFavApps(responseFavApp);
}
)
}, []);
//Обновление видимости процесса (собрания приложений) в зависимости от того находится ли в избранном хотя бы одно приложение
useEffect( () => {
if (menuApps === null) { return }
//Массив с процессами, в которых есть хотя бы одно избранное приложение
const favProcsArr:string[] = [];
//Мы собираем все избранные приложения в массив-состояние, чтобы при изменении состояния каждого приложения (например при переключении switcher) проверять входит ли это приложение в избранные. Если входит - его родительский процесс не прячем, так как в его дочерних приложениях есть избранные
Object.entries(menuApps.proccesses).forEach( (procData) => {
//Флаг - есть ли в приложениях процесса хотя бы 1 избранное
const hasFavorite: boolean = procData[1].tabs.split(';').some( (tab: string) => {
return favApps.includes(tab);
})
if (hideScriptClass !== 'script-hide' || hasFavorite) {
favProcsArr.push(procData[0]);
}
})
setFavProcs(favProcsArr);
}, [hideScriptClass, menuApps, favApps]);
//console.log(sanctumToken())
//ГАВРИЛОВ
//ПОЧЕМУ КОД НИЖЕ ИСПОЛНЯЕТСЯ ПОСТОЯННО ПОКА НЕ БУДЕТ ПОЛУЧЕН КОНТЕНТ ДЛЯ СТРАНИЦЫ ИЗ ПРОМИСОВ ВЫШЕ, А НЕ ОТРАБАТЫВАЕТ ТОЛЬКО ПРИ РЕНДЕРИНГЕ СТРАНИЦЫ
//Уместно ли отслеживать состояние до получения запросов fetch в подобном виде?
if (menuApps === null || favApps === null) {
return <span>прелоадер</span>
} else {
return (
<div id = 'menu-container'>
<div id = 'menu__left-block'>
<div className = 'menu__left-block__call-app'>
<i className = 'fa fa-th-large'></i>
<div className = 'mleft-block__call-app__title'>Приложения</div>
</div>
</div>
<div id = 'menu__app-container'>
<Button text='Текст' />
<div id = 'menu__app-container__app-block'>
{/* {console.log(menuApps.scripts)} */}
{Object.entries(menuApps.proccesses).map( (proc_val, proc_index) => (
<div className = "apps-block__proc" key = { proc_index } data-proc = {proc_val[0]}>
{/* В зависимости от того входит процесс в массив избранных делаем его видимым или скрываем */}
<div className = {`proc__title ${favProcs.includes(proc_val[0]) ? 'proc-visible' : 'proc-hide'}`}>{proc_val[1].title}</div>
<div className = "proc__script-list">
{proc_val[1].tabs.split(';').map( (app_el, app_index) => (
<AppElem
appIndex = { app_index }
appName = { app_el }
appTitle = { menuApps.scripts[app_el]?.title || "Неизвестный скрипт - " + app_el }
appUrl = { menuApps.scripts[app_el]?.url }
favIconClassName = { favApps.includes(app_el) ? 'favorite' : 'not_favorite' }
hideAppClass = { hideScriptClass }
//Функция обновления актуальный список избранных приложений
setUpdateFavApps = { (newFavApp) => setFavApps(newFavApp) }
/>
))}
</div>
</div>
)) }
</div>
<Switcher
toggleAppsVisible = { setHideScriptClass }
switcherId = "switcher-menu"
/>
</div>
</div>
)
}
}
//Гаврилов
//ПЕРЕПИСАТЬ АРГУМЕНТЫ ПОД ОБЪЕКТ СО СВОЙСТВАМИ, ИСПОЛЬЗОВАТЬ ...obj
/**
*
* @param {int} appIndex ключ для экземлпяра компонента
* @param {string} appName уникальное имя скрипта
* @param {string} appTitle название скрипта в меню
* @param {string} favIconClassName класс для иконки избранного
* @param {string} hideAppClass класс для видимости приложения
* @param {string} setUpdateFavApps функция для обновления состояния избранны приложений (при снятия, установки признака избранности)
* @returns
*/
function AppElem({
appIndex,
appName,
appTitle,
appUrl,
favIconClassName,
hideAppClass,
setUpdateFavApps
} : {
appIndex: string,
appName: string,
appTitle: string,
appUrl: string,
favIconClassName: string,
hideAppClass: string,
setUpdateFavApps: string
}) {
const [appElemClass, changeFav] = useState(favIconClassName);
const callChangeFav = ( changeAppName: string ) => {
fetch('api/user_fav_app', {
method: 'post',
body: JSON.stringify({ appName: changeAppName }),
headers: {
'content-type': 'application/json'
}
}
)
.then( (response) => response.json() )
.then( (response) => {
//Если скрипт был избранным - удаляем из избранного и наоборот
appElemClass == 'favorite' ? changeFav('not_favorite') : changeFav('favorite');
//Возвращаем обновленный список избранных приложений пользователя
setUpdateFavApps(response?.fav_apps?.split( ';' ) || [])
} )
}
return (
//Если приложение в избранном - оно всегда должно быть "видимо", даже если переключатель в положении Скрыть неизбранные приложения
<div key = { appIndex } className = {`script-list__el ${appElemClass == 'favorite' ? '' : hideAppClass}`} data-scriptname = { appName }>
<div className = "script-list__el__script-name"><a target="_blank" href={ appUrl }>{ appTitle }</a></div>
{/* Анонимная функция нужна для создания функции смены состояния для каждого компонента в отдельности, в противном случае вызов функции в одном экземпляре компонента вызывает функцию изменения для каждого экземпляра компонента */}
<i className = {`${(appElemClass == 'favorite' ? 'fas' : 'far')} fa-star ${appElemClass}`} onClick = { () => callChangeFav(appName) }></i>
</div>
)
}
//TODO
//ВЫНЕСИ В ПАПКУ COMPONENTS/MAIN
/**
* Компонент переключателя В ДАННОМ СЛУЧАЕ видимости скриптов (видны только избранные или все)
* @param {string} switcherId идентификатор переключателя (для стилей css), так как в будущем, на странице можно будет размещать несколько переключателей
* @param {function} toggleAppsVisible функция обновления состояния hideScriptClass
* @returns
*/
function Switcher({ switcherId, toggleAppsVisible }) {
const [favSwitcherClass, setFavSwitcherClass] = useState('showAll');
//Переключение состояния переключателя после рендеринга страницы
useEffect( () => {
//Получаем из локал сторадж состояние переключателя
const savedState = localStorage.getItem('magicMenuFavSwitcher');
if (savedState) {
setFavSwitcherClass(savedState);
}
}, []);
//При изменении состояния Переключателя изменяется состояние всех приложени (переключается их видимость в зависимости от состояния переключателя - только избранные или все)
useEffect( () => {
//Синхронизируем зависимые компоненты
toggleAppsVisible(favSwitcherClass === 'showFav' ? 'script-hide' : '');
}, [favSwitcherClass] );
const callToggleFavSwitcher = () => {
let switcherState = (favSwitcherClass === 'showFav' ? 'showAll' : 'showFav');
//favSwitcherClass => favSwitcherClass = ... - функциональное обновление. Здесь оно не особо нужно, но чтобы не забыть
//Меняем класс switcher, а также меняем состояние видимости всех приложений (в зависимости от того избранные они или нет)
setFavSwitcherClass( favSwitcherClass => {
if (favSwitcherClass === 'showFav') {
toggleAppsVisible('script-hide')
} else {
toggleAppsVisible('')
}
localStorage.setItem('magicMenuFavSwitcher', switcherState)
return switcherState;
} )
}
return (
<div id = { switcherId } className = "switcher-container">
<label className = "switch">
<input type = "checkbox" className = {`switcher__favorite-app ${favSwitcherClass}`} onChange = { callToggleFavSwitcher }/>
<span className = "slider round"></span>
</label>
</div>
)
}
export default MenuApp;
@@ -0,0 +1,120 @@
import React, { useEffect, useMemo, useState, useContext } from "react";
import { createPortal } from "react-dom";
import './../../../css/components/entityHistory.css';
import { HistoryContext } from "../../contexts/HistoryContext";
//Тип с параметрами сущности, история по которой будет выводиться
export type EntityHistoryProps = {
changeAction: string, //Совершенное действие: создание, удаление, редактирование, архивирование и т.д.
changeAuthor: string, //Автор изменения: логин, сервисная УЗ
changeDate: Date //Объект даты изменения. В объекте будет либо время (тогда показываем), либо время будет отсутствовать (тогда не показываем) //что будем передавать? объект new Date или библиотечный объект для работы с датами?
changeTime?: string //Время изменений. Передавать не нужно, на этабе сборки компонента будет формироваться на основе пропса changeDate
// changeDetails?: HistoryEntityElDetails[] //Массив объектов с деталями изменений формата: изменяемое свойство(поле, например) - значение изменяемого свойства
changeDetails?: Record<string, string> //Массив объектов с деталями изменений формата: изменяемое свойство(поле, например) - значение изменяемого свойства
};
//Тип с деталями изменений
// type HistoryEntityElDetails = {
// propName: string, //Имя измененного свойства
// propValue: string //Значение измененного свойства
// }
//Компонент отображения истории изменений сущности
export default function EntityHistory ()
{
//ГАВРИЛОВ
//ИДЕНТИЧНЫЙ СПРАВОЧНИК УКАЗАН НА СТОРОНЕ ЛАРАВЕЛЬ APP/ENUMS/LOGBUSINESSEVENTS. ЕСТЬ ВОЗМОЖНОСТЬ ОБЪЕДИНИТЬ ИХ?
const HistoryActions = {
create: 'создание',
edit: 'редактирование',
archive: 'архивация',
restore: 'восстановление',
delete: 'удаление',
cancel: 'отмена',
}
const historyContext = useContext(HistoryContext);
//Перестраиваем объект с изменениями, группируя их по датам
const groupDataByDate = useMemo( () => {
let changesByDate = {},
historyContent = historyContext.content.length ? historyContext.content : []
//Сортируем все изменения от большего к меньшему по дате и времени
//ГАВРИЛОВ. или нужно отсортировать от меньшего к большему? мы хотим сверху самые актуальные данные а ниже менее актуальные или наоборот?
//ПРОВЕРИТЬ СОРТИРУЕТСЯ ЛИ ПО ДАТАМ
historyContent.sort( (a, b) => {return b.changeDate - a.changeDate} )
//console.log(historyContext)
historyContent.forEach( (changeEl: EntityHistoryProps) => {
let dateToString = changeEl.changeDate.toLocaleString('ru-RU').split(',')[0];
if ( !(dateToString in changesByDate) ) {
changesByDate[dateToString] = [];
}
changesByDate[dateToString].push(changeEl);
});
return changesByDate;
}, [historyContext.content]);
//Сокрытие блока показа истории
function hideHistoryBlock(){
historyContext.hideHistory()
}
return (
historyContext.content && historyContext.visible ?
createPortal(
<div>
<div className="entity-hist-container">
<div className="hist__header-block">
<div className="hist__header__title">
<h3>{ historyContext.headerText.length ? historyContext.headerText : `История изменений запроса ${historyContext.entityId}` }</h3>
</div>
<div className="hist__header__buttons" onClick={hideHistoryBlock}>
<i className="fa fa-plus header__buttons__close"></i>
</div>
</div>
<div className="hist__content-block">
{Object.entries(groupDataByDate).map( (dateChanges, dateIndex) =>
<div key={dateIndex} className="hist-container__date-block">
<div className="date-block__date">
<div>{ dateChanges[0] }</div>
</div>
<div className="date-block__changes">
{ dateChanges[1].map( (changeEl: EntityHistoryProps, elIndex: number) =>
<div className="changes__action-block" key={ elIndex }>
<div className="action-block__action-name">
{ HistoryActions[changeEl.changeAction] }
</div>
<div className="changes__date-time-block">
<div>
{ changeEl.changeAuthor }
</div>
<div>
{ changeEl.changeTime }
</div>
</div>
<div className="changes__details">
{Object.entries(changeEl.changeDetails).map( (detail, detailIndex) =>
<div className="changes__details__el" key={ detailIndex }>
<div className="changes__details__el__prop-name">
{ (historyContext.dataFields && historyContext.dataFields[detail[0]]) ? historyContext.dataFields[detail[0]] : detail[0] }
</div>
<div className="changes__details__el__prop-val">
{ detail[1] }
</div>
</div>
)}
</div>
</div>
)}
</div>
</div>
)}
</div>
</div>
</div>,
document.body
)
: ''
)
}
@@ -0,0 +1,30 @@
import React, { useEffect, useState } from "react";
/**
* Объект с данными для отображения в блоке с ошибками
*/
export interface FormValidErrObject
{
fieldName: string | null,
fieldErrors: string[] | []
}
export default function FormValidErr ( {visible, validErrorsObj}: {visible: boolean, validErrorsObj: FormValidErrObject[]} )
{
const [blockVisible, setBlockVisible] = useState<boolean>(false);
useEffect ( () => {
setBlockVisible(visible)
}, [visible]);
return (
<div id="form-valid-err-container" className={`form-valid-err--${blockVisible ? "visible" : 'hide'}`}>
<ul>
{ validErrorsObj.map( (validErrObj: FormValidErrObject, index: number) => (
<li key={index}>
<b>{ validErrObj.fieldName }</b>:
<span>{ validErrObj.fieldErrors }</span>
</li>
)) }
</ul>
</div>
)
}
+51
View File
@@ -0,0 +1,51 @@
import React, { useEffect, useState } from "react";
import { Button } from '@SharePoint/rencredit_uikit';
export default function Header(){
const[appName, setAppName] = useState<string>('');
useEffect( () => {
setAppName(document.getElementById('page__header-block')?.dataset.app_name || '');
}, [])
return (
<div className="header-block__header-container">
<div className="header-container__block">
<div>
<Button
type = 'button'
onClick = {() => document.location.href='/public/menu'}
text = 'ЛОГОТИП ТУТ'
ui = 'secondaryPurple'
/>
</div>
<div>
<Button
type = 'button'
onClick = {() => document.location.href='/public/menu'}
text = 'Меню'
ui = 'secondaryPurple'
/>
</div>
<div className="header-container__block__app-name">{appName}</div>
</div>
<div className="header-container__block">
<div>
<Button
type = 'button'
onClick = {() => document.location.href='/public/request_access'}
text = 'Заказать доступ'
ui = 'secondaryPurple'
/>
</div>
<div>
<Button
type = 'button'
onClick = {() => document.location.href='/public/logout'}
text = 'Выход'
ui = 'secondaryPurple'
/>
</div>
</div>
</div>
)
}
@@ -0,0 +1,39 @@
import "./../../../css/components/preloader.css";
import React from "react";
export default function Preloader ( props: {visible: boolean, text?: string | null}) {
const {text = 'загрузка'} = props;
let preloaderColorArray: string[] = ['preloader-ruby-circle', 'preloader-emerald-circle', 'preloader-graphite-circle'],
//Код ниже реализует возможность присваивать каждую перезагрузку разные классы с цветом полос
//Рандомное число, по которому получим первый класс
firstColorIndex: number = Math.floor(Math.random() * 3),
firstCircleClass: typeof preloaderColorArray[number] = preloaderColorArray[firstColorIndex],
secondCircleClass: typeof preloaderColorArray[number],
thirdCircleClass: typeof preloaderColorArray[number];
//Удалим уже используемый класс цвета из массива
preloaderColorArray.splice(firstColorIndex, 1);
//Оставшиеся 2 класса распределеяем в зависимости от проостой проверки четности/нечетности
if (Math.floor(Math.random() * 2) % 2 === 0) {
secondCircleClass = preloaderColorArray[0];
thirdCircleClass = preloaderColorArray[1];
} else {
secondCircleClass = preloaderColorArray[1];
thirdCircleClass = preloaderColorArray[0];
}
return (
props.visible ?
<div id='preloader_container'>
<div id='preloader'>
<div className={'circle ' + firstCircleClass} id='circle_one'></div>
<div className={'circle ' + secondCircleClass} id='circle_two'></div>
<div className={'circle ' + thirdCircleClass} id='circle_three'></div>
<div id='logo'>
<div className='logo_square' id='right-bottom'></div>
<div className='logo_square' id='left-top'></div>
</div>
<div id='preloader_text'>{ text }</div>
</div>
</div>
: null
)
}