add locales
This commit is contained in:
parent
acaf99c878
commit
edf8bf7bc9
@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
# Build commands:
|
# Build commands:
|
||||||
# - $VERSION=0.6
|
# - $VERSION=0.7
|
||||||
# - rm build -r -Force ; rm ./node_modules/.cache/babel-loader -r -Force ; yarn build
|
# - rm build -r -Force ; rm ./node_modules/.cache/babel-loader -r -Force ; yarn build
|
||||||
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
|
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
|
||||||
# - docker image push --all-tags oncharterliz/multi-frigate
|
# - docker image push --all-tags oncharterliz/multi-frigate
|
||||||
|
|||||||
@ -27,6 +27,8 @@
|
|||||||
"date-fns": "^3.3.1",
|
"date-fns": "^3.3.1",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"embla-carousel-react": "^8.0.0-rc10",
|
"embla-carousel-react": "^8.0.0-rc10",
|
||||||
|
"i18next": "^23.10.1",
|
||||||
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"mantine-react-table": "^1.0.0-beta.25",
|
"mantine-react-table": "^1.0.0-beta.25",
|
||||||
@ -39,6 +41,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-i18next": "^14.1.0",
|
||||||
"react-oidc-context": "^2.2.2",
|
"react-oidc-context": "^2.2.2",
|
||||||
"react-router-dom": "^6.14.1",
|
"react-router-dom": "^6.14.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
|||||||
@ -62,6 +62,7 @@ function App() {
|
|||||||
urlParams.delete('state');
|
urlParams.delete('state');
|
||||||
urlParams.delete('session_state');
|
urlParams.delete('session_state');
|
||||||
urlParams.delete('code');
|
urlParams.delete('code');
|
||||||
|
urlParams.delete('iss');
|
||||||
navigate(`${location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`, { replace: true })
|
navigate(`${location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`, { replace: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,24 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import { AppShell, useMantineTheme, } from "@mantine/core";
|
||||||
import { AppShell, useMantineTheme, } from "@mantine/core"
|
|
||||||
import { HeaderAction } from './widgets/header/HeaderAction';
|
|
||||||
import { headerLinks } from './widgets/header/header.links';
|
|
||||||
import AppRouter from './router/AppRouter';
|
|
||||||
import { Context } from '.';
|
|
||||||
import SideBar from './shared/components/SideBar';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useContext, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Context } from '.';
|
||||||
|
import AppRouter from './router/AppRouter';
|
||||||
|
import { routesPath } from './router/routes.path';
|
||||||
|
import SideBar from './shared/components/SideBar';
|
||||||
import { isProduction } from './shared/env.const';
|
import { isProduction } from './shared/env.const';
|
||||||
|
import { HeaderAction } from './widgets/header/HeaderAction';
|
||||||
|
|
||||||
const AppBody = () => {
|
const AppBody = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const headerLinks = [
|
||||||
|
{ link: routesPath.MAIN_PATH, label: t('header.home') },
|
||||||
|
{ link: routesPath.SETTINGS_PATH, label: t('header.settings'), admin: true },
|
||||||
|
{ link: routesPath.RECORDINGS_PATH, label: t('header.recordings') },
|
||||||
|
{ link: routesPath.HOSTS_PATH, label: t('header.hostsConfig'), admin: true },
|
||||||
|
{ link: routesPath.ACCESS_PATH, label: t('header.acessSettings'), admin: true },
|
||||||
|
]
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import RootStore from './shared/stores/root.store';
|
|||||||
import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
|
import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
|
||||||
import { isProduction, oidpSettings } from './shared/env.const';
|
import { isProduction, oidpSettings } from './shared/env.const';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import './services/i18n';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById('root') as HTMLElement
|
||||||
@ -23,6 +24,7 @@ export const keycloakConfig: AuthProviderProps = {
|
|||||||
params.delete('state');
|
params.delete('state');
|
||||||
params.delete('session_state');
|
params.delete('session_state');
|
||||||
params.delete('code');
|
params.delete('code');
|
||||||
|
params.delete('iss');
|
||||||
const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`
|
const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`
|
||||||
window.history.replaceState({}, document.title, newUrl)
|
window.history.replaceState({}, document.title, newUrl)
|
||||||
}
|
}
|
||||||
|
|||||||
63
src/locales/en.ts
Normal file
63
src/locales/en.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
const en = {
|
||||||
|
header: {
|
||||||
|
home: 'Main',
|
||||||
|
settings: 'Settings',
|
||||||
|
recordings: 'Recordings',
|
||||||
|
hostsConfig: 'Frigate servers',
|
||||||
|
acessSettings: 'Access settings',
|
||||||
|
}, hostArr: {
|
||||||
|
host: 'Host',
|
||||||
|
name: 'Host name',
|
||||||
|
url: 'Address',
|
||||||
|
enabled: 'Enabled',
|
||||||
|
},
|
||||||
|
player: {
|
||||||
|
startVideo: 'Enable Video',
|
||||||
|
stopVideo: 'Disable Video',
|
||||||
|
object: 'Object',
|
||||||
|
duration: 'Duration',
|
||||||
|
startTime: 'Start',
|
||||||
|
endTime: 'End',
|
||||||
|
doubleClickToFullHint: 'Double click for fullscreen',
|
||||||
|
rating: 'Rating',
|
||||||
|
},
|
||||||
|
pleaseSelectRole: 'Please select Role',
|
||||||
|
pleaseSelectHost: 'Please select Host',
|
||||||
|
pleaseSelectCamera: 'Please select Camera',
|
||||||
|
pleaseSelectDate: 'Please select Date',
|
||||||
|
nothingHere: 'Nothing here',
|
||||||
|
allowed: 'Allowed',
|
||||||
|
notAllowed: 'Not allowed',
|
||||||
|
camera: 'Camera',
|
||||||
|
camersDoesNotExist: 'No cameras',
|
||||||
|
search: 'Search',
|
||||||
|
recordings: 'Recordings',
|
||||||
|
hour: 'Hour',
|
||||||
|
events: 'Events',
|
||||||
|
notHaveEvents: 'No events',
|
||||||
|
day: 'Day',
|
||||||
|
selectHost: 'Select host',
|
||||||
|
selectCamera: 'Select Camera',
|
||||||
|
selectRange: 'Select period',
|
||||||
|
changeTheme: "Change theme",
|
||||||
|
logout: "Logout",
|
||||||
|
enterQuantity: "Enter quantity:",
|
||||||
|
quantity: "Quantity",
|
||||||
|
tooltipСlose: "Press Enter",
|
||||||
|
hide: "Hide",
|
||||||
|
confirm: "Confirm",
|
||||||
|
save: "Save",
|
||||||
|
discard: "Cancel",
|
||||||
|
next: "Next",
|
||||||
|
back: "Back",
|
||||||
|
goToMainPage: "Return to main page",
|
||||||
|
retry: "Retry",
|
||||||
|
youCanRetryOrGoToMain: "You can retry or return to the main page",
|
||||||
|
errors: {
|
||||||
|
somthingGoesWrong: "Something went wrong",
|
||||||
|
403: "Sorry, you do not have access",
|
||||||
|
404: "Sorry, we cannot find that page",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default en
|
||||||
64
src/locales/ru.ts
Normal file
64
src/locales/ru.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
const ru = {
|
||||||
|
header: {
|
||||||
|
home: 'Главная',
|
||||||
|
settings: 'Настройки',
|
||||||
|
recordings: 'Записи',
|
||||||
|
hostsConfig: 'Серверы Frigate',
|
||||||
|
acessSettings: 'Настройка доступа',
|
||||||
|
},
|
||||||
|
hostArr: {
|
||||||
|
host: 'Хост',
|
||||||
|
name: 'Имя хоста',
|
||||||
|
url: 'Адрес',
|
||||||
|
enabled: 'Включен',
|
||||||
|
},
|
||||||
|
player: {
|
||||||
|
startVideo: 'Вкл. Видео',
|
||||||
|
stopVideo: 'Выкл. Видео',
|
||||||
|
object: 'Объект',
|
||||||
|
duration: 'Длительность',
|
||||||
|
startTime: 'Начало',
|
||||||
|
endTime: 'Конец',
|
||||||
|
doubleClickToFullHint: 'Двойное нажатие мышью для полноэкранного просмотра',
|
||||||
|
rating: 'Рейтинг',
|
||||||
|
},
|
||||||
|
pleaseSelectRole: 'Пожалуйста выберите роль',
|
||||||
|
pleaseSelectHost: 'Пожалуйста выберите хост',
|
||||||
|
pleaseSelectCamera: 'Пожалуйста выберите камеру',
|
||||||
|
pleaseSelectDate: 'Пожалуйста выберите дату',
|
||||||
|
nothingHere: 'Ничего нет',
|
||||||
|
allowed: 'Разрешено',
|
||||||
|
notAllowed: 'Не разрешено',
|
||||||
|
camera: 'Камера',
|
||||||
|
camersDoesNotExist: 'Камер нет',
|
||||||
|
search: 'Поиск',
|
||||||
|
recordings: 'Записи',
|
||||||
|
hour: 'Час',
|
||||||
|
events: 'События',
|
||||||
|
notHaveEvents: 'Событий нет',
|
||||||
|
day: 'День',
|
||||||
|
selectHost:'Выбери хост',
|
||||||
|
selectCamera: 'Выбери камеру',
|
||||||
|
selectRange: 'Выбери период',
|
||||||
|
changeTheme: "Изменить тему",
|
||||||
|
logout: "Выйти",
|
||||||
|
enterQuantity: "Введите количество:",
|
||||||
|
quantity: "Количество",
|
||||||
|
tooltipСlose: "Нажмите Enter",
|
||||||
|
hide: "Скрыть",
|
||||||
|
confirm: "Подтвердить",
|
||||||
|
save: "Сохранить",
|
||||||
|
discard: "Отменить",
|
||||||
|
next: "Далее",
|
||||||
|
back: "Назад",
|
||||||
|
goToMainPage: "Вернуться на главную",
|
||||||
|
retry: "Повторить",
|
||||||
|
youCanRetryOrGoToMain: "Вы можете повторить или вернуться на главную",
|
||||||
|
errors: {
|
||||||
|
somthingGoesWrong: "Что-то пошло не так",
|
||||||
|
403: "Извините, у вас нет доступа",
|
||||||
|
404: "Извините, мы не можем найти такую страницу",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ru
|
||||||
@ -1,12 +1,13 @@
|
|||||||
import { Button, Flex, Text } from '@mantine/core';
|
import { Button, Flex, Text } from '@mantine/core';
|
||||||
import React, { useContext, useEffect, useRef } from 'react';
|
|
||||||
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { routesPath } from '../router/routes.path';
|
|
||||||
import { Context } from '..';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useContext, useEffect, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Context } from '..';
|
||||||
|
import { routesPath } from '../router/routes.path';
|
||||||
|
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
||||||
|
|
||||||
const Forbidden = () => {
|
const Forbidden = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
|
||||||
@ -25,9 +26,9 @@ const Forbidden = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h='100%' direction='column' justify='center' align='center' gap='1rem'>
|
<Flex h='100%' direction='column' justify='center' align='center' gap='1rem'>
|
||||||
<Text fz='lg' fw={700}>{strings.errors[403]}</Text>
|
<Text fz='lg' fw={700}>{t('errors.403')}</Text>
|
||||||
<CogWheelWithText text='403' />
|
<CogWheelWithText text='403' />
|
||||||
<Button onClick={handleGoToMain}>{strings.goToMainPage}</Button>
|
<Button onClick={handleGoToMain}>{t('goToMainPage')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { Button, Flex, Text } from '@mantine/core';
|
import { Button, Flex, Text } from '@mantine/core';
|
||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useContext, useEffect, useRef } from 'react';
|
||||||
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { routesPath } from '../router/routes.path';
|
import { routesPath } from '../router/routes.path';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const NotFound = () => {
|
const NotFound = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
|
||||||
@ -25,9 +26,9 @@ const NotFound = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h='100%' direction='column' justify='center' align='center' gap='1rem'>
|
<Flex h='100%' direction='column' justify='center' align='center' gap='1rem'>
|
||||||
<Text fz='lg' fw={700}>{strings.errors[404]}</Text>
|
<Text fz='lg' fw={700}>{t('errors.404')}</Text>
|
||||||
<CogWheelWithText text='404' />
|
<CogWheelWithText text='404' />
|
||||||
<Button onClick={handleGoToMain}>{strings.goToMainPage}</Button>
|
<Button onClick={handleGoToMain}>{t('goToMainPage')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,21 +1,22 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
|
||||||
import { Flex, Group, Select, Text } from '@mantine/core';
|
import { Flex, Group, Select, Text } from '@mantine/core';
|
||||||
import { OneSelectItem } from '../shared/components/filters/OneSelectFilter';
|
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
import { dimensions } from '../shared/dimensions/dimensions';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
|
||||||
import { Context } from '..';
|
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
|
||||||
import Forbidden from './403';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Context } from '..';
|
||||||
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
|
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
||||||
|
import { OneSelectItem } from '../shared/components/filters/OneSelectFilter';
|
||||||
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
|
import { dimensions } from '../shared/dimensions/dimensions';
|
||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
|
import Forbidden from './403';
|
||||||
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
|
|
||||||
const AccessSettings = () => {
|
const AccessSettings = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { data, isPending, isError, refetch } = useQuery({
|
const { data, isPending, isError, refetch } = useQuery({
|
||||||
queryKey: [frigateQueryKeys.getRoles],
|
queryKey: [frigateQueryKeys.getRoles],
|
||||||
@ -49,7 +50,7 @@ const AccessSettings = () => {
|
|||||||
if (!isProduction) console.log('AccessSettings rendered')
|
if (!isProduction) console.log('AccessSettings rendered')
|
||||||
return (
|
return (
|
||||||
<Flex w='100%' h='100%' direction='column'>
|
<Flex w='100%' h='100%' direction='column'>
|
||||||
<Text align='center' size='xl'>{strings.pleaseSelectRole}</Text>
|
<Text align='center' size='xl'>{t('pleaseSelectRole')}</Text>
|
||||||
<Flex justify='space-between' align='center' w='100%'>
|
<Flex justify='space-between' align='center' w='100%'>
|
||||||
{!isMobile ? <Group w='40%' /> : <></>}
|
{!isMobile ? <Group w='40%' /> : <></>}
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -1,22 +1,24 @@
|
|||||||
import { Button, Flex, Text } from '@mantine/core';
|
import { Button, Flex, Text } from '@mantine/core';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
import { IconAlertCircle } from '@tabler/icons-react';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useContext, useEffect, useRef, useState } from 'react';
|
import { useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import FrigateHostsTable from '../widgets/FrigateHostsTable';
|
import FrigateHostsTable from '../widgets/FrigateHostsTable';
|
||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
import { notifications } from '@mantine/notifications';
|
|
||||||
import { IconAlertCircle } from '@tabler/icons-react';
|
|
||||||
|
|
||||||
|
|
||||||
const FrigateHostsPage = () => {
|
const FrigateHostsPage = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { isPending: hostsPending, error: hostsError, data } = useQuery({
|
const { isPending: hostsPending, error: hostsError, data } = useQuery({
|
||||||
@ -106,8 +108,8 @@ const FrigateHostsPage = () => {
|
|||||||
<Flex w='100%' h='100%' direction='column'>
|
<Flex w='100%' h='100%' direction='column'>
|
||||||
<FrigateHostsTable data={pageData} showAddButton changedCallback={handleChange} />
|
<FrigateHostsTable data={pageData} showAddButton changedCallback={handleChange} />
|
||||||
<Flex justify='center'>
|
<Flex justify='center'>
|
||||||
<Button m='0.5rem' onClick={handleDiscard}>{strings.discard}</Button>
|
<Button m='0.5rem' onClick={handleDiscard}>{t('discard')}</Button>
|
||||||
<Button m='0.5rem' onClick={handleSave}>{strings.save}</Button>
|
<Button m='0.5rem' onClick={handleSave}>{t('save')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,11 +8,12 @@ import CenterLoader from '../shared/components/loaders/CenterLoader';
|
|||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
import Player from '../widgets/Player';
|
import Player from '../widgets/Player';
|
||||||
import { Button, Flex, Text } from '@mantine/core';
|
import { Button, Flex, Text } from '@mantine/core';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { routesPath } from '../router/routes.path';
|
import { routesPath } from '../router/routes.path';
|
||||||
import { recordingsPageQuery } from './RecordingsPage';
|
import { recordingsPageQuery } from './RecordingsPage';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const LiveCameraPage = () => {
|
const LiveCameraPage = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
let { id: cameraId } = useParams<'id'>()
|
let { id: cameraId } = useParams<'id'>()
|
||||||
@ -48,9 +49,9 @@ const LiveCameraPage = () => {
|
|||||||
return (
|
return (
|
||||||
<Flex w='100%' h='100%' justify='center' align='center' direction='column'>
|
<Flex w='100%' h='100%' justify='center' align='center' direction='column'>
|
||||||
<Flex w='100%' justify='center' align='baseline' mb='1rem'>
|
<Flex w='100%' justify='center' align='baseline' mb='1rem'>
|
||||||
<Text mr='1rem'>{strings.camera}: {camera.name} {camera.frigateHost ? `/ ${camera.frigateHost.name}` : ''}</Text>
|
<Text mr='1rem'>{t('camera')}: {camera.name} {camera.frigateHost ? `/ ${camera.frigateHost.name}` : ''}</Text>
|
||||||
{!camera.frigateHost ? <></> :
|
{!camera.frigateHost ? <></> :
|
||||||
<Button onClick={handleOpenRecordings}>{strings.recordings}</Button>
|
<Button onClick={handleOpenRecordings}>{t('recordings')}</Button>
|
||||||
}
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Player camera={camera} />
|
<Player camera={camera} />
|
||||||
|
|||||||
@ -8,11 +8,13 @@ import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.
|
|||||||
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
||||||
import HostSelect from '../shared/components/filters/HostSelect';
|
import HostSelect from '../shared/components/filters/HostSelect';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import CameraCard from '../widgets/CameraCard';
|
import CameraCard from '../widgets/CameraCard';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
|
import ClearableTextInput from '../shared/components/inputs/ClearableTextInput';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const MainPage = () => {
|
const MainPage = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
const [searchQuery, setSearchQuery] = useState<string>()
|
const [searchQuery, setSearchQuery] = useState<string>()
|
||||||
@ -88,10 +90,11 @@ const MainPage = () => {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextInput
|
<ClearableTextInput
|
||||||
|
clerable
|
||||||
maw={400}
|
maw={400}
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
placeholder={strings.search}
|
placeholder={t('search')}
|
||||||
icon={<IconSearch size="0.9rem" stroke={1.5} />}
|
icon={<IconSearch size="0.9rem" stroke={1.5} />}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
||||||
@ -101,7 +104,7 @@ const MainPage = () => {
|
|||||||
onChange={handleSelectHost}
|
onChange={handleSelectHost}
|
||||||
ml='1rem'
|
ml='1rem'
|
||||||
spaceBetween='0px'
|
spaceBetween='0px'
|
||||||
placeholder={strings.selectHost}
|
placeholder={t('selectHost')}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import RecordingsFiltersRightSide from '../widgets/RecordingsFiltersRightSide';
|
|||||||
import SelectedCameraList from '../widgets/SelectedCameraList';
|
import SelectedCameraList from '../widgets/SelectedCameraList';
|
||||||
import SelectedDayList from '../widgets/SelectedDayList';
|
import SelectedDayList from '../widgets/SelectedDayList';
|
||||||
import SelectedHostList from '../widgets/SelectedHostList';
|
import SelectedHostList from '../widgets/SelectedHostList';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
export const recordingsPageQuery = {
|
export const recordingsPageQuery = {
|
||||||
@ -21,6 +22,7 @@ export const recordingsPageQuery = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RecordingsPage = () => {
|
const RecordingsPage = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { sideBarsStore, recordingsStore: recStore } = useContext(Context)
|
const { sideBarsStore, recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
@ -116,10 +118,10 @@ const RecordingsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<Flex w='100%' h='100%' direction='column' justify='center' align='center'>
|
<Flex w='100%' h='100%' direction='column' justify='center' align='center'>
|
||||||
{!hostId ?
|
{!hostId ?
|
||||||
<Text size='xl'>Please select host</Text>
|
<Text size='xl'>{t('pleaseSelectHost')}</Text>
|
||||||
: <></>}
|
: <></>}
|
||||||
{hostId && !(startDay && endDay) ?
|
{hostId && !(startDay && endDay) ?
|
||||||
<Text size='xl'>Please select date</Text>
|
<Text size='xl'>{t('pleaseSelectDate')}</Text>
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Flex, Button, Text } from '@mantine/core';
|
import { Flex, Button, Text } from '@mantine/core';
|
||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useContext, useEffect, useRef } from 'react';
|
||||||
import { routesPath } from '../router/routes.path';
|
import { routesPath } from '../router/routes.path';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel';
|
import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface RetryErrorPageProps {
|
interface RetryErrorPageProps {
|
||||||
repeatVisible?: boolean
|
repeatVisible?: boolean
|
||||||
@ -20,6 +20,7 @@ const RetryErrorPage = ({
|
|||||||
mainVisible = true,
|
mainVisible = true,
|
||||||
onRetry
|
onRetry
|
||||||
}: RetryErrorPageProps) => {
|
}: RetryErrorPageProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -49,13 +50,13 @@ const RetryErrorPage = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h='100%' direction='column' justify='center' align='center' gap='1rem'>
|
<Flex h='100%' direction='column' justify='center' align='center' gap='1rem'>
|
||||||
<Text fz='lg' fw={700}>{strings.errors.somthengGoesWrong}</Text>
|
<Text fz='lg' fw={700}>{t('errors.somthingGoesWrong')}</Text>
|
||||||
{ExclamationCogWheel}
|
{ExclamationCogWheel}
|
||||||
<Text fz='lg' fw={700}>{strings.youCanRetryOrGoToMain}</Text>
|
<Text fz='lg' fw={700}>{t('youCanRetryOrGoToMain')}</Text>
|
||||||
<Flex>
|
<Flex>
|
||||||
{repeatVisible ? <Button ml='1rem' onClick={handleRetry}>{strings.retry}</Button> : null}
|
{repeatVisible ? <Button ml='1rem' onClick={handleRetry}>{t('retry')}</Button> : null}
|
||||||
{ backVisible ? <Button ml='1rem' onClick={handleGoBack}>{strings.back}</Button> : null }
|
{ backVisible ? <Button ml='1rem' onClick={handleGoBack}>{t('back')}</Button> : null }
|
||||||
{ mainVisible ? <Button ml='1rem' onClick={handleGoToMain}>{strings.goToMainPage}</Button> : null }
|
{ mainVisible ? <Button ml='1rem' onClick={handleGoToMain}>{t('goToMainPage')}</Button> : null }
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,25 +1,26 @@
|
|||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
||||||
import {
|
|
||||||
useQuery,
|
|
||||||
useMutation,
|
|
||||||
useQueryClient,
|
|
||||||
} from '@tanstack/react-query'
|
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
|
||||||
import { Button, Flex, Space } from '@mantine/core';
|
import { Button, Flex, Space } from '@mantine/core';
|
||||||
import { FloatingLabelInput } from '../shared/components/inputs/FloatingLabelInput';
|
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { dimensions } from '../shared/dimensions/dimensions';
|
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
import { GetConfig } from '../services/frigate.proxy/frigate.schema';
|
import {
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
} from '@tanstack/react-query';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import Forbidden from './403';
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { GetConfig } from '../services/frigate.proxy/frigate.schema';
|
||||||
|
import { FloatingLabelInput } from '../shared/components/inputs/FloatingLabelInput';
|
||||||
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
|
import { dimensions } from '../shared/dimensions/dimensions';
|
||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
|
import Forbidden from './403';
|
||||||
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { isPending: configPending, error: configError, data, refetch } = useQuery({
|
const { isPending: configPending, error: configError, data, refetch } = useQuery({
|
||||||
@ -126,8 +127,8 @@ const SettingsPage = () => {
|
|||||||
))}
|
))}
|
||||||
<Space h='2%' />
|
<Space h='2%' />
|
||||||
<Flex w='100%' justify='stretch' wrap='nowrap' align='center'>
|
<Flex w='100%' justify='stretch' wrap='nowrap' align='center'>
|
||||||
<Button w='100%' onClick={handleDiscard} m='0.5rem'>{strings.discard}</Button>
|
<Button w='100%' onClick={handleDiscard} m='0.5rem'>{t('discard')}</Button>
|
||||||
<Button w='100%' type="submit" m='0.5rem'>{strings.confirm}</Button>
|
<Button w='100%' type="submit" m='0.5rem'>{t('confirm')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export const getToken = (): string | undefined => {
|
|||||||
|
|
||||||
const instanceApi = axios.create({
|
const instanceApi = axios.create({
|
||||||
baseURL: proxyURL.toString(),
|
baseURL: proxyURL.toString(),
|
||||||
timeout: 60 * 1000,
|
timeout: 20 * 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
instanceApi.interceptors.request.use(
|
instanceApi.interceptors.request.use(
|
||||||
|
|||||||
21
src/services/i18n.ts
Normal file
21
src/services/i18n.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import i18n from 'i18next'
|
||||||
|
import { initReactI18next } from 'react-i18next'
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||||
|
import en from '../locales/en'
|
||||||
|
import ru from '../locales/ru'
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(initReactI18next)
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.init({
|
||||||
|
resources: {
|
||||||
|
en: { translation: en},
|
||||||
|
ru: { translation: ru},
|
||||||
|
},
|
||||||
|
fallbackLng: 'en',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
@ -1,12 +1,11 @@
|
|||||||
|
import { Button, Flex, Text, TransferList, TransferListData, TransferListItem } from '@mantine/core';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import React, { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { frigateApi, frigateQueryKeys } from '../../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../../services/frigate.proxy/frigate.api';
|
||||||
import CogwheelLoader from './loaders/CogwheelLoader';
|
|
||||||
import RetryError from './RetryError';
|
|
||||||
import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core';
|
|
||||||
import { OneSelectItem } from './filters/OneSelectFilter';
|
|
||||||
import { strings } from '../strings/strings';
|
|
||||||
import { isProduction } from '../env.const';
|
import { isProduction } from '../env.const';
|
||||||
|
import RetryError from './RetryError';
|
||||||
|
import CogwheelLoader from './loaders/CogwheelLoader';
|
||||||
|
|
||||||
interface CamerasTransferListProps {
|
interface CamerasTransferListProps {
|
||||||
roleId: string
|
roleId: string
|
||||||
@ -15,6 +14,7 @@ interface CamerasTransferListProps {
|
|||||||
const CamerasTransferList = ({
|
const CamerasTransferList = ({
|
||||||
roleId,
|
roleId,
|
||||||
}: CamerasTransferListProps) => {
|
}: CamerasTransferListProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { data: cameras, isPending, isError, refetch } = useQuery({
|
const { data: cameras, isPending, isError, refetch } = useQuery({
|
||||||
queryKey: [frigateQueryKeys.getCamerasWHost, roleId],
|
queryKey: [frigateQueryKeys.getCamerasWHost, roleId],
|
||||||
@ -50,7 +50,7 @@ const CamerasTransferList = ({
|
|||||||
|
|
||||||
if (isPending) return <CogwheelLoader />
|
if (isPending) return <CogwheelLoader />
|
||||||
if (isError || !cameras) return <RetryError onRetry={refetch} />
|
if (isError || !cameras) return <RetryError onRetry={refetch} />
|
||||||
if (cameras.length < 1) return <Text> {strings.camersDoesNotExist}</Text>
|
if (cameras.length < 1) return <Text> {t('camersDoesNotExist')}</Text>
|
||||||
|
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
@ -65,8 +65,8 @@ const CamerasTransferList = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex w='100%' justify='center'>
|
<Flex w='100%' justify='center'>
|
||||||
<Button mt='1rem' miw='6rem' mr='1rem' onClick={handleDiscard}>{strings.discard}</Button>
|
<Button mt='1rem' miw='6rem' mr='1rem' onClick={handleDiscard}>{t('discard')}</Button>
|
||||||
<Button mt='1rem' miw='5rem' onClick={handleSave}>{strings.save}</Button>
|
<Button mt='1rem' miw='5rem' onClick={handleSave}>{t('save')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<TransferList
|
<TransferList
|
||||||
transferAllMatchingFilter
|
transferAllMatchingFilter
|
||||||
@ -74,9 +74,9 @@ const CamerasTransferList = ({
|
|||||||
mt='1rem'
|
mt='1rem'
|
||||||
value={lists}
|
value={lists}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
searchPlaceholder={strings.search}
|
searchPlaceholder={t('search')}
|
||||||
nothingFound={strings.nothingHere}
|
nothingFound={t('nothingHere')}
|
||||||
titles={[strings.notAllowed, strings.allowed]}
|
titles={[t('notAllowed'), t('allowed')]}
|
||||||
breakpoint="sm"
|
breakpoint="sm"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Aside, Button, Navbar, createStyles } from "@mantine/core";
|
import { Aside, Button, Navbar, createStyles } from "@mantine/core";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Context } from '../..';
|
import { Context } from '../..';
|
||||||
import { dimensions } from '../dimensions/dimensions';
|
import { dimensions } from '../dimensions/dimensions';
|
||||||
import { strings } from '../strings/strings';
|
|
||||||
import { useMantineSize } from '../utils/mantine.size.convertor';
|
import { useMantineSize } from '../utils/mantine.size.convertor';
|
||||||
import { SideButton } from './SideButton';
|
import { SideButton } from './SideButton';
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ const useStyles = createStyles((theme,
|
|||||||
|
|
||||||
|
|
||||||
const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
||||||
const initialVisible = () => {
|
const initialVisible = () => {
|
||||||
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||||
@ -97,7 +98,7 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
|||||||
p={dimensions.hideSidebarsSize}
|
p={dimensions.hideSidebarsSize}
|
||||||
width={{ sm: 200, lg: 300, }}
|
width={{ sm: 200, lg: 300, }}
|
||||||
>
|
>
|
||||||
<Button onClick={() => handleClickVisible(false)}>{strings.hide}</Button>
|
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||||
{leftChildren}
|
{leftChildren}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
:
|
:
|
||||||
@ -105,7 +106,7 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
|||||||
className={classes.aside}
|
className={classes.aside}
|
||||||
p={dimensions.hideSidebarsSize}
|
p={dimensions.hideSidebarsSize}
|
||||||
width={{ sm: 200, lg: 300 }}>
|
width={{ sm: 200, lg: 300 }}>
|
||||||
<Button onClick={() => handleClickVisible(false)}>{strings.hide}</Button>
|
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||||
{rightChildren}
|
{rightChildren}
|
||||||
</Aside>
|
</Aside>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,25 @@
|
|||||||
import React from 'react';
|
import { Avatar, Button, Flex, Group, Menu, Text } from "@mantine/core";
|
||||||
import { Avatar, Group, Menu, Text, Button, Flex } from "@mantine/core";
|
|
||||||
import { useAuth } from 'react-oidc-context';
|
|
||||||
import { strings } from '../strings/strings';
|
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAuth } from 'react-oidc-context';
|
||||||
|
import { keycloakConfig } from '../..';
|
||||||
import { dimensions } from '../dimensions/dimensions';
|
import { dimensions } from '../dimensions/dimensions';
|
||||||
import ColorSchemeToggle from './buttons/ColorSchemeToggle';
|
import ColorSchemeToggle from './buttons/ColorSchemeToggle';
|
||||||
import { keycloakConfig } from '../..';
|
|
||||||
|
|
||||||
interface UserMenuProps {
|
interface UserMenuProps {
|
||||||
user: { name: string; image: string }
|
user: { name: string; image: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserMenu = ({ user }: UserMenuProps) => {
|
const UserMenu = ({ user }: UserMenuProps) => {
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation()
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
{ lng: 'en', name: 'Eng' },
|
||||||
|
{ lng: 'ru', name: 'Rus' },
|
||||||
|
]
|
||||||
|
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
const isMiddleScreen = useMediaQuery(dimensions.middleScreenSize)
|
const isMiddleScreen = useMediaQuery(dimensions.middleScreenSize)
|
||||||
|
|
||||||
@ -23,6 +31,24 @@ const UserMenu = ({ user }: UserMenuProps) => {
|
|||||||
await auth.signoutRedirect({ post_logout_redirect_uri: keycloakConfig.redirect_uri, id_token_hint: id_token_hint })
|
await auth.signoutRedirect({ post_logout_redirect_uri: keycloakConfig.redirect_uri, id_token_hint: id_token_hint })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleChangeLanguage = async (lng: string) => {
|
||||||
|
await i18n.changeLanguage(lng)
|
||||||
|
}
|
||||||
|
|
||||||
|
const languageSelector = useCallback(() => {
|
||||||
|
return languages.map(lang => (
|
||||||
|
<Button
|
||||||
|
key={lang.lng}
|
||||||
|
size='xs'
|
||||||
|
component="a"
|
||||||
|
variant="outline"
|
||||||
|
disabled={i18n.resolvedLanguage === lang.lng}
|
||||||
|
onClick={() => handleChangeLanguage(lang.lng)}>
|
||||||
|
{lang.name}
|
||||||
|
</Button>
|
||||||
|
))
|
||||||
|
}, [i18n.resolvedLanguage])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
width={260}
|
width={260}
|
||||||
@ -43,20 +69,17 @@ const UserMenu = ({ user }: UserMenuProps) => {
|
|||||||
{
|
{
|
||||||
isMiddleScreen ?
|
isMiddleScreen ?
|
||||||
<Flex w='100%' justify='space-between' align='center'>
|
<Flex w='100%' justify='space-between' align='center'>
|
||||||
<Text fz='sm' ml='0.7rem'>{strings.changeTheme}</Text>
|
<Text fz='sm' ml='0.7rem'>{t('changeTheme')}</Text>
|
||||||
<ColorSchemeToggle />
|
<ColorSchemeToggle />
|
||||||
</Flex>
|
</Flex>
|
||||||
:
|
:
|
||||||
<></>
|
<></>
|
||||||
}
|
}
|
||||||
{/* <Menu.Item>
|
{
|
||||||
{strings.settings}
|
languageSelector()
|
||||||
</Menu.Item>
|
}
|
||||||
<Menu.Item onClick={handleAboutMe}>
|
|
||||||
{strings.aboutMe}
|
|
||||||
</Menu.Item> */}
|
|
||||||
<Menu.Item onClick={handleLogout}>
|
<Menu.Item onClick={handleLogout}>
|
||||||
{strings.logout}
|
{t('logout')}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import { Accordion, Center, Loader } from '@mantine/core';
|
import { Accordion, Center, Loader } from '@mantine/core';
|
||||||
import React, { useContext, useMemo } from 'react';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
|
||||||
import DayAccordion from './DayAccordion';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useContext, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '../../..';
|
import { Context } from '../../..';
|
||||||
import { getResolvedTimeZone, parseQueryDateToDate } from '../../utils/dateUtil';
|
import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
||||||
import RetryError from '../RetryError';
|
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import { RecordSummary } from '../../../types/record';
|
import { RecordSummary } from '../../../types/record';
|
||||||
import { isProduction } from '../../env.const';
|
import { isProduction } from '../../env.const';
|
||||||
|
import { getResolvedTimeZone, parseQueryDateToDate } from '../../utils/dateUtil';
|
||||||
|
import RetryError from '../RetryError';
|
||||||
|
import DayAccordion from './DayAccordion';
|
||||||
|
|
||||||
const CameraAccordion = () => {
|
const CameraAccordion = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
const camera = recStore.openedCamera || recStore.filteredCamera
|
const camera = recStore.openedCamera || recStore.filteredCamera
|
||||||
@ -30,7 +31,7 @@ const CameraAccordion = () => {
|
|||||||
|
|
||||||
const recodItem = (record: RecordSummary) => (
|
const recodItem = (record: RecordSummary) => (
|
||||||
<Accordion.Item key={record.day} value={record.day}>
|
<Accordion.Item key={record.day} value={record.day}>
|
||||||
<Accordion.Control key={record.day + 'control'}>{strings.day}: {record.day}</Accordion.Control>
|
<Accordion.Control key={record.day + 'control'}>{t('day')}: {record.day}</Accordion.Control>
|
||||||
<Accordion.Panel key={record.day + 'panel'}>
|
<Accordion.Panel key={record.day + 'panel'}>
|
||||||
<DayAccordion key={record.day + 'day'} recordSummary={record} />
|
<DayAccordion key={record.day + 'day'} recordSummary={record} />
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Accordion, Flex, Group, Text } from '@mantine/core';
|
import { Accordion, Flex, Group, Text } from '@mantine/core';
|
||||||
import { IconExternalLink } from '@tabler/icons-react';
|
import { IconExternalLink } from '@tabler/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { routesPath } from '../../../router/routes.path';
|
import { routesPath } from '../../../router/routes.path';
|
||||||
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
||||||
import { RecordHour, RecordSummary } from '../../../types/record';
|
import { RecordHour, RecordSummary } from '../../../types/record';
|
||||||
import { isProduction } from '../../env.const';
|
import { isProduction } from '../../env.const';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import { getResolvedTimeZone, mapDateHourToUnixTime } from '../../utils/dateUtil';
|
import { getResolvedTimeZone, mapDateHourToUnixTime } from '../../utils/dateUtil';
|
||||||
import AccordionControlButton from '../buttons/AccordionControlButton';
|
import AccordionControlButton from '../buttons/AccordionControlButton';
|
||||||
import AccordionShareButton from '../buttons/AccordionShareButton';
|
import AccordionShareButton from '../buttons/AccordionShareButton';
|
||||||
@ -30,6 +30,7 @@ const DayAccordionItem = ({
|
|||||||
played,
|
played,
|
||||||
openPlayer
|
openPlayer
|
||||||
}: DayAccordionItemProps) => {
|
}: DayAccordionItemProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [playedURL, setPlayedUrl] = useState<string>()
|
const [playedURL, setPlayedUrl] = useState<string>()
|
||||||
|
|
||||||
@ -72,11 +73,11 @@ const DayAccordionItem = ({
|
|||||||
<Accordion.Control key={hour + 'Control'}>
|
<Accordion.Control key={hour + 'Control'}>
|
||||||
<Flex justify='space-between'>
|
<Flex justify='space-between'>
|
||||||
<Group>
|
<Group>
|
||||||
<Text>{strings.hour}: {hour}:00</Text>
|
<Text>{t('hour')}: {hour}:00</Text>
|
||||||
{recordHour.events > 0 ?
|
{recordHour.events > 0 ?
|
||||||
<Text>{strings.events}: {recordHour.events}</Text>
|
<Text>{t('events')}: {recordHour.events}</Text>
|
||||||
:
|
:
|
||||||
<Text>{strings.notHaveEvents}</Text>
|
<Text>{t('notHaveEvents')}</Text>
|
||||||
}
|
}
|
||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Accordion, Text } from '@mantine/core';
|
import { Accordion, Text } from '@mantine/core';
|
||||||
import React, { Suspense, lazy, useState } from 'react';
|
import { Suspense, lazy, useState } from 'react';
|
||||||
import { strings } from '../../strings/strings';
|
import { useTranslation } from 'react-i18next';
|
||||||
const EventsAccordion = lazy(() => import('./EventsAccordion'))
|
const EventsAccordion = lazy(() => import('./EventsAccordion'))
|
||||||
|
|
||||||
interface DayEventsAccordionProps {
|
interface DayEventsAccordionProps {
|
||||||
@ -14,6 +14,7 @@ const DayEventsAccordion = ({
|
|||||||
hour,
|
hour,
|
||||||
qty,
|
qty,
|
||||||
}: DayEventsAccordionProps) => {
|
}: DayEventsAccordionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [openedItem, setOpenedItem] = useState<string>()
|
const [openedItem, setOpenedItem] = useState<string>()
|
||||||
|
|
||||||
const handleClick = (value: string | null) => {
|
const handleClick = (value: string | null) => {
|
||||||
@ -22,7 +23,7 @@ const DayEventsAccordion = ({
|
|||||||
return (
|
return (
|
||||||
<Accordion onChange={handleClick}>
|
<Accordion onChange={handleClick}>
|
||||||
<Accordion.Item value={hour}>
|
<Accordion.Item value={hour}>
|
||||||
<Accordion.Control><Text>{strings.events}: {qty}</Text></Accordion.Control>
|
<Accordion.Control><Text>{t('events')}: {qty}</Text></Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
{openedItem === hour ?
|
{openedItem === hour ?
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Accordion, Center, Flex, Text } from '@mantine/core';
|
import { Accordion, Center, Flex, Text } from '@mantine/core';
|
||||||
import VideoDownloader from '../../../widgets/VideoDownloader';
|
import VideoDownloader from '../../../widgets/VideoDownloader';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import VideoPlayer from '../players/VideoPlayer';
|
import VideoPlayer from '../players/VideoPlayer';
|
||||||
import DayEventsAccordion from './DayEventsAccordion';
|
import DayEventsAccordion from './DayEventsAccordion';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface DayPanelProps {
|
interface DayPanelProps {
|
||||||
day: string,
|
day: string,
|
||||||
@ -27,6 +27,7 @@ const DayPanel = ({
|
|||||||
startUnixTime,
|
startUnixTime,
|
||||||
endUnixTime,
|
endUnixTime,
|
||||||
}: DayPanelProps) => {
|
}: DayPanelProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Accordion.Panel key={hour + 'Panel'}>
|
<Accordion.Panel key={hour + 'Panel'}>
|
||||||
{playedURL && playedURL === videoURL ? <VideoPlayer videoUrl={playedURL} /> : <></>}
|
{playedURL && playedURL === videoURL ? <VideoPlayer videoUrl={playedURL} /> : <></>}
|
||||||
@ -43,7 +44,7 @@ const DayPanel = ({
|
|||||||
{events > 0 ?
|
{events > 0 ?
|
||||||
<DayEventsAccordion day={day} hour={hour} qty={events} />
|
<DayEventsAccordion day={day} hour={hour} qty={events} />
|
||||||
:
|
:
|
||||||
<Center><Text>{strings.notHaveEvents}</Text></Center>
|
<Center><Text>{t('notHaveEvents')}</Text></Center>
|
||||||
}
|
}
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { Button, Flex, Text } from '@mantine/core';
|
|||||||
import { IconExternalLink } from '@tabler/icons-react';
|
import { IconExternalLink } from '@tabler/icons-react';
|
||||||
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
||||||
import { EventFrigate } from '../../../types/event';
|
import { EventFrigate } from '../../../types/event';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import { getDurationFromTimestamps, unixTimeToDate } from '../../utils/dateUtil';
|
import { getDurationFromTimestamps, unixTimeToDate } from '../../utils/dateUtil';
|
||||||
import BlobImage from '../images/BlobImage';
|
import BlobImage from '../images/BlobImage';
|
||||||
import VideoPlayer from '../players/VideoPlayer';
|
import VideoPlayer from '../players/VideoPlayer';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface EventPanelProps {
|
interface EventPanelProps {
|
||||||
event: EventFrigate
|
event: EventFrigate
|
||||||
@ -20,6 +20,7 @@ const EventPanel = ({
|
|||||||
videoURL,
|
videoURL,
|
||||||
playedURL,
|
playedURL,
|
||||||
}: EventPanelProps) => {
|
}: EventPanelProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -45,12 +46,12 @@ const EventPanel = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
<Text mt='1rem'>{strings.camera}: {event.camera}</Text>
|
<Text mt='1rem'>{t('camera')}: {event.camera}</Text>
|
||||||
<Text>{strings.player.object}: {event.label}</Text>
|
<Text>{t('player.object')}: {event.label}</Text>
|
||||||
<Text>{strings.player.startTime}: {unixTimeToDate(event.start_time)}</Text>
|
<Text>{t('player.startTime')}: {unixTimeToDate(event.start_time)}</Text>
|
||||||
<Text>{strings.player.duration}: {getDurationFromTimestamps(event.start_time, event.end_time)}</Text>
|
<Text>{t('player.duration')}: {getDurationFromTimestamps(event.start_time, event.end_time)}</Text>
|
||||||
{!event.data?.score? <></> :
|
{!event.data?.score? <></> :
|
||||||
<Text>{strings.player.rating}: {(event.data.score * 100).toFixed(2)}%</Text>
|
<Text>{t('player.rating')}: {(event.data.score * 100).toFixed(2)}%</Text>
|
||||||
}
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import AccordionShareButton from '../buttons/AccordionShareButton';
|
|||||||
import PlayControl from '../buttons/PlayControl';
|
import PlayControl from '../buttons/PlayControl';
|
||||||
import EventPanel from './EventPanel';
|
import EventPanel from './EventPanel';
|
||||||
import { EventFrigate } from '../../../types/event';
|
import { EventFrigate } from '../../../types/event';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import { unixTimeToDate, getDurationFromTimestamps } from '../../utils/dateUtil';
|
import { unixTimeToDate, getDurationFromTimestamps } from '../../utils/dateUtil';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { routesPath } from '../../../router/routes.path';
|
import { routesPath } from '../../../router/routes.path';
|
||||||
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
interface EventsAccordionItemProps {
|
interface EventsAccordionItemProps {
|
||||||
@ -25,8 +25,8 @@ const EventsAccordionItem = ({
|
|||||||
hostName,
|
hostName,
|
||||||
played,
|
played,
|
||||||
openPlayer,
|
openPlayer,
|
||||||
} : EventsAccordionItemProps) => {
|
}: EventsAccordionItemProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [playedURL, setPlayedUrl] = useState<string>()
|
const [playedURL, setPlayedUrl] = useState<string>()
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -44,7 +44,7 @@ const EventsAccordionItem = ({
|
|||||||
const duration = getDurationFromTimestamps(event.start_time, event.end_time)
|
const duration = getDurationFromTimestamps(event.start_time, event.end_time)
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Text>{strings.player.object}: {event.label}</Text>
|
<Text>{t('player.object')}: {event.label}</Text>
|
||||||
<Text>{time}</Text>
|
<Text>{time}</Text>
|
||||||
{duration ?
|
{duration ?
|
||||||
<Text>{duration}</Text>
|
<Text>{duration}</Text>
|
||||||
@ -84,7 +84,7 @@ const EventsAccordionItem = ({
|
|||||||
<IconExternalLink />
|
<IconExternalLink />
|
||||||
</AccordionControlButton>
|
</AccordionControlButton>
|
||||||
<PlayControl
|
<PlayControl
|
||||||
played={played ? played : false }
|
played={played ? played : false}
|
||||||
onClick={handleOpenPlayer} />
|
onClick={handleOpenPlayer} />
|
||||||
</Group>
|
</Group>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Button, ButtonProps, Group, UnstyledButton, createStyles } from '@mantine/core';
|
import { ButtonProps, Group, createStyles } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Tooltip, CloseButton, CloseButtonProps } from '@mantine/core';
|
import { Tooltip, CloseButton, CloseButtonProps } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
|
|
||||||
|
|
||||||
interface CloseWithTooltipProps {
|
interface CloseWithTooltipProps {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Flex, createStyles } from '@mantine/core';
|
import { Flex, createStyles } from '@mantine/core';
|
||||||
import { IconPlayerPlayFilled, IconPlayerStopFilled } from '@tabler/icons-react';
|
import { IconPlayerPlayFilled, IconPlayerStopFilled } from '@tabler/icons-react';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import AccordionControlButton from './AccordionControlButton';
|
import AccordionControlButton from './AccordionControlButton';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
@ -22,6 +22,7 @@ const PlayControl = ({
|
|||||||
played,
|
played,
|
||||||
onClick
|
onClick
|
||||||
}: PlayControlProps) => {
|
}: PlayControlProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
@ -32,7 +33,7 @@ const PlayControl = ({
|
|||||||
onClick={() => { handleClick() }}
|
onClick={() => { handleClick() }}
|
||||||
>
|
>
|
||||||
<Flex align='center'>
|
<Flex align='center'>
|
||||||
{played ? strings.player.stopVideo : strings.player.startVideo}
|
{played ? t('player.stopVideo') : t('player.startVideo')}
|
||||||
{played ?
|
{played ?
|
||||||
<IconPlayerStopFilled
|
<IconPlayerStopFilled
|
||||||
className={classes.iconStop} />
|
className={classes.iconStop} />
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/fr
|
|||||||
import CogwheelLoader from '../loaders/CogwheelLoader';
|
import CogwheelLoader from '../loaders/CogwheelLoader';
|
||||||
import { Center, Loader, Text } from '@mantine/core';
|
import { Center, Loader, Text } from '@mantine/core';
|
||||||
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import RetryError from '../RetryError';
|
import RetryError from '../RetryError';
|
||||||
import { isProduction } from '../../env.const';
|
import { isProduction } from '../../env.const';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface CameraSelectFilterProps {
|
interface CameraSelectFilterProps {
|
||||||
selectedHostId: string,
|
selectedHostId: string,
|
||||||
@ -17,6 +17,7 @@ interface CameraSelectFilterProps {
|
|||||||
const CameraSelectFilter = ({
|
const CameraSelectFilter = ({
|
||||||
selectedHostId,
|
selectedHostId,
|
||||||
}: CameraSelectFilterProps) => {
|
}: CameraSelectFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
const { data, isError, isPending, isSuccess, refetch } = useQuery({
|
const { data, isError, isPending, isSuccess, refetch } = useQuery({
|
||||||
@ -53,7 +54,7 @@ const CameraSelectFilter = ({
|
|||||||
return (
|
return (
|
||||||
<OneSelectFilter
|
<OneSelectFilter
|
||||||
id='frigate-cameras'
|
id='frigate-cameras'
|
||||||
label={strings.selectCamera}
|
label={t('selectCamera')}
|
||||||
spaceBetween='1rem'
|
spaceBetween='1rem'
|
||||||
value={recStore.filteredCamera?.id || ''}
|
value={recStore.filteredCamera?.id || ''}
|
||||||
defaultValue={recStore.filteredCamera?.id || ''}
|
defaultValue={recStore.filteredCamera?.id || ''}
|
||||||
|
|||||||
@ -2,15 +2,16 @@ import { Box, Flex, Indicator, Text } from '@mantine/core';
|
|||||||
import { DatePickerInput } from '@mantine/dates';
|
import { DatePickerInput } from '@mantine/dates';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '../../..';
|
import { Context } from '../../..';
|
||||||
import { isProduction } from '../../env.const';
|
import { isProduction } from '../../env.const';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
|
|
||||||
interface DateRangeSelectFilterProps {}
|
interface DateRangeSelectFilterProps {}
|
||||||
|
|
||||||
const DateRangeSelectFilter = ({
|
const DateRangeSelectFilter = ({
|
||||||
|
|
||||||
}: DateRangeSelectFilterProps) => {
|
}: DateRangeSelectFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
const handlePick = (value: [Date | null, Date | null]) => {
|
const handlePick = (value: [Date | null, Date | null]) => {
|
||||||
@ -23,7 +24,7 @@ const DateRangeSelectFilter = ({
|
|||||||
<Flex
|
<Flex
|
||||||
mt='1rem'
|
mt='1rem'
|
||||||
justify='space-between'>
|
justify='space-between'>
|
||||||
<Text>{strings.selectRange}</Text>
|
<Text>{t('selectRange')}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DatePickerInput
|
<DatePickerInput
|
||||||
w='100%'
|
w='100%'
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { frigateQueryKeys, frigateApi } from '../../../services/frigate.proxy/frigate.api';
|
import { frigateQueryKeys, frigateApi } from '../../../services/frigate.proxy/frigate.api';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import RetryError from '../RetryError';
|
import RetryError from '../RetryError';
|
||||||
import CogwheelLoader from '../loaders/CogwheelLoader';
|
import CogwheelLoader from '../loaders/CogwheelLoader';
|
||||||
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { SystemProp, SpacingValue, Box, Flex, CloseButton, MultiSelect, SelectItem, MultiSelectProps, Text, Tooltip } from '@mantine/core';
|
import { Box, Flex, MultiSelect, MultiSelectProps, SelectItem, SpacingValue, SystemProp, Text } from '@mantine/core';
|
||||||
import React, { CSSProperties, useState } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
import { strings } from '../../strings/strings';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
|
|
||||||
interface MultiSelectFilterProps {
|
interface MultiSelectFilterProps {
|
||||||
@ -22,6 +22,7 @@ const MultiSelectFilter = ({
|
|||||||
label, defaultValue, textClassName,
|
label, defaultValue, textClassName,
|
||||||
selectProps, display, showClose, changedState, onClose
|
selectProps, display, showClose, changedState, onClose
|
||||||
}: MultiSelectFilterProps) => {
|
}: MultiSelectFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleOnChange = (value: string[]) => {
|
const handleOnChange = (value: string[]) => {
|
||||||
if (changedState) {
|
if (changedState) {
|
||||||
@ -34,7 +35,7 @@ const MultiSelectFilter = ({
|
|||||||
<Flex justify='space-between'>
|
<Flex justify='space-between'>
|
||||||
<Text className={textClassName}>{label}</Text>
|
<Text className={textClassName}>{label}</Text>
|
||||||
{showClose ?
|
{showClose ?
|
||||||
<CloseWithTooltip label={strings.hide} onClose={onClose} />
|
<CloseWithTooltip label={t('hide')} onClose={onClose} />
|
||||||
: null}
|
: null}
|
||||||
</Flex>
|
</Flex>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { SelectItem, SystemProp, SpacingValue, SelectProps, Box, Flex, CloseButton, Text, Select } from '@mantine/core';
|
import { Box, Flex, Select, SelectProps, SpacingValue, SystemProp, Text } from '@mantine/core';
|
||||||
import React, { CSSProperties } from 'react';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
|
|
||||||
|
|
||||||
export interface OneSelectItem {
|
export interface OneSelectItem {
|
||||||
@ -30,6 +29,7 @@ const OneSelectFilter = ({
|
|||||||
label, defaultValue, textClassName,
|
label, defaultValue, textClassName,
|
||||||
showClose, value, onChange: onChange, onClose, ...selectProps
|
showClose, value, onChange: onChange, onClose, ...selectProps
|
||||||
}: OneSelectFilterProps) => {
|
}: OneSelectFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleOnChange = (value: string) => {
|
const handleOnChange = (value: string) => {
|
||||||
if (onChange) onChange(value, id,)
|
if (onChange) onChange(value, id,)
|
||||||
@ -44,7 +44,7 @@ const OneSelectFilter = ({
|
|||||||
{!label ? null :
|
{!label ? null :
|
||||||
<Flex justify='space-between'>
|
<Flex justify='space-between'>
|
||||||
<Text className={textClassName}>{label}</Text>
|
<Text className={textClassName}>{label}</Text>
|
||||||
{showClose ? <CloseWithTooltip label={strings.hide} onClose={handleOnClose} />
|
{showClose ? <CloseWithTooltip label={t('hide')} onClose={handleOnClose} />
|
||||||
: null}
|
: null}
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { SystemProp, SpacingValue, SliderProps, Box, RangeSlider, RangeSliderProps, Text, Flex, CloseButton } from '@mantine/core';
|
import { Box, Flex, RangeSlider, RangeSliderProps, SpacingValue, SystemProp, Text } from '@mantine/core';
|
||||||
import React, { CSSProperties, useState } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
|
|
||||||
interface SliderFilterProps {
|
interface SliderFilterProps {
|
||||||
id: string
|
id: string
|
||||||
@ -16,13 +16,14 @@ interface SliderFilterProps {
|
|||||||
display?: SystemProp<CSSProperties['display']>
|
display?: SystemProp<CSSProperties['display']>
|
||||||
showClose?: boolean,
|
showClose?: boolean,
|
||||||
changedState?(id: string, value: [number, number]): void
|
changedState?(id: string, value: [number, number]): void
|
||||||
onClose?():void
|
onClose?(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const RangeSliderFilter = ({ id, min, max, value, spaceBetween,
|
const RangeSliderFilter = ({ id, min, max, value, spaceBetween,
|
||||||
label, defaultValue, textClassName,
|
label, defaultValue, textClassName,
|
||||||
sliderProps, display, showClose, changedState, onClose }: SliderFilterProps) => {
|
sliderProps, display, showClose, changedState, onClose }: SliderFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const handleOnChange = (value: [number, number]) => {
|
const handleOnChange = (value: [number, number]) => {
|
||||||
if (changedState) {
|
if (changedState) {
|
||||||
changedState(id, value)
|
changedState(id, value)
|
||||||
@ -33,7 +34,7 @@ const RangeSliderFilter = ({ id, min, max, value, spaceBetween,
|
|||||||
<Box display={display} mt={spaceBetween}>
|
<Box display={display} mt={spaceBetween}>
|
||||||
<Flex justify='space-between'>
|
<Flex justify='space-between'>
|
||||||
<Text className={textClassName}>{label}</Text>
|
<Text className={textClassName}>{label}</Text>
|
||||||
{showClose? <CloseWithTooltip label={strings.hide} onClose={onClose} /> : null}
|
{showClose ? <CloseWithTooltip label={t('hide')} onClose={onClose} /> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
<RangeSlider {...sliderProps} value={value} onChangeEnd={handleOnChange} min={min} max={max} defaultValue={defaultValue} pl='1rem' mt='0.5rem' />
|
<RangeSlider {...sliderProps} value={value} onChangeEnd={handleOnChange} min={min} max={max} defaultValue={defaultValue} pl='1rem' mt='0.5rem' />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -3,10 +3,11 @@ import { observer } from 'mobx-react-lite';
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Context } from '../../..';
|
import { Context } from '../../..';
|
||||||
import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/frigate.api';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import HostSelect from './HostSelect';
|
import HostSelect from './HostSelect';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const RecordingsHostFilter = () => {
|
const RecordingsHostFilter = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
const { data: hosts } = useQuery({
|
const { data: hosts } = useQuery({
|
||||||
@ -38,7 +39,7 @@ const RecordingsHostFilter = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HostSelect
|
<HostSelect
|
||||||
label={strings.selectHost}
|
label={t('selectHost')}
|
||||||
valueId={recStore.filteredHost?.id}
|
valueId={recStore.filteredHost?.id}
|
||||||
defaultId={recStore.filteredHost?.id}
|
defaultId={recStore.filteredHost?.id}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Box, CloseButton, Flex, Slider, SliderProps, SpacingValue, SystemProp, Text } from '@mantine/core';
|
import { Box, Flex, Slider, SliderProps, SpacingValue, SystemProp, Text } from '@mantine/core';
|
||||||
import React, { CSSProperties, useState, } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
|
|
||||||
interface SliderFilterProps {
|
interface SliderFilterProps {
|
||||||
id: string
|
id: string
|
||||||
@ -16,11 +16,12 @@ interface SliderFilterProps {
|
|||||||
display?: SystemProp<CSSProperties['display']>
|
display?: SystemProp<CSSProperties['display']>
|
||||||
showClose?: boolean,
|
showClose?: boolean,
|
||||||
changedState?(id: string, value: number): void
|
changedState?(id: string, value: number): void
|
||||||
onClose?():void
|
onClose?(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SliderFilter = ({ id, min, max, value, spaceBetween, label, defaultValue, textClassName, sliderProps, display, showClose, changedState, onClose }: SliderFilterProps) => {
|
const SliderFilter = ({ id, min, max, value, spaceBetween, label, defaultValue, textClassName, sliderProps, display, showClose, changedState, onClose }: SliderFilterProps) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleOnChange = (value: number) => {
|
const handleOnChange = (value: number) => {
|
||||||
if (changedState) {
|
if (changedState) {
|
||||||
@ -32,7 +33,7 @@ const SliderFilter = ({ id, min, max, value, spaceBetween, label, defaultValue,
|
|||||||
<Box display={display} mt={spaceBetween}>
|
<Box display={display} mt={spaceBetween}>
|
||||||
<Flex justify='space-between'>
|
<Flex justify='space-between'>
|
||||||
<Text className={textClassName}>{label}</Text>
|
<Text className={textClassName}>{label}</Text>
|
||||||
{showClose ? <CloseWithTooltip label={strings.hide} onClose={onClose} /> : null}
|
{showClose ? <CloseWithTooltip label={t('hide')} onClose={onClose} /> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Slider {...sliderProps}
|
<Slider {...sliderProps}
|
||||||
onChangeEnd={handleOnChange}
|
onChangeEnd={handleOnChange}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { SystemProp, SpacingValue, Flex, Switch, Text, CloseButton, Group, Box } from '@mantine/core';
|
import { Box, Flex, SpacingValue, Switch, SystemProp, Text } from '@mantine/core';
|
||||||
import React, { CSSProperties, ChangeEvent } from 'react';
|
import { CSSProperties, ChangeEvent } from 'react';
|
||||||
import { boolean } from 'zod';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
|
|
||||||
interface SwitchFilterProps {
|
interface SwitchFilterProps {
|
||||||
id: string
|
id: string
|
||||||
@ -14,7 +13,7 @@ interface SwitchFilterProps {
|
|||||||
display?: SystemProp<CSSProperties['display']>
|
display?: SystemProp<CSSProperties['display']>
|
||||||
showClose?: boolean
|
showClose?: boolean
|
||||||
changedState?(id: string, value: boolean): void
|
changedState?(id: string, value: boolean): void
|
||||||
onClose?():void
|
onClose?(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SwitchChangeState {
|
export interface SwitchChangeState {
|
||||||
@ -23,6 +22,7 @@ export interface SwitchChangeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SwitchFilter = ({ id, value, defaultValue, spaceBetween, label, textClassName, display, showClose, changedState, onClose }: SwitchFilterProps) => {
|
const SwitchFilter = ({ id, value, defaultValue, spaceBetween, label, textClassName, display, showClose, changedState, onClose }: SwitchFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const handleChange = (event: ChangeEvent<HTMLInputElement> | undefined) => {
|
const handleChange = (event: ChangeEvent<HTMLInputElement> | undefined) => {
|
||||||
const checked = event?.currentTarget.checked
|
const checked = event?.currentTarget.checked
|
||||||
if (changedState && typeof checked === 'boolean') {
|
if (changedState && typeof checked === 'boolean') {
|
||||||
@ -35,7 +35,7 @@ const SwitchFilter = ({ id, value, defaultValue, spaceBetween, label, textClassN
|
|||||||
<Flex align='center' w='100%'>
|
<Flex align='center' w='100%'>
|
||||||
<Text className={textClassName}>{label}</Text>
|
<Text className={textClassName}>{label}</Text>
|
||||||
<Switch onChange={handleChange} checked={value} defaultChecked={defaultValue} ml='lg' mr='md' />
|
<Switch onChange={handleChange} checked={value} defaultChecked={defaultValue} ml='lg' mr='md' />
|
||||||
{showClose ? <CloseWithTooltip label={strings.hide} onClose={onClose} /> : null}
|
{showClose ? <CloseWithTooltip label={t('hide')} onClose={onClose} /> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
43
src/shared/components/inputs/ClearableTextInput.tsx
Normal file
43
src/shared/components/inputs/ClearableTextInput.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { CloseButton, TextInput, TextInputProps } from '@mantine/core';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface ClearableTextInputProps extends TextInputProps {
|
||||||
|
clerable?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const ClearableTextInput: React.FC<ClearableTextInputProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
clerable,
|
||||||
|
...textInputProps
|
||||||
|
}) => {
|
||||||
|
const [text, setText] = useState(value)
|
||||||
|
|
||||||
|
const handleClear = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
setText(''); // обновляем локальное состояние
|
||||||
|
if (onChange) {
|
||||||
|
const fakeEvent = {
|
||||||
|
target: { value: '' },
|
||||||
|
currentTarget: { value: '' }
|
||||||
|
} as unknown as React.ChangeEvent<HTMLInputElement>;
|
||||||
|
onChange(fakeEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (value: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (onChange) onChange(value)
|
||||||
|
setText(value.currentTarget.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
rightSection={clerable ? <CloseButton onClick={handleClear} /> : null}
|
||||||
|
value={text}
|
||||||
|
onChange={handleChange}
|
||||||
|
{...textInputProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClearableTextInput;
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { ActionIcon, CloseButton, Flex, Modal, NumberInput, TextInput, Tooltip, createStyles, } from '@mantine/core';
|
import { ActionIcon, CloseButton, Flex, Modal, NumberInput, TextInput, Tooltip, createStyles, } from '@mantine/core';
|
||||||
import { getHotkeyHandler, useMediaQuery } from '@mantine/hooks';
|
import { getHotkeyHandler, useMediaQuery } from '@mantine/hooks';
|
||||||
import React, { ReactEventHandler, useState, FocusEvent, useRef, Ref } from 'react';
|
import React, { ReactEventHandler, useState, FocusEvent, useRef, Ref } from 'react';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import { IconAlertCircle, IconX } from '@tabler/icons-react';
|
import { IconAlertCircle, IconX } from '@tabler/icons-react';
|
||||||
import { dimensions } from '../../dimensions/dimensions';
|
import { dimensions } from '../../dimensions/dimensions';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
rightSection: {
|
rightSection: {
|
||||||
@ -21,6 +21,7 @@ interface InputModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps) => {
|
const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { classes } = useStyles()
|
const { classes } = useStyles()
|
||||||
const [value, setValue] = useState(inValue)
|
const [value, setValue] = useState(inValue)
|
||||||
const isMobile = useMediaQuery(dimensions.mobileSize)
|
const isMobile = useMediaQuery(dimensions.mobileSize)
|
||||||
@ -54,7 +55,7 @@ const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps)
|
|||||||
fullScreen={isMobile}
|
fullScreen={isMobile}
|
||||||
>
|
>
|
||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
<div>{strings.enterQuantity}</div>
|
<div>{t('enterQuantity')}</div>
|
||||||
<CloseButton size="lg" onClick={handleClose} />
|
<CloseButton size="lg" onClick={handleClose} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
@ -64,7 +65,7 @@ const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps)
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={handleSetValue}
|
onChange={handleSetValue}
|
||||||
data-autofocus
|
data-autofocus
|
||||||
placeholder={strings.quantity}
|
placeholder={t('quantity')}
|
||||||
hideControls
|
hideControls
|
||||||
min={0}
|
min={0}
|
||||||
onFocus={handeLoaded}
|
onFocus={handeLoaded}
|
||||||
@ -73,9 +74,9 @@ const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps)
|
|||||||
['Enter', handleClose]
|
['Enter', handleClose]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
rightSection={ // value.toString().length > 0 ? <ActionIcon onClick={(event) => handeClear()}><IconX size="1.4rem" /></ActionIcon> : null // todo move to textinput
|
rightSection={
|
||||||
<Flex w='100%' h='100%' justify='right' align='center'>
|
<Flex w='100%' h='100%' justify='right' align='center'>
|
||||||
<Tooltip label={strings.tooltip_close} position="top-end" withArrow>
|
<Tooltip label={t('tooltipСlose')} position="top-end" withArrow>
|
||||||
<div>
|
<div>
|
||||||
<IconAlertCircle size="1.4rem" style={{ display: 'block', opacity: 0.5 }} />
|
<IconAlertCircle size="1.4rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
// @ts-ignore we know this doesn't have types
|
// @ts-ignore we know this doesn't have types
|
||||||
import JSMpeg from "@cycjimmy/jsmpeg-player";
|
import JSMpeg from "@cycjimmy/jsmpeg-player";
|
||||||
import { Flex } from "@mantine/core";
|
|
||||||
import { useViewportSize } from "@mantine/hooks";
|
import { useViewportSize } from "@mantine/hooks";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { strings } from "../../strings/strings";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type JSMpegPlayerProps = {
|
type JSMpegPlayerProps = {
|
||||||
wsUrl: string;
|
wsUrl: string;
|
||||||
@ -18,6 +17,7 @@ const JSMpegPlayer = (
|
|||||||
cameraHeight = 800,
|
cameraHeight = 800,
|
||||||
}: JSMpegPlayerProps
|
}: JSMpegPlayerProps
|
||||||
) => {
|
) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const playerRef = useRef<HTMLDivElement>(null);
|
const playerRef = useRef<HTMLDivElement>(null);
|
||||||
const [playerInitialized, setPlayerInitialized] = useState(false)
|
const [playerInitialized, setPlayerInitialized] = useState(false)
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ const JSMpegPlayer = (
|
|||||||
playerRef.current,
|
playerRef.current,
|
||||||
wsUrl,
|
wsUrl,
|
||||||
{},
|
{},
|
||||||
{protocols: [], audio: false, videoBufferSize: 1024*1024*4}
|
{ protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
const toggleFullscreen = () => {
|
||||||
@ -54,7 +54,7 @@ const JSMpegPlayer = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
video.els.canvas.addEventListener('dblclick',toggleFullscreen);
|
video.els.canvas.addEventListener('dblclick', toggleFullscreen);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
video.destroy();
|
video.destroy();
|
||||||
@ -66,8 +66,8 @@ const JSMpegPlayer = (
|
|||||||
<div
|
<div
|
||||||
ref={playerRef}
|
ref={playerRef}
|
||||||
key={wsUrl}
|
key={wsUrl}
|
||||||
title={strings.player.doubleClickToFullHint}
|
title={t('player.doubleClickToFullHint')}
|
||||||
style={{width:cameraWidth, height:cameraHeight, maxWidth:maxWidth, maxHeight: maxHeight-100, }} />
|
style={{ width: cameraWidth, height: cameraHeight, maxWidth: maxWidth, maxHeight: maxHeight - 100, }} />
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,53 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { productString } from '../../strings/product.strings';
|
|
||||||
import SortedTh from './SortedTh';
|
|
||||||
|
|
||||||
interface TableHeadProps {
|
|
||||||
reverseSortDirection: boolean
|
|
||||||
sortBy: string | null
|
|
||||||
setSorting: (title: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProductsTableHead = ({ reverseSortDirection, sortBy, setSorting }: TableHeadProps) => {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<SortedTh
|
|
||||||
title={productString.name}
|
|
||||||
reversed={reverseSortDirection}
|
|
||||||
sortedName={sortBy}
|
|
||||||
onSort={setSorting}
|
|
||||||
/>
|
|
||||||
<SortedTh
|
|
||||||
title={productString.cost}
|
|
||||||
reversed={reverseSortDirection}
|
|
||||||
sortedName={sortBy}
|
|
||||||
onSort={setSorting}
|
|
||||||
/>
|
|
||||||
<SortedTh
|
|
||||||
title={productString.image}
|
|
||||||
reversed={reverseSortDirection}
|
|
||||||
sortedName={sortBy}
|
|
||||||
onSort={setSorting}
|
|
||||||
textProps={{ w: '6rem', truncate: true }}
|
|
||||||
/>
|
|
||||||
<SortedTh
|
|
||||||
title={productString.qty}
|
|
||||||
reversed={reverseSortDirection}
|
|
||||||
sortedName={sortBy}
|
|
||||||
onSort={setSorting}
|
|
||||||
textProps={{ w: '1rem', truncate: true }}
|
|
||||||
/>
|
|
||||||
<SortedTh
|
|
||||||
title={productString.buy}
|
|
||||||
reversed={reverseSortDirection}
|
|
||||||
sortedName={sortBy}
|
|
||||||
onSort={setSorting}
|
|
||||||
/>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProductsTableHead;
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
export const headerMenu = {
|
|
||||||
home:"На главную",
|
|
||||||
test:"Тест",
|
|
||||||
settings:"Настройки",
|
|
||||||
recordings:"Записи",
|
|
||||||
acessSettings:" Настройка доступа",
|
|
||||||
hostsConfig:"Серверы Frigate",
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
export const productString = {
|
|
||||||
name: "Наименование",
|
|
||||||
cost: "Цена",
|
|
||||||
image: "Изображение",
|
|
||||||
qty: "Количество",
|
|
||||||
buy: "Купить",
|
|
||||||
number: "Артикул",
|
|
||||||
manufactory: "Производитель",
|
|
||||||
oem: "OEM",
|
|
||||||
stock: "Наличие",
|
|
||||||
receiptDate: "Дата поступления",
|
|
||||||
discount:"Скидка",
|
|
||||||
parameters: "Характеристики",
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
export const strings = {
|
|
||||||
host: 'Хост',
|
|
||||||
hostArr: {
|
|
||||||
name: 'Имя хоста',
|
|
||||||
url: 'Адрес',
|
|
||||||
enabled: 'Включен',
|
|
||||||
},
|
|
||||||
player: {
|
|
||||||
startVideo: 'Вкл Видео',
|
|
||||||
stopVideo: 'Выкл Видео',
|
|
||||||
object: 'Объект',
|
|
||||||
duration: 'Длительность',
|
|
||||||
startTime: 'Начало',
|
|
||||||
endTime: 'Конец',
|
|
||||||
doubleClickToFullHint: 'Двойное нажатие мышью для полноэкранного просмотра',
|
|
||||||
rating: 'Рейтинг',
|
|
||||||
},
|
|
||||||
|
|
||||||
empty: 'Пусто',
|
|
||||||
pleaseSelectRole: 'Пожалуйста выберите роль',
|
|
||||||
nothingHere: 'Ни чего нет',
|
|
||||||
allowed: 'Разрешено',
|
|
||||||
notAllowed: 'Не разрешено',
|
|
||||||
camera: 'Камера',
|
|
||||||
camersDoesNotExist: 'Камер нет',
|
|
||||||
search: 'Поиск',
|
|
||||||
recordings: 'Записи',
|
|
||||||
hour: 'Час',
|
|
||||||
minute: 'Минута',
|
|
||||||
second: 'Секунда',
|
|
||||||
events: 'События',
|
|
||||||
event: 'Событие',
|
|
||||||
notHaveEvents: 'Нет событий',
|
|
||||||
date: 'Дата',
|
|
||||||
day: 'День',
|
|
||||||
selectHost:'Выбери хост',
|
|
||||||
selectCamera: 'Выбери камеру',
|
|
||||||
selectDate: 'Выбери дату',
|
|
||||||
selectRange: 'Выбери период',
|
|
||||||
aboutMe: "Обо мне",
|
|
||||||
settings: "Настройки",
|
|
||||||
changeTheme: "Изменить тему",
|
|
||||||
logout: "Выйти",
|
|
||||||
userProfile: "Профиль пользователя",
|
|
||||||
firstName: "Имя",
|
|
||||||
middleName: "Фамилия",
|
|
||||||
lastName: "Отчетство",
|
|
||||||
taxNumber: "ИНН",
|
|
||||||
phone: "Телефон",
|
|
||||||
managerName: "Имя менеджера",
|
|
||||||
managerPhone: "Телефон менеджера",
|
|
||||||
defaultContract: "Договор по умолчанию",
|
|
||||||
myShippingAddresses: "Мои адреса доставки:",
|
|
||||||
myConditions: "Мои условия:",
|
|
||||||
warehouse: "Склад",
|
|
||||||
name: "Имя",
|
|
||||||
schedule: "Расписание",
|
|
||||||
address: "Адрес",
|
|
||||||
edit: "Изменить",
|
|
||||||
delete: "Удалить",
|
|
||||||
error: "Ошибка",
|
|
||||||
discounts: "Скидки",
|
|
||||||
delivery: "Доставка",
|
|
||||||
selectDeliveryMethod: "Выберите метод доставки:",
|
|
||||||
pickUpByMyself: "Я заберу самостоятельно",
|
|
||||||
courierDelivery: "Доставка курьером",
|
|
||||||
deliveryPoint: "Адрес доставки",
|
|
||||||
selectYourDeliveryAddress: "Выберите свой адрес доставки:",
|
|
||||||
deliveryDate: "Дата доставки",
|
|
||||||
selectDeliveryDate: "Выберите дату доставки:",
|
|
||||||
enterQuantity: "Введите количество:",
|
|
||||||
quantity: "Количество",
|
|
||||||
tooltip_close: "нажмите Enter",
|
|
||||||
currency: "₽",
|
|
||||||
category: "Категории:",
|
|
||||||
collapse: "Свернуть",
|
|
||||||
hide: "Скрыть",
|
|
||||||
show: "Показать",
|
|
||||||
showAll: "Показать всё",
|
|
||||||
true: "Да",
|
|
||||||
false: "Нет",
|
|
||||||
// order section
|
|
||||||
cart: "Корзина",
|
|
||||||
order: "Заказ",
|
|
||||||
confirmOrder: "Подтвердить заказ",
|
|
||||||
orderParams: "Параметры",
|
|
||||||
chooseParams: "Выбрать параметры заказа",
|
|
||||||
payment: "Оплата",
|
|
||||||
inputPaymentValues: "Ввести платежные данные",
|
|
||||||
summary: "Общие итоги",
|
|
||||||
positions: "Позиции:",
|
|
||||||
weight: "Вес:",
|
|
||||||
total: "Итого:",
|
|
||||||
confirm: "Подтвердить",
|
|
||||||
save: "Сохранить",
|
|
||||||
discard: "Отменить",
|
|
||||||
next: "Далее",
|
|
||||||
back: "Назад",
|
|
||||||
paymentMethod: "Метод оплаты",
|
|
||||||
selectPaymentMethod: "Выберите метод оплаты",
|
|
||||||
cashToCourier: "Наличными курьеру",
|
|
||||||
bankTransfer: "Банковским переводом",
|
|
||||||
onlineByCard: "Онлайн банковской картой",
|
|
||||||
thanksForYourPurchase: "Спасибо за вашу покупку!",
|
|
||||||
orderConfirmed: (order: string) => `Ордер ${order} подтвержден!`,
|
|
||||||
goToMainPage: "Вернуться на главную",
|
|
||||||
goToOrder: "Посмотреть ордер",
|
|
||||||
retry: "Повторить",
|
|
||||||
youCanRetryOrGoToMain: "Вы можете повторить или вернуться на главную",
|
|
||||||
errors: {
|
|
||||||
somthengGoesWrong: "Что-то пошло не так",
|
|
||||||
cartIsEmpty: "Корзина пуста",
|
|
||||||
choosePaymentMethod: "Выберите метод оплаты",
|
|
||||||
chooseDeliveryMethod: "Выберите метод доставки",
|
|
||||||
chooseDeliveryPoint: "Выберите адрес доставки",
|
|
||||||
chooseDate: "Выберите дату",
|
|
||||||
403: "Извините у вас нет доступа",
|
|
||||||
404: "Извините мы не можем найти такую страницу",
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,7 +7,7 @@ import { routesPath } from '../router/routes.path';
|
|||||||
import { mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
import { mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||||
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
||||||
import AutoUpdatedImage from '../shared/components/images/AutoUpdatedImage';
|
import AutoUpdatedImage from '../shared/components/images/AutoUpdatedImage';
|
||||||
import { strings } from '../shared/strings/strings';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
mainCard: {
|
mainCard: {
|
||||||
@ -41,6 +41,7 @@ interface CameraCardProps {
|
|||||||
const CameraCard = ({
|
const CameraCard = ({
|
||||||
camera
|
camera
|
||||||
}: CameraCardProps) => {
|
}: CameraCardProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [renderImage, setRenderImage] = useState<boolean>(false)
|
const [renderImage, setRenderImage] = useState<boolean>(false)
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const { ref, entry } = useIntersection({ threshold: 0.5, })
|
const { ref, entry } = useIntersection({ threshold: 0.5, })
|
||||||
@ -71,7 +72,7 @@ const CameraCard = ({
|
|||||||
<Group
|
<Group
|
||||||
className={classes.bottomGroup}>
|
className={classes.bottomGroup}>
|
||||||
<Flex justify='space-evenly' mt='0.5rem' w='100%'>
|
<Flex justify='space-evenly' mt='0.5rem' w='100%'>
|
||||||
<Button size='sm' onClick={handleOpenRecordings}>{strings.recordings}</Button>
|
<Button size='sm' onClick={handleOpenRecordings}>{t('recordings')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -7,10 +7,10 @@ import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
|||||||
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
|
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
|
||||||
import SortedTh from '../shared/components/table.aps/SortedTh';
|
import SortedTh from '../shared/components/table.aps/SortedTh';
|
||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import StateCell from './hosts.table/StateCell';
|
import StateCell from './hosts.table/StateCell';
|
||||||
import SwitchCell from './hosts.table/SwitchCell';
|
import SwitchCell from './hosts.table/SwitchCell';
|
||||||
import TextInputCell from './hosts.table/TextInputCell';
|
import TextInputCell from './hosts.table/TextInputCell';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface TableProps<T> {
|
interface TableProps<T> {
|
||||||
data: T[],
|
data: T[],
|
||||||
@ -20,6 +20,8 @@ interface TableProps<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps<GetFrigateHost>) => {
|
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps<GetFrigateHost>) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [tableData, setTableData] = useState(data)
|
const [tableData, setTableData] = useState(data)
|
||||||
const [reversed, setReversed] = useState(false)
|
const [reversed, setReversed] = useState(false)
|
||||||
const [sortedName, setSortedName] = useState<string | null>(null)
|
const [sortedName, setSortedName] = useState<string | null>(null)
|
||||||
@ -59,9 +61,9 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headTitle = [
|
const headTitle = [
|
||||||
{ propertyName: 'name', title: strings.hostArr.name },
|
{ propertyName: 'name', title: t('hostArr.host') },
|
||||||
{ propertyName: 'host', title: strings.hostArr.url },
|
{ propertyName: 'host', title: t('hostArr.url') },
|
||||||
{ propertyName: 'enabled', title: strings.hostArr.enabled },
|
{ propertyName: 'enabled', title: t('hostArr.enabled') },
|
||||||
{ title: '', sorting: false },
|
{ title: '', sorting: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { frigateQueryKeys, frigateApi } from '../services/frigate.proxy/frigate.
|
|||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
import RetryErrorPage from '../pages/RetryErrorPage';
|
import RetryErrorPage from '../pages/RetryErrorPage';
|
||||||
import { strings } from '../shared/strings/strings';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
const CameraAccordion = lazy(() => import('../shared/components/accordion/CameraAccordion'));
|
const CameraAccordion = lazy(() => import('../shared/components/accordion/CameraAccordion'));
|
||||||
|
|
||||||
interface SelectedHostListProps {
|
interface SelectedHostListProps {
|
||||||
@ -16,7 +16,7 @@ interface SelectedHostListProps {
|
|||||||
const SelectedHostList = ({
|
const SelectedHostList = ({
|
||||||
hostId
|
hostId
|
||||||
}: SelectedHostListProps) => {
|
}: SelectedHostListProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
const [openCameraId, setOpenCameraId] = useState<string | null>(null)
|
const [openCameraId, setOpenCameraId] = useState<string | null>(null)
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ const SelectedHostList = ({
|
|||||||
const camerasItems = camerasQuery.map(camera => {
|
const camerasItems = camerasQuery.map(camera => {
|
||||||
return (
|
return (
|
||||||
<Accordion.Item key={camera.id + 'Item'} value={camera.id}>
|
<Accordion.Item key={camera.id + 'Item'} value={camera.id}>
|
||||||
<Accordion.Control key={camera.id + 'Control'}>{strings.camera}: {camera.name}</Accordion.Control>
|
<Accordion.Control key={camera.id + 'Control'}>{t('camera')}: {camera.name}</Accordion.Control>
|
||||||
<Accordion.Panel key={camera.id + 'Panel'}>
|
<Accordion.Panel key={camera.id + 'Panel'}>
|
||||||
{openCameraId === camera.id && (
|
{openCameraId === camera.id && (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
@ -61,7 +61,7 @@ const SelectedHostList = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w='100%' h='100%' direction='column' align='center'>
|
<Flex w='100%' h='100%' direction='column' align='center'>
|
||||||
<Text>{strings.host}: {camerasQuery[0].frigateHost?.name}</Text>
|
<Text>{t('hostArr.host')}: {camerasQuery[0].frigateHost?.name}</Text>
|
||||||
<Accordion
|
<Accordion
|
||||||
mt='1rem'
|
mt='1rem'
|
||||||
variant='separated'
|
variant='separated'
|
||||||
|
|||||||
@ -122,15 +122,6 @@ const VideoDownloader = ({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO delete
|
|
||||||
// const handleCancel = () => {
|
|
||||||
// clearTimeout(timer)
|
|
||||||
// setTimer(undefined)
|
|
||||||
// setCreateName(undefined)
|
|
||||||
// setLink(undefined)
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
if (startUnixTime === 0 || endUnixTime === 0) return null
|
if (startUnixTime === 0 || endUnixTime === 0) return null
|
||||||
if (error) return <RetryError onRetry={() => createVideo.mutate()} />
|
if (error) return <RetryError onRetry={() => createVideo.mutate()} />
|
||||||
if (link && progress && !videoSrc) return (
|
if (link && progress && !videoSrc) return (
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
import { routesPath } from "../../router/routes.path";
|
|
||||||
import { headerMenu } from "../../shared/strings/header.menu.strings";
|
|
||||||
import { LinkItem } from "./HeaderAction";
|
|
||||||
|
|
||||||
export const headerLinks: LinkItem[] = [
|
|
||||||
{ link: routesPath.MAIN_PATH, label: headerMenu.home },
|
|
||||||
{ link: routesPath.SETTINGS_PATH, label: headerMenu.settings, admin: true },
|
|
||||||
{ link: routesPath.RECORDINGS_PATH, label: headerMenu.recordings },
|
|
||||||
{ link: routesPath.HOSTS_PATH, label: headerMenu.hostsConfig, admin: true },
|
|
||||||
{ link: routesPath.ACCESS_PATH, label: headerMenu.acessSettings, admin: true },
|
|
||||||
]
|
|
||||||
36
yarn.lock
36
yarn.lock
@ -1122,7 +1122,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||||
|
|
||||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||||
version "7.24.0"
|
version "7.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e"
|
||||||
integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==
|
integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==
|
||||||
@ -5684,6 +5684,13 @@ html-minifier-terser@^6.0.2:
|
|||||||
relateurl "^0.2.7"
|
relateurl "^0.2.7"
|
||||||
terser "^5.10.0"
|
terser "^5.10.0"
|
||||||
|
|
||||||
|
html-parse-stringify@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||||
|
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||||
|
dependencies:
|
||||||
|
void-elements "3.1.0"
|
||||||
|
|
||||||
html-webpack-plugin@^5.5.0:
|
html-webpack-plugin@^5.5.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0"
|
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0"
|
||||||
@ -5778,6 +5785,20 @@ human-signals@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||||
|
|
||||||
|
i18next-browser-languagedetector@^7.2.0:
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz#de0321cba6881be37d82e20e4d6f05aa75f6e37f"
|
||||||
|
integrity sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.23.2"
|
||||||
|
|
||||||
|
i18next@^23.10.1:
|
||||||
|
version "23.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.10.1.tgz#217ce93b75edbe559ac42be00a20566b53937df6"
|
||||||
|
integrity sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.23.2"
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
version "0.4.24"
|
version "0.4.24"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
@ -8647,6 +8668,14 @@ react-error-overlay@^6.0.11:
|
|||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
|
||||||
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
|
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
|
||||||
|
|
||||||
|
react-i18next@^14.1.0:
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.0.tgz#44da74fbffd416f5d0c5307ef31735cf10cc91d9"
|
||||||
|
integrity sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.23.9"
|
||||||
|
html-parse-stringify "^3.0.1"
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
@ -10271,6 +10300,11 @@ videojs-vtt.js@0.15.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
global "^4.3.1"
|
global "^4.3.1"
|
||||||
|
|
||||||
|
void-elements@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||||
|
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
|
||||||
|
|
||||||
vscode-jsonrpc@8.2.0:
|
vscode-jsonrpc@8.2.0:
|
||||||
version "8.2.0"
|
version "8.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
|
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user