diff --git a/V3.md b/V3.md new file mode 100644 index 00000000..43a08e8c --- /dev/null +++ b/V3.md @@ -0,0 +1,50 @@ +## Breaking + +- Remove dependency on `@doc-tools/transform` + `@doc-tools/transform` js and css should be attached directly to final projects + + +### ErrorPage +- Removed `lang` prop + +### Feedback +- Removed `lang` prop +- Removed `singlePage` prop +- Pick `dislikeVariants` from i18n by default + +### Contributors +- Removed `lang` prop + +### MiniToc +- Removed `lang` prop +- MiniToc returns `null` on empty headings. (Previously it render useless title) + +### SettingsControl +- Removed `lang` prop + +### PdfControl +- Removed `lang` prop +- Wrapped by react `memo` + +### SinglePageControl +- Removed `lang` prop +- Wrapped by react `memo` + +### FullScreenControl +- Removed `lang` prop +- Wrapped by react `memo` + +### EditButton +- Removed `lang` prop +- Wrapped by react `memo` + +### SearchPage +- Removed `lang` prop + +### SearchBar +- Removed `lang` prop +- Wrapped by react `memo` + +### SearchItem +- Removed `lang` prop +- Wrapped by react `memo` diff --git a/demo/.babelrc.json b/demo/.babelrc.json new file mode 100644 index 00000000..603379f7 --- /dev/null +++ b/demo/.babelrc.json @@ -0,0 +1,18 @@ +{ + "sourceType": "unambiguous", + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "chrome": 100, + "safari": 15, + "firefox": 91 + } + } + ], + "@babel/preset-react", + "@babel/preset-typescript" + ], + "plugins": [] +} diff --git a/demo/.storybook/main.js b/demo/.storybook/main.js new file mode 100644 index 00000000..8d777502 --- /dev/null +++ b/demo/.storybook/main.js @@ -0,0 +1,49 @@ +// import {join, dirname} from 'path'; + +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +// function getAbsolutePath(value) { +// return dirname(require.resolve(join(value, 'package.json'))); +// } + +/** @type { import('@storybook/react-webpack5').StorybookConfig } */ +const config = { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + { + name: '@storybook/addon-styling', + options: { + sass: { + implementation: require("sass"), + }, + } + }, + '@storybook/addon-onboarding', + '@storybook/addon-interactions', + + ], + framework: { + name: '@storybook/react-webpack5', + options: {}, + }, + docs: { + autodocs: 'tag', + }, + async webpackFinal(config, { configType }) { + config.module.rules.push({ + test: /\.svg$/, + type: 'javascript/auto', + use: ['@svgr/webpack'], + }); + + config.module.rules.forEach(rule => console.log(rule.test, rule.use)); + + return config; + } +}; + +export default config; diff --git a/demo/.storybook/preview.js b/demo/.storybook/preview.js new file mode 100644 index 00000000..ecf5d4f0 --- /dev/null +++ b/demo/.storybook/preview.js @@ -0,0 +1,14 @@ +/** @type { import('@storybook/react').Preview } */ +const preview = { + parameters: { + actions: {argTypesRegex: '^on[A-Z].*'}, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, +}; + +export default preview; diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 00000000..20e980c2 --- /dev/null +++ b/demo/package.json @@ -0,0 +1,38 @@ +{ + "name": "@doc-tools/components-demo", + "private": true, + "license": "MIT", + "version": "1.0.0", + "scripts": { + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "devDependencies": { + "@babel/preset-env": "^7.22.14", + "@babel/preset-react": "^7.22.5", + "@doc-tools/transform": "^3.10.2", + "@storybook/addon-essentials": "^7.4.0", + "@storybook/addon-interactions": "^7.4.0", + "@storybook/addon-knobs": "^7.0.2", + "@storybook/addon-links": "^7.4.0", + "@storybook/addon-onboarding": "^1.0.8", + "@storybook/addon-styling-webpack": "^0.0.3", + "@storybook/blocks": "^7.4.0", + "@storybook/react": "^7.4.0", + "@storybook/react-webpack5": "^7.4.0", + "@storybook/testing-library": "^0.2.0", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "storybook": "^7.4.0" + }, + "dependencies": { + "@babel/preset-typescript": "^7.22.11", + "@doc-tools/components": "^2.8.3", + "@storybook/addon-styling": "^1.3.7", + "@svgr/webpack": "^8.1.0", + "css-loader": "^6.8.1", + "sass": "^1.66.1", + "sass-loader": "^13.3.2" + } +} diff --git a/demo/src/Components/DocLeadingPage/index.tsx b/demo/src/Components/DocLeadingPage/index.tsx new file mode 100644 index 00000000..6cf5e62e --- /dev/null +++ b/demo/src/Components/DocLeadingPage/index.tsx @@ -0,0 +1,38 @@ +import React, {useState} from 'react'; +import cn from 'bem-cn-lite'; +import {DocLeadingPage, DocLeadingPageData, DEFAULT_SETTINGS} from '@doc-tools/components'; +import Header from '../Header/Header'; +import {getIsMobile} from '../../controls/settings'; +import getLangControl from '../../controls/lang'; +import pageContent from './page.json'; + +const layoutBlock = cn('Layout'); + +const DocLeadingPageDemo = () => { + const langValue = getLangControl(); + const isMobile = getIsMobile(); + const router = {pathname: '/docs/compute'}; + + const [fullScreen, onChangeFullScreen] = useState(DEFAULT_SETTINGS.fullScreen); + const [lang, onChangeLang] = useState(langValue); + + return ( +
+
+
+ +
+
+ ); +}; + +export default DocLeadingPageDemo; diff --git a/demo/src/Components/DocLeadingPage/page.json b/demo/src/Components/DocLeadingPage/page.json new file mode 100644 index 00000000..ba66e0c5 --- /dev/null +++ b/demo/src/Components/DocLeadingPage/page.json @@ -0,0 +1,1486 @@ +{ + "toc": { + "title": "Yandex Compute Cloud", + "href": "/docs/compute/", + "items": [ + { + "name": "Начало работы", + "items": [ + { + "name": "Обзор", + "href": "/docs/compute/quickstart/", + "id": "2b1fc8b8c3fb717e87c9bf403f56d997" + }, + { + "name": "Создание виртуальной машины Linux", + "href": "/docs/compute/quickstart/quick-create-linux", + "id": "525156d7631981ed1fd0025eb2d38ac6" + }, + { + "name": "Создание виртуальной машины Windows", + "href": "/docs/compute/quickstart/quick-create-windows", + "id": "33f2b39cbddb23929b4fa20ba78367b3" + }, + { + "name": "Создание группы виртуальных машин", + "href": "/docs/compute/quickstart/ig", + "id": "d4b7d652f22d3e137f6fcc4e33669046" + } + ], + "id": "e7bade82ebbf6557c6fe306cfe69b62a" + }, + { + "name": "Пошаговые инструкции", + "items": [ + { + "name": "Все инструкции", + "href": "/docs/compute/operations/", + "id": "0d611622724e5dff64175797fa90000b" + }, + { + "name": "Создание виртуальной машины", + "items": [ + { + "name": "Создать ВМ Linux", + "href": "/docs/compute/operations/vm-create/create-linux-vm", + "id": "097b0c0dae78bb8f7c5b4bf995531f8b" + }, + { + "name": "Создать ВМ Windows", + "href": "/docs/compute/operations/vm-create/create-windows-vm", + "id": "96c6d6fb217fe80f746bb14dc0d58891" + }, + { + "name": "Создать ВМ из набора дисков", + "href": "/docs/compute/operations/vm-create/create-from-disks", + "id": "744fe79d8c1a08b1221b171ccf161cc8" + }, + { + "name": "Создать ВМ с дисками из снимков", + "href": "/docs/compute/operations/vm-create/create-from-snapshots", + "id": "1c15840f0022321f21b9451e050816ab" + }, + { + "name": "Создать ВМ из пользовательского образа", + "href": "/docs/compute/operations/vm-create/create-from-user-image", + "id": "f9d73a7be5ad533ee12c7490c0c56ac0" + }, + { + "name": "Создать прерываемую ВМ", + "href": "/docs/compute/operations/vm-create/create-preemptible-vm", + "id": "448dbdd7f0bd62eda18a8e78e8948364" + }, + { + "name": "Создать ВМ c GPU", + "href": "/docs/compute/operations/vm-create/create-vm-with-gpu", + "id": "4ce394283f98084a41183f22732ba4c0" + } + ], + "id": "202d23c522cc4abcc765fe0c8d314cd6" + }, + { + "name": "DSVM", + "items": [ + { + "name": "Обзор", + "href": "/docs/compute/operations/dsvm/", + "id": "06e6e1f19b30eb9e0ae287bd39aa5fbb" + }, + { + "name": "Создать ВМ из публичного образа DSVM", + "href": "/docs/compute/operations/dsvm/quickstart", + "id": "5838b5b6bfad03e5a6a039d24e4f06a8" + } + ], + "id": "893a628d7a9e1540ab14394b1b2be78d" + }, + { + "name": "Группы размещения", + "items": [ + { + "name": "Создать группу размещения", + "href": "/docs/compute/operations/placement-groups/create", + "id": "742efaa6e0058c84979823eee6d26c48" + }, + { + "name": "Удалить группу размещения", + "href": "/docs/compute/operations/placement-groups/delete", + "id": "dd141f3f258b5f18b57be7817eea42dc" + }, + { + "name": "Создать ВМ в группе размещения", + "href": "/docs/compute/operations/placement-groups/create-vm-in-pg", + "id": "8cc9dfba72f9ac7c34ed89ae81a4a064" + }, + { + "name": "Добавить ВМ в группу размещения", + "href": "/docs/compute/operations/placement-groups/add-vm", + "id": "0eccdbf70eb5012f7cb19bc1cae42fea" + }, + { + "name": "Исключить ВМ из группы размещения", + "href": "/docs/compute/operations/placement-groups/delete-vm", + "id": "29c3cda26e600165be543bda66ff2141" + } + ], + "id": "649653e7b44375c7530d29ac2d713ffc" + }, + { + "name": "Образы с предустановленным ПО", + "items": [ + { + "name": "Создать ВМ из публичного образа", + "href": "/docs/compute/operations/images-with-pre-installed-software/create", + "id": "53d082e0dd61e3fb71d1b58050ba3d48" + }, + { + "name": "Настроить ПО", + "href": "/docs/compute/operations/images-with-pre-installed-software/setup", + "id": "b725ab490b39c14796dc3b5e701b0340" + }, + { + "name": "Работа с ВМ на базе публичного образа", + "href": "/docs/compute/operations/images-with-pre-installed-software/operate", + "id": "2aa01248658d9bca73f8883faa62618b" + }, + { + "name": "Получить список публичных образов", + "href": "/docs/compute/operations/images-with-pre-installed-software/get-list", + "id": "c5f78060588b30093f078c2ad2499649" + } + ], + "id": "eaae70b245e84cae69729647e56141b3" + }, + { + "name": "Получение информации о виртуальной машине", + "items": [ + { + "name": "Получить информацию о ВМ", + "href": "/docs/compute/operations/vm-info/get-info", + "id": "d3f6dd43a99d56e80e092f01b8318dc1" + }, + { + "name": "Получить вывод последовательного порта", + "href": "/docs/compute/operations/vm-info/get-serial-port-output", + "id": "8cb88af2ed1ef49294dd4cf03cdc0b98" + } + ], + "id": "9657fcaf9ecc34ebd98ed890a8d479b9" + }, + { + "name": "Управление виртуальной машиной", + "items": [ + { + "name": "Остановить и запустить ВМ", + "href": "/docs/compute/operations/vm-control/vm-stop-and-start", + "id": "c9285f14c796379c07a4d93ba04013b7" + }, + { + "name": "Подключить диск к ВМ", + "href": "/docs/compute/operations/vm-control/vm-attach-disk", + "id": "b25d5f41f89b869d23eb1e6435950945" + }, + { + "name": "Отключить диск от ВМ", + "href": "/docs/compute/operations/vm-control/vm-detach-disk", + "id": "2e23067c1366b0d315c2f9d580eeb950" + }, + { + "name": "Перенести ВМ в другую зону доступности", + "href": "/docs/compute/operations/vm-control/vm-change-zone", + "id": "f4db404dbb16896066e1f2ae17b9394c" + }, + { + "name": "Сделать публичный IP-адрес ВМ статическим", + "href": "/docs/compute/operations/vm-control/vm-set-static-ip", + "id": "7df529e93e61f30a3c9cefbcd2d271b3" + }, + { + "name": "Изменить ВМ", + "href": "/docs/compute/operations/vm-control/vm-update", + "id": "a024103c9a61c19a5c3b938a5ee98668" + }, + { + "name": "Изменить вычислительные ресурсы ВМ", + "href": "/docs/compute/operations/vm-control/vm-update-resources", + "id": "8b28bdcdf089ec44140802dd69a86048" + }, + { + "name": "Удалить ВМ", + "href": "/docs/compute/operations/vm-control/vm-delete", + "id": "edae79bd12dd5c7d2d2337a63cff1170" + } + ], + "id": "41b5d169b5a879a9f26f53b638af3c80" + }, + { + "name": "Работа на виртуальной машине", + "items": [ + { + "name": "Подключиться к ВМ по SSH", + "href": "/docs/compute/operations/vm-connect/ssh", + "id": "4937b718ce8d5dba56f42dfca2229ae9" + }, + { + "name": "Подключиться к ВМ по RDP", + "href": "/docs/compute/operations/vm-connect/rdp", + "id": "8009e4f53b5126a98b29b1939042236b" + }, + { + "name": "Подключиться к ВМ через PowerShell", + "href": "/docs/compute/operations/vm-connect/powershell", + "id": "f1b9415a33ad3d2c3e18b4fdf4d46a66" + }, + { + "name": "Работа с Yandex.Cloud изнутри ВМ", + "href": "/docs/compute/operations/vm-connect/auth-inside-vm", + "id": "70d4e6376271a28d85f84cfa71740d10" + }, + { + "name": "Установить NVIDIA-драйверы", + "href": "/docs/compute/operations/vm-operate/install-nvidia-drivers", + "id": "0a5b26c3353cb05eb4914658e13ee8b6" + }, + { + "name": "Восстановить доступ к ВМ", + "href": "/docs/compute/operations/vm-connect/recovery-access", + "id": "5fa057a38e18bb78203114ca09fef3f4" + } + ], + "id": "d334bd798474237e73ac29f1591cb43d" + }, + { + "name": "Создание нового диска", + "items": [ + { + "name": "Создать пустой диcк", + "href": "/docs/compute/operations/disk-create/empty", + "id": "2125a206cb1625ab58042778415fba12" + }, + { + "name": "Создать пустой диск с блоком большого размера", + "href": "/docs/compute/operations/disk-create/empty-disk-blocksize", + "id": "846c476e44dcc470d99a13eb821ba99a" + }, + { + "name": "Создать нереплицируемый диск", + "href": "/docs/compute/operations/disk-create/nonreplicated", + "id": "83dda0e7796b8eeb920633ff1761af3b" + } + ], + "id": "7e21fff691b7e021939e0f64977b0a32" + }, + { + "name": "Управление диском", + "items": [ + { + "name": "Создать снимок диска", + "href": "/docs/compute/operations/disk-control/create-snapshot", + "id": "0cc9f2e81dc6fdc2e6aa63870dffba47" + }, + { + "name": "Изменить диск", + "href": "/docs/compute/operations/disk-control/update", + "id": "3e9beba4dafd9f7e3e9d477086a838d9" + }, + { + "name": "Удалить диcк", + "href": "/docs/compute/operations/disk-control/delete", + "id": "32c2adb207b86021e04d396b8e2f490f" + }, + { + "name": "Удалить снимок диска", + "href": "/docs/compute/operations/snapshot-control/delete", + "id": "8d4126c099cbecd7a0fa7892684d4a4e" + } + ], + "id": "09bf4f27793a28dd8486b631182bb756" + }, + { + "name": "Группы размещения дисков", + "items": [ + { + "name": "Создать группу размещения дисков", + "href": "/docs/compute/operations/disk-placement-groups/create", + "id": "e24064c1c66e9b8d130932fbb73ec91d" + }, + { + "name": "Удалить диск из группы размещения", + "href": "/docs/compute/operations/disk-placement-groups/remove-disk", + "id": "603617df3ee117be0180b5d25272b8f9" + } + ], + "id": "2e1a239532bb45c6010ca9c13a59acf4" + }, + { + "name": "Создание нового образа", + "items": [ + { + "name": "Подготовить образ диска", + "href": "/docs/compute/operations/image-create/custom-image", + "id": "c62b6f69dd4210d8621629962882fb3c" + }, + { + "name": "Загрузить свой образ", + "href": "/docs/compute/operations/image-create/upload", + "id": "9c9f49866e0e27f9d82c53c5c867494e" + } + ], + "id": "13e495f58a5a6827f0cdef10f18fd750" + }, + { + "name": "Управление образом", + "items": [ + { + "name": "Удалить образ", + "href": "/docs/compute/operations/image-control/delete", + "id": "9e68880ee44a373113ae28dd97c01880" + } + ], + "id": "afd841339862d4f32b8e3acb3bb20c49" + }, + { + "name": "Управление серийной консолью", + "items": [ + { + "name": "Начало работы", + "href": "/docs/compute/operations/serial-console/", + "id": "e9d715705edc19799d721119678c9ba9" + }, + { + "name": "Подключиться к серийной консоли по SSH", + "href": "/docs/compute/operations/serial-console/connect-ssh", + "id": "d6febb580c9522013585bdd67669e8c7" + }, + { + "name": "Подключиться к серийной консоли с помощью CLI", + "href": "/docs/compute/operations/serial-console/connect-cli", + "id": "e030afc9cc525dd989aba4885b382e09" + }, + { + "name": "Запустить командную оболочку в Windows SAC", + "href": "/docs/compute/operations/serial-console/windows-sac", + "id": "150209a9224a84f4486d55e062d00f13" + }, + { + "name": "Отключить доступ к серийной консоли", + "href": "/docs/compute/operations/serial-console/disable", + "id": "d7f703bee09caa5415e1d2e2b735d5d4" + } + ], + "id": "ffc8951f2fa849ea203d7e24ab6e7186" + }, + { + "name": "Создание группы виртуальных машин", + "items": [ + { + "name": "Создать группу ВМ фиксированного размера", + "href": "/docs/compute/operations/instance-groups/create-fixed-group", + "id": "dbfd6456f8e16d76b0a3a1964e1f60b8" + }, + { + "name": "Создать группу ВМ фиксированного размера с сетевым балансировщиком", + "href": "/docs/compute/operations/instance-groups/create-with-balancer", + "id": "38b1095f5e2b6149bd3ac710bcac5fe2" + }, + { + "name": "Создать автоматически масштабируемую группу ВМ", + "href": "/docs/compute/operations/instance-groups/create-autoscaled-group", + "id": "e21f55f1b5c7254008c2e37e39db4ab5" + }, + { + "name": "Создать группу ВМ с Container Optimized Image", + "href": "/docs/compute/operations/instance-groups/create-with-coi", + "id": "e922f477fedcf68783003128e6836fec" + } + ], + "id": "29ce44223e454ceb013860c3f5e7b2b2" + }, + { + "name": "Получение информации о группе виртуальных машин", + "items": [ + { + "name": "Получить список групп ВМ", + "href": "/docs/compute/operations/instance-groups/get-list", + "id": "6e849183d448865595463c5d388d3d9a" + }, + { + "name": "Получить информацию о группе ВМ", + "href": "/docs/compute/operations/instance-groups/get-info", + "id": "856fcfc3d82e23d1f26a526e7324376b" + }, + { + "name": "Получить список ВМ в группе", + "href": "/docs/compute/operations/instance-groups/get-list-instances", + "id": "c7f0bfa8c3a9a752ca4e4e2812f7418a" + } + ], + "id": "9a1f185f2447a61c048f9d5a05204a89" + }, + { + "name": "Управление группой виртуальных машин", + "items": [ + { + "name": "Изменить группу ВМ", + "href": "/docs/compute/operations/instance-groups/update", + "id": "dbbd57f0cd5b113b99bcea2322513ca1" + }, + { + "name": "Настроить проверку состояния приложения на ВМ", + "href": "/docs/compute/operations/instance-groups/enable-autohealing", + "id": "e3a003e332b4de7ce0c2e4c49804833f" + }, + { + "name": "Обновить группу", + "items": [ + { + "name": "Постепенное обновление", + "href": "/docs/compute/operations/instance-groups/deploy/rolling-update", + "id": "f2538229a48e285028645250e3b27fff" + }, + { + "name": "Обновление без простоя", + "href": "/docs/compute/operations/instance-groups/deploy/zero-downtime", + "id": "8a267fa54d9b5d8c564a451fbdd97018" + } + ], + "id": "cc0a67aa8973db0dc7ff8d56b9e6d863" + }, + { + "name": "Остановить группу ВМ", + "href": "/docs/compute/operations/instance-groups/stop", + "id": "6e7e81f59e17b5091f7c4287fb214773" + }, + { + "name": "Запустить группу ВМ", + "href": "/docs/compute/operations/instance-groups/start", + "id": "57585d922551d33e5ca925732579ab5a" + }, + { + "name": "Удалить группу ВМ", + "href": "/docs/compute/operations/instance-groups/delete", + "id": "a2b5a96ee776d990a8b1b5e1dc5ec5b3" + } + ], + "id": "e223df49ee0b73df2dd3d473c5e160ed" + }, + { + "name": "Выделенные хосты", + "items": [ + { + "name": "Создать ВМ в группе выделенных хостов", + "href": "/docs/compute/operations/dedicated-host/running-host-group-vms", + "id": "edf35741be1188f978c526a39b3fa618" + }, + { + "name": "Создать ВМ на выделенном хосте", + "href": "/docs/compute/operations/dedicated-host/running-host-vms", + "id": "a7371db28a644d2cd8126488f212d2c4" + } + ], + "id": "9b6d2dc4f06cb2b63dd03933a843010e" + } + ], + "id": "06211b0dd4947721dc2b15cbc35cd783" + }, + { + "name": "Yandex Container Solution", + "href": "/docs/cos", + "id": "4f713cceebd273aec0ce8d54e3bfd61c" + }, + { + "name": "Сценарии использования", + "items": [ + { + "name": "Настройка синхронизации времени NTP", + "href": "/docs/compute/solutions/ntp", + "id": "699440aa8e2a2ca7bbb347e239c9425d" + }, + { + "name": "Работа с группой ВМ с автоматическим масштабированием", + "href": "/docs/compute/solutions/vm-autoscale", + "id": "c62f66e69535d261ad97ad9b8c87fb84" + }, + { + "name": "Развертывание Remote Desktop Gateway", + "href": "/docs/compute/solutions/rds-gw", + "id": "02f5baeed5dfc97441e48500770ef4cb" + } + ], + "id": "62111ba2dea597fa4b7e1aed31fc64e7" + }, + { + "name": "Концепции", + "items": [ + { + "name": "Взаимосвязь ресурсов", + "href": "/docs/compute/concepts/", + "id": "2d00d0fab5f8529b918b661e1920f18e" + }, + { + "name": "Виртуальные машины", + "items": [ + { + "name": "Обзор", + "href": "/docs/compute/concepts/vm", + "id": "6ce77159075b0937bf43463b19400ac4" + }, + { + "name": "Платформы", + "href": "/docs/compute/concepts/vm-platforms", + "id": "c075341cb0ab99ce4f3e01f7971efd60" + }, + { + "name": "Уровни производительности vCPU", + "href": "/docs/compute/concepts/performance-levels", + "id": "d4e4d102765748f7ca142e1e2053ff91" + }, + { + "name": "Прерываемые виртуальные машины", + "href": "/docs/compute/concepts/preemptible-vm", + "id": "32ba37896370ce863d61279ac041cff3" + }, + { + "name": "Сеть на виртуальной машине", + "href": "/docs/compute/concepts/network", + "id": "2558324b1932419f89cfc9dabfb171d2" + }, + { + "name": "Программно-ускоренная сеть", + "href": "/docs/compute/concepts/software-accelerated-network", + "id": "4793a03826725c8fdeeb913058beb5f2" + }, + { + "name": "Динамическая миграция", + "href": "/docs/compute/concepts/live-migration", + "id": "8d595bf47cbd0d2e019445199a7dfe6e" + }, + { + "name": "Группы размещения ВМ", + "href": "/docs/compute/concepts/placement-groups", + "id": "d0f1db64f7ba8d0e59de5fd0fda2e59a" + }, + { + "name": "Статусы", + "href": "/docs/compute/concepts/vm-statuses", + "id": "43b3b28bc7b0ebe379f7c8c35e5e5b34" + }, + { + "name": "Метаданные", + "href": "/docs/compute/concepts/vm-metadata", + "id": "e1c95b834834045659eff415f7025d8a" + } + ], + "id": "1d6c11690e2bf720c57adce97f7dc5a7" + }, + { + "name": "Графические ускорители GPU и vGPU", + "href": "/docs/compute/concepts/gpus", + "id": "8667ac4e98c101e4bf99a29ac2b3940a" + }, + { + "name": "Диски", + "items": [ + { + "name": "Обзор", + "href": "/docs/compute/concepts/disk", + "id": "abb707d81ac66d8487efff92266d797f" + }, + { + "name": "Снимки дисков", + "href": "/docs/compute/concepts/snapshot", + "id": "c55c22b709a7854abb9d78028022992b" + }, + { + "name": "Группы размещения нереплицируемых дисков", + "href": "/docs/compute/concepts/disk-placement-group", + "id": "9f4c67900412fb1f107a4bb4c2938603" + } + ], + "id": "342192a76b117ba7a1790f1af41452be" + }, + { + "name": "Образы", + "href": "/docs/compute/concepts/image", + "id": "933a6662d39cd2dbac5f0d716a6b3b06" + }, + { + "name": "Группы виртуальных машин", + "items": [ + { + "name": "Обзор", + "href": "/docs/compute/concepts/instance-groups/", + "id": "7868565e9b66bae20549b5a2bc29447e" + }, + { + "name": "Доступ", + "href": "/docs/compute/concepts/instance-groups/access", + "id": "2d44df48a11dd67359b8b9d5f5a19f44" + }, + { + "name": "Шаблон виртуальной машины", + "href": "/docs/compute/concepts/instance-groups/instance-template", + "id": "57cb89522dfe46be0adb7e60dfe7b489" + }, + { + "name": "Переменные в шаблоне виртуальной машины", + "href": "/docs/compute/concepts/instance-groups/variables-in-the-template", + "id": "91fdb579790f8fa9f094dd0b9f31585d" + }, + { + "name": "Политики", + "items": [ + { + "name": "Обзор", + "href": "/docs/compute/concepts/instance-groups/policies/", + "id": "f3466c602803fc8e81092476f1b983b5" + }, + { + "name": "Политика распределения", + "href": "/docs/compute/concepts/instance-groups/policies/allocation-policy", + "id": "5e905361837425dae46e9c1262be7245" + }, + { + "name": "Политика развертывания", + "href": "/docs/compute/concepts/instance-groups/policies/deploy-policy", + "id": "b40369d01a92f89c002d92865e51a7a7" + }, + { + "name": "Политика масштабирования", + "href": "/docs/compute/concepts/instance-groups/policies/scale-policy", + "id": "3bc7477a5b720f275645c9fdf7e668d9" + } + ], + "id": "476b12b1ce9b4697c490bb1683c17f95" + }, + { + "name": "Типы масштабирования", + "href": "/docs/compute/concepts/instance-groups/scale", + "id": "9a9478592e46cd197131be9045fd67bf" + }, + { + "name": "Автоматическое восстановление", + "href": "/docs/compute/concepts/instance-groups/autohealing", + "id": "1cc2dd77752e0865498cdaf85cbb210e" + }, + { + "name": "Обновление", + "items": [ + { + "name": "Обзор", + "href": "/docs/compute/concepts/instance-groups/deploy/", + "id": "a368e738b64acd3c2e1d22609fbbaeb5" + }, + { + "name": "Распределение виртуальных машин по зонам", + "href": "/docs/compute/concepts/instance-groups/deploy/zones", + "id": "cdbc90b2e1196525fbb8407846c6940e" + }, + { + "name": "Алгоритм развертывания", + "href": "/docs/compute/concepts/instance-groups/deploy/deploy", + "id": "1ccf766d02dc0eb838f5db7354130323" + }, + { + "name": "Правила обновления виртуальных машин", + "href": "/docs/compute/concepts/instance-groups/deploy/instance", + "id": "418a33538fe126aa74fefe1f03b63df9" + }, + { + "name": "Изменение дополнительных дисков в шаблоне виртуальной машины", + "href": "/docs/compute/concepts/instance-groups/deploy/secondary-disk", + "id": "522dce017feed3b00b1f10d7bdd0149b" + } + ], + "id": "6d2d48de86f064426741d71455085e25" + }, + { + "name": "Статусы", + "href": "/docs/compute/concepts/instance-groups/statuses", + "id": "31db35561f58a4494b24f3145559b3f4" + } + ], + "id": "fbe7550f4fe70a41146457532da3d6f2" + }, + { + "name": "Выделенный хост", + "href": "/docs/compute/concepts/dedicated-host", + "id": "760f698a1e11b8f15102348be64658db" + }, + { + "name": "Резервное копирование", + "href": "/docs/compute/concepts/backups", + "id": "c60dedf0e50bab29bc0c924d9c1a609a" + }, + { + "name": "Квоты и лимиты", + "href": "/docs/compute/concepts/limits", + "id": "f921689102dc26f2c275626950a932bc" + } + ], + "id": "fb4285a2292fce2292d1cc009322b02e" + }, + { + "name": "Управление доступом", + "href": "/docs/compute/security/", + "id": "46e626b38a31451a1dc4dc759d1d4de3" + }, + { + "name": "Правила тарификации", + "items": [ + { + "name": "Действующие правила", + "href": "/docs/compute/pricing", + "id": "1739ac88cfe4424b5a4f22a90e8d780b" + }, + { + "name": "Архив", + "items": [ + { + "name": "До 1 января 2019 года", + "href": "/docs/compute/pricing-archive/pricing-01012019", + "id": "15f10124ab26784a2f0f8c72baa8d40a" + }, + { + "name": "С 1 января до 1 марта 2019 года", + "href": "/docs/compute/pricing-archive/pricing-01032019", + "id": "9b350bc153ed60ca00be3fe7ad4059a0" + }, + { + "name": "С 1 марта до 1 мая 2019 года", + "href": "/docs/compute/pricing-archive/pricing-30042019", + "id": "86db546d079e3bd7445d6d1f69757243" + } + ], + "id": "222722de5e9ba1ce9165b1b99524aa55" + } + ], + "id": "3bc947ea79e55d6ff2b6d9e5eb74ddca" + }, + { + "name": "Справочник API (англ.)", + "items": [ + { + "name": "Authentication", + "href": "/docs/compute/api-ref/authentication", + "id": "4065c2b4fcec9060399cab5e8636bda8" + }, + { + "name": "gRPC", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/grpc/", + "id": "7d33758f3b8effb2a5d0e9d6cdbdaf6a" + }, + { + "name": "DiskPlacementGroupService", + "href": "/docs/compute/api-ref/grpc/disk_placement_group_service", + "id": "fc9b237bce3986734dbc7133e81db823" + }, + { + "name": "DiskService", + "href": "/docs/compute/api-ref/grpc/disk_service", + "id": "183568887f8002b1d805ef31000ee8ab" + }, + { + "name": "DiskTypeService", + "href": "/docs/compute/api-ref/grpc/disk_type_service", + "id": "43880210bbcd80a05f2397386d753a50" + }, + { + "name": "HostGroupService", + "href": "/docs/compute/api-ref/grpc/host_group_service", + "id": "14b2b64ff13ae0d432308f15c9633b40" + }, + { + "name": "HostTypeService", + "href": "/docs/compute/api-ref/grpc/host_type_service", + "id": "15cd45977a9b8a946e47cbea1808a8c9" + }, + { + "name": "ImageService", + "href": "/docs/compute/api-ref/grpc/image_service", + "id": "83dfefde5b5e75865193adc192ccb451" + }, + { + "name": "InstanceService", + "href": "/docs/compute/api-ref/grpc/instance_service", + "id": "c766edb445cb0951fbce81ec973129cf" + }, + { + "name": "PlacementGroupService", + "href": "/docs/compute/api-ref/grpc/placement_group_service", + "id": "39758d87c7686a9c4461cf751f827ef8" + }, + { + "name": "SnapshotService", + "href": "/docs/compute/api-ref/grpc/snapshot_service", + "id": "a5458c54835ff07d7146829edac1ecc3" + }, + { + "name": "ZoneService", + "href": "/docs/compute/api-ref/grpc/zone_service", + "id": "4f71b442d3512c41e1bf1ee5fafba42c" + }, + { + "name": "InstanceGroupService", + "href": "/docs/compute/api-ref/grpc/instance_group_service", + "id": "eb8b6d6d7f0b6f2403cdf953368fe79d" + }, + { + "name": "OperationService", + "href": "/docs/compute/api-ref/grpc/operation_service", + "id": "d66c6d778116d6d68fe5feb94e50d916" + } + ], + "id": "1303e29f9c971322530f90473943f153" + }, + { + "name": "REST", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/", + "id": "206dafd3cbc05521d69e3e17a7e027fd" + }, + { + "name": "Disk", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/Disk/", + "id": "10c2dc04ad65390102cb067d3c63591d" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/Disk/create", + "id": "57fd42d9dbf2a79c725246d6027dec60" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/Disk/delete", + "id": "27fcdea4b5a38c4778acf381b7f323e6" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/Disk/get", + "id": "387479b42e1e5e5fabb3f74f1db71606" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/Disk/list", + "id": "3507f355b267bc78f2c752d4f72061ad" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/Disk/listOperations", + "id": "a9b86fee5b49d955310beab3c9e8094f" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/Disk/update", + "id": "a156926cd7645b84651382131f937ba3" + } + ], + "id": "d3fe77edd4d0f4dd9fc65d4f397ac71e" + }, + { + "name": "DiskPlacementGroup", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/DiskPlacementGroup/", + "id": "7cb17d1db5e91657508864de04489db7" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/DiskPlacementGroup/create", + "id": "18d27168befb2e9ccc082f6d148b080b" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/DiskPlacementGroup/delete", + "id": "2ff143ecd3c6d5ac416ef4976ffd016b" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/DiskPlacementGroup/get", + "id": "aa2d8f50e64933aba7523e507c8c81d3" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/DiskPlacementGroup/list", + "id": "33656e7a90b6e9c8fe4f42c217a33a83" + }, + { + "name": "listDisks", + "href": "/docs/compute/api-ref/DiskPlacementGroup/listDisks", + "id": "83b5a71eaa9987c18ff7feab53417adf" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/DiskPlacementGroup/listOperations", + "id": "045e47f69b810ec6b79fff741ce0752a" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/DiskPlacementGroup/update", + "id": "549ef65faf30eea0b0a3ac44658ed21a" + } + ], + "id": "91ec069554da6e246478cb3d782bd637" + }, + { + "name": "DiskType", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/DiskType/", + "id": "e80ac2765bd3b557fe1dfcf2b918129f" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/DiskType/get", + "id": "5b6fe17b678326f1774a000e3349bf9b" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/DiskType/list", + "id": "a99237dfcf1c7261cc7118f8104ed04c" + } + ], + "id": "0777ffeb4aa87404830de3d98d24806c" + }, + { + "name": "HostGroup", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/HostGroup/", + "id": "fd4788d84c5fe50da6387191a371cf99" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/HostGroup/create", + "id": "1d7fead29ab1e5f657db327579290b9d" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/HostGroup/delete", + "id": "a41f33ed5c62843d7e6cf22cccdd677b" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/HostGroup/get", + "id": "c1fed3a1da8b1d44b31bcf5e95d61f83" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/HostGroup/list", + "id": "f472b2cc137d83848c2719ce8654da88" + }, + { + "name": "listHosts", + "href": "/docs/compute/api-ref/HostGroup/listHosts", + "id": "4db59034471bd3eb4797f96824e70654" + }, + { + "name": "listInstances", + "href": "/docs/compute/api-ref/HostGroup/listInstances", + "id": "7ce7be6bf220ae58e510ccd32016187d" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/HostGroup/listOperations", + "id": "bd1fb46734dc731fc4382fc9a47abec6" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/HostGroup/update", + "id": "fa1f4d1cdf2572e1429e503f4fa8ae1a" + } + ], + "id": "b1705d18eaccc471487091dfc65838e6" + }, + { + "name": "HostType", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/HostType/", + "id": "da1073582ae7dc920a070c35e78e57fa" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/HostType/get", + "id": "3ad68c7cb512d76d533b09137430f526" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/HostType/list", + "id": "23673587930ef8826c09b29de3f43d98" + } + ], + "id": "d62dedb95fa88bf5d213408e619f6291" + }, + { + "name": "Image", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/Image/", + "id": "12f38909e9e20dd8a17ae95ce54d8596" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/Image/create", + "id": "9e1a4a6496addad0b97be9b17dc11bcb" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/Image/delete", + "id": "cb0dac57aa204abd6104ce028ece8639" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/Image/get", + "id": "49d88ad14c50de30f2bc6d55db88f0d2" + }, + { + "name": "getLatestByFamily", + "href": "/docs/compute/api-ref/Image/getLatestByFamily", + "id": "de72019255b5c136dba632a365348847" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/Image/list", + "id": "de5ded9c8f876bc16fb093e71b5bfe7c" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/Image/listOperations", + "id": "d6d4979c0534148c56a56305361b2257" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/Image/update", + "id": "dd33eb8bfb4ad1308b133604ba0e6626" + } + ], + "id": "8f62f52989f1eeaa0b9a43faf1c6d2c2" + }, + { + "name": "Instance", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/Instance/", + "id": "59c61b8c709f1a65662cc9c353e2000c" + }, + { + "name": "addOneToOneNat", + "href": "/docs/compute/api-ref/Instance/addOneToOneNat", + "id": "16c0de22c1badc8ccd180782bcd642ad" + }, + { + "name": "attachDisk", + "href": "/docs/compute/api-ref/Instance/attachDisk", + "id": "b6c923e6fc373a2efba43726f53a3ecb" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/Instance/create", + "id": "c66ce715d6cb184d064cf7cc9694a14b" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/Instance/delete", + "id": "20d98520f4495fe6fd3d6b1aa2d6df0f" + }, + { + "name": "detachDisk", + "href": "/docs/compute/api-ref/Instance/detachDisk", + "id": "c0efb15408b03219f2ae8d0c0515c7c2" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/Instance/get", + "id": "3ab1bf4644f6c25270ea6ef7245000f8" + }, + { + "name": "getSerialPortOutput", + "href": "/docs/compute/api-ref/Instance/getSerialPortOutput", + "id": "5c90204a67e5c2b65315f1e4418c5aba" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/Instance/list", + "id": "3bc78b14cbac79c4fbcd9f897bbe52ea" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/Instance/listOperations", + "id": "0838d206cb281f0ee5f383b572d1b180" + }, + { + "name": "removeOneToOneNat", + "href": "/docs/compute/api-ref/Instance/removeOneToOneNat", + "id": "982c875a3d410a13db74c36cb4a1f1b4" + }, + { + "name": "restart", + "href": "/docs/compute/api-ref/Instance/restart", + "id": "2e15cb3d8ad4d1d05e87addd2ae85f71" + }, + { + "name": "start", + "href": "/docs/compute/api-ref/Instance/start", + "id": "1d6e14276ae4343534a750d3c617f58e" + }, + { + "name": "stop", + "href": "/docs/compute/api-ref/Instance/stop", + "id": "3fded9541f39283ee3e24cd67858b50b" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/Instance/update", + "id": "11fc6acf5cab9ab1b1238f5390ed5903" + }, + { + "name": "updateMetadata", + "href": "/docs/compute/api-ref/Instance/updateMetadata", + "id": "9befc40838dd8e56183ab82c6eae2ecb" + }, + { + "name": "updateNetworkInterface", + "href": "/docs/compute/api-ref/Instance/updateNetworkInterface", + "id": "b087a67c55a0ddbc13c1e108cb5729b0" + } + ], + "id": "75621caf15b2ac16d9793e595d75758c" + }, + { + "name": "PlacementGroup", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/PlacementGroup/", + "id": "6e99bc9c10355a62957300d95b8e129f" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/PlacementGroup/create", + "id": "45d68e07a4f69e223bfa96c5c54fed0b" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/PlacementGroup/delete", + "id": "578831ec3cfceccc19fa464fbdacdb3c" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/PlacementGroup/get", + "id": "386083f739b14e9b696aa6d63e27cca7" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/PlacementGroup/list", + "id": "2be8f3cd703838f80351e4a7c93e5694" + }, + { + "name": "listInstances", + "href": "/docs/compute/api-ref/PlacementGroup/listInstances", + "id": "eefe4737ebdf94e38c15c4c37fb7ea8f" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/PlacementGroup/listOperations", + "id": "8e89dc9276515e4b64f98f3b0e22f72a" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/PlacementGroup/update", + "id": "ce58a7d9e59acf1993debd6207fef553" + } + ], + "id": "fa71bc1d3b6e7d2946c63add21c445d0" + }, + { + "name": "Snapshot", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/Snapshot/", + "id": "e3fa043c876b999d6ce86ce04c789706" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/Snapshot/create", + "id": "675fa7429c69e7c860b92496ff6eebaf" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/Snapshot/delete", + "id": "0c405401a906732839dcaae0979e06b4" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/Snapshot/get", + "id": "58a9e37a93fe61f9ea09989e26aabddd" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/Snapshot/list", + "id": "f0d7cc1c7dc6b422efbbf804a50886b9" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/Snapshot/listOperations", + "id": "965270fffcfdaaa95151496ac1b3313f" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/Snapshot/update", + "id": "9f895c5e30f4bcd3b9c667c4757aeb99" + } + ], + "id": "5ee42b468ddb414e2a8c7f29f8004018" + }, + { + "name": "Zone", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/Zone/", + "id": "d98945085a2a15d5534f216061bc2830" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/Zone/get", + "id": "856253b846a25e8df3cc9ee6e2aefdd5" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/Zone/list", + "id": "4589276c0069a5da6d180a2469b95150" + } + ], + "id": "87637f8990558328af315e0be8cc5645" + }, + { + "name": "Operation", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/Operation/", + "id": "af31006d769dd452e3db35fc50aeb40f" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/Operation/get", + "id": "6a3541298c2be2b9efb0159646ff4006" + } + ], + "id": "6659a4de82e1b552285059d510a31b02" + }, + { + "name": "InstanceGroup", + "items": [ + { + "name": "Overview", + "href": "/docs/compute/api-ref/InstanceGroup/", + "id": "422ad4733fc1f431c6251bcaad1aef5c" + }, + { + "name": "list", + "href": "/docs/compute/api-ref/InstanceGroup/list", + "id": "aa28579ea49b026f5ff102f43caad4dc" + }, + { + "name": "get", + "href": "/docs/compute/api-ref/InstanceGroup/get", + "id": "444644e0a8093f0bcd80b1f686d0c128" + }, + { + "name": "listLogRecords", + "href": "/docs/compute/api-ref/InstanceGroup/listLogRecords", + "id": "1efb47decccf2cc2b9af872bb9d157eb" + }, + { + "name": "updateFromYaml", + "href": "/docs/compute/api-ref/InstanceGroup/updateFromYaml", + "id": "9585f5a13966e6aa6b0dabc0909d2a9d" + }, + { + "name": "updateAccessBindings", + "href": "/docs/compute/api-ref/InstanceGroup/updateAccessBindings", + "id": "344d58cc7834cc665e2d56b291b76e2f" + }, + { + "name": "stop", + "href": "/docs/compute/api-ref/InstanceGroup/stop", + "id": "3517e5448f34444c781ce803389411ca" + }, + { + "name": "start", + "href": "/docs/compute/api-ref/InstanceGroup/start", + "id": "240d9ffc106d620af16b733ea025a3a3" + }, + { + "name": "delete", + "href": "/docs/compute/api-ref/InstanceGroup/delete", + "id": "61525a20eb261219f092afd049dd203e" + }, + { + "name": "listInstances", + "href": "/docs/compute/api-ref/InstanceGroup/listInstances", + "id": "8ae489d16fbb65a7b6014cd180006dcd" + }, + { + "name": "createFromYaml", + "href": "/docs/compute/api-ref/InstanceGroup/createFromYaml", + "id": "c6ac1c0c0a86478f2f1abf6a9cb05cdc" + }, + { + "name": "update", + "href": "/docs/compute/api-ref/InstanceGroup/update", + "id": "db66fae49886770f8e1a085c3d768e66" + }, + { + "name": "setAccessBindings", + "href": "/docs/compute/api-ref/InstanceGroup/setAccessBindings", + "id": "13f8eb2b63c7957da2f83ec3e55bc046" + }, + { + "name": "listOperations", + "href": "/docs/compute/api-ref/InstanceGroup/listOperations", + "id": "4b933c0b6f02c32d0d79f5142fa6156e" + }, + { + "name": "create", + "href": "/docs/compute/api-ref/InstanceGroup/create", + "id": "cd15bb21eb1d72c71dc12e9707781275" + }, + { + "name": "listAccessBindings", + "href": "/docs/compute/api-ref/InstanceGroup/listAccessBindings", + "id": "e4f8edc4d48ecc2ce465ec3b55f6d1ec" + } + ], + "id": "b7a4e142bc80f9a5150c1cdaaa210892" + } + ], + "id": "bdf1444c41a074065430400fa317354a" + } + ], + "id": "84782b84f9addcd45b1809bee30342ed" + }, + { + "name": "Вопросы и ответы", + "items": [ + { + "name": "Общие вопросы", + "href": "/docs/compute/qa/general", + "id": "20cbea75319a2f7c9ff3a2b579238327" + }, + { + "name": "Виртуальные машины", + "href": "/docs/compute/qa/vm", + "id": "387c884cf5075a3837b5f3efd7df9ad4" + }, + { + "name": "Диски и снимки", + "href": "/docs/compute/qa/disks", + "id": "0f3cf57b87ab585e6ba472ff5a2e850c" + }, + { + "name": "Аварийное восстановление", + "href": "/docs/compute/qa/disaster-recovery", + "id": "26ef1411439e23e7c68a9d26c3cf923c" + }, + { + "name": "Лицензирование", + "href": "/docs/compute/qa/licensing", + "id": "7192931858e7517337a2535ddc4ae630" + }, + { + "name": "Все вопросы на одной странице", + "href": "/docs/compute/qa/all", + "id": "9c694a04c9e955a9c8ec10010c6f179f" + } + ], + "id": "1c4e87c19c3c976038a9de66e1a50663" + } + ], + "base": "ru/compute" + }, + "leading": true, + "data": { + "title": "Yandex Compute Cloud", + "description": [ + "Сервис Yandex Compute Cloud предоставляет масштабируемые вычислительные мощности для создания виртуальных машин и управления ими. Сервис поддерживает прерываемые виртуальные машины, а также отказоустойчивые группы виртуальных машин.", + "Вы можете подключать к виртуальным машинам диски с образами на базе OC Linux и Windows, доступные в Marketplace. Каждый диск автоматически реплицируется внутри своей зоны доступности, что обеспечивает надежное хранение данных. Также, для удобного переноса данных с одного диска на другой, Compute Cloud поддерживает снимки дисков.", + "Инфраструктура Yandex.Cloud защищена в соответствии с Федеральным законом Российской Федерации «О персональных данных» № 152-ФЗ.", + "Для сервиса действует соглашение об уровне обслуживания. Уровень обслуживания сервиса определен в документе Уровень обслуживания Yandex Compute Cloud." + ], + "meta": { + "title": "Yandex Compute Cloud" + }, + "links": [ + { + "title": "Начало работы", + "description": "Создайте первую виртуальную машину или группу ВМ", + "imgSrc": "http://s3.mds.yandex.net/catboost-opensource/icon__loss-functions.png", + "links": [ + { + "title": "Ссылка 1", + "imgSrc": "http://s3.mds.yandex.net/catboost-opensource/icon__loss-functions.png", + "href": "#" + } + ] + }, + { + "title": "Пошаговые инструкции", + "description": "Инструкции по выполнению рутинных операций", + "href": "operations/" + }, + { + "title": "Концепции", + "description": "Узнайте про взаимосвязь виртуальных машин, дисков, снимков и образов", + "href": "concepts/" + }, + { + "title": "Справочник API (англ.)", + "description": "Описание методов HTTP API", + "href": "api-ref/" + }, + { + "title": "Квоты и лимиты", + "description": "Технические и организационные ограничения сервиса", + "href": "concepts/limits" + }, + { + "title": "Управление доступом", + "description": "Настройте права доступа для работы с сервисом", + "href": "security/" + }, + { + "title": "Правила тарификации", + "description": "Цены и правила расчета стоимости услуг сервиса", + "href": "pricing" + } + ] + }, + "meta": { + "description": "", + "author": { + "avatar": "https://storage.yandexcloud.net/cloud-www-assets/constructor/content-program/icons/yandexcloud.svg", + "email": "", + "login": "", + "name": "Yandex.Cloud", + "url": "https://cloud.yandex.ru/" + } + } +} diff --git a/demo/src/Components/DocPage/data.ts b/demo/src/Components/DocPage/data.ts new file mode 100644 index 00000000..8031b248 --- /dev/null +++ b/demo/src/Components/DocPage/data.ts @@ -0,0 +1,19 @@ +import {Lang} from '../../../../src'; +import pageContentRu from './page-ru.json'; +import pageContentEn from './page-en.json'; +import singlePageContentRu from './single-page-ru.json'; +import singlePageContentEn from './single-page-en.json'; + +export const getContent = (lang: Lang, singlePage: boolean) => { + if (singlePage && lang === 'ru') { + return singlePageContentRu; + } else if (singlePage) { + return singlePageContentEn; + } + + if (lang === 'ru') { + return pageContentRu; + } + + return pageContentEn; +}; diff --git a/demo/src/Components/DocPage/index.scss b/demo/src/Components/DocPage/index.scss new file mode 100644 index 00000000..afe907ba --- /dev/null +++ b/demo/src/Components/DocPage/index.scss @@ -0,0 +1,4 @@ +@import "@gravity-ui/uikit/styles/styles.css"; +@import "@doc-tools/transform/dist/css/yfm.css"; +@import "@doc-tools/components"; + diff --git a/demo/src/Components/DocPage/index.stories.tsx b/demo/src/Components/DocPage/index.stories.tsx new file mode 100644 index 00000000..dfb35e5b --- /dev/null +++ b/demo/src/Components/DocPage/index.stories.tsx @@ -0,0 +1,218 @@ +import React, {useCallback, useEffect, useState} from 'react'; +import cn from 'bem-cn-lite'; + +import {configure as configureUikit} from '@gravity-ui/uikit'; +import './index.scss'; + +import {DocPage, FeedbackSendData, FeedbackType, Theme, DEFAULT_SETTINGS} from '@doc-tools/components'; +import Header from '../Header/Header'; +import {getIsMobile} from '../../controls/settings'; +import getLangControl from '../../controls/lang'; +import getVcsControl from '../../controls/vcs'; +import {getHasBookmark} from '../../decorators/bookmark'; +import {getHasSubscribe} from '../../decorators/subscribe'; +import {getContent} from './data'; + +import {join} from 'path'; + +const layoutBlock = cn('Layout'); + +function updateBodyClassName(theme: Theme) { + const bodyEl = document.body; + + if (!bodyEl.classList.contains('g-root')) { + bodyEl.classList.add('g-root'); + } + + bodyEl.classList.toggle('g-root_theme_light', theme === 'light'); + bodyEl.classList.toggle('g-root_theme_dark', theme === 'dark'); +} + +const DocPageDemo = () => { + const langValue = getLangControl(); + const vcsType = getVcsControl(); + const isMobile = getIsMobile(); + const hasBookmark = getHasBookmark(); + const hasSubscribe = getHasSubscribe(); + const router = {pathname: '/docs/overview/concepts/quotas-limits'}; + const langs = ['ru', 'en', 'cs']; + const pdfLink = 'https://doc.yandex-team.ru/help/diy/diy-guide.pdf'; + + const [fullScreen, onChangeFullScreen] = useState(DEFAULT_SETTINGS.fullScreen); + const [wideFormat, onChangeWideFormat] = useState(DEFAULT_SETTINGS.wideFormat); + const [showMiniToc, onChangeShowMiniToc] = useState(DEFAULT_SETTINGS.showMiniToc); + const [theme, onChangeTheme] = useState(DEFAULT_SETTINGS.theme); + const [textSize, onChangeTextSize] = useState(DEFAULT_SETTINGS.textSize); + const [singlePage, onChangeSinglePage] = useState(DEFAULT_SETTINGS.singlePage); + const [isLiked, setIsLiked] = useState(DEFAULT_SETTINGS.isLiked); + const [isDisliked, setIsDisliked] = useState(DEFAULT_SETTINGS.isDisliked); + const [isPinned, setIsPinned] = useState(DEFAULT_SETTINGS.isPinned as boolean); + const [lang, onChangeLang] = useState(langValue); + const [showSearchBar, setShowSearchBar] = useState(true); + const onCloseSearchBar = () => setShowSearchBar(false); + const [searchQuery, setSearchQuery] = useState(''); + const [searchWords, setSearchWords] = useState([]); + + const onSendFeedback = useCallback((data: FeedbackSendData) => { + const {type} = data; + + if (type === FeedbackType.like) { + setIsLiked(true); + setIsDisliked(false); + } else if (type === FeedbackType.dislike) { + setIsLiked(false); + setIsDisliked(true); + } else { + setIsLiked(false); + setIsDisliked(false); + } + + console.log('Feedback:', data); + }, []); + + const onChangeBookmarkPage = useCallback((data: boolean) => { + setIsPinned(data); + console.log(`This page pinned: ${data}`); + }, []); + + updateBodyClassName(theme); + + useEffect(() => { + const newSearchWords = searchQuery.split(' ').filter((word) => { + if (!word) { + return false; + } + + if (searchQuery.length > 10) { + return word.length > 3; + } + + return true; + }); + + setSearchWords(newSearchWords); + }, [searchQuery]); + + const onNotFoundWords = useCallback(() => { + console.log(`Not found words for the query: ${searchQuery}`); + }, [searchQuery]); + const onContentMutation = useCallback(() => { + console.log('onContentMutation'); + }, []); + const onContentLoaded = useCallback(() => { + console.log('onContentLoaded'); + }, []); + + const onChangeLangWrapper = useCallback((value) => { + onChangeLang(value); + configureUikit({lang: value}); + }, []); + + const props = { + ...getContent(lang, singlePage), + vcsType, + lang, + onChangeLang: onChangeLangWrapper, + langs, + router, + // headerHeight: fullScreen ? 0 : 64, + // headerHeight: fullScreen ? 0 : 64, + fullScreen, + onChangeFullScreen, + wideFormat, + onChangeWideFormat, + showMiniToc, + onChangeShowMiniToc, + onSubscribe: hasSubscribe === 'true' ? () => {} : undefined, + theme, + onChangeTheme: (themeValue: Theme) => { + updateBodyClassName(themeValue); + onChangeTheme(themeValue); + }, + textSize, + onChangeTextSize, + singlePage, + onChangeSinglePage, + isLiked, + isDisliked, + onSendFeedback, + pdfLink, + // onNotFoundWords, + // showSearchBar, + // searchWords, + // searchQuery, + // onCloseSearchBar, + // useSearchBar: true, + // bookmarkedPage: hasBookmark === 'true' && isPinned, + // onChangeBookmarkPage: hasBookmark === 'true' ? onChangeBookmarkPage : undefined, + }; + + const tocTitleIcon = ( + + {/* eslint-disable-next-line max-len */} + + + ); + const convertPathToOriginalArticle = (path: string) => join('prefix', path); + const generatePathToVcs = (path: string) => + join(`https://github.com/yandex-cloud/docs/blob/master/${props.lang}`, path); + const renderLoader = () => 'Loading...'; + // const onChangeSearch = (value: string) => { + // setShowSearchBar(true); + // setSearchQuery(value); + // }; + + return ( +
+ {/*{props.fullScreen ? null : (*/} + {/* */} + {/*)}*/} +
+ +
+
+ ); +}; + +export default { + title: 'Example/DocPage', + component: DocPageDemo, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + // tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes + // argTypes: { + // backgroundColor: { control: 'color' }, + // }, +}; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Primary = { + args: { + primary: true, + label: 'Button', + }, +}; + diff --git a/demo/src/Components/DocPage/page-en.json b/demo/src/Components/DocPage/page-en.json new file mode 100644 index 00000000..8119c91a --- /dev/null +++ b/demo/src/Components/DocPage/page-en.json @@ -0,0 +1,260 @@ +{ + "html": "

Yandex.Cloud services can be subject to quotas and limits:

\n\n

You can view your current account quotas in the management console.

\n

When designing your infrastructure in Yandex.Cloud, consider the limits as the boundary of opportunities that Yandex Cloud can provide to you. Quotas are changeable restrictions that can potentially be increased to the values of limits.

\n

Why quotas are needed

\n

Quotas serve as a soft restriction for requesting resources, and allow Yandex Cloud to guarantee the stability of the service: new users cannot take up too much resources for testing purposes. If you are ready to use more resources, contact the technical support and tell us exactly which quotas you need to increase, and how.

\n

Technical support decides whether or not to increase quotas on an individual basis.

\n

Quotas and limits defaults for Yandex.Cloud services

\n

Quotas are listed with default values that match the quotas of the trial period.

\n

Yandex Compute Cloud

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of virtual machines per cloud12
Total number of vCPUs for all VMs per cloud32
Total virtual memory for all VMs per cloud128 GB
Total number of disks per cloud32
Total SSD storage capacity per cloud200 GB
Total HDD storage capacity per cloud500 GB
Total number of disk snapshots per cloud32
Total storage capacity of all disk snapshots per cloud400 GB
Number of images per cloud8
Number of instance groups per cloud10
Total number of GPUs for all VMs per cloud*0
Number of concurrent operations in the cloud15
Maximum number of VM instances in a placement group5
Maximum number of placement groups per cloud2
\n

* To create a VM with a GPU, contact technical support.

\n

VM limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Maximum number of vCPUs per VM32 and 64 for Intel Broadwell and Intel Cascade Lake platforms, respectively
Maximum virtual memory per VM256 GB and 512 GB for Intel Broadwell and Intel Cascade Lake platforms, respectively
Maximum number of disks connected to a single VM7
Maximum number of GPUs connected to a single VM4
Maximum number of vCPUs for VMs with GPUs32
Maximum RAM for VMs with GPUs384
\n

Disk limits

\n
\n
\n
Network SSD
\n
Network HDD
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Maximum disk size4 TB
Maximum disk snapshot size4 TB
Allocation unit size32 GB
Maximum* IOPS for writes, per disk40,000
Maximum* IOPS for writes, per allocation unit1000
Maximum** bandwidth for writes, per disk450 MB/s
Maximum** bandwidth for writes, per allocation unit15 MB/s
Maximum* IOPS for reads, per disk12,000
Maximum* IOPS for reads, per allocation unit400
Maximum** bandwidth for reads, per disk450 MB/s
Maximum** bandwidth for reads, per allocation unit15 MB/s
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Maximum disk size4 TB
Maximum disk snapshot size4 TB
Allocation unit size256 GB
Maximum* IOPS for writes, per disk11,000
Maximum* IOPS for writes, per allocation unit300
Maximum** bandwidth for writes, per disk240 MB/s
Maximum** bandwidth for writes, per allocation unit30 MB/s
Maximum* IOPS for reads, per disk300
Maximum* IOPS for reads, per allocation unit100
Maximum** bandwidth for reads, per disk240 MB/s
Maximum** bandwidth for reads, per allocation unit30 MB/s
\n
\n
\n
*
\n

To achieve maximum IOPS, we recommend performing read and write operations that are 4 KB and less.

\n
**
\n

To achieve the maximum possible bandwidth, we recommend performing 4 MB reads and writes.

\n

Yandex Object Storage

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Storage volume in a cloud5 TB
Number of buckets per cloud25
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Maximum object size5 TB
Total header size per request to the HTTP API8 KB
Size of user-defined metadata in an object2 KB
Maximum size of data to be uploaded per request5 GB
Minimum size of data parts for multipart uploading, except the last one5 MB
Maximum number of parts in multi-part uploading10,000
\n

Yandex Virtual Private Cloud

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of cloud networks per cloud2
Number of subnets per cloud6
Number of all public IP addresses per cloud8
Number of static public IP addresses per cloud2
Number of route tables per cloud8
Number of static routes per cloud256
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Minimum CIDR size for a subnet/28
Maximum CIDR size for a subnet/16
Maximum number of simultaneous TCP/UDP connections per VM50000
\n

Filtering outgoing traffic

\n

Yandex.Cloud always blocks outgoing traffic to TCP port 25:

\n\n

Yandex.Cloud may open TCP port 25 by request via technical support if you comply with Acceptable Use Policy. Yandex.Cloud is entitled to block outgoing traffic on TCP port 25 if you violate the Use Policy.

\n

Yandex Resource Manager

\n

There are no quotas or limits for Yandex Resource Manager.

\n

Yandex Load Balancer

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of load balancers per cloud2
Number of target groups per cloud2
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of resources per target group254
Number of listening ports10
Number of health checks per attached target group1
Status check protocolTCP, HTTP
\n

Other restrictions

\n

A particular target group can only contain target resources from a single cloud network.

\n

A target group can include resources that are connected to the same subnet within a single availability zone.

\n

You can create a load balancer without a listener.

\n

Health checks are transmitted from the IP address range 198.18.235.0/24.

\n

When connecting resources to the load balancer, keep in mind the limit on the maximum number of simultaneous TCP/UDP connections per VM.

\n

Yandex Managed Service for ClickHouse

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of clusters per cloud16
Total number of processor cores for all DB hosts per cloud64
Total virtual memory for all DB hosts per cloud512 GB
Total storage capacity for all clusters per cloud4096 GB
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Lowest host classb1.nano (5% × 2 vCPU Intel Broadwell, 2 GB RAM)
Highest host classm2.8xlarge (64 vCPU Intel Cascade Lake, 512 GB RAM)
Maximum number of hosts in the cluster10
Maximum network storage capacity4096 GB
Maximum local storage capacity1400 GB
\n

Yandex Managed Service for MongoDB

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of clusters per cloud16
Total number of processor cores for all DB hosts per cloud64
Total virtual memory for all DB hosts per cloud512 GB
Total storage capacity for all clusters per cloud4096 GB
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Lowest host classb1.nano (5% × 2 vCPU Intel Broadwell, 2 GB RAM)
Highest host classm2.8xlarge (64 vCPU Intel Cascade Lake, 512 GB RAM)
Maximum number of hosts per cluster MongoDB5
Maximum storage capacity for a cluster MongoDB512 GB
\n

Yandex Managed Service for MySQL

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of clusters per cloud16
Total number of processor cores for all DB hosts per cloud64
Total virtual memory for all DB hosts per cloud512 GB
Total storage capacity for all clusters per cloud4096 GB
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Lowest host classb1.nano (5% × 2 vCPU Intel Broadwell, 2 GB RAM)
Highest host classm2.8xlarge (64 vCPU Intel Cascade Lake, 512 GB RAM)
Maximum number of hosts per cluster7
Maximum network storage capacity2048 GB
Maximum local storage capacity1400 GB
\n

Yandex Managed Service for PostgreSQL

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of clusters per cloud16
Total number of processor cores for all DB hosts per cloud64
Total virtual memory for all DB hosts per cloud512 GB
Total storage capacity for all clusters per cloud4096 GB
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Lowest host classb1.nano (5% × 2 vCPU Intel Broadwell, 2 GB RAM)
Highest host classm2.8xlarge (64 vCPU Intel Cascade Lake, 512 GB RAM)
Maximum number of hosts per cluster10
Maximum network storage capacity2048 GB
Maximum local storage capacity1400 GB
\n

Yandex Managed Service for Redis

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of clusters per cloud16
Total number of processor cores for all DB hosts per cloud64
Total virtual memory for all hosts per cloud512 GB
Total disk storage capacity for all clusters per cloud4096 GB
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Lowest host classb1.nano (burstable with 2 GB RAM)
Highest host classhm1.9xlarge (high-memory with 256 GB RAM)
Maximum number of hosts per cluster7
Minimum disk size per cluster2 times more than the amount of RAM selected
Maximum disk size per cluster8 times more than the amount of RAM selected
\n

Yandex Message Queue

\n

Quotas

\n
Messages
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of SendMessage and SendMessageBatch calls per queue300 calls per second for standard queues

30 calls per second for FIFO queues
Number of ReceiveMessage calls per queue300 calls per second for standard queues

30 calls per second for FIFO queues
Number of DeleteMessage and DeleteMessageBatch calls per queue300 calls per second for standard queues

30 calls per second for FIFO queues
Number of ChangeMessageVisibility and ChangeMessageVisibilityBatch calls per queue300 calls per second for standard queues

30 calls per second for FIFO queues
Number of CreateQueue calls per cloud2 calls per second
Number of DeleteQueue calls per cloud5 calls per second
Number of other request calls per cloud100 calls per second
Number of queues per cloud10
\n

Limits

\n
Queues
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Minimum message enqueue delay (DelaySeconds parameter)0 seconds
Maximum message enqueue delay (DelaySeconds parameter)900 seconds (15 minutes)
Number of messages being processed per standard queue120,000
Number of messages being processed per FIFO queue20,000
Queue nameMaximum of 80 characters, including numbers, small and capital Latin letters, hyphens, and underscores. The name of a FIFO queue must end with the .fifo suffix.
\n
Messages
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Batch entry IDMaximum of 80 characters, including numbers, small and capital Latin letters, hyphens, and underscores.
Maximum number of message attributes10
Maximum number of entries per batch10
Message contentXML, JSON, and unformatted text. The following Unicode characters are supported:
  • #x9
  • #xA
  • #xD
  • from #x20 to #xD7FF
  • from #xE000 to #xFFFD
  • from #x10000 to #x10FFFF
Maximum period for retaining messages in a queue1209600 seconds (14 days)
Minimum period for retaining messages in a queue60 seconds (1 minute)
Maximum message enqueue delay (DelaySeconds parameter)900 seconds (15 minutes)
Minimum message enqueue delay (DelaySeconds parameter)0 seconds
Maximum message size262144 bytes (256 KB)
Minimum message size1 byte
Maximum message visibility timeout12 hours
Minimum message visibility timeout0 seconds
Maximum clients waiting time to wait for the message from an empty queue (WaitTimeSeconds parameter)20 seconds
\n

Yandex SpeechKit

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Short audio recognition
Requests per second20
Streaming mode of short audio recognition
Requests per second40
Long audio recognition
Recognition requests per hour500
Operation status check requests per hour2500
Billable hours of audio per day10,000
Speech synthesis
Requests per second40
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Short audio recognition
Maximum file size1 MB
Maximum duration of audio30 seconds
Maximum number of audio channels1
Streaming mode of short audio recognition
Maximum duration of transmitted audio for the entire session5 minutes
Maximum size of transmitted audio data10 MB
Maximum number of audio channels1
Long audio recognition
Maximum file size1 GB
Maximum duration of audio4 hours
Period for storing recognition results on the server3 days
Speech synthesis
Maximum request size5000 characters
\n

Yandex Translate

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Calls of a single API method per second20
Characters sent for translation per hour1 million
Characters sent for language detection per hour1 million
\n

Limits

\n

There are no limits in the service. For limitations on the field values in the request body, see the API reference.

\n

Yandex Vision

\n

Quotas

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Number of requests per second10
Text recognition attempts per second5
Face detection attempts per second5
Image classification attempts per second5
\n

Limits

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Type of limitValue
Maximum file size1 MB
Maximum image size20 MP (length × width)
Maximum number of pages in a PDF file8
\n", + "title": "Quotas and limits", + "headings": [ + { + "title": "Why quotas are needed", + "href": "#quotas", + "level": 2 + }, + { + "title": "Quotas and limits defaults for Yandex.Cloud services", + "href": "#quotas-and-limits-defaults-for-yandex.cloud-services", + "level": 2, + "items": [ + { + "title": "Yandex Compute Cloud", + "href": "#compute", + "level": 3, + "items": [] + }, + { + "title": "Yandex Object Storage", + "href": "#storage", + "level": 3, + "items": [] + }, + { + "title": "Yandex Virtual Private Cloud", + "href": "#vpc", + "level": 3, + "items": [] + }, + { + "title": "Yandex Resource Manager", + "href": "#resource-manager", + "level": 3 + }, + { + "title": "Yandex Load Balancer", + "href": "#load-balancer", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for ClickHouse", + "href": "#mch", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for MongoDB", + "href": "#mmg", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for MySQL", + "href": "#mmy", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for PostgreSQL", + "href": "#mpg", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for Redis", + "href": "#mrd", + "level": 3, + "items": [] + }, + { + "title": "Yandex Message Queue", + "href": "#mq", + "level": 3, + "items": [] + }, + { + "title": "Yandex SpeechKit", + "href": "#speechkit", + "level": 3, + "items": [] + }, + { + "title": "Yandex Translate", + "href": "#translate", + "level": 3, + "items": [] + }, + { + "title": "Yandex Vision", + "href": "#vision", + "level": 3, + "items": [] + } + ] + } + ], + "meta": { + "author": { + "avatar": "", + "email": "robot-dataui-vcs@yandex-team.ru", + "login": "", + "name": "", + "url": "http://yandex.ru/" + }, + "contributors": [ + { + "avatar": "https://avatars.githubusercontent.com/u/2485945?v=6", + "email": "robot-dataui-vcs@yandex-team.ru", + "login": "", + "name": "DataUI VCS Robot", + "url": "" + }, + { + "avatar": "", + "email": "skanunnikov@yandex-team.ru", + "login": "", + "name": "Sergey Kanunnikov", + "url": "" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/2485935?v=4", + "email": "", + "login": "zamulla", + "name": "Aleksey Zamulla", + "url": "http://yandex.ru/" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/2485945?v=6", + "email": "migelle@yandex-team.ru", + "login": "migelle", + "name": "", + "url": "http://yandex.ru/" + }, + { + "avatar": "https://avatars.githubusercontent.com/u/2485945?v=6", + "email": "leeuw@yandex-team.ru", + "login": "", + "name": "", + "url": "http://yandex.ru/" + }, + { + "avatar": "", + "email": "dottir@yandex-team.ru", + "login": "", + "name": "Anastasia Karavaeva", + "url": "" + } + ] + }, + "toc": { + "title": "Yandex.Cloud overview", + "href": "/docs/overview/", + "items": [ + { + "name": "Yandex.Cloud services", + "href": "/docs/overview/concepts/services", + "id": "86fb5a9101b917e55d57955e41a4a773" + }, + { + "name": "Equivalent services on other platforms", + "expanded": true, + "items": [ + { + "name": "Overview", + "href": "/docs/overview/platform-comparison/", + "id": "db26ca551a9e94ece774f19a472cc500" + }, + { + "name": "Equivalents for Amazon Web Services", + "href": "/docs/overview/platform-comparison/aws", + "id": "c735da730d10e0f31b4b9a722251c0f0" + }, + { + "name": "Equivalents for Google Cloud Platform", + "href": "/docs/overview/platform-comparison/gcp", + "id": "f7aff73e87f0aca60595896b050a002d" + }, + { + "name": "Equivalents for Microsoft Azure", + "href": "/docs/overview/platform-comparison/azure", + "id": "ca1deb97788e2d54631f4908ac1b5ca6" + } + ], + "id": "c5625beecf9017c1290bd748fd9dc854" + }, + { + "name": "Availability zones", + "href": "/docs/overview/concepts/geo-scope", + "id": "685e6c38153524c07dcd17f415e1f516" + }, + { + "name": "Getting started", + "href": "/docs/overview/quickstart", + "id": "ab338ee86e023121249934c955ed9810" + }, + { + "name": "Release stages", + "href": "/docs/overview/concepts/launch-stages", + "id": "de2baab2bd603f5630a1a398d25fa370" + }, + { + "name": "Quotas and limits", + "href": "/docs/overview/concepts/quotas-limits", + "id": "a3bb97002572fbdca0f056d75d42acd2" + }, + { + "name": "API", + "href": "/docs/overview/api", + "id": "136bdc27253376a450eee99e4539c186" + }, + { + "name": "Security and compliance", + "items": [ + { + "name": "Security bulletin", + "href": "/docs/overview/security-bulletins/", + "id": "51e1474da0ee2ab04126285a0e3ca7df" + }, + { + "name": "Rules for performing external security scans", + "href": "/docs/overview/compliance/pentest", + "id": "50ced679abe4df4b724e5b2dc462846e" + } + ], + "id": "a2541cbb86e50d2f6ed9c2eba245611d" + }, + { + "name": "Deleting user data", + "href": "/docs/overview/concepts/data-deletion", + "id": "bba0c3f3dc2cd1b71c1aeb83d67d77a8" + }, + { + "name": "SLA", + "href": "/docs/overview/sla", + "id": "1636c6203504367fc47f73077e95ebad" + }, + { + "name": "Questions and answers", + "href": "/docs/overview/qa", + "id": "6293190c6aa223973af0014e104c70ef" + } + ] + }, + "breadcrumbs": [ + { + "name": "Documentation" + }, + { + "name": "Quotas and limits" + } + ], + "filePath": "/cache/c12878f1-cc2e-445f-8f96-7374fe76b074/en/overview/concepts/quotas-limits.md", + "githubUrl": "https://github.com/yandex-cloud/docs/tree/master/en/overview/concepts/quotas-limits.md", + "vcsType": "github", + "vcsUrl": "https://github.com/yandex-cloud/docs/tree/master/en/overview/concepts/quotas-limits.md" +} diff --git a/demo/src/Components/DocPage/page-ru.json b/demo/src/Components/DocPage/page-ru.json new file mode 100644 index 00000000..fa452b63 --- /dev/null +++ b/demo/src/Components/DocPage/page-ru.json @@ -0,0 +1,271 @@ +{ + "html": "

В сервисах Яндекс.Облака могут действовать квоты и лимиты:

\n\n

Квоты, установленные для вашего аккаунта, можно посмотреть в консоли управления.

\n

Проектируя инфраструктуру в Облаке, учитывайте лимиты как предел возможностей, которые Облако может вам предоставить. Квоты — изменяемые ограничения, которые потенциально могут быть увеличены до значения лимитов.

\n

Зачем нужны квоты

\n

Квоты служат мягким ограничением для запроса ресурсов, и позволяют Облаку гарантировать стабильность работы сервиса: новые пользователи не могут занять слишком много ресурсов в тестовых целях. Если вы готовы использовать большее количество ресурсов, обратитесь в техническую поддержку и расскажите, какие именно квоты нужно увеличить, и каким образом.

\n

Техническая поддержка принимает решение увеличивать или не увеличивать квоты в индивидуальном порядке.

\n

Квоты и лимиты по умолчанию для сервисов Облака

\n

Квоты приведены со значениями по умолчанию, которые совпадают с квотами на время пробного периода.

\n

Yandex Compute Cloud

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество виртуальных машин в одном облаке12
Суммарное количество vCPU для всех виртуальных машин в одном облаке32
Суммарный объем виртуальной памяти для всех виртуальных машин в одном облаке128 ГБ
Суммарное количество дисков в одном облаке32
Суммарный объем SSD-дисков в одном облаке200 ГБ
Суммарный объем HDD-дисков в одном облаке500 ГБ
Суммарное количество снимков дисков в одном облаке32
Суммарный объем всех снимков дисков в одном облаке400 ГБ
Количество образов в одном облаке8
Количество групп виртуальных машин в одном облаке10
Суммарное количество GPU для всех виртуальных машин в одном облаке*0
Количество одновременно выполняемых операций в облаке15
Максимальное количество ВМ в одной группе размещения5
Максимальное количество групп размещения в одном облаке2
\n

* Чтобы создать виртуальную машину с GPU, обратитесь в техническую поддержку.

\n

Лимиты виртуальных машин

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Максимальное количество vCPU для одной виртуальной машины32 и 64 для платформ Intel Broadwell и Intel Cascade Lake соответственно
Максимальный объем виртуальной памяти для одной виртуальной машины256 ГБ и 512 ГБ для платформ Intel Broadwell и Intel Cascade Lake соответственно
Максимальное количество дисков, подключенных к одной виртуальной машине7
Максимальное количество GPU, подключенных к одной виртуальной машине4
Максимальное количество vCPU для виртуальных машин с GPU32
Максимальное количество RAM для виртуальных машин с GPU384
\n

Лимиты дисков

\n
\n
\n
Сетевой SSD-диск
\n
Cетевой HDD-диск
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Максимальный размер диска4 ТБ
Максимальный размер снимка диска4 ТБ
Размер блока размещения32 ГБ
Максимальный* IOPS на запись, на 1 диск40000
Максимальный* IOPS на запись, на блок размещения1000
Максимальная** пропускная способность на запись, на 1 диск450 МБ/с
Максимальная** пропускная способность на запись, на блок размещения15 МБ/с
Максимальный* IOPS на чтение, на 1 диск12000
Максимальный* IOPS на чтение, на блок размещения400
Максимальная** пропускная способность на чтение, на 1 диск450 МБ/с
Максимальная** пропускная способность на чтение, на блок размещения15 МБ/с
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Максимальный размер диска4 ТБ
Максимальный размер снимка диска4 ТБ
Размер блока размещения256 ГБ
Максимальный* IOPS на запись, на 1 диск11000
Максимальный* IOPS на запись, на блок размещения300
Максимальная** пропускная способность на запись, на 1 диск240 МБ/с
Максимальная** пропускная способность на запись, на блок размещения30 МБ/с
Максимальный* IOPS на чтение, на 1 диск300
Максимальный* IOPS на чтение, на блок размещения100
Максимальная** пропускная способность на чтение, на 1 диск240 МБ/с
Максимальная** пропускная способность на чтение, на блок размещения30 МБ/с
\n
\n
\n

Операции чтения и записи потребляют один и тот же дисковый ресурс — чем больше производится операций чтения, тем меньше операций записи, и наоборот. Подробнее читайте в разделе Диски.

\n
*
\n

Для получения максимального значения IOPS рекомендуется делать чтения и записи, не превышающие 4 КБ.

\n
**
\n

Для получения максимального значения пропускной способности рекомендуется делать чтения и записи размером 4 МБ.

\n

Yandex Object Storage

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Объем хранилища в одном облаке5 ТБ
Количество бакетов в одном облаке25
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Максимальный размер объекта5 TБ
Общий размер заголовков для 1 запроса к HTTP API8 KБ
Размер пользовательских метаданных объекта2 KБ
Максимальный размер данных для загрузки за 1 запрос5 ГБ
Минимальный размер части данных для составной загрузки, кроме последнего5 МБ
Максимальное количество частей в составной загрузке10000
\n

Yandex Virtual Private Cloud

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество облачных сетей в одном облаке2
Количество подсетей в одном облаке6
Количество всех публичных IP-адресов в одном облаке8
Количество статических публичных IP-адресов в одном облаке2
Количество таблиц маршрутизации в одном облаке8
Количество статических маршрутов в одном облаке256
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Минимальный размер CIDR для подсети/28
Максимальный размер CIDR для подсети/16
Максимальное количество одновременно установленных TCP/UDP-соединений для одной виртуальной машины50000
\n

Фильтрация исходящего трафика

\n

В Яндекс.Облаке всегда блокируется исходящий трафик на TCP-порт 25:

\n\n

Яндекс.Облако может открыть TCP-порт 25 по запросу в поддержку, если вы соблюдаете Правила допустимого использования. При этом Яндекс.Облако всегда может снова заблокировать исходящий трафик на TCP-порте 25, если вы нарушите Правила.

\n

Yandex Resource Manager

\n

Квоты и лимиты для сервиса Yandex Resource Manager не определены.

\n

Yandex Load Balancer

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество балансировщиков в одном облаке2
Количество целевых групп в одном облаке2
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество ресурсов в целевой группе254
Количество портов обработчика10
Количество проверок состояния на подключенную целевую группу1
Протокол проверок состоянияTCP, HTTP
\n

Прочие ограничения

\n

В одной целевой группе могут находиться целевые ресурсы только из одной облачной сети.

\n

В пределах одной зоны доступности в целевую группу могут входить ресурсы, подключенные к одной подсети.

\n

Можно создать балансировщик без обработчика.

\n

Проверки состояния передаются из диапазона адресов 198.18.235.0/24.

\n

При подключении ресурсов к балансировщику учитывайте лимит на максимальное количество одновременно установленных TCP/UDP-соединений для одной виртуальной машины.

\n

Yandex Managed Service for ClickHouse

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество кластеров в одном облаке16
Суммарное количество ядер процессора для всех хостов баз данных в одном облаке64
Суммарный объем виртуальной памяти для всех хостов баз данных в одном облаке512 ГБ
Суммарный объем хранилищ для всех кластеров в одном облаке4096 ГБ
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Минимальный класс хостаb1.nano (5% × 2 vCPU Intel Broadwell, 2 ГБ RAM)
Максимальный класс хостаm2.8xlarge (64 vCPU Intel Cascade Lake, 512 ГБ RAM)
Максимальное количество шардов в одном кластере10
Максимальное количество хостов в одном шарде7
Максимальное количество хостов в одном кластере73 (10 шардов × 7 хостов + 3 хоста ZooKeeper)
Максимальный объем данных при использовании сетевого хранилища4096 ГБ
Максимальный объем данных при использовании локального хранилища1400 ГБ
\n

Yandex Managed Service for MongoDB

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество кластеров в одном облаке16
Суммарное количество ядер процессора для всех хостов баз данных в одном облаке64
Суммарный объем виртуальной памяти для всех хостов баз данных в одном облаке512 ГБ
Суммарный объем хранилищ для всех кластеров в одном облаке4096 ГБ
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Минимальный класс хостаb1.nano (5% × 2 vCPU Intel Broadwell, 2 ГБ RAM)
Максимальный класс хостаm2.8xlarge (64 vCPU Intel Cascade Lake, 512 ГБ RAM)
Максимальное количество шардов в одном кластере MongoDB10
Максимальное количество хостов в одном шарде7
Максимальное количество хостов в одном кластере70 (10 шардов × 7 хостов)
Максимальный объем хранилища для кластера512 ГБ
\n

Yandex Managed Service for MySQL

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество кластеров в одном облаке16
Суммарное количество ядер процессора для всех хостов баз данных в одном облаке64
Суммарный объем виртуальной памяти для всех хостов баз данных в одном облаке512 ГБ
Суммарный объем хранилищ для всех кластеров в одном облаке4096 ГБ
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Минимальный класс хостаb1.nano (5% × 2 vCPU Intel Broadwell, 2 ГБ RAM)
Максимальный класс хостаm2.8xlarge (64 vCPU Intel Cascade Lake, 512 ГБ RAM)
Максимальное количество хостов в одном кластере7
Максимальный объем данных при использовании сетевого хранилища2048 ГБ
Максимальный объем данных при использовании локального хранилища1400 ГБ
\n

Yandex Managed Service for PostgreSQL

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество кластеров в одном облаке16
Суммарное количество ядер процессора для всех хостов баз данных в одном облаке64
Суммарный объем виртуальной памяти для всех хостов баз данных в одном облаке512 ГБ
Суммарный объем хранилищ для всех кластеров в одном облаке4096 ГБ
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Минимальный класс хостаb1.nano (5% × 2 vCPU Intel Broadwell, 2 ГБ RAM)
Максимальный класс хостаm2.8xlarge (64 vCPU Intel Cascade Lake, 512 ГБ RAM)
Максимальное количество хостов в одном кластере7
Максимальный объем данных при использовании сетевого хранилища2048 ГБ
Максимальный объем данных при использовании локального хранилища1400 ГБ
\n

Yandex Managed Service for Redis

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество кластеров в одном облаке16
Суммарное количество ядер процессора для всех хостов баз данных в одном облаке64
Суммарный объем виртуальной памяти для всех хостов в одном облаке512 ГБ
Суммарный объем дисков для всех кластеров в одном облаке4096 ГБ
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Минимальный класс хостаb1.nano (burstable с 2 ГБ RAM)
Максимальный класс хостаhm1.9xlarge (high-memory с 256 ГБ RAM)
Максимальное количество хостов в одном кластере7
Минимальный размер диска для кластераВ 2 раза больше выбранного объема RAM
Максимальный размер диска для кластераВ 8 раз больше выбранного объема RAM
\n

Yandex Message Queue

\n

Квоты

\n
Сообщения
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество вызовов SendMessage и SendMessageBatch на одну очередь300 вызовов/с для стандартной очереди

30 вызовов/с для очереди FIFO
Количество вызовов ReceiveMessage на одну очередь300 вызовов/с для стандартной очереди

30 вызовов/с для очереди FIFO
Количество вызовов DeleteMessage и DeleteMessageBatch на одну очередь300 вызовов/с для стандартной очереди

30 вызовов/с для очереди FIFO
Количество вызовов ChangeMessageVisibility и ChangeMessageVisibilityBatch на одну очередь300 вызовов/с для стандартной очереди

30 вызовов/с для очереди FIFO
Количество вызовов CreateQueue на одно облако2 вызова/с
Количество вызовов DeleteQueue на одно облако5 вызовов/с
Количество вызовов других запросов на одно облако100 вызовов/с
Количество очередей в одном облаке10
\n

Лимиты

\n
Очереди
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Минимальное время задержки доставки сообщения в очередь (параметр DelaySeconds)0 секунд
Максимальное время задержки доставки сообщения в очередь (параметр DelaySeconds)900 секунд (15 минут)
Количество сообщений в обработке на одну стандартную очередь120000
Количество сообщений в обработке на одну очередь FIFO20000
Имя очередиНе более 80 символов: цифр, маленьких и заглавных латинских букв, дефисов и подчеркиваний. Имя очереди FIFO должно заканчиваться суффиксом .fifo.
\n
Сообщения
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Идентификатор сообщения в группеНе более 80 символов: цифр, маленьких и заглавных латинских букв, дефисов и подчеркиваний.
Максимальное количество атрибутов сообщения10
Максимальное количество сообщений в группе10
Содержимое сообщенийXML, JSON и неформатированный текст. Поддерживаются следующие символы Unicode:
  • #x9
  • #xA
  • #xD
  • от #x20 до #xD7FF
  • от #xE000 до #xFFFD
  • от #x10000 до #x10FFFF
Максимальный срок хранения сообщений в очереди1209600 секунд (14 дней)
Минимальный срок хранения сообщений в очереди60 секунд (1 минута)
Максимальное время задержки доставки сообщения в очередь (параметр DelaySeconds)900 секунд (15 минут)
Минимальное время задержки доставки сообщения в очередь (параметр DelaySeconds)0 секунд
Максимальный размер сообщения262144 байт (256 КБ)
Минимальный размер сообщения1 байт
Максимальный таймаут видимости сообщения12 часов
Минимальный таймаут видимости сообщения0 секунд
Максимальное время ожидания клиентом сообщения в пустой очереди. (параметр WaitTimeSeconds)20 секунд
\n

Yandex SpeechKit

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Распознавание коротких аудио
Запросов в секунду20
Потоковый режим распознавания коротких аудио
Запросов в секунду40
Распознавание длинных аудио
Запросов на распознавание в час500
Запросов на проверку статуса операции в час2500
Тарифицированных часов аудио в день10000
Cинтез речи
Запросов в секунду40
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Распознавание коротких аудио
Максимальный размер файла1 МБ
Максимальная длительность аудио30 секунд
Максимальное количество аудиоканалов1
Потоковый режим распознавания коротких аудио
Максимальная длительность переданного аудио за всю сессию5 минут
Максимальный размер переданных аудиоданных10 МБ
Максимальное количество аудиоканалов1
Распознавание длинных аудио
Максимальный размер файла1 ГБ
Максимальная длительность аудио4 часа
Срок хранения результатов распознавания на сервере3 суток
Синтез речи
Максимальный размер запроса5000 символов
\n

Yandex Translate

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Вызовов одного метода API в секунду20
Символов, отправленных на перевод, в час1 млн
Символов, отправленных на определение языка, в час1 млн
\n

Лимиты

\n

Лимиты в сервисе отсутствуют. Ограничения на значения полей в теле запроса см. в справочнике API.

\n

Yandex Vision

\n

Квоты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Количество запросов в секунду10
Распознаваний текста в секунду5
Обнаружений лиц в секунду5
Классификаций изображений в секунду5
\n

Лимиты

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Вид ограниченияЗначение
Максимальный размер файла1 МБ
Максимальный размер изображения20 мегапикселей (длина × ширина)
Максимальное количество страниц в PDF-файле8
\n", + "title": "Квоты и лимиты", + "headings": [ + { + "title": "Зачем нужны квоты", + "href": "#quotas", + "level": 2 + }, + { + "title": "Квоты и лимиты по умолчанию для сервисов Облака", + "href": "#kvoty-i-limity-po-umolchaniyu-dlya-servisov-oblaka", + "level": 2, + "items": [ + { + "title": "Yandex Compute Cloud", + "href": "#compute", + "level": 3, + "items": [] + }, + { + "title": "Yandex Object Storage", + "href": "#storage", + "level": 3, + "items": [] + }, + { + "title": "Yandex Virtual Private Cloud", + "href": "#vpc", + "level": 3, + "items": [] + }, + { + "title": "Yandex Resource Manager", + "href": "#resource-manager", + "level": 3 + }, + { + "title": "Yandex Load Balancer", + "href": "#load-balancer", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for ClickHouse", + "href": "#mch", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for MongoDB", + "href": "#mmg", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for MySQL", + "href": "#mmy", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for PostgreSQL", + "href": "#mpg", + "level": 3, + "items": [] + }, + { + "title": "Yandex Managed Service for Redis", + "href": "#mrd", + "level": 3, + "items": [] + }, + { + "title": "Yandex Message Queue", + "href": "#mq", + "level": 3, + "items": [] + }, + { + "title": "Yandex SpeechKit", + "href": "#speechkit", + "level": 3, + "items": [] + }, + { + "title": "Yandex Translate", + "href": "#translate", + "level": 3, + "items": [] + }, + { + "title": "Yandex Vision", + "href": "#vision", + "level": 3, + "items": [] + } + ] + } + ], + "meta": { + "author": { + "avatar": "https://avatars.githubusercontent.com/u/2485935?v=4", + "email": "", + "login": "robot-dataui-vcs@yandex-team.ru", + "name": "DataUI VCS Robot", + "url": "http://yandex.ru/" + }, + "contributors": [ + { + "avatar": "https://avatars.githubusercontent.com/u/2485945?v=6", + "email": "robot-dataui-vcs@yandex-team.ru", + "login": "", + "name": "DataUI VCS Robot", + "url": "" + } + ] + }, + "toc": { + "title": "Обзор платформы", + "href": "/docs/overview/", + "items": [ + { + "name": "Сервисы Яндекс.Облака", + "href": "/docs/overview/concepts/services", + "id": "e619422ec28a87ea25c41d4ad7e45563" + }, + { + "name": "Сопоставление с другими платформами", + "expanded": true, + "items": [ + { + "name": "Обзор", + "href": "/docs/overview/platform-comparison/", + "id": "6cb52f285fd5d6ec04cbd96854dffab6" + }, + { + "name": "Сопоставление с Amazon Web Services", + "href": "/docs/overview/platform-comparison/aws", + "id": "1d84cf0e31f3f0968bdc6e8c2e11ba2f" + }, + { + "name": "Сопоставление с Google Cloud Platform", + "href": "/docs/overview/platform-comparison/gcp", + "id": "931f7302b4162c44fbe9bb07cea05b1e" + }, + { + "name": "Сопоставление с Microsoft Azure", + "href": "/docs/overview/platform-comparison/azure", + "id": "c14b119f6944d57afd8d4b3e61c37e21" + } + ], + "id": "fa5cef5c0b0cf236fdaa1562cbc48eb8" + }, + { + "name": "Зоны доступности", + "href": "/docs/overview/concepts/geo-scope", + "id": "054328058d7c7b60ff85e3c95be1a264" + }, + { + "name": "Начало работы", + "href": "/docs/overview/quickstart", + "id": "c984893c04c28bc3fe91f5ff87b79c29" + }, + { + "name": "Стадии готовности сервисов", + "href": "/docs/overview/concepts/launch-stages", + "id": "c53a86addb3cf17f0847acde5282ad84" + }, + { + "name": "Квоты и лимиты", + "href": "/docs/overview/concepts/quotas-limits", + "id": "56baa37ef41a57f75c4fce09722ab06c" + }, + { + "name": "API", + "href": "/docs/overview/api", + "id": "136bdc27253376a450eee99e4539c186" + }, + { + "name": "Безопасность и соответствие стандартам", + "items": [ + { + "name": "Рекомендации по безопасности", + "href": "/docs/overview/security-bulletins/", + "id": "c87ac8d995548646dec5661b1b28e45c" + }, + { + "name": "Правила проведения внешних сканирований безопасности", + "href": "/docs/overview/compliance/pentest", + "id": "544f4d243ace1fc32591d87d2460742d" + }, + { + "name": "Безопасность платформы Яндекс.Облако", + "items": [ + { + "name": "Обзор", + "href": "/docs/overview/security/", + "id": "022cc6311c43fdd3c2848a238c92de1e" + }, + { + "name": "Ключевые принципы безопасности", + "href": "/docs/overview/security/principles", + "id": "fba46ca0333fec72f16d0e29fb78e239" + }, + { + "name": "Разделение ответственности за обеспечение безопасности", + "href": "/docs/overview/security/respons", + "id": "177e184118e1e45bc890a53bf76971c7" + }, + { + "name": "Следование лучшим практикам и стандартам", + "href": "/docs/overview/security/standarts", + "id": "778f32125645fc81b0f680d2d9d09d23" + }, + { + "name": "Соответствие требованиям", + "href": "/docs/overview/security/conform", + "id": "4d7329262a479edc4b28a7607a31c2b4" + }, + { + "name": "Технические меры защиты на стороне провайдера", + "href": "/docs/overview/security/tech-measures", + "id": "98cfa650bb690aa5b9a9aa66f3e7e52b" + }, + { + "name": "Средства защиты, доступные пользователям облачных сервисов", + "href": "/docs/overview/security/user-side", + "id": "aaffdd260266d59cd003e6fcab17a1ec" + }, + { + "name": "Полезные ресурсы", + "href": "/docs/overview/security/resources", + "id": "72071e3e599234cb4df3fe56ac378dc1" + } + ], + "id": "f8cca556a8df3df325a1dce2ffec52dc" + } + ], + "id": "7f196892c00a04c9d36a10bc2275ccb8" + }, + { + "name": "Удаление данных пользователей", + "href": "/docs/overview/concepts/data-deletion", + "id": "66fe49c2a324ffd44541fe6a33e14ed0" + }, + { + "name": "SLA", + "href": "/docs/overview/sla", + "id": "1636c6203504367fc47f73077e95ebad" + }, + { + "name": "Вопросы и ответы", + "href": "/docs/overview/qa", + "id": "ff7aacc415c714c83d0ea5715eabb016" + } + ] + }, + "breadcrumbs": [ + { + "name": "Документация" + }, + { + "name": "Квоты и лимиты" + } + ], + "filePath": "/cache/c12878f1-cc2e-445f-8f96-7374fe76b074/ru/overview/concepts/quotas-limits.md", + "githubUrl": "https://github.com/yandex-cloud/docs/tree/master/ru/overview/concepts/quotas-limits.md", + "vcsType": "github", + "vcsUrl": "https://github.com/yandex-cloud/docs/tree/master/ru/overview/concepts/quotas-limits.md" +} diff --git a/demo/src/Components/DocPage/single-page-en.json b/demo/src/Components/DocPage/single-page-en.json new file mode 100644 index 00000000..d24f3073 --- /dev/null +++ b/demo/src/Components/DocPage/single-page-en.json @@ -0,0 +1,30 @@ +{ + "html": "

Get started

\n

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

\n
\n

Second page

\n

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?

\n", + "title": "", + "headings": [ + {"title": "Get started", "href": "#_en_index_get-started", "level": 2}, + {"title": "Second page", "href": "#_en_second-page_second-page", "level": 2} + ], + "meta": {}, + "toc": { + "title": "Test langs", + "href": "/test-lang/", + "items": [ + { + "name": "Get started", + "href": "/test-lang/#_en_index", + "id": "5b421eb9a84e5f57b317fa003da0b25c" + }, + { + "name": "Second page", + "href": "/test-lang/#_en_second-page", + "id": "291ab0582d4b7c0831afdf82a80a8c92" + } + ], + "base": "en", + "singlePage": true + }, + "breadcrumbs": [], + "vcsUrl": "https://github.com/yandex-cloud/docs/tree/test-lang/1/en/_single_page/index.md", + "vcsType": "github" +} diff --git a/demo/src/Components/DocPage/single-page-ru.json b/demo/src/Components/DocPage/single-page-ru.json new file mode 100644 index 00000000..40206f88 --- /dev/null +++ b/demo/src/Components/DocPage/single-page-ru.json @@ -0,0 +1,30 @@ +{ + "html": "

Ознакомление

\n

Lorem Ipsum - это текст-"рыба", часто используемый в печати и вэб-дизайне. Lorem Ipsum является стандартной "рыбой" для текстов на латинице с начала XVI века. В то время некий безымянный печатник создал большую коллекцию размеров и форм шрифтов, используя Lorem Ipsum для распечатки образцов. Lorem Ipsum не только успешно пережил без заметных изменений пять веков, но и перешагнул в электронный дизайн. Его популяризации в новое время послужили публикация листов Letraset с образцами Lorem Ipsum в 60-х годах и, в более недавнее время, программы электронной вёрстки типа Aldus PageMaker, в шаблонах которых используется Lorem Ipsum.

\n
\n

Вторая страница

\n

Давно выяснено, что при оценке дизайна и композиции читаемый текст мешает сосредоточиться. Lorem Ipsum используют потому, что тот обеспечивает более или менее стандартное заполнение шаблона, а также реальное распределение букв и пробелов в абзацах, которое не получается при простой дубликации "Здесь ваш текст.. Здесь ваш текст.. Здесь ваш текст.." Многие программы электронной вёрстки и редакторы HTML используют Lorem Ipsum в качестве текста по умолчанию, так что поиск по ключевым словам "lorem ipsum" сразу показывает, как много веб-страниц всё ещё дожидаются своего настоящего рождения. За прошедшие годы текст Lorem Ipsum получил много версий. Некоторые версии появились по ошибке, некоторые - намеренно (например, юмористические варианты).

\n", + "title": "", + "headings": [ + {"title": "Ознакомление", "href": "#_ru_index_oznakomlenie", "level": 2}, + {"title": "Вторая страница", "href": "#_ru_second-page_vtoraya-stranica", "level": 2} + ], + "meta": {}, + "toc": { + "title": "Тест языков", + "href": "/test-lang/", + "items": [ + { + "name": "Ознакомление", + "href": "/test-lang/#_ru_index", + "id": "7c9fe5f5f46c91e3c70bc359bef65b22" + }, + { + "name": "Вторая страница", + "href": "/test-lang/#_ru_second-page", + "id": "dc946863157ca4b7430495cf64689a64" + } + ], + "base": "ru", + "singlePage": true + }, + "breadcrumbs": [], + "vcsUrl": "https://github.com/yandex-cloud/docs/tree/test-lang/1/ru/_single_page/index.md", + "vcsType": "github" +} diff --git a/demo/src/Components/ErrorPage/index.tsx b/demo/src/Components/ErrorPage/index.tsx new file mode 100644 index 00000000..96401277 --- /dev/null +++ b/demo/src/Components/ErrorPage/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import {ErrorPage, ERROR_CODES} from '@doc-tools/components'; +import {getIsMobile} from '../../controls/settings'; +import getLangControl from '../../controls/lang'; +import {radios, text} from '@storybook/addon-knobs'; + +const ErrorPageDemo = () => { + const langValue = getLangControl(); + const isMobile = getIsMobile(); + const errorCode = getErrorCode(); + + return ( +
+ +
+ ); +}; + +function getErrorCode() { + return radios('Errors', ERROR_CODES, ERROR_CODES.SERVER_ERROR); +} + +export default ErrorPageDemo; diff --git a/demo/src/Components/Feedback/index.stories.tsx b/demo/src/Components/Feedback/index.stories.tsx new file mode 100644 index 00000000..ceef1156 --- /dev/null +++ b/demo/src/Components/Feedback/index.stories.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +import {configure, Feedback} from '@doc-tools/components'; + +configure(); + +const FeedbackDemo = () => { + return ( + + ); +}; + +export default { + title: 'Example/Feedback', + component: FeedbackDemo, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + // tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes + // argTypes: { + // backgroundColor: { control: 'color' }, + // }, +}; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Primary = { + args: { + primary: true, + label: 'Button', + }, +}; + diff --git a/demo/src/Components/Header/Header.scss b/demo/src/Components/Header/Header.scss new file mode 100644 index 00000000..6fdf87dc --- /dev/null +++ b/demo/src/Components/Header/Header.scss @@ -0,0 +1,5 @@ +.Header { + &__control-input { + max-width: 170px; + } +} diff --git a/demo/src/Components/Header/Header.tsx b/demo/src/Components/Header/Header.tsx new file mode 100644 index 00000000..b571b6de --- /dev/null +++ b/demo/src/Components/Header/Header.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import cn from 'bem-cn-lite'; +import {TextInput} from '@gravity-ui/uikit'; +import { + Lang, + ControlSizes, + LangControl, + FullScreenControl, + DividerControl, +} from '@doc-tools/components'; +import './Header.scss'; + +const headBlock = cn('Header'); +const layoutBlock = cn('Layout'); + +export interface HeaderProps { + lang: Lang; + fullScreen: boolean; + searchText?: string; + onChangeLang?: (lang: Lang) => void; + onChangeFullScreen?: (value: boolean) => void; + onChangeSearch?: (value: string) => void; +} + +const Header: React.FC = ({ + lang, + fullScreen, + searchText, + onChangeFullScreen, + onChangeLang, + onChangeSearch, +}) => { + return ( +
+
+ + + + + {onChangeFullScreen ? ( + + ) : null} +
+
+ ); +}; + +export default Header; diff --git a/demo/src/Components/Paginator/index.tsx b/demo/src/Components/Paginator/index.tsx new file mode 100644 index 00000000..d9a2f1a8 --- /dev/null +++ b/demo/src/Components/Paginator/index.tsx @@ -0,0 +1,21 @@ +import React, {useState} from 'react'; + +import {Paginator} from '@doc-tools/components'; + +const PaginatorDemo = () => { + const [page, setPage] = useState(1); + + return ( + + ); +}; + +export default PaginatorDemo; diff --git a/demo/src/Components/SearchItem/index.tsx b/demo/src/Components/SearchItem/index.tsx new file mode 100644 index 00000000..4a7e77a5 --- /dev/null +++ b/demo/src/Components/SearchItem/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +import {SearchItem} from '@doc-tools/components'; +import data from './page.json'; + +const SearchItemDemo = () => { + return ; +}; + +export default SearchItemDemo; diff --git a/demo/src/Components/SearchItem/page.json b/demo/src/Components/SearchItem/page.json new file mode 100644 index 00000000..3e79f05f --- /dev/null +++ b/demo/src/Components/SearchItem/page.json @@ -0,0 +1,5 @@ +{ + "title": "Add a chart", + "description": "Follow these instruction to add a chart to your dashboard.", + "url": "/en/data-visualization/step-by-step/charts/add-chart" +} diff --git a/demo/src/Components/SearchPage/data.ts b/demo/src/Components/SearchPage/data.ts new file mode 100644 index 00000000..a5d2cf75 --- /dev/null +++ b/demo/src/Components/SearchPage/data.ts @@ -0,0 +1,26 @@ +export default [ + { + title: 'Lorem ipsum dolor sit amet', + url: '/en/data-visualization/step-by-step/charts/add-chart', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec nisi eget tellus pharetra pharetra sit amet in metus. Fusce eu consequat nisl. Sed dictum porta scelerisque. Fusce auctor enim ligula, at rhoncus ligula placerat id. Aliquam venenatis cursus ante, quis feugiat tortor mattis et. Donec sed justo eu est egestas malesuada.', + }, + { + title: 'Vivamus scelerisque dictum blandit', + url: '/en/data-visualization/step-by-step/charts/add-chart', + description: + 'Vivamus scelerisque dictum blandit. Curabitur metus odio, lobortis eu est id, iaculis accumsan massa. Ut et euismod nisl. Donec mollis odio at elementum sagittis. Quisque dapibus eros purus, eu vulputate nisl pellentesque et. Ut vehicula mattis euismod. Vestibulum ornare nulla vel nisi consectetur accumsan. Quisque id felis tempus, porttitor ex et, pellentesque ligula.', + }, + { + title: 'Integer tincidunt rhoncus purus', + url: '/en/data-visualization/step-by-step/charts/add-chart', + description: + 'Integer tincidunt rhoncus purus, nec laoreet arcu. Ut fermentum nulla sit amet arcu tristique vulputate. Vestibulum et tempor arcu. Cras laoreet ipsum ac mi rutrum, in sagittis metus pharetra. In mi nibh, lobortis sed tempor quis, fermentu', + }, + { + title: 'Cras aliquam et eros ut', + url: '/en/data-visualization/step-by-step/charts/add-chart', + description: + 'Cras aliquam et eros ut lobortis. Quisque malesuada, nunc vitae placerat varius, ante nulla pharetra libero, at fringilla magna sem non velit. Curabitur finibus mauris vitae est pellentesque, vitae molestie tortor condimentum. ', + }, +]; diff --git a/demo/src/Components/SearchPage/index.tsx b/demo/src/Components/SearchPage/index.tsx new file mode 100644 index 00000000..cd9163f9 --- /dev/null +++ b/demo/src/Components/SearchPage/index.tsx @@ -0,0 +1,40 @@ +import React, {useState} from 'react'; + +import {ISearchItem, SearchPage} from '@doc-tools/components'; +import {getIsMobile} from '../../controls/settings'; +import getLangControl from '../../controls/lang'; +import mockData from './data'; + +const SearchPageDemo = () => { + const langValue = getLangControl(); + const isMobile = getIsMobile(); + const [page, setPage] = useState(1); + const [items, setItems] = useState(getItems(page, mockData)); + + function getItems(newPage: number, data: ISearchItem[]): ISearchItem[] { + return newPage === 1 ? data.slice(0, 2) : data.slice(2); + } + + return ( +
+ { + setPage(newPage); + setItems(getItems(newPage, mockData)); + }} + onSubmit={() => setItems(getItems(page, mockData))} + itemOnClick={(item) => console.log('Click on search result', item)} + irrelevantOnClick={(item) => console.log('Click on dislike button', item)} + relevantOnClick={(item) => console.log('Click on like button', item)} + itemsPerPage={2} + totalItems={mockData.length} + lang={langValue} + /> +
+ ); +}; + +export default SearchPageDemo; diff --git a/demo/src/controls/lang.tsx b/demo/src/controls/lang.tsx new file mode 100644 index 00000000..92c440c4 --- /dev/null +++ b/demo/src/controls/lang.tsx @@ -0,0 +1,13 @@ +import {radios} from '@storybook/addon-knobs'; +import {Lang} from '@doc-tools/components'; + +export default function getLangControl() { + const label = 'Language'; + const options = { + ru: Lang.Ru, + en: Lang.En, + }; + const defaultValue = Lang.Ru; + + return radios(label, options, defaultValue); +} diff --git a/demo/src/controls/settings.tsx b/demo/src/controls/settings.tsx new file mode 100644 index 00000000..1361508b --- /dev/null +++ b/demo/src/controls/settings.tsx @@ -0,0 +1,12 @@ +import {radios} from '@storybook/addon-knobs'; + +export function getIsMobile() { + const label = 'Mobile version'; + const options = { + enabled: 'true', + disabled: 'false', + }; + const defaultValue = 'false'; + + return radios(label, options, defaultValue); +} diff --git a/demo/src/controls/vcs.tsx b/demo/src/controls/vcs.tsx new file mode 100644 index 00000000..45b12e72 --- /dev/null +++ b/demo/src/controls/vcs.tsx @@ -0,0 +1,13 @@ +import {radios} from '@storybook/addon-knobs'; +import {Vcs} from '@doc-tools/components'; + +export default function getVcsControl() { + const label = 'VCS'; + const options = { + github: Vcs.Github, + arcanum: Vcs.Arcanum, + }; + const defaultValue = Vcs.Github; + + return radios(label, options, defaultValue); +} diff --git a/demo/src/decorators/bookmark.ts b/demo/src/decorators/bookmark.ts new file mode 100644 index 00000000..25c10cee --- /dev/null +++ b/demo/src/decorators/bookmark.ts @@ -0,0 +1,12 @@ +import {radios} from '@storybook/addon-knobs'; + +export function getHasBookmark() { + const label = 'Bookmark page'; + const options = { + enabled: 'true', + disabled: 'false', + }; + const defaultValue = 'false'; + + return radios(label, options, defaultValue); +} diff --git a/demo/src/decorators/subscribe.tsx b/demo/src/decorators/subscribe.tsx new file mode 100644 index 00000000..d8e7b260 --- /dev/null +++ b/demo/src/decorators/subscribe.tsx @@ -0,0 +1,12 @@ +import {radios} from '@storybook/addon-knobs'; + +export function getHasSubscribe() { + const label = 'Subscribe button'; + const options = { + enabled: 'true', + disabled: 'false', + }; + const defaultValue = 'true'; + + return radios(label, options, defaultValue); +} diff --git a/demo/src/decorators/withTheme.tsx b/demo/src/decorators/withTheme.tsx new file mode 100644 index 00000000..f221ac0c --- /dev/null +++ b/demo/src/decorators/withTheme.tsx @@ -0,0 +1,23 @@ +// import {StoryFn} from '@storybook/addons'; +import {radios} from '@storybook/addon-knobs'; +import {Theme} from '@doc-tools/components'; + +export function getThemeSelector() { + const label = 'Theme'; + const options = { + Light: Theme.Light, + Dark: Theme.Dark, + }; + const defaultValue = Theme.Dark; + + return radios(label, options, defaultValue); +} + +export default function withTheme(story: any) { + const theme = getThemeSelector(); + document.body.classList.add('yc-root'); + document.body.classList.toggle('yc-root_theme_light', theme === 'light'); + document.body.classList.toggle('yc-root_theme_dark', theme === 'dark'); + + return story(); +} diff --git a/demo/src/reset-storybook.scss b/demo/src/reset-storybook.scss new file mode 100644 index 00000000..e0d4fe31 --- /dev/null +++ b/demo/src/reset-storybook.scss @@ -0,0 +1,129 @@ +@import '../../styles/mixins'; + +*, +*::before, +*::after { + box-sizing: inherit; +} + +html, +body { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +#root { + min-height: 100vh; +} + +@import './variables'; +@import './mixins'; + +body.yc-root { + font-feature-settings: 'liga', 'kern'; + text-size-adjust: 100%; + + /* stylelint-disable-next-line value-keyword-case */ + text-rendering: optimizeLegibility; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +h1 { + @include heading1(); +} + +h2 { + @include heading2(); +} + +h3 { + @include heading3(); +} + +h4 { + @include heading4(); +} + +h5 { + @include heading5(); +} + +h6 { + @include heading6(); +} + +p, +div.p { + margin: 0 0 20px; + + &:last-child { + margin-bottom: 0; + } +} + +sub, +sup { + font-size: 0.75em; + line-height: 0; +} + +a { + @include link(); +} + +.yc-root { + & code[class*='language-'], + pre[class*='language-'] { + background: initial; + padding: initial; + margin: 0 0 15px; + text-shadow: none; + } +} + +body.yc-root { + --dc-header-height: 64px; +} + +.yc-root .Header { + display: flex; + justify-content: flex-end; + align-items: center; + height: 64px; + padding: 0 24px; + background-color: var(--g-color-base-background); + box-shadow: inset 0 -1px 0 var(--g-color-line-generic); + + &__control { + color: var(--g-color-text-primary); + } + + &__control-input { + height: 28px; + border-radius: 4px; + border: 1px solid var(--g-color-line-generic); + margin-right: 6px; + display: flex; + align-items: center; + justify-content: center; + } + + &__divider { + margin: 0 4px; + } +} + +.Layout__header { + position: sticky; + top: 0; + z-index: 101; +} diff --git a/demo/tsconfig.json b/demo/tsconfig.json new file mode 100644 index 00000000..a837a784 --- /dev/null +++ b/demo/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@yandex-cloud/tsconfig/tsconfig", + "compilerOptions": { + "module": "esNext", + "allowJs": false, + "importHelpers": true, + "resolveJsonModule": true, + "declaration": true, + "jsx": "react", + "outDir": "build", + "baseUrl": "." + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/package.json b/package.json index 5402126e..d3153085 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,10 @@ "build", "styles" ], + "workspaces": [ + ".", + "./demo" + ], "exports": { ".": { "import": { @@ -106,6 +110,7 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "sass": "^1.66.1", "stylelint": "^15.10.3", "svgo": "2.8.0", "typescript": "^5.2.2" diff --git a/src/.eslintrc b/src/.eslintrc index 9e3fa058..9ecc6200 100644 --- a/src/.eslintrc +++ b/src/.eslintrc @@ -1,3 +1,3 @@ { - "extends": "@yandex-cloud/eslint-config/client" + "extends": "@gravity-ui/eslint-config/client" } diff --git a/src/components/Contributors/Contributors.tsx b/src/components/Contributors/Contributors.tsx index f472c87c..7db8e145 100644 --- a/src/components/Contributors/Contributors.tsx +++ b/src/components/Contributors/Contributors.tsx @@ -1,16 +1,16 @@ -import React, {useEffect} from 'react'; +import React from 'react'; + import block from 'bem-cn-lite'; -import {useTranslation} from 'react-i18next'; +import {useTranslation} from '../../hooks'; +import {Contributor} from '../../models'; import {ContributorAvatars} from '../ContributorAvatars'; -import {Lang, Contributor} from '../../models'; import './Contributors.scss'; const b = block('contributors'); export interface ContributorsProps { - lang: Lang; users: Contributor[]; onlyAuthor?: boolean; isAuthor?: boolean; @@ -18,18 +18,8 @@ export interface ContributorsProps { } const Contributors: React.FC = (props) => { - const { - users, - lang, - onlyAuthor = false, - isAuthor = false, - translationName = 'contributors', - } = props; - const {t, i18n} = useTranslation(translationName); - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); + const {users, onlyAuthor = false, isAuthor = false, translationName = 'contributors'} = props; + const {t} = useTranslation(translationName); return (
diff --git a/src/components/Control/Control.tsx b/src/components/Control/Control.tsx index 8ee101bf..340ff21f 100644 --- a/src/components/Control/Control.tsx +++ b/src/components/Control/Control.tsx @@ -1,9 +1,10 @@ -import React, {useCallback, useState, useRef} from 'react'; +import React, {forwardRef, useCallback, useImperativeHandle, useRef} from 'react'; + +import {Button, Popup} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import {Popup, Button} from '@gravity-ui/uikit'; +import {PopperPosition, usePopupState} from '../../hooks'; import {ControlSizes} from '../../models'; -import {PopperPosition} from '../../hooks'; import './Control.scss'; @@ -30,23 +31,21 @@ const ICONS_SIZES = { [ControlSizes.L]: 20, }; -const Control = (props: ControlProps) => { +const Control = forwardRef((props: ControlProps, ref) => { const { onClick, className, tooltipText, isVerticalView, - setRef, size = ControlSizes.M, icon, popupPosition, } = props; const controlRef = useRef(null); - const [isVisibleTooltip, setIsVisibleTooltip] = useState(false); - const showTooltip = () => setIsVisibleTooltip(true); - const hideTooltip = () => setIsVisibleTooltip(false); + const popupState = usePopupState({autoclose: 3000}); + const getTooltipAlign = useCallback(() => { if (popupPosition) { return popupPosition; @@ -54,16 +53,8 @@ const Control = (props: ControlProps) => { return isVerticalView ? PopperPosition.LEFT_START : PopperPosition.BOTTOM_END; }, [isVerticalView, popupPosition]); - const _setRef = useCallback( - (ref: HTMLButtonElement) => { - controlRef.current = ref; - - if (setRef) { - setRef(ref); - } - }, - [setRef], - ); + + useImperativeHandle(ref, () => controlRef.current, [controlRef]); const position = getTooltipAlign(); const Icon = icon; @@ -74,9 +65,9 @@ const Control = (props: ControlProps) => { - - {tooltipText} - + {controlRef.current && ( + + {tooltipText} + + )} ); -}; +}); Control.displayName = 'DCControl'; diff --git a/src/components/Controls/Controls.tsx b/src/components/Controls/Controls.tsx index 6d350b6e..c402e5ae 100644 --- a/src/components/Controls/Controls.tsx +++ b/src/components/Controls/Controls.tsx @@ -1,28 +1,30 @@ -import React from 'react'; +import React, {memo} from 'react'; + import block from 'bem-cn-lite'; -import {withTranslation, WithTranslation, WithTranslationProps} from 'react-i18next'; -import {Control} from '../Control'; +import {PopperPosition} from '../../hooks'; +import {ControlSizes, FeedbackSendData, Lang, SubscribeData, TextSizes, Theme} from '../../models'; import {Feedback, FeedbackView} from '../Feedback'; import {Subscribe, SubscribeView} from '../Subscribe'; + +import {CommonPropsContext} from './contexts'; + import { + DividerControl, + EditControl, FullScreenControl, - SettingsControl, - SinglePageControl, LangControl, - DividerControl, PdfControl, + SettingsControl, + SinglePageControl, } from './'; -import {PopperPosition} from '../../hooks'; -import {Lang, TextSizes, Theme, FeedbackSendData, ControlSizes, SubscribeData} from '../../models'; -import EditIcon from '@gravity-ui/icons/svgs/pencil.svg'; import './Controls.scss'; const b = block('dc-controls'); export interface ControlsProps { - lang: Lang; + lang?: Lang; langs?: string[]; fullScreen?: boolean; singlePage?: boolean; @@ -53,207 +55,148 @@ export interface ControlsProps { popupPosition?: PopperPosition; } -type ControlsInnerProps = ControlsProps & WithTranslation & WithTranslationProps; - -class Controls extends React.Component { - componentDidUpdate(prevProps: ControlsProps) { - const {i18n, lang} = this.props; - - if (prevProps.lang !== lang) { - i18n.changeLanguage(lang); - } - } - - render() { - const {lang, i18n, className, isVerticalView} = this.props; - - if (i18n.language !== lang) { - i18n.changeLanguage(lang); - } - - return ( -
- {this.renderCommonControls()} - {this.renderEditLink()} - {this.renderFeedbackControls()} - {this.renderSubscribeControls()} -
- ); - } - - private renderEditLink() { - const { - vcsUrl, - vcsType, - showEditControl, - singlePage, - isVerticalView, - controlSize, - popupPosition, - t, - } = this.props; - - if (!showEditControl || singlePage) { - return null; - } - - return ( - - - - - - - ); - } - - private renderCommonControls() { - const { - fullScreen, - singlePage, - theme, - wideFormat, - showMiniToc, - textSize, - onChangeFullScreen, - onChangeTheme, - onChangeShowMiniToc, - onChangeTextSize, - onChangeWideFormat, - onChangeLang, - onChangeSinglePage, - isVerticalView, - controlSize, - lang, - langs, - popupPosition, - pdfLink, - } = this.props; - - return ( - - - - - - - - ); - } - - private renderFeedbackControls = () => { - const { - lang, - singlePage, - onSendFeedback, - isLiked, - isDisliked, - dislikeVariants, - isVerticalView, - hideFeedbackControls, - popupPosition, - } = this.props; - - if (singlePage || !onSendFeedback || hideFeedbackControls) { - return null; - } - - return ( - - - - - ); - }; - - private renderSubscribeControls = () => { - const {lang, singlePage, onSubscribe, isVerticalView, popupPosition} = this.props; - - if (singlePage || !onSubscribe) { - return null; - } - - return ( - - - - - ); - }; -} - -export default withTranslation('controls')(Controls); +type Defined = { + [P in keyof ControlsProps]-?: ControlsProps[P]; +}; + +const Controls = memo((props) => { + const { + className, + fullScreen, + singlePage, + theme, + wideFormat, + showMiniToc, + showEditControl, + hideFeedbackControls, + textSize, + onChangeFullScreen, + onChangeTheme, + onChangeShowMiniToc, + onChangeTextSize, + onChangeWideFormat, + onChangeLang, + onChangeSinglePage, + onSendFeedback, + onSubscribe, + isVerticalView = false, + controlSize = ControlSizes.M, + popupPosition, + lang, + langs, + pdfLink, + vcsUrl, + vcsType, + isLiked, + isDisliked, + dislikeVariants, + } = props; + + const withFullscreenControl = Boolean(onChangeFullScreen); + const withSettingsControl = Boolean( + onChangeWideFormat && onChangeTheme && onChangeShowMiniToc && onChangeTextSize, + ); + const withLangControl = Boolean(lang && onChangeLang); + const withSinglePageControl = Boolean(onChangeSinglePage); + const withPdfControl = Boolean(pdfLink); + const withEditControl = Boolean(!singlePage && showEditControl && vcsUrl); + const withFeedbackControl = Boolean(!singlePage && onSendFeedback && !hideFeedbackControls); + const withSubscribeControls = Boolean(!singlePage && onSubscribe); + + const controls = [ + withFullscreenControl && ( + + ), + withSettingsControl && ( + + ), + withLangControl && ( + + ), + withSinglePageControl && ( + + ), + withPdfControl && , + '---', + withEditControl && ( + + ), + '---', + withFeedbackControl && ( + + ), + '---', + withSubscribeControls && ( + + ), + ] + .filter(Boolean) + .reduce((result, control, index, array) => { + if (control === '---') { + if (array[index - 1] && array[index + 1] && array[index + 1] !== '---') { + result.push( + , + ); + } + } else { + result.push(control as React.ReactElement); + } + + return result; + }, [] as React.ReactElement[]); + + return ( + +
{controls}
+
+ ); +}); + +Controls.displayName = 'DCControls'; + +export default Controls; diff --git a/src/components/Controls/contexts.ts b/src/components/Controls/contexts.ts new file mode 100644 index 00000000..c7bb390d --- /dev/null +++ b/src/components/Controls/contexts.ts @@ -0,0 +1,16 @@ +import {createContext} from 'react'; + +import {PopperPosition} from '../../hooks'; +import {ControlSizes} from '../../models'; + +export const CommonPropsContext = createContext<{ + isVerticalView?: boolean; + controlClassName?: string; + controlSize?: ControlSizes; + popupPosition?: PopperPosition; +}>({ + controlClassName: '', + isVerticalView: false, + controlSize: ControlSizes.M, + // popupPosition?: PopperPosition; +}); diff --git a/src/components/Controls/single-controls/DividerControl/DividerControl.tsx b/src/components/Controls/single-controls/DividerControl/DividerControl.tsx index 9c58c159..963f37d1 100644 --- a/src/components/Controls/single-controls/DividerControl/DividerControl.tsx +++ b/src/components/Controls/single-controls/DividerControl/DividerControl.tsx @@ -1,24 +1,21 @@ -import React from 'react'; +import React, {useContext} from 'react'; + import cn from 'bem-cn-lite'; -import {ControlSizes} from '../../../../models'; +import {CommonPropsContext} from '../../contexts'; import './DividerControl.scss'; const b = cn('dc-divider-control'); interface DividerControlProps { - size?: ControlSizes; className?: string; - isVerticalView?: boolean; } -const DividerControl = ({ - size = ControlSizes.M, - className, - isVerticalView = true, -}: DividerControlProps) => { - return
; +const DividerControl: React.FC = ({className}) => { + const {isVerticalView, controlSize} = useContext(CommonPropsContext); + + return
; }; export default DividerControl; diff --git a/src/components/Controls/single-controls/EditControl.tsx b/src/components/Controls/single-controls/EditControl.tsx new file mode 100644 index 00000000..97d4d886 --- /dev/null +++ b/src/components/Controls/single-controls/EditControl.tsx @@ -0,0 +1,40 @@ +import React, {memo, useContext} from 'react'; + +import block from 'bem-cn-lite'; + +import {useTranslation} from '../../../hooks'; +import {Control} from '../../Control'; +import {CommonPropsContext} from '../contexts'; + +import EditIcon from '@gravity-ui/icons/svgs/pencil.svg'; + +interface EditControlProps { + vcsUrl: string; + vcsType: string; +} + +const b = block('dc-controls'); + +const EditControl = memo((props) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {vcsUrl, vcsType} = props; + + return ( + + + + ); +}); + +EditControl.displayName = 'EditControl'; + +export default EditControl; diff --git a/src/components/Controls/single-controls/FullScreenControl.tsx b/src/components/Controls/single-controls/FullScreenControl.tsx index df913e4b..3f1fba14 100644 --- a/src/components/Controls/single-controls/FullScreenControl.tsx +++ b/src/components/Controls/single-controls/FullScreenControl.tsx @@ -1,27 +1,24 @@ -import React, {useCallback, useEffect} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import {Icon as IconComponent} from '@gravity-ui/uikit'; +import React, {memo, useCallback, useContext, useEffect} from 'react'; +import {Icon} from '@gravity-ui/uikit'; + +import {useTranslation} from '../../../hooks'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; +import {CommonPropsContext} from '../contexts'; import FullScreenClickedIcon from '../../../../assets/icons/full-screen-clicked.svg'; import FullScreenIcon from '@gravity-ui/icons/svgs/square-dashed.svg'; interface ControlProps { - lang: Lang; value?: boolean; onChange?: (value: boolean) => void; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - popupPosition?: PopperPosition; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const FullScreenControl = (props: ControlInnerProps) => { - const {className, isVerticalView, size, value, onChange, lang, popupPosition, i18n, t} = props; +const FullScreenControl = memo((props) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {value, onChange} = props; const onClick = useCallback(() => { if (onChange) { @@ -46,29 +43,23 @@ const FullScreenControl = (props: ControlInnerProps) => { }; }, [onKeyDown]); - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - const activeMode = value ? 'enabled' : 'disabled'; - if (!onChange) { - return null; - } - return ( ( - + )} popupPosition={popupPosition} /> ); -}; +}); + +FullScreenControl.displayName = 'FullScreenControl'; -export default withTranslation('controls')(FullScreenControl); +export default FullScreenControl; diff --git a/src/components/Controls/single-controls/LangControl.tsx b/src/components/Controls/single-controls/LangControl.tsx index 8fe65c8a..3d4fa0c6 100644 --- a/src/components/Controls/single-controls/LangControl.tsx +++ b/src/components/Controls/single-controls/LangControl.tsx @@ -1,18 +1,25 @@ -import React, {useCallback, useEffect, useState, useRef} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import allLangs from 'langs'; -import {Popup, Icon as IconComponent, List} from '@gravity-ui/uikit'; +import React, {useCallback, useContext, useMemo, useRef} from 'react'; + +import {Icon, List, Popup} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; +import allLangs from 'langs'; +import {usePopupState, useTranslation} from '../../../hooks'; +import {Lang} from '../../../models'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; +import {CommonPropsContext} from '../contexts'; + import {getPopupPosition} from './utils'; -import {PopperPosition} from '../../../hooks'; import LangIcon from '@gravity-ui/icons/svgs/globe.svg'; import '../Controls.scss'; +const ICONS: Record = { + en: '🇬🇧', + ru: '🇷🇺', +}; +const DEFAULT_LANGS = ['en', 'ru']; const LEGACY_LANG_ITEMS = [ {value: Lang.En, text: 'English', icon: '🇬🇧'}, {value: Lang.Ru, text: 'Русский', icon: '🇷🇺'}, @@ -23,11 +30,7 @@ const b = block('dc-controls'); interface ControlProps { lang: Lang; langs?: string[]; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - onChangeLang?: (lang: Lang) => void; - popupPosition?: PopperPosition; + onChangeLang: (lang: Lang) => void; } interface ListItem { @@ -38,37 +41,16 @@ interface ListItem { const LIST_ITEM_HEIGHT = 36; -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const LangControl = (props: ControlInnerProps) => { - const { - className, - isVerticalView, - size, - lang, - langs = [], - i18n, - onChangeLang, - popupPosition, - t, - } = props; - - const [langItems, setLangItems] = useState(LEGACY_LANG_ITEMS); +const LangControl = (props: ControlProps) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {lang, langs = DEFAULT_LANGS, onChangeLang} = props; + const controlRef = useRef(null); - const [isVisiblePopup, setIsVisiblePopup] = useState(false); - const showPopup = () => setIsVisiblePopup(true); - const hidePopup = () => setIsVisiblePopup(false); - - const _onChangeLang = useCallback( - (value: Lang) => { - if (onChangeLang) { - onChangeLang(value); - } - }, - [onChangeLang], - ); - useEffect(() => { + const popupState = usePopupState(); + const langItems = useMemo(() => { const preparedLangs = langs .map((code) => { const langData = allLangs.where('1', code); @@ -77,29 +59,27 @@ const LangControl = (props: ControlInnerProps) => { ? { text: langData.name, value: langData['1'], + icon: ICONS[code] || '', } : undefined; }) .filter(Boolean) as ListItem[]; - if (preparedLangs.length) { - setLangItems(preparedLangs); - } else { - setLangItems(LEGACY_LANG_ITEMS); - } + return preparedLangs.length ? preparedLangs : LEGACY_LANG_ITEMS; }, [langs]); - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - const setRef = useCallback((ref: HTMLButtonElement) => { - controlRef.current = ref; + const renderItem = useCallback((item: ListItem) => { + return ( +
+
{item.icon}
{item.text} +
+ ); }, []); - - if (!onChangeLang) { - return null; - } + const onItemClick = useCallback( + (item: ListItem) => { + onChangeLang(item.value as Lang); + }, + [onChangeLang], + ); const itemsHeight = LIST_ITEM_HEIGHT * langItems.length; const selectedItemIndex = langItems.findIndex(({value}) => value === lang); @@ -107,42 +87,36 @@ const LangControl = (props: ControlInnerProps) => { return ( } - setRef={setRef} + icon={(args) => } popupPosition={popupPosition} /> - - { - _onChangeLang(item.value as Lang); - }} - selectedItemIndex={selectedItemIndex} - itemHeight={LIST_ITEM_HEIGHT} - itemsHeight={itemsHeight} - renderItem={(item) => { - return ( -
-
{item.icon}
{item.text} -
- ); - }} - /> -
+ {popupState.visible && ( + + + + )}
); }; -export default withTranslation('controls')(LangControl); +export default LangControl; diff --git a/src/components/Controls/single-controls/PdfControl.tsx b/src/components/Controls/single-controls/PdfControl.tsx index 1b5a4acb..51c27cb0 100644 --- a/src/components/Controls/single-controls/PdfControl.tsx +++ b/src/components/Controls/single-controls/PdfControl.tsx @@ -1,47 +1,37 @@ -import React, {useEffect} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import {Icon as IconComponent} from '@gravity-ui/uikit'; +import React, {memo, useContext} from 'react'; +import {Icon} from '@gravity-ui/uikit'; + +import {useTranslation} from '../../../hooks'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; -import {PopperPosition} from '../../../hooks'; +import {CommonPropsContext} from '../contexts'; import PdfIcon from '../../../../assets/icons/pdf.svg'; interface ControlProps { - lang: Lang; - pdfLink?: string; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - popupPosition?: PopperPosition; + pdfLink: string; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const PdfControl = (props: ControlInnerProps) => { - const {className, isVerticalView, size, pdfLink, lang, i18n, popupPosition, t} = props; - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - if (!pdfLink) { - return null; - } +const PdfControl = memo((props) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {pdfLink} = props; return ( } + icon={(args) => } popupPosition={popupPosition} /> ); -}; +}); + +PdfControl.displayName = 'PdfControl'; -export default withTranslation('controls')(PdfControl); +export default PdfControl; diff --git a/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx b/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx index 7a04f6e3..d59602c1 100644 --- a/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx +++ b/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx @@ -1,12 +1,12 @@ -import React, {useCallback, useEffect, useState, useRef} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; +import React, {ReactElement, useCallback, useContext, useRef, useState} from 'react'; + +import {Button, Icon, List, Popup, Switch} from '@gravity-ui/uikit'; import cn from 'bem-cn-lite'; -import {Button, Popup, Switch, List, Icon as IconComponent} from '@gravity-ui/uikit'; +import {useTranslation} from '../../../../hooks'; +import {TextSizes, Theme} from '../../../../models'; import {Control} from '../../../Control'; -import {ControlSizes, Lang, TextSizes, Theme} from '../../../../models'; -import {PopperPosition} from '../../../../hooks'; - +import {CommonPropsContext} from '../../contexts'; import {getPopupPosition} from '../utils'; import SettingsIcon from '@gravity-ui/icons/svgs/gear.svg'; @@ -23,32 +23,23 @@ interface ControlProps { showMiniToc?: boolean; theme?: Theme; textSize?: TextSizes; - lang: Lang; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; onChangeWideFormat?: (value: boolean) => void; onChangeShowMiniToc?: (value: boolean) => void; onChangeTheme?: (theme: Theme) => void; onChangeTextSize?: (textSize: TextSizes) => void; - popupPosition?: PopperPosition; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - interface SettingControlItem { text: string; description: string; - control: Element; + control: ReactElement; } -const SettingsControl = (props: ControlInnerProps) => { +const SettingsControl = (props: ControlProps) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); const { - className, - isVerticalView, - size, - lang, - i18n, textSize, theme, wideFormat, @@ -59,8 +50,6 @@ const SettingsControl = (props: ControlInnerProps) => { onChangeWideFormat, onChangeShowMiniToc, onChangeTextSize, - popupPosition, - t, } = props; const controlRef = useRef(null); @@ -69,7 +58,7 @@ const SettingsControl = (props: ControlInnerProps) => { const hidePopup = () => setIsVisiblePopup(false); const makeOnChangeTextSize = useCallback( - (textSizeKey) => () => { + (textSizeKey: TextSizes) => () => { if (onChangeTextSize) { onChangeTextSize(textSizeKey); } @@ -176,31 +165,19 @@ const SettingsControl = (props: ControlInnerProps) => { makeOnChangeTextSize, ]); - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - const setRef = useCallback((ref: HTMLButtonElement) => { - controlRef.current = ref; - }, []); - - if (!(onChangeWideFormat || onChangeTheme || onChangeShowMiniToc || onChangeTextSize)) { - return null; - } - const settingsItems = getSettingsItems(); return ( } + icon={(args) => } /> { itemHeight={ITEM_HEIGHT} itemsHeight={ITEM_HEIGHT * settingsItems.length} filterable={false} - renderItem={(item) => { + renderItem={(item: SettingControlItem) => { return (
@@ -234,4 +211,4 @@ const SettingsControl = (props: ControlInnerProps) => { ); }; -export default withTranslation('controls')(SettingsControl); +export default SettingsControl; diff --git a/src/components/Controls/single-controls/SinglePageControl.tsx b/src/components/Controls/single-controls/SinglePageControl.tsx index bed7cd43..170cff1e 100644 --- a/src/components/Controls/single-controls/SinglePageControl.tsx +++ b/src/components/Controls/single-controls/SinglePageControl.tsx @@ -1,58 +1,46 @@ -import React, {useCallback, useEffect} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import {Icon as IconComponent} from '@gravity-ui/uikit'; +import React, {memo, useCallback, useContext} from 'react'; +import {Icon} from '@gravity-ui/uikit'; + +import {useTranslation} from '../../../hooks'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; -import {PopperPosition} from '../../../hooks'; +import {CommonPropsContext} from '../contexts'; -import SinglePageIcon from '../../../../assets/icons/single-page.svg'; import SinglePageClickedIcon from '../../../../assets/icons/single-page-clicked.svg'; +import SinglePageIcon from '../../../../assets/icons/single-page.svg'; interface ControlProps { - lang: Lang; value?: boolean; - onChange?: (value: boolean) => void; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - popupPosition?: PopperPosition; + onChange: (value: boolean) => void; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const SinglePageControl = (props: ControlInnerProps) => { - const {className, isVerticalView, size, value, onChange, lang, i18n, popupPosition, t} = props; +const SinglePageControl = memo((props) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {value, onChange} = props; const onClick = useCallback(() => { - if (onChange) { - onChange(!value); - } + onChange(!value); }, [value, onChange]); - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - const activeMode = value ? 'enabled' : 'disabled'; - if (!onChange) { - return null; - } - return ( ( - + )} popupPosition={popupPosition} /> ); -}; +}); + +SinglePageControl.displayName = 'SinglePageControl'; -export default withTranslation('controls')(SinglePageControl); +export default SinglePageControl; diff --git a/src/components/Controls/single-controls/index.ts b/src/components/Controls/single-controls/index.ts index b0c435a8..e3211962 100644 --- a/src/components/Controls/single-controls/index.ts +++ b/src/components/Controls/single-controls/index.ts @@ -4,3 +4,4 @@ export {default as SinglePageControl} from './SinglePageControl'; export {default as LangControl} from './LangControl'; export {default as DividerControl} from './DividerControl/DividerControl'; export {default as PdfControl} from './PdfControl'; +export {default as EditControl} from './EditControl'; diff --git a/src/components/DocLayout/DocLayout.tsx b/src/components/DocLayout/DocLayout.tsx index ad9ee9f7..6e8bbc70 100644 --- a/src/components/DocLayout/DocLayout.tsx +++ b/src/components/DocLayout/DocLayout.tsx @@ -2,7 +2,7 @@ import React, {PropsWithChildren, ReactElement} from 'react'; import block from 'bem-cn-lite'; -import {TocData, Router, Lang} from '../../models'; +import {Router, TocData} from '../../models'; import {getStateKey} from '../../utils'; import {Toc} from '../Toc'; @@ -17,7 +17,6 @@ const Right: React.FC = () => null; export interface DocLayoutProps { toc: TocData; router: Router; - lang: Lang; children: (ReactElement | null)[] | ReactElement; fullScreen?: boolean; hideRight?: boolean; @@ -104,7 +103,6 @@ export class DocLayout extends React.Component { wideFormat, hideTocHeader, hideToc, - lang, singlePage, onChangeSinglePage, pdfLink, @@ -124,7 +122,6 @@ export class DocLayout extends React.Component { headerHeight={headerHeight} tocTitleIcon={tocTitleIcon} hideTocHeader={hideTocHeader} - lang={lang} singlePage={singlePage} onChangeSinglePage={onChangeSinglePage} pdfLink={pdfLink} diff --git a/src/components/DocLeadingPage/DocLeadingPage.tsx b/src/components/DocLeadingPage/DocLeadingPage.tsx index eea825f7..786bd58e 100644 --- a/src/components/DocLeadingPage/DocLeadingPage.tsx +++ b/src/components/DocLeadingPage/DocLeadingPage.tsx @@ -2,8 +2,8 @@ import React from 'react'; import block from 'bem-cn-lite'; -import {DocLeadingPageData, DocLeadingLinks, Router, Lang} from '../../models'; import {DEFAULT_SETTINGS} from '../../constants'; +import {DocLeadingLinks, DocLeadingPageData, Router} from '../../models'; import {DocLayout} from '../DocLayout'; import {DocPageTitle} from '../DocPageTitle'; import {HTML} from '../HTML'; @@ -17,7 +17,6 @@ const {wideFormat: defaultWideFormat} = DEFAULT_SETTINGS; export interface DocLeadingPageProps extends DocLeadingPageData { router: Router; - lang: Lang; headerHeight?: number; wideFormat?: boolean; hideTocHeader?: boolean; @@ -106,7 +105,6 @@ export const DocLeadingPage: React.FC = ({ data: {title, description, links}, toc, router, - lang, headerHeight, wideFormat = defaultWideFormat, hideTocHeader, @@ -122,7 +120,6 @@ export const DocLeadingPage: React.FC = ({ { const { toc, router, - lang, headerHeight, wideFormat, fullScreen, @@ -155,7 +154,6 @@ class DocPage extends React.Component { { } private addLinksToOriginalArticle = () => { - const {singlePage, lang, convertPathToOriginalArticle, generatePathToVcs} = this.props; + const {singlePage, convertPathToOriginalArticle, generatePathToVcs} = this.props; if (singlePage) { const elements = document.querySelectorAll('[data-original-article]'); @@ -291,7 +289,8 @@ class DocPage extends React.Component { const vcsHref = callSafe(generatePathToVcs, href); const linkToVcs = createElementFromHTML( ReactDOMServer.renderToStaticMarkup( - , + // FIXME: add common context + , ), ); linkWrapperEl.append(linkToVcs); @@ -373,7 +372,7 @@ class DocPage extends React.Component { } private renderAuthor(onlyAuthor: boolean) { - const {meta, lang} = this.props; + const {meta} = this.props; if (!isContributor(meta?.author)) { return null; @@ -381,7 +380,6 @@ class DocPage extends React.Component { return ( { } private renderContributors() { - const {meta, lang} = this.props; + const {meta} = this.props; if (!meta?.contributors || meta.contributors.length === 0) { return null; } - return ; + return ; } private renderContentMiniToc() { @@ -463,7 +461,7 @@ class DocPage extends React.Component { } private renderAsideMiniToc() { - const {headings, router, headerHeight, lang} = this.props; + const {headings, router, headerHeight} = this.props; const {keyDOM} = this.state; return ( @@ -472,7 +470,6 @@ class DocPage extends React.Component { headings={headings} router={router} headerHeight={headerHeight} - lang={lang} key={keyDOM} />
@@ -480,29 +477,16 @@ class DocPage extends React.Component { } private renderFeedback() { - const { - toc, - lang, - singlePage, - isLiked, - isDisliked, - onSendFeedback, - dislikeVariants, - hideFeedbackControls, - } = this.props; + const {toc, isLiked, isDisliked, onSendFeedback, dislikeVariants, hideFeedbackControls} = + this.props; - if (!toc || toc.singlePage || hideFeedbackControls) { + if (!toc || toc.singlePage || hideFeedbackControls || !onSendFeedback) { return null; } - const isVerticalView = this.getIsVerticalView(); - return (
{ } private renderTocNavPanel() { - const {toc, router, fullScreen, lang} = this.props; + const {toc, router, fullScreen} = this.props; if (!toc || toc.singlePage) { return null; @@ -523,7 +507,6 @@ class DocPage extends React.Component { return ( { onClickPrevSearch, onClickNextSearch, onCloseSearchBar, - lang, singlePage, } = this.props; @@ -567,7 +549,6 @@ class DocPage extends React.Component { return (
{ - componentDidUpdate(prevProps: EditButtonProps) { - const {i18n, lang} = this.props; - if (prevProps.lang !== lang) { - i18n.changeLanguage(lang); - } - } - - render() { - const {t, href} = this.props; - - return ( - - - - ); - } -} +const EditButton = memo(({href}) => { + const {t} = useTranslation('controls'); + + return ( + + + + ); +}); + +EditButton.displayName = 'EditButton'; -export default withTranslation('controls')(EditButton); +export default EditButton; diff --git a/src/components/ErrorPage/ErrorPage.tsx b/src/components/ErrorPage/ErrorPage.tsx index 980ea549..6277d29f 100644 --- a/src/components/ErrorPage/ErrorPage.tsx +++ b/src/components/ErrorPage/ErrorPage.tsx @@ -2,10 +2,9 @@ import React from 'react'; import {Button, Link} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import {withTranslation, WithTranslation, WithTranslationProps} from 'react-i18next'; -import {Lang} from '../../models'; import {ERROR_CODES} from '../../constants'; +import {useTranslation} from '../../hooks'; import './ErrorPage.scss'; @@ -13,29 +12,20 @@ const b = block('ErrorPage'); export interface ErrorPageProps { code?: number; - lang?: Lang; pageGroup?: string; homeUrl?: string; receiveAccessUrl?: string; } -type ErrorPagePropsInnerProps = ErrorPageProps & WithTranslation & WithTranslationProps; - -const ErrorPage = ({ +const ErrorPage: React.FC = ({ code = 500, - lang = Lang.En, - i18n, - t, homeUrl, receiveAccessUrl, pageGroup, -}: ErrorPagePropsInnerProps): JSX.Element => { - if (i18n.language !== lang) { - i18n.changeLanguage(lang); - } - +}) => { let title; let description; + const {t} = useTranslation('error'); const href = homeUrl || '/'; const homeLink = ( @@ -91,4 +81,4 @@ const ErrorPage = ({ ); }; -export default withTranslation('error')(ErrorPage); +export default ErrorPage; diff --git a/src/components/Feedback/Feedback.scss b/src/components/Feedback/Feedback.scss index b3bea843..92469aaf 100644 --- a/src/components/Feedback/Feedback.scss +++ b/src/components/Feedback/Feedback.scss @@ -39,12 +39,6 @@ $popupWidth: 320px; } } - &__like-button { - &_active { - color: var(--g-color-base-brand); - } - } - &__success-popup { padding: $popupPadding 50px $popupPadding $popupPadding; width: $popupWidth; @@ -145,10 +139,4 @@ $popupWidth: 320px; } } } - - &__feedback-button { - &_active { - color: var(--g-color-base-brand); - } - } } diff --git a/src/components/Feedback/Feedback.tsx b/src/components/Feedback/Feedback.tsx index 08ef49f3..b091f0d1 100644 --- a/src/components/Feedback/Feedback.tsx +++ b/src/components/Feedback/Feedback.tsx @@ -1,15 +1,14 @@ -import React, {useCallback, useState, useEffect, useRef} from 'react'; +import React, {PropsWithChildren, useCallback, useEffect, useRef, useState} from 'react'; + import block from 'bem-cn-lite'; -import {withTranslation, WithTranslation, WithTranslationProps} from 'react-i18next'; -import {Checkbox, Popup, TextArea, Button, Icon as IconComponent} from '@gravity-ui/uikit'; -import {Control} from '../Control'; -import {PopperPosition} from '../../hooks'; -import {FeedbackSendData, FeedbackType, Lang} from '../../models'; -import {DISLIKE_VARIANTS} from '../../constants'; +import {usePopupState, useTranslation} from '../../hooks'; +import {FeedbackSendData, FeedbackType} from '../../models'; -import DislikeActiveIcon from '@gravity-ui/icons/svgs/thumbs-down-fill.svg'; -import DislikeIcon from '@gravity-ui/icons/svgs/thumbs-down.svg'; +import DislikeControl from './controls/DislikeControl'; +import DislikeVariantsPopup, {FormData} from './controls/DislikeVariantsPopup'; +import LikeControl from './controls/LikeControl'; +import SuccessPopup from './controls/SuccessPopup'; import './Feedback.scss'; @@ -21,455 +20,144 @@ export enum FeedbackView { } export interface FeedbackProps { - lang: Lang; - singlePage?: boolean; isLiked?: boolean; isDisliked?: boolean; dislikeVariants?: string[]; - isVerticalView?: boolean; - onSendFeedback?: (data: FeedbackSendData) => void; + onSendFeedback: (data: FeedbackSendData) => void; view?: FeedbackView; - classNameControl?: string; - popupPosition?: PopperPosition; -} - -interface FeedbackCheckboxes { - [key: string]: boolean; } -type FeedbackInnerProps = FeedbackProps & WithTranslation & WithTranslationProps; +const getInnerState = (isLiked: boolean, isDisliked: boolean) => { + return isDisliked + ? FeedbackType.dislike + : isLiked + ? FeedbackType.like + : FeedbackType.indeterminate; +}; -const Feedback: React.FC = (props) => { +const Feedback: React.FC = (props) => { + const {i18n} = useTranslation('feedback-variants'); const { - lang, - singlePage, - isLiked, - isDisliked, - dislikeVariants = DISLIKE_VARIANTS[lang], - isVerticalView, + isLiked = false, + isDisliked = false, onSendFeedback, - view, - classNameControl, - i18n, - popupPosition, - t, + dislikeVariants = i18n.getResourceBundle(i18n.language, 'feedback-variants'), + view = FeedbackView.Regular, } = props; const likeControlRef = useRef(null); const dislikeControlRef = useRef(null); - const timerId = useRef(); - const timeout = 3000; - - const [innerIsDisliked, setInnerIsDisliked] = useState(isDisliked); - const [feedbackComment, setFeedbackComment] = useState(''); - const [feedbackCheckboxes, setFeedbackCheckboxes] = useState({} as FeedbackCheckboxes); - const [showLikeSuccessPopup, setShowLikeSuccessPopup] = useState(false); - const [showDislikeSuccessPopup, setShowDislikeSuccessPopup] = useState(false); - const [showDislikeVariantsPopup, setShowDislikeVariantsPopup] = useState(false); - - const hideFeedbackPopups = useCallback(() => { - setShowDislikeSuccessPopup(false); - setShowLikeSuccessPopup(false); - setShowDislikeVariantsPopup(false); - }, []); - - const resetFeedbackAdditionalInfo = useCallback(() => { - setFeedbackComment(''); - setFeedbackCheckboxes({}); - }, []); - - useEffect(() => { - setInnerIsDisliked(isDisliked); - }, [isDisliked]); - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - const setTimer = useCallback((callback: () => void) => { - timerId.current = setTimeout(async () => { - callback(); - }, timeout); - }, []); - - const clearTimer = useCallback(() => { - clearTimeout(timerId.current as number); - timerId.current = undefined; - }, []); + const [innerState, setInnerState] = useState(getInnerState(isLiked, isDisliked)); useEffect(() => { - if (showLikeSuccessPopup || showDislikeSuccessPopup) { - setTimer(() => { - setShowDislikeSuccessPopup(false); - setShowLikeSuccessPopup(false); - clearTimer(); - }); - } - }, [isDisliked, clearTimer, setTimer, showLikeSuccessPopup, showDislikeSuccessPopup]); - - const setLikeControlRef = useCallback((ref) => { - likeControlRef.current = ref; - }, []); - - const setDislikeControlRef = useCallback((ref) => { - dislikeControlRef.current = ref; - }, []); - - const onOutsideClick = useCallback(() => { - hideFeedbackPopups(); - - if (showDislikeVariantsPopup && innerIsDisliked && !isDisliked) { - setInnerIsDisliked(false); - } - }, [ - isDisliked, - innerIsDisliked, - hideFeedbackPopups, - setInnerIsDisliked, - showDislikeVariantsPopup, - ]); - - const onSendDislikeInformation = useCallback(() => { - setShowDislikeSuccessPopup(true); - setShowLikeSuccessPopup(false); - setShowDislikeVariantsPopup(false); - - if (onSendFeedback) { - const type = FeedbackType.dislike; - - const additionalInfo = getPreparedFeedbackAdditionalInfo( - feedbackComment, - feedbackCheckboxes, - ); - const data = { - type, - ...additionalInfo, - }; - - onSendFeedback(data); - - resetFeedbackAdditionalInfo(); - } - }, [onSendFeedback, feedbackComment, feedbackCheckboxes, resetFeedbackAdditionalInfo]); + setInnerState(getInnerState(isLiked, isDisliked)); + }, [isLiked, isDisliked, setInnerState]); - const getPopupPosition = useCallback(() => { - if (!view || view === FeedbackView.Regular) { - return isVerticalView ? PopperPosition.LEFT_START : PopperPosition.BOTTOM_END; - } + const likeSuccessPopup = usePopupState({autoclose: 3000}); + const dislikeSuccessPopup = usePopupState({autoclose: 3000}); + const dislikeVariantsPopup = usePopupState(); - return PopperPosition.RIGHT; - }, [isVerticalView, view]); + const hideFeedbackPopups = useCallback(() => { + likeSuccessPopup.close(); + dislikeSuccessPopup.close(); + dislikeVariantsPopup.close(); + }, [likeSuccessPopup.close, dislikeSuccessPopup.close, dislikeVariantsPopup.close]); const onChangeLike = useCallback(() => { - setShowLikeSuccessPopup(true); - setShowDislikeSuccessPopup(false); - setShowDislikeVariantsPopup(false); - setInnerIsDisliked(false); + hideFeedbackPopups(); - if (onSendFeedback) { - onSendFeedback({ - type: isLiked ? FeedbackType.indeterminate : FeedbackType.like, - }); + if (innerState === FeedbackType.like) { + setInnerState(FeedbackType.indeterminate); + onSendFeedback({type: FeedbackType.indeterminate}); + } else { + setInnerState(FeedbackType.like); + onSendFeedback({type: FeedbackType.like}); + likeSuccessPopup.open(); } - }, [isLiked, onSendFeedback]); + }, [isLiked, onSendFeedback, setInnerState, likeSuccessPopup.open, hideFeedbackPopups]); const onChangeDislike = useCallback(() => { - if (!isDisliked && !innerIsDisliked) { - // Нажать дизлайк и показать окно с доп. информацией - setShowDislikeVariantsPopup(true); - setInnerIsDisliked(true); - setShowLikeSuccessPopup(false); - setShowDislikeSuccessPopup(false); - - if (isLiked && onSendFeedback) { - onSendFeedback({type: FeedbackType.indeterminate}); - } - } else if (!isDisliked && innerIsDisliked) { - hideFeedbackPopups(); - setInnerIsDisliked(false); - } else if (isDisliked && innerIsDisliked) { - // Отжать дизлайк и отправить событие в неопределенное состояние - hideFeedbackPopups(); - setInnerIsDisliked(false); - - if (onSendFeedback) { - onSendFeedback({type: FeedbackType.indeterminate}); - } - } - }, [innerIsDisliked, isDisliked, isLiked, onSendFeedback, hideFeedbackPopups]); - - const renderLikeControl = useCallback(() => { - return ( - ( - - )} - popupPosition={popupPosition} - /> - ); - }, [ - onChangeLike, - classNameControl, - view, - isVerticalView, - isLiked, - setLikeControlRef, - popupPosition, - t, - ]); - - const renderDislikeControl = useCallback(() => { - return ( - ( - - )} - /> - ); - }, [ - innerIsDisliked, - onChangeDislike, - classNameControl, - view, - isVerticalView, - setDislikeControlRef, - t, - ]); - - const renderRegularFeedbackControls = useCallback(() => { - return ( - - {renderLikeControl()} - {renderDislikeControl()} - - ); - }, [renderLikeControl, renderDislikeControl]); - - const renderWideFeedbackControls = useCallback(() => { - return ( -
-
-

{t('main-question')}

-
- - -
-
-
- ); - }, [ - innerIsDisliked, - isLiked, - view, - t, - setLikeControlRef, - setDislikeControlRef, - onChangeLike, - onChangeDislike, - ]); - - const renderFeedbackControls = useCallback(() => { - return view === FeedbackView.Regular - ? renderRegularFeedbackControls() - : renderWideFeedbackControls(); - }, [view, renderRegularFeedbackControls, renderWideFeedbackControls]); - - const renderFeedbackSuccessPopup = useCallback(() => { - const anchor = showLikeSuccessPopup ? likeControlRef : dislikeControlRef; - const visible = showLikeSuccessPopup || showDislikeSuccessPopup; - - if (!visible) { - return null; - } - - return ( - -

{t('success-title')}

-

{t('success-text')}

-
- ); - }, [ - showLikeSuccessPopup, - showDislikeSuccessPopup, - hideFeedbackPopups, - view, - getPopupPosition, - t, - ]); - - const renderDislikeVariantsList = useCallback(() => { - if (!dislikeVariants.length) { - return null; - } - - return ( -
- {dislikeVariants.map((variant, index) => ( - { - setFeedbackCheckboxes({ - ...feedbackCheckboxes, - [variant]: checked, - }); - }} - content={variant} - /> - ))} -
- ); - }, [dislikeVariants, feedbackCheckboxes]); - - const renderDislikeVariantsTextArea = useCallback(() => { - return ( -
-