Добавлена возможность все очень гибко настраивать. Добавлены новые типы. Сделаны примеры.
79
README.md
@ -124,11 +124,32 @@ ecosystem/
|
||||
"accent": "violet",
|
||||
"showNumbers": false,
|
||||
"showTreeTags": true,
|
||||
"treeTwoColumn": false,
|
||||
"showTreeType": false,
|
||||
"treeMetaLayout": "inline",
|
||||
"treeColumns": 2,
|
||||
"autoCollapseSearch": true,
|
||||
"sidebarWidth": 390,
|
||||
"topBarHeight": 140,
|
||||
"shellMaxWidth": 2800,
|
||||
"sidebarMinWidth": 260,
|
||||
"sidebarMaxWidth": 800,
|
||||
"sidebarMaxWidthWide": 1400,
|
||||
"treeSidebarBaseWidth": 420,
|
||||
"treeSidebarColumnWidth": 120,
|
||||
"topBarMinHeight": 80,
|
||||
"topBarMaxHeight": 560,
|
||||
"searchPlaceholder": "Grafana, S3, журнал, сервер..."
|
||||
},
|
||||
"limits": {
|
||||
"shellMaxWidth": { "min": 1200, "max": 3840 },
|
||||
"sidebarMinWidth": { "min": 200, "max": 480 },
|
||||
"sidebarMaxWidth": { "min": 400, "max": 1200 },
|
||||
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
|
||||
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
|
||||
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
|
||||
"topBarMinHeight": { "min": 64, "max": 240 },
|
||||
"topBarMaxHeight": { "min": 160, "max": 800 }
|
||||
},
|
||||
"features": {
|
||||
"allowNavLayoutChange": true,
|
||||
"allowSearch": true,
|
||||
@ -136,6 +157,7 @@ ecosystem/
|
||||
"allowShowNumbers": true,
|
||||
"allowTreeOptions": true,
|
||||
"allowSidebarResize": true,
|
||||
"allowLayoutLimitsChange": true,
|
||||
"allowThemeChange": true,
|
||||
"allowAccentChange": true
|
||||
}
|
||||
@ -144,7 +166,8 @@ ecosystem/
|
||||
|
||||
| `defaults` | Варианты | Описание |
|
||||
|------------|----------|----------|
|
||||
| `navLayout` | `"sidebar"`, `"top"` | `"sidebar"` — дерево слева; `"top"` — шапка с названием и поиском, без дерева |
|
||||
| `navLayout` | `"sidebar"`, `"top"` | `"sidebar"` — панель слева; `"top"` — шапка с названием и поиском |
|
||||
| `showTree` | `true`, `false` | Показывать дерево навигации (в любом режиме макета) |
|
||||
| `theme` | `"dark"`, `"light"` | Стартовая цветовая тема |
|
||||
| `view` | `"cards"`, `"list"`, `"compact-cards"`, `"compact-list"` | Режим отображения каталога |
|
||||
| `accent` | `"violet"`, `"cyan"`, `"lime"`, `"amber"`, `"custom"` | Пресет акцента; при `"custom"` используются поля градиента ниже |
|
||||
@ -152,26 +175,54 @@ ecosystem/
|
||||
| `customAccentTo` | `#rrggbb` | Цвет «до» для `accent: "custom"` (по умолчанию `#22d3ee`) |
|
||||
| `customAccentAngle` | `0`–`360` | Угол градиента в градусах (по умолчанию `135`) |
|
||||
| `showNumbers` | `true`, `false` | Показывать номера разделов, подразделов и элементов |
|
||||
| `showTreeTags` | `true`, `false` | Теги рядом с элементами в дереве (взаимоисключимо с `treeTwoColumn`) |
|
||||
| `treeTwoColumn` | `true`, `false` | Элементы папок дерева в 2 колонки (взаимоисключимо с `showTreeTags`) |
|
||||
| `showTreeTags` | `true`, `false` | Теги элементов в дереве (оформление как на карточках) |
|
||||
| `showTreeType` | `true`, `false` | Подпись типа сервиса в дереве |
|
||||
| `treeMetaLayout` | `"inline"`, `"stacked"` | Расположение тегов и типа: сбоку от названия или снизу |
|
||||
| `treeColumns` | `1`–`6` (по умолчанию `2`) | Число колонок элементов внутри папок дерева |
|
||||
| `autoCollapseSearch` | `true`, `false` | При поиске сворачивать ветки дерева без совпадений |
|
||||
| `sidebarWidth` | `260`–`560` (`760` при `treeTwoColumn: true`) | Ширина боковой панели в px; игнорируется при `navLayout: "top"` |
|
||||
| `sidebarWidth` | `sidebarMinWidth`–`sidebarMaxWidth` (или `sidebarMaxWidthWide` при `treeColumns` > 1) | Текущая ширина бокового меню (drag-resize); при `navLayout: "top"` не применяется |
|
||||
| `topBarHeight` | `topBarMinHeight`–`topBarMaxHeight` (до ~75% высоты окна) | Текущая высота верхней шапки (drag-resize); только при `navLayout: "top"` |
|
||||
| `shellMaxWidth` | см. `limits.shellMaxWidth` | Максимальная ширина контейнера `.shell` (вся страница) |
|
||||
| `sidebarMinWidth` | см. `limits.sidebarMinWidth` | Минимальная ширина бокового меню при drag-resize |
|
||||
| `sidebarMaxWidth` | см. `limits.sidebarMaxWidth` | Максимальная ширина меню при `treeColumns: 1` |
|
||||
| `sidebarMaxWidthWide` | см. `limits.sidebarMaxWidthWide` | Потолок ширины меню при нескольких колонках дерева |
|
||||
| `treeSidebarBaseWidth` | см. `limits.treeSidebarBaseWidth` | База формулы ширины меню: `base + columns × step` |
|
||||
| `treeSidebarColumnWidth` | см. `limits.treeSidebarColumnWidth` | Прирост ширины меню за каждую колонку дерева |
|
||||
| `topBarMinHeight` | см. `limits.topBarMinHeight` | Минимальная высота шапки в режиме «Сверху» |
|
||||
| `topBarMaxHeight` | см. `limits.topBarMaxHeight` | Максимальная высота шапки (также ограничена ~75% окна) |
|
||||
| `searchPlaceholder` | произвольная строка | Текст-подсказка в поле поиска |
|
||||
|
||||
| `limits` | Формат | Описание |
|
||||
|----------|--------|----------|
|
||||
| `shellMaxWidth` | `{ "min", "max" }` | Диапазон слайдера «Ширина контента» в ⚙ → Габариты |
|
||||
| `sidebarMinWidth` | `{ "min", "max" }` | Диапазон «Меню: мин. ширина» |
|
||||
| `sidebarMaxWidth` | `{ "min", "max" }` | Диапазон «Меню: макс. (1 кол.)» |
|
||||
| `sidebarMaxWidthWide` | `{ "min", "max" }` | Диапазон «Меню: макс. (неск. кол.)» |
|
||||
| `treeSidebarBaseWidth` | `{ "min", "max" }` | Диапазон «Дерево: база ширины» |
|
||||
| `treeSidebarColumnWidth` | `{ "min", "max" }` | Диапазон «Дерево: ширина колонки» |
|
||||
| `topBarMinHeight` | `{ "min", "max" }` | Диапазон «Шапка: мин. высота» |
|
||||
| `topBarMaxHeight` | `{ "min", "max" }` | Диапазон «Шапка: макс. высота» |
|
||||
|
||||
При `treeColumns > 1` фактический потолок ширины меню: `min(sidebarMaxWidthWide, treeSidebarBaseWidth + treeColumns × treeSidebarColumnWidth)`.
|
||||
|
||||
`shellMaxWidth` автоматически не опускается ниже `sidebarMaxWidthWide + 320`, чтобы оставалось место для каталога.
|
||||
|
||||
| `features` | Варианты | Описание |
|
||||
|------------|----------|----------|
|
||||
| `allowNavLayoutChange` | `true`, `false` | Переключатель «Сбоку / Сверху» |
|
||||
| `allowShowTreeChange` | `true`, `false` | Показ/скрытие дерева и кнопка «Скрыть дерево» в панели |
|
||||
| `allowSearch` | `true`, `false` | Поле поиска |
|
||||
| `allowViewChange` | `true`, `false` | Режимы каталога |
|
||||
| `allowShowNumbers` | `true`, `false` | Нумерация |
|
||||
| `allowTreeOptions` | `true`, `false` | Теги, 2 колонки, авто-раскрытие дерева |
|
||||
| `allowSidebarResize` | `true`, `false` | Drag-resize боковой панели |
|
||||
| `allowTreeOptions` | `true`, `false` | Колонки, теги, тип, расположение мета, авто-раскрытие дерева |
|
||||
| `allowSidebarResize` | `true`, `false` | Drag-resize боковой панели (ширина) или верхней шапки (высота) |
|
||||
| `allowLayoutLimitsChange` | `true`, `false` | Раздел ⚙ «Габариты»: лимиты ширины контента и навигации |
|
||||
| `allowThemeChange` | `true`, `false` | Светлая / тёмная тема |
|
||||
| `allowAccentChange` | `true`, `false` | Пресеты и свой градиент |
|
||||
|
||||
Пользовательские изменения сохраняются в `localStorage`, но заблокированные `features` всегда возвращаются к `defaults`.
|
||||
|
||||
Пример пресета «только шапка»: [`settings-top-bar.example.json`](settings-top-bar.example.json) — `navLayout: "top"`, дерево и resize отключены.
|
||||
Пример пресета «шапка сверху»: [`settings-top-bar.example.json`](settings-top-bar.example.json) — `navLayout: "top"`, дерево по умолчанию скрыто (`showTree: false`), но его можно включить.
|
||||
|
||||
### Форматы иерархии
|
||||
|
||||
@ -282,7 +333,7 @@ npx serve .
|
||||
|
||||
Базовые типы заданы в `script.js` (`defaultServiceMeta`):
|
||||
|
||||
`ceph`, `cloudbeaver`, `config`, `gitea`, `grafana`, `ipmi`, `kubernetes`, `ldap`, `messages`, `opensearch`, `postgresql`, `prometheus`, `rabbitmq`, `redmine`, `rules`, `xpath`, `xmpp`
|
||||
`analytics`, `api`, `calendar`, `ceph`, `chat`, `cicd`, `cloudbeaver`, `config`, `drive`, `forum`, `gitea`, `grafana`, `ipmi`, `kubernetes`, `ldap`, `messages`, `opensearch`, `postgresql`, `prometheus`, `rabbitmq`, `redmine`, `rules`, `social`, `vault`, `video`, `vpn`, `wiki`, `xpath`, `xmpp`
|
||||
|
||||
Каждый тип имеет собственную SVG в `assets/` (например, `postgresql.svg`, `cloudbeaver.svg`). Иконка показывается на карточке каталога и в дереве навигации.
|
||||
|
||||
@ -329,14 +380,18 @@ Popup сгруппирован по разделам. Доступные бло
|
||||
|
||||
| Раздел | Настройки |
|
||||
|--------|-----------|
|
||||
| **Макет** | Панель навигации: **Сбоку** (дерево + поиск) или **Сверху** (только название и поиск) |
|
||||
| **Макет** | **Сбоку** / **Сверху**; при «Сбоку» — toggle «Показывать дерево» и кнопка «Скрыть/Показать дерево» в панели |
|
||||
| **Каталог** | Вид карточек, нумерация |
|
||||
| **Дерево** | Теги, 2 колонки, авто-раскрытие поиска (только при `navLayout: sidebar`) |
|
||||
| **Дерево** | Теги, 2 колонки, авто-раскрытие (при `showTree: true`; 2 колонки — только «Сбоку») |
|
||||
| **Оформление** | Тема, акцент, свой градиент |
|
||||
|
||||
### Режим «Сверху»
|
||||
|
||||
Компактная шапка над каталогом: заголовок панели, поиск и ⚙. Дерево навигации скрыто — переход только через карточки каталога. Подходит для простых порталов и когда дерево избыточно.
|
||||
Компактная шапка: заголовок, поиск и ⚙. Дерево по умолчанию скрыто, но его можно включить (`showTree: true`) — оно появится под шапкой на всю ширину.
|
||||
|
||||
### Скрытие дерева
|
||||
|
||||
Дерево управляется отдельно от макета (`showTree`). Переключение — в ⚙ → **Макет** или кнопкой **Скрыть/Показать дерево** под поиском.
|
||||
|
||||
### Свой градиент
|
||||
|
||||
|
||||
1
assets/analytics.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-analytics)"/><rect x="40" y="92" width="18" height="32" rx="6" fill="white"/><rect x="71" y="72" width="18" height="52" rx="6" fill="white" fill-opacity=".9"/><rect x="102" y="52" width="18" height="72" rx="6" fill="white" fill-opacity=".78"/><path d="M44 60l28 18 28-28 16 12" stroke="white" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><defs><linearGradient id="g-analytics" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#6EE7B7"/><stop offset="1" stop-color="#059669"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 680 B |
1
assets/api.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-api)"/><path d="M52 52h-8v56h8M108 52h8v56h-8M68 80h24" stroke="white" stroke-width="10" stroke-linecap="round"/><circle cx="80" cy="80" r="28" stroke="white" stroke-width="8" fill="none"/><defs><linearGradient id="g-api" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#FDE68A"/><stop offset="1" stop-color="#D97706"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
1
assets/calendar.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-calendar)"/><rect x="36" y="48" width="88" height="76" rx="14" fill="white"/><path d="M52 40v16M108 40v16" stroke="white" stroke-width="8" stroke-linecap="round"/><path d="M36 68h88" stroke="#4F46E5" stroke-width="6"/><rect x="50" y="82" width="14" height="14" rx="4" fill="#4F46E5"/><rect x="73" y="82" width="14" height="14" rx="4" fill="#4F46E5" fill-opacity=".7"/><rect x="96" y="82" width="14" height="14" rx="4" fill="#4F46E5" fill-opacity=".45"/><rect x="50" y="102" width="14" height="14" rx="4" fill="#4F46E5" fill-opacity=".7"/><rect x="73" y="102" width="14" height="14" rx="4" fill="#4F46E5"/><defs><linearGradient id="g-calendar" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#A5B4FC"/><stop offset="1" stop-color="#4F46E5"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 926 B |
1
assets/chat.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-chat)"/><path d="M34 52h52c6 0 11 5 11 11v28c0 6-5 11-11 11H62L42 118V91H34c-6 0-11-5-11-11V63c0-6 5-11 11-11z" fill="white"/><path d="M74 58h52c6 0 11 5 11 11v28c0 6-5 11-11 11h-8l-20 27V98H74c-6 0-11-5-11-11V69c0-6 5-11 11-11z" fill="white" fill-opacity=".78"/><circle cx="52" cy="74" r="4" fill="#7C3AED"/><circle cx="68" cy="74" r="4" fill="#7C3AED"/><circle cx="84" cy="74" r="4" fill="#7C3AED"/><defs><linearGradient id="g-chat" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#C4B5FD"/><stop offset="1" stop-color="#7C3AED"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 718 B |
1
assets/cicd.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-cicd)"/><path d="M80 44a36 36 0 1 1-25 59" stroke="white" stroke-width="10" stroke-linecap="round" fill="none"/><path d="M55 44h25v25" stroke="white" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/><circle cx="80" cy="80" r="10" fill="#1D4ED8"/><path d="M96 96l18 18" stroke="white" stroke-width="8" stroke-linecap="round"/><defs><linearGradient id="g-cicd" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#93C5FD"/><stop offset="1" stop-color="#1D4ED8"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 659 B |
1
assets/drive.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-drive)"/><path d="M44 98l18-36h36l18 36H44z" fill="white"/><path d="M62 62l14-18h18l14 18" stroke="white" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><rect x="48" y="104" width="64" height="14" rx="7" fill="white" fill-opacity=".85"/><defs><linearGradient id="g-drive" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#93C5FD"/><stop offset="1" stop-color="#2563EB"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 573 B |
1
assets/forum.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-forum)"/><rect x="36" y="44" width="88" height="18" rx="9" fill="white"/><rect x="36" y="72" width="72" height="18" rx="9" fill="white" fill-opacity=".88"/><rect x="36" y="100" width="80" height="18" rx="9" fill="white" fill-opacity=".76"/><circle cx="124" cy="53" r="8" fill="#EA580C"/><circle cx="116" cy="81" r="8" fill="#EA580C" fill-opacity=".85"/><circle cx="122" cy="109" r="8" fill="#EA580C" fill-opacity=".7"/><defs><linearGradient id="g-forum" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#FDBA74"/><stop offset="1" stop-color="#EA580C"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 737 B |
1
assets/social.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-social)"/><circle cx="56" cy="68" r="16" fill="white"/><circle cx="104" cy="68" r="16" fill="white"/><circle cx="80" cy="108" r="18" fill="white" fill-opacity=".92"/><path d="M68 118c6 8 18 12 28 12s22-4 28-12" stroke="white" stroke-width="6" stroke-linecap="round"/><defs><linearGradient id="g-social" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#F9A8D4"/><stop offset="1" stop-color="#DB2777"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 586 B |
1
assets/vault.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-vault)"/><rect x="38" y="48" width="84" height="72" rx="16" fill="white"/><circle cx="80" cy="84" r="18" stroke="#6D28D9" stroke-width="8" fill="none"/><circle cx="80" cy="84" r="6" fill="#6D28D9"/><rect x="74" y="84" width="12" height="18" rx="2" fill="#6D28D9"/><defs><linearGradient id="g-vault" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#DDD6FE"/><stop offset="1" stop-color="#6D28D9"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 582 B |
1
assets/video.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-video)"/><rect x="34" y="50" width="92" height="60" rx="14" fill="white"/><path d="M88 68l28-16v56l-28-16V68z" fill="#DC2626"/><circle cx="58" cy="118" r="10" fill="white"/><circle cx="102" cy="118" r="10" fill="white" fill-opacity=".85"/><defs><linearGradient id="g-video" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#FCA5A5"/><stop offset="1" stop-color="#DC2626"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 557 B |
1
assets/vpn.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-vpn)"/><path d="M80 38l44 24v36c0 24-20 44-44 44S36 122 36 98V62l44-24z" fill="white"/><path d="M72 92c0-4 4-8 8-8s8 4 8 8v8H72v-8z" fill="#0F766E"/><rect x="68" y="76" width="24" height="18" rx="8" stroke="#0F766E" stroke-width="6"/><defs><linearGradient id="g-vpn" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#5EEAD4"/><stop offset="1" stop-color="#0F766E"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 550 B |
1
assets/wiki.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-wiki)"/><path d="M48 44h64l8 92H40l8-92z" fill="white"/><path d="M56 56h48M56 76h40M56 96h44M56 116h32" stroke="#0369A1" stroke-width="6" stroke-linecap="round"/><path d="M80 44v16" stroke="white" stroke-width="8" stroke-linecap="round"/><defs><linearGradient id="g-wiki" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#7DD3FC"/><stop offset="1" stop-color="#0369A1"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 555 B |
@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Демо панели: плоский список элементов без разделов."
|
||||
content="Навигационная панель экосистемы с разделами, подразделами, поиском и быстрыми переходами."
|
||||
/>
|
||||
<title>Пример: плоский список</title>
|
||||
<title>Панель экосистемы</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
@ -42,7 +42,7 @@
|
||||
⚙
|
||||
</button>
|
||||
|
||||
<p class="eyebrow" id="page-title">Пример: плоский список</p>
|
||||
<p class="eyebrow" id="page-title">Панель экосистемы</p>
|
||||
|
||||
<!-- Поиск фильтрует каталог и подсвечивает совпадения в дереве и карточках. -->
|
||||
<label class="search-field" data-search-field>
|
||||
@ -57,6 +57,15 @@
|
||||
<!-- Дерево генерируется из ecosystemSections в script.js. -->
|
||||
<nav class="nav-tree" aria-label="Дерево навигации" data-nav-tree></nav>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="top-bar-resizer"
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label="Изменить высоту верхней панели"
|
||||
data-top-bar-resizer
|
||||
hidden
|
||||
></div>
|
||||
</aside>
|
||||
|
||||
<!-- Вертикальная ручка для изменения ширины бокового меню мышью/указателем. -->
|
||||
@ -82,10 +91,7 @@
|
||||
-->
|
||||
<div class="control-panel" aria-label="Настройки панели" data-settings-panel>
|
||||
<div class="settings-panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">View settings</p>
|
||||
<h2>Настройки</h2>
|
||||
</div>
|
||||
<p class="eyebrow settings-panel-eyebrow">Настройки</p>
|
||||
<button class="settings-close" type="button" aria-label="Закрыть настройки" data-settings-close>
|
||||
×
|
||||
</button>
|
||||
@ -106,8 +112,109 @@
|
||||
<p class="settings-section-hint">
|
||||
Режим «Сверху» — компактная шапка с названием и поиском, без дерева.
|
||||
</p>
|
||||
<label class="toggle-row" data-feature="allowShowTreeChange" data-show-when-sidebar>
|
||||
<span>
|
||||
<strong>Показывать дерево</strong>
|
||||
<small>Только для панели «Сбоку»</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree checked />
|
||||
</label>
|
||||
<label class="toggle-row" data-feature="allowSidebarResize">
|
||||
<span>
|
||||
<strong>Ручки изменения размера</strong>
|
||||
<small>Ширина меню или высота шапки</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-resize-handles checked />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<details class="settings-section settings-section-collapsible" data-settings-section="dimensions">
|
||||
<summary class="settings-section-summary">
|
||||
<span class="settings-section-title">Габариты</span>
|
||||
<span class="collapse-indicator" aria-hidden="true">⌄</span>
|
||||
</summary>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-section-hint">
|
||||
Ограничения ширины страницы, бокового меню и верхней шапки. Значения применяются сразу;
|
||||
текущий размер меню и шапки можно также менять перетаскиванием ручек на странице.
|
||||
Допустимые диапазоны ползунков задаёт администратор в <code>settings.json</code> →
|
||||
<code>limits</code>.
|
||||
</p>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина всей панели (.shell): насколько широко вместе могут располагаться меню и каталог."
|
||||
>
|
||||
<span>Ширина контента (max)</span>
|
||||
<input type="range" data-layout-limit="shellMaxWidth" />
|
||||
<output data-layout-limit-value="shellMaxWidth">2800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная ширина бокового меню при перетаскивании вертикальной ручки между меню и каталогом."
|
||||
>
|
||||
<span>Меню: мин. ширина</span>
|
||||
<input type="range" data-layout-limit="sidebarMinWidth" />
|
||||
<output data-layout-limit-value="sidebarMinWidth">260</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина меню, когда элементы дерева в папках показываются в одну колонку."
|
||||
>
|
||||
<span>Меню: макс. (1 кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidth" />
|
||||
<output data-layout-limit-value="sidebarMaxWidth">800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Верхний предел ширины меню при нескольких колонках в дереве. Фактический максимум также зависит от базы и шага колонки."
|
||||
>
|
||||
<span>Меню: макс. (неск. кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidthWide" />
|
||||
<output data-layout-limit-value="sidebarMaxWidthWide">1400</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="База формулы ширины меню при нескольких колонках: min(макс. неск. кол., база + колонки × шаг)."
|
||||
>
|
||||
<span>Дерево: база ширины</span>
|
||||
<input type="range" data-layout-limit="treeSidebarBaseWidth" />
|
||||
<output data-layout-limit-value="treeSidebarBaseWidth">420</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Сколько пикселей добавляется к базовой ширине меню за каждую колонку элементов в папках дерева."
|
||||
>
|
||||
<span>Дерево: ширина колонки</span>
|
||||
<input type="range" data-layout-limit="treeSidebarColumnWidth" />
|
||||
<output data-layout-limit-value="treeSidebarColumnWidth">120</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная высота верхней шапки в режиме «Сверху» — нижняя граница при перетаскивании горизонтальной ручки."
|
||||
>
|
||||
<span>Шапка: мин. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMinHeight" />
|
||||
<output data-layout-limit-value="topBarMinHeight">80</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная высота верхней шапки в режиме «Сверху». Также ограничена примерно 75% высоты окна браузера."
|
||||
>
|
||||
<span>Шапка: макс. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMaxHeight" />
|
||||
<output data-layout-limit-value="topBarMaxHeight">560</output>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="settings-section" data-settings-section="catalog">
|
||||
<p class="settings-section-title">Каталог</p>
|
||||
|
||||
@ -128,6 +235,34 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="catalog-columns-group" data-feature="allowCatalogColumnsChange">
|
||||
<p class="settings-section-hint">Колонок в строке для каждого вида каталога.</p>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCardsColumns">3</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Список</span>
|
||||
<input type="range" data-catalog-columns="catalogListColumns" />
|
||||
<output data-catalog-columns-value="catalogListColumns">1</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactCardsColumns">5</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт список</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactListColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactListColumns">3</output>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Показывать номера разделов, подразделов и элементов. -->
|
||||
<label class="toggle-row" data-feature="allowShowNumbers">
|
||||
<span>
|
||||
@ -141,25 +276,38 @@
|
||||
<section class="settings-section" data-settings-section="tree">
|
||||
<p class="settings-section-title">Дерево</p>
|
||||
|
||||
<!-- Показывать поисковые теги рядом с файлами в дереве навигации. -->
|
||||
<label class="accent-custom-row">
|
||||
<span>Колонок</span>
|
||||
<input type="range" min="1" max="6" step="1" value="2" data-tree-columns />
|
||||
<output data-tree-columns-value>2</output>
|
||||
</label>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Показывать теги дерева</strong>
|
||||
<small>Теги рядом с элементом, без 2 колонок</small>
|
||||
<strong>Показывать теги</strong>
|
||||
<small>Оформление как на карточках</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree-tags />
|
||||
</label>
|
||||
|
||||
<!-- Элементы внутри папок дерева отображаются в две колонки без тегов. -->
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>2 колонки в дереве</strong>
|
||||
<small>Компактный вид элементов папок</small>
|
||||
<strong>Показывать тип</strong>
|
||||
<small>Подпись типа сервиса</small>
|
||||
</span>
|
||||
<input type="checkbox" data-tree-two-column />
|
||||
<input type="checkbox" data-show-tree-type />
|
||||
</label>
|
||||
|
||||
<!-- Управляет тем, раскрывает ли поиск только ветки с совпадениями. -->
|
||||
<div class="control-group">
|
||||
<span>Теги и тип</span>
|
||||
<button class="segmented is-active" type="button" data-tree-meta-layout="inline">
|
||||
Сбоку
|
||||
</button>
|
||||
<button class="segmented" type="button" data-tree-meta-layout="stacked">
|
||||
Снизу
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Авто раскрывать поиск</strong>
|
||||
|
||||
@ -15,6 +15,13 @@
|
||||
"tags": ["prometheus", "метрики"],
|
||||
"url": "https://prometheus.example.com"
|
||||
},
|
||||
{
|
||||
"id": "analytics",
|
||||
"title": "Портал аналитики",
|
||||
"type": "analytics",
|
||||
"tags": ["аналитика", "отчёты"],
|
||||
"url": "https://analytics.example.com"
|
||||
},
|
||||
{
|
||||
"id": "opensearch",
|
||||
"title": "OpenSearch",
|
||||
@ -29,6 +36,20 @@
|
||||
"tags": ["k8s", "кластер"],
|
||||
"url": "https://k8s.example.com"
|
||||
},
|
||||
{
|
||||
"id": "cicd",
|
||||
"title": "CI/CD",
|
||||
"type": "cicd",
|
||||
"tags": ["ci", "cd", "deploy"],
|
||||
"url": "https://ci.example.com"
|
||||
},
|
||||
{
|
||||
"id": "vault",
|
||||
"title": "Vault — секреты",
|
||||
"type": "vault",
|
||||
"tags": ["vault", "секреты"],
|
||||
"url": "https://vault.example.com"
|
||||
},
|
||||
{
|
||||
"id": "gitea",
|
||||
"title": "Gitea",
|
||||
@ -36,6 +57,13 @@
|
||||
"tags": ["git", "repo"],
|
||||
"url": "https://gitea.example.com"
|
||||
},
|
||||
{
|
||||
"id": "api",
|
||||
"title": "API Gateway",
|
||||
"type": "api",
|
||||
"tags": ["api", "интеграции"],
|
||||
"url": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"id": "postgresql",
|
||||
"title": "PostgreSQL",
|
||||
@ -57,6 +85,76 @@
|
||||
"tags": ["s3", "хранилище"],
|
||||
"url": "https://s3.example.com"
|
||||
},
|
||||
{
|
||||
"id": "drive",
|
||||
"title": "Корпоративный диск",
|
||||
"type": "drive",
|
||||
"tags": ["файлы", "диск"],
|
||||
"url": "https://drive.example.com"
|
||||
},
|
||||
{
|
||||
"id": "social",
|
||||
"title": "Корпоративная соцсеть",
|
||||
"type": "social",
|
||||
"tags": ["соцсеть", "лента"],
|
||||
"url": "https://social.example.com"
|
||||
},
|
||||
{
|
||||
"id": "chat",
|
||||
"title": "Командные беседы",
|
||||
"type": "chat",
|
||||
"tags": ["беседа", "чат"],
|
||||
"url": "https://chat.example.com"
|
||||
},
|
||||
{
|
||||
"id": "forum",
|
||||
"title": "Форум сотрудников",
|
||||
"type": "forum",
|
||||
"tags": ["форум", "обсуждения"],
|
||||
"url": "https://forum.example.com"
|
||||
},
|
||||
{
|
||||
"id": "wiki",
|
||||
"title": "Wiki",
|
||||
"type": "wiki",
|
||||
"tags": ["wiki", "документация"],
|
||||
"url": "https://wiki.example.com"
|
||||
},
|
||||
{
|
||||
"id": "video",
|
||||
"title": "Видеоконференции",
|
||||
"type": "video",
|
||||
"tags": ["видео", "meet"],
|
||||
"url": "https://meet.example.com"
|
||||
},
|
||||
{
|
||||
"id": "calendar",
|
||||
"title": "Календарь",
|
||||
"type": "calendar",
|
||||
"tags": ["календарь", "встречи"],
|
||||
"url": "https://calendar.example.com"
|
||||
},
|
||||
{
|
||||
"id": "messages",
|
||||
"title": "Почта",
|
||||
"type": "messages",
|
||||
"tags": ["почта", "email"],
|
||||
"url": "https://mail.example.com"
|
||||
},
|
||||
{
|
||||
"id": "xmpp",
|
||||
"title": "XMPP-чат",
|
||||
"type": "xmpp",
|
||||
"tags": ["xmpp", "мессенджер"],
|
||||
"url": "https://xmpp.example.com"
|
||||
},
|
||||
{
|
||||
"id": "vpn",
|
||||
"title": "VPN-портал",
|
||||
"type": "vpn",
|
||||
"tags": ["vpn", "доступ"],
|
||||
"url": "https://vpn.example.com"
|
||||
},
|
||||
{
|
||||
"id": "ldap",
|
||||
"title": "LDAP",
|
||||
|
||||
@ -3,23 +3,60 @@
|
||||
"data": null,
|
||||
"defaults": {
|
||||
"navLayout": "sidebar",
|
||||
"showTree": true,
|
||||
"showResizeHandles": true,
|
||||
"theme": "dark",
|
||||
"view": "cards",
|
||||
"accent": "violet",
|
||||
"customAccentFrom": "#8b5cf6",
|
||||
"customAccentTo": "#22d3ee",
|
||||
"customAccentAngle": 135,
|
||||
"showNumbers": false,
|
||||
"showTreeTags": true,
|
||||
"treeTwoColumn": false,
|
||||
"showTreeType": false,
|
||||
"treeMetaLayout": "inline",
|
||||
"treeColumns": 2,
|
||||
"autoCollapseSearch": true,
|
||||
"sidebarWidth": 390,
|
||||
"topBarHeight": 140,
|
||||
"shellMaxWidth": 2800,
|
||||
"sidebarMinWidth": 260,
|
||||
"sidebarMaxWidth": 800,
|
||||
"sidebarMaxWidthWide": 1400,
|
||||
"treeSidebarBaseWidth": 420,
|
||||
"treeSidebarColumnWidth": 120,
|
||||
"topBarMinHeight": 80,
|
||||
"topBarMaxHeight": 560,
|
||||
"catalogCardsColumns": 3,
|
||||
"catalogListColumns": 1,
|
||||
"catalogCompactCardsColumns": 5,
|
||||
"catalogCompactListColumns": 3,
|
||||
"searchPlaceholder": "Grafana, S3, журнал, сервер..."
|
||||
},
|
||||
"limits": {
|
||||
"shellMaxWidth": { "min": 1200, "max": 3840 },
|
||||
"sidebarMinWidth": { "min": 200, "max": 480 },
|
||||
"sidebarMaxWidth": { "min": 400, "max": 1200 },
|
||||
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
|
||||
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
|
||||
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
|
||||
"topBarMinHeight": { "min": 64, "max": 240 },
|
||||
"topBarMaxHeight": { "min": 160, "max": 800 },
|
||||
"catalogCardsColumns": { "min": 1, "max": 6 },
|
||||
"catalogListColumns": { "min": 1, "max": 4 },
|
||||
"catalogCompactCardsColumns": { "min": 2, "max": 8 },
|
||||
"catalogCompactListColumns": { "min": 1, "max": 6 }
|
||||
},
|
||||
"features": {
|
||||
"allowNavLayoutChange": true,
|
||||
"allowShowTreeChange": true,
|
||||
"allowSearch": true,
|
||||
"allowViewChange": true,
|
||||
"allowCatalogColumnsChange": true,
|
||||
"allowShowNumbers": true,
|
||||
"allowTreeOptions": true,
|
||||
"allowSidebarResize": true,
|
||||
"allowLayoutLimitsChange": true,
|
||||
"allowThemeChange": true,
|
||||
"allowAccentChange": true
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Демо панели: полная иерархия section → group → item."
|
||||
content="Навигационная панель экосистемы с разделами, подразделами, поиском и быстрыми переходами."
|
||||
/>
|
||||
<title>Пример: section → group → item</title>
|
||||
<title>Панель экосистемы</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
@ -42,7 +42,7 @@
|
||||
⚙
|
||||
</button>
|
||||
|
||||
<p class="eyebrow" id="page-title">Пример: section → group → item</p>
|
||||
<p class="eyebrow" id="page-title">Панель экосистемы</p>
|
||||
|
||||
<!-- Поиск фильтрует каталог и подсвечивает совпадения в дереве и карточках. -->
|
||||
<label class="search-field" data-search-field>
|
||||
@ -57,6 +57,15 @@
|
||||
<!-- Дерево генерируется из ecosystemSections в script.js. -->
|
||||
<nav class="nav-tree" aria-label="Дерево навигации" data-nav-tree></nav>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="top-bar-resizer"
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label="Изменить высоту верхней панели"
|
||||
data-top-bar-resizer
|
||||
hidden
|
||||
></div>
|
||||
</aside>
|
||||
|
||||
<!-- Вертикальная ручка для изменения ширины бокового меню мышью/указателем. -->
|
||||
@ -82,10 +91,7 @@
|
||||
-->
|
||||
<div class="control-panel" aria-label="Настройки панели" data-settings-panel>
|
||||
<div class="settings-panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">View settings</p>
|
||||
<h2>Настройки</h2>
|
||||
</div>
|
||||
<p class="eyebrow settings-panel-eyebrow">Настройки</p>
|
||||
<button class="settings-close" type="button" aria-label="Закрыть настройки" data-settings-close>
|
||||
×
|
||||
</button>
|
||||
@ -106,8 +112,109 @@
|
||||
<p class="settings-section-hint">
|
||||
Режим «Сверху» — компактная шапка с названием и поиском, без дерева.
|
||||
</p>
|
||||
<label class="toggle-row" data-feature="allowShowTreeChange" data-show-when-sidebar>
|
||||
<span>
|
||||
<strong>Показывать дерево</strong>
|
||||
<small>Только для панели «Сбоку»</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree checked />
|
||||
</label>
|
||||
<label class="toggle-row" data-feature="allowSidebarResize">
|
||||
<span>
|
||||
<strong>Ручки изменения размера</strong>
|
||||
<small>Ширина меню или высота шапки</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-resize-handles checked />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<details class="settings-section settings-section-collapsible" data-settings-section="dimensions">
|
||||
<summary class="settings-section-summary">
|
||||
<span class="settings-section-title">Габариты</span>
|
||||
<span class="collapse-indicator" aria-hidden="true">⌄</span>
|
||||
</summary>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-section-hint">
|
||||
Ограничения ширины страницы, бокового меню и верхней шапки. Значения применяются сразу;
|
||||
текущий размер меню и шапки можно также менять перетаскиванием ручек на странице.
|
||||
Допустимые диапазоны ползунков задаёт администратор в <code>settings.json</code> →
|
||||
<code>limits</code>.
|
||||
</p>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина всей панели (.shell): насколько широко вместе могут располагаться меню и каталог."
|
||||
>
|
||||
<span>Ширина контента (max)</span>
|
||||
<input type="range" data-layout-limit="shellMaxWidth" />
|
||||
<output data-layout-limit-value="shellMaxWidth">2800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная ширина бокового меню при перетаскивании вертикальной ручки между меню и каталогом."
|
||||
>
|
||||
<span>Меню: мин. ширина</span>
|
||||
<input type="range" data-layout-limit="sidebarMinWidth" />
|
||||
<output data-layout-limit-value="sidebarMinWidth">260</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина меню, когда элементы дерева в папках показываются в одну колонку."
|
||||
>
|
||||
<span>Меню: макс. (1 кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidth" />
|
||||
<output data-layout-limit-value="sidebarMaxWidth">800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Верхний предел ширины меню при нескольких колонках в дереве. Фактический максимум также зависит от базы и шага колонки."
|
||||
>
|
||||
<span>Меню: макс. (неск. кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidthWide" />
|
||||
<output data-layout-limit-value="sidebarMaxWidthWide">1400</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="База формулы ширины меню при нескольких колонках: min(макс. неск. кол., база + колонки × шаг)."
|
||||
>
|
||||
<span>Дерево: база ширины</span>
|
||||
<input type="range" data-layout-limit="treeSidebarBaseWidth" />
|
||||
<output data-layout-limit-value="treeSidebarBaseWidth">420</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Сколько пикселей добавляется к базовой ширине меню за каждую колонку элементов в папках дерева."
|
||||
>
|
||||
<span>Дерево: ширина колонки</span>
|
||||
<input type="range" data-layout-limit="treeSidebarColumnWidth" />
|
||||
<output data-layout-limit-value="treeSidebarColumnWidth">120</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная высота верхней шапки в режиме «Сверху» — нижняя граница при перетаскивании горизонтальной ручки."
|
||||
>
|
||||
<span>Шапка: мин. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMinHeight" />
|
||||
<output data-layout-limit-value="topBarMinHeight">80</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная высота верхней шапки в режиме «Сверху». Также ограничена примерно 75% высоты окна браузера."
|
||||
>
|
||||
<span>Шапка: макс. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMaxHeight" />
|
||||
<output data-layout-limit-value="topBarMaxHeight">560</output>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="settings-section" data-settings-section="catalog">
|
||||
<p class="settings-section-title">Каталог</p>
|
||||
|
||||
@ -128,6 +235,34 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="catalog-columns-group" data-feature="allowCatalogColumnsChange">
|
||||
<p class="settings-section-hint">Колонок в строке для каждого вида каталога.</p>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCardsColumns">3</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Список</span>
|
||||
<input type="range" data-catalog-columns="catalogListColumns" />
|
||||
<output data-catalog-columns-value="catalogListColumns">1</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactCardsColumns">5</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт список</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactListColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactListColumns">3</output>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Показывать номера разделов, подразделов и элементов. -->
|
||||
<label class="toggle-row" data-feature="allowShowNumbers">
|
||||
<span>
|
||||
@ -141,25 +276,38 @@
|
||||
<section class="settings-section" data-settings-section="tree">
|
||||
<p class="settings-section-title">Дерево</p>
|
||||
|
||||
<!-- Показывать поисковые теги рядом с файлами в дереве навигации. -->
|
||||
<label class="accent-custom-row">
|
||||
<span>Колонок</span>
|
||||
<input type="range" min="1" max="6" step="1" value="2" data-tree-columns />
|
||||
<output data-tree-columns-value>2</output>
|
||||
</label>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Показывать теги дерева</strong>
|
||||
<small>Теги рядом с элементом, без 2 колонок</small>
|
||||
<strong>Показывать теги</strong>
|
||||
<small>Оформление как на карточках</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree-tags />
|
||||
</label>
|
||||
|
||||
<!-- Элементы внутри папок дерева отображаются в две колонки без тегов. -->
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>2 колонки в дереве</strong>
|
||||
<small>Компактный вид элементов папок</small>
|
||||
<strong>Показывать тип</strong>
|
||||
<small>Подпись типа сервиса</small>
|
||||
</span>
|
||||
<input type="checkbox" data-tree-two-column />
|
||||
<input type="checkbox" data-show-tree-type />
|
||||
</label>
|
||||
|
||||
<!-- Управляет тем, раскрывает ли поиск только ветки с совпадениями. -->
|
||||
<div class="control-group">
|
||||
<span>Теги и тип</span>
|
||||
<button class="segmented is-active" type="button" data-tree-meta-layout="inline">
|
||||
Сбоку
|
||||
</button>
|
||||
<button class="segmented" type="button" data-tree-meta-layout="stacked">
|
||||
Снизу
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Авто раскрывать поиск</strong>
|
||||
|
||||
@ -30,8 +30,16 @@
|
||||
"url": "https://prometheus.example.com"
|
||||
},
|
||||
{
|
||||
"id": "alerts",
|
||||
"id": "analytics",
|
||||
"number": "1.1.3",
|
||||
"title": "Портал аналитики",
|
||||
"type": "analytics",
|
||||
"tags": ["аналитика", "отчёты", "kpi"],
|
||||
"url": "https://analytics.example.com"
|
||||
},
|
||||
{
|
||||
"id": "alerts",
|
||||
"number": "1.1.4",
|
||||
"title": "Правила алертинга",
|
||||
"type": "rules",
|
||||
"tags": ["алерты", "правила"],
|
||||
@ -92,6 +100,22 @@
|
||||
"type": "rabbitmq",
|
||||
"tags": ["очереди", "rabbitmq"],
|
||||
"url": "https://rabbitmq.example.com"
|
||||
},
|
||||
{
|
||||
"id": "cicd",
|
||||
"number": "2.1.3",
|
||||
"title": "CI/CD — пайплайны",
|
||||
"type": "cicd",
|
||||
"tags": ["ci", "cd", "deploy"],
|
||||
"url": "https://ci.example.com"
|
||||
},
|
||||
{
|
||||
"id": "vault",
|
||||
"number": "2.1.4",
|
||||
"title": "HashiCorp Vault",
|
||||
"type": "vault",
|
||||
"tags": ["секреты", "vault", "безопасность"],
|
||||
"url": "https://vault.example.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -110,8 +134,16 @@
|
||||
"url": "https://s3.example.com"
|
||||
},
|
||||
{
|
||||
"id": "postgresql",
|
||||
"id": "drive",
|
||||
"number": "2.2.2",
|
||||
"title": "Корпоративный диск",
|
||||
"type": "drive",
|
||||
"tags": ["файлы", "диск", "обмен"],
|
||||
"url": "https://drive.example.com"
|
||||
},
|
||||
{
|
||||
"id": "postgresql",
|
||||
"number": "2.2.3",
|
||||
"title": "PostgreSQL",
|
||||
"type": "postgresql",
|
||||
"tags": ["postgresql", "база", "sql"],
|
||||
@ -119,7 +151,7 @@
|
||||
},
|
||||
{
|
||||
"id": "cloudbeaver",
|
||||
"number": "2.2.3",
|
||||
"number": "2.2.4",
|
||||
"title": "CloudBeaver",
|
||||
"type": "cloudbeaver",
|
||||
"tags": ["sql", "база", "редактор"],
|
||||
@ -128,6 +160,156 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "communications",
|
||||
"number": "3",
|
||||
"title": "Коммуникации",
|
||||
"description": "Соцсеть, чаты, форумы и видеосвязь.",
|
||||
"groups": [
|
||||
{
|
||||
"id": "social-collab",
|
||||
"number": "3.1",
|
||||
"title": "Совместная работа",
|
||||
"description": "Лента, обсуждения и база знаний.",
|
||||
"items": [
|
||||
{
|
||||
"id": "social-feed",
|
||||
"number": "3.1.1",
|
||||
"title": "Корпоративная соцсеть",
|
||||
"type": "social",
|
||||
"tags": ["соцсеть", "лента", "новости"],
|
||||
"url": "https://social.example.com"
|
||||
},
|
||||
{
|
||||
"id": "forum",
|
||||
"number": "3.1.2",
|
||||
"title": "Форум сотрудников",
|
||||
"type": "forum",
|
||||
"tags": ["форум", "обсуждения", "q&a"],
|
||||
"url": "https://forum.example.com"
|
||||
},
|
||||
{
|
||||
"id": "wiki",
|
||||
"number": "3.1.3",
|
||||
"title": "Wiki — база знаний",
|
||||
"type": "wiki",
|
||||
"tags": ["wiki", "документация", "знания"],
|
||||
"url": "https://wiki.example.com"
|
||||
},
|
||||
{
|
||||
"id": "calendar",
|
||||
"number": "3.1.4",
|
||||
"title": "Календарь и встречи",
|
||||
"type": "calendar",
|
||||
"tags": ["календарь", "встречи", "расписание"],
|
||||
"url": "https://calendar.example.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "messaging",
|
||||
"number": "3.2",
|
||||
"title": "Сообщения и звонки",
|
||||
"description": "Мессенджеры, почта и видеоконференции.",
|
||||
"items": [
|
||||
{
|
||||
"id": "chat-team",
|
||||
"number": "3.2.1",
|
||||
"title": "Командные беседы",
|
||||
"type": "chat",
|
||||
"tags": ["беседа", "чат", "команда"],
|
||||
"url": "https://chat.example.com"
|
||||
},
|
||||
{
|
||||
"id": "xmpp",
|
||||
"number": "3.2.2",
|
||||
"title": "XMPP-сервер",
|
||||
"type": "xmpp",
|
||||
"tags": ["xmpp", "мессенджер"],
|
||||
"url": "https://xmpp.example.com"
|
||||
},
|
||||
{
|
||||
"id": "mail",
|
||||
"number": "3.2.3",
|
||||
"title": "Корпоративная почта",
|
||||
"type": "messages",
|
||||
"tags": ["почта", "email"],
|
||||
"url": "https://mail.example.com"
|
||||
},
|
||||
{
|
||||
"id": "video-conf",
|
||||
"number": "3.2.4",
|
||||
"title": "Видеоконференции",
|
||||
"type": "video",
|
||||
"tags": ["видео", "звонки", "meet"],
|
||||
"url": "https://meet.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "access",
|
||||
"number": "4",
|
||||
"title": "Доступ и интеграции",
|
||||
"description": "VPN, API и внешние сервисы.",
|
||||
"groups": [
|
||||
{
|
||||
"id": "remote-access",
|
||||
"number": "4.1",
|
||||
"title": "Удалённый доступ",
|
||||
"items": [
|
||||
{
|
||||
"id": "vpn",
|
||||
"number": "4.1.1",
|
||||
"title": "VPN-портал",
|
||||
"type": "vpn",
|
||||
"tags": ["vpn", "удалёнка", "доступ"],
|
||||
"url": "https://vpn.example.com"
|
||||
},
|
||||
{
|
||||
"id": "ldap",
|
||||
"number": "4.1.2",
|
||||
"title": "LDAP Account Manager",
|
||||
"type": "ldap",
|
||||
"tags": ["ldap", "пользователи", "iam"],
|
||||
"url": "https://ldap.example.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "integrations",
|
||||
"number": "4.2",
|
||||
"title": "API и задачи",
|
||||
"items": [
|
||||
{
|
||||
"id": "api-gateway",
|
||||
"number": "4.2.1",
|
||||
"title": "API Gateway",
|
||||
"type": "api",
|
||||
"tags": ["api", "gateway", "интеграции"],
|
||||
"url": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"id": "redmine",
|
||||
"number": "4.2.2",
|
||||
"title": "Redmine — задачи",
|
||||
"type": "redmine",
|
||||
"tags": ["redmine", "задачи", "трекер"],
|
||||
"url": "https://redmine.example.com"
|
||||
},
|
||||
{
|
||||
"id": "gitea",
|
||||
"number": "4.2.3",
|
||||
"title": "Gitea — репозитории",
|
||||
"type": "gitea",
|
||||
"tags": ["git", "repo", "gitea"],
|
||||
"url": "https://gitea.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,23 +3,60 @@
|
||||
"data": null,
|
||||
"defaults": {
|
||||
"navLayout": "sidebar",
|
||||
"showTree": true,
|
||||
"showResizeHandles": true,
|
||||
"theme": "dark",
|
||||
"view": "cards",
|
||||
"accent": "violet",
|
||||
"customAccentFrom": "#8b5cf6",
|
||||
"customAccentTo": "#22d3ee",
|
||||
"customAccentAngle": 135,
|
||||
"showNumbers": false,
|
||||
"showTreeTags": true,
|
||||
"treeTwoColumn": false,
|
||||
"showTreeType": false,
|
||||
"treeMetaLayout": "inline",
|
||||
"treeColumns": 2,
|
||||
"autoCollapseSearch": true,
|
||||
"sidebarWidth": 390,
|
||||
"topBarHeight": 140,
|
||||
"shellMaxWidth": 2800,
|
||||
"sidebarMinWidth": 260,
|
||||
"sidebarMaxWidth": 800,
|
||||
"sidebarMaxWidthWide": 1400,
|
||||
"treeSidebarBaseWidth": 420,
|
||||
"treeSidebarColumnWidth": 120,
|
||||
"topBarMinHeight": 80,
|
||||
"topBarMaxHeight": 560,
|
||||
"catalogCardsColumns": 3,
|
||||
"catalogListColumns": 1,
|
||||
"catalogCompactCardsColumns": 5,
|
||||
"catalogCompactListColumns": 3,
|
||||
"searchPlaceholder": "Grafana, S3, журнал, сервер..."
|
||||
},
|
||||
"limits": {
|
||||
"shellMaxWidth": { "min": 1200, "max": 3840 },
|
||||
"sidebarMinWidth": { "min": 200, "max": 480 },
|
||||
"sidebarMaxWidth": { "min": 400, "max": 1200 },
|
||||
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
|
||||
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
|
||||
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
|
||||
"topBarMinHeight": { "min": 64, "max": 240 },
|
||||
"topBarMaxHeight": { "min": 160, "max": 800 },
|
||||
"catalogCardsColumns": { "min": 1, "max": 6 },
|
||||
"catalogListColumns": { "min": 1, "max": 4 },
|
||||
"catalogCompactCardsColumns": { "min": 2, "max": 8 },
|
||||
"catalogCompactListColumns": { "min": 1, "max": 6 }
|
||||
},
|
||||
"features": {
|
||||
"allowNavLayoutChange": true,
|
||||
"allowShowTreeChange": true,
|
||||
"allowSearch": true,
|
||||
"allowViewChange": true,
|
||||
"allowCatalogColumnsChange": true,
|
||||
"allowShowNumbers": true,
|
||||
"allowTreeOptions": true,
|
||||
"allowSidebarResize": true,
|
||||
"allowLayoutLimitsChange": true,
|
||||
"allowThemeChange": true,
|
||||
"allowAccentChange": true
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Демо панели: раздел с прямыми элементами и подразделами одновременно."
|
||||
content="Навигационная панель экосистемы с разделами, подразделами, поиском и быстрыми переходами."
|
||||
/>
|
||||
<title>Пример: смешанный раздел</title>
|
||||
<title>Панель экосистемы</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
@ -42,7 +42,7 @@
|
||||
⚙
|
||||
</button>
|
||||
|
||||
<p class="eyebrow" id="page-title">Пример: смешанный раздел</p>
|
||||
<p class="eyebrow" id="page-title">Панель экосистемы</p>
|
||||
|
||||
<!-- Поиск фильтрует каталог и подсвечивает совпадения в дереве и карточках. -->
|
||||
<label class="search-field" data-search-field>
|
||||
@ -57,6 +57,15 @@
|
||||
<!-- Дерево генерируется из ecosystemSections в script.js. -->
|
||||
<nav class="nav-tree" aria-label="Дерево навигации" data-nav-tree></nav>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="top-bar-resizer"
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label="Изменить высоту верхней панели"
|
||||
data-top-bar-resizer
|
||||
hidden
|
||||
></div>
|
||||
</aside>
|
||||
|
||||
<!-- Вертикальная ручка для изменения ширины бокового меню мышью/указателем. -->
|
||||
@ -82,10 +91,7 @@
|
||||
-->
|
||||
<div class="control-panel" aria-label="Настройки панели" data-settings-panel>
|
||||
<div class="settings-panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">View settings</p>
|
||||
<h2>Настройки</h2>
|
||||
</div>
|
||||
<p class="eyebrow settings-panel-eyebrow">Настройки</p>
|
||||
<button class="settings-close" type="button" aria-label="Закрыть настройки" data-settings-close>
|
||||
×
|
||||
</button>
|
||||
@ -106,8 +112,109 @@
|
||||
<p class="settings-section-hint">
|
||||
Режим «Сверху» — компактная шапка с названием и поиском, без дерева.
|
||||
</p>
|
||||
<label class="toggle-row" data-feature="allowShowTreeChange" data-show-when-sidebar>
|
||||
<span>
|
||||
<strong>Показывать дерево</strong>
|
||||
<small>Только для панели «Сбоку»</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree checked />
|
||||
</label>
|
||||
<label class="toggle-row" data-feature="allowSidebarResize">
|
||||
<span>
|
||||
<strong>Ручки изменения размера</strong>
|
||||
<small>Ширина меню или высота шапки</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-resize-handles checked />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<details class="settings-section settings-section-collapsible" data-settings-section="dimensions">
|
||||
<summary class="settings-section-summary">
|
||||
<span class="settings-section-title">Габариты</span>
|
||||
<span class="collapse-indicator" aria-hidden="true">⌄</span>
|
||||
</summary>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-section-hint">
|
||||
Ограничения ширины страницы, бокового меню и верхней шапки. Значения применяются сразу;
|
||||
текущий размер меню и шапки можно также менять перетаскиванием ручек на странице.
|
||||
Допустимые диапазоны ползунков задаёт администратор в <code>settings.json</code> →
|
||||
<code>limits</code>.
|
||||
</p>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина всей панели (.shell): насколько широко вместе могут располагаться меню и каталог."
|
||||
>
|
||||
<span>Ширина контента (max)</span>
|
||||
<input type="range" data-layout-limit="shellMaxWidth" />
|
||||
<output data-layout-limit-value="shellMaxWidth">2800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная ширина бокового меню при перетаскивании вертикальной ручки между меню и каталогом."
|
||||
>
|
||||
<span>Меню: мин. ширина</span>
|
||||
<input type="range" data-layout-limit="sidebarMinWidth" />
|
||||
<output data-layout-limit-value="sidebarMinWidth">260</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина меню, когда элементы дерева в папках показываются в одну колонку."
|
||||
>
|
||||
<span>Меню: макс. (1 кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidth" />
|
||||
<output data-layout-limit-value="sidebarMaxWidth">800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Верхний предел ширины меню при нескольких колонках в дереве. Фактический максимум также зависит от базы и шага колонки."
|
||||
>
|
||||
<span>Меню: макс. (неск. кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidthWide" />
|
||||
<output data-layout-limit-value="sidebarMaxWidthWide">1400</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="База формулы ширины меню при нескольких колонках: min(макс. неск. кол., база + колонки × шаг)."
|
||||
>
|
||||
<span>Дерево: база ширины</span>
|
||||
<input type="range" data-layout-limit="treeSidebarBaseWidth" />
|
||||
<output data-layout-limit-value="treeSidebarBaseWidth">420</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Сколько пикселей добавляется к базовой ширине меню за каждую колонку элементов в папках дерева."
|
||||
>
|
||||
<span>Дерево: ширина колонки</span>
|
||||
<input type="range" data-layout-limit="treeSidebarColumnWidth" />
|
||||
<output data-layout-limit-value="treeSidebarColumnWidth">120</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная высота верхней шапки в режиме «Сверху» — нижняя граница при перетаскивании горизонтальной ручки."
|
||||
>
|
||||
<span>Шапка: мин. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMinHeight" />
|
||||
<output data-layout-limit-value="topBarMinHeight">80</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная высота верхней шапки в режиме «Сверху». Также ограничена примерно 75% высоты окна браузера."
|
||||
>
|
||||
<span>Шапка: макс. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMaxHeight" />
|
||||
<output data-layout-limit-value="topBarMaxHeight">560</output>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="settings-section" data-settings-section="catalog">
|
||||
<p class="settings-section-title">Каталог</p>
|
||||
|
||||
@ -128,6 +235,34 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="catalog-columns-group" data-feature="allowCatalogColumnsChange">
|
||||
<p class="settings-section-hint">Колонок в строке для каждого вида каталога.</p>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCardsColumns">3</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Список</span>
|
||||
<input type="range" data-catalog-columns="catalogListColumns" />
|
||||
<output data-catalog-columns-value="catalogListColumns">1</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactCardsColumns">5</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт список</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactListColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactListColumns">3</output>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Показывать номера разделов, подразделов и элементов. -->
|
||||
<label class="toggle-row" data-feature="allowShowNumbers">
|
||||
<span>
|
||||
@ -141,25 +276,38 @@
|
||||
<section class="settings-section" data-settings-section="tree">
|
||||
<p class="settings-section-title">Дерево</p>
|
||||
|
||||
<!-- Показывать поисковые теги рядом с файлами в дереве навигации. -->
|
||||
<label class="accent-custom-row">
|
||||
<span>Колонок</span>
|
||||
<input type="range" min="1" max="6" step="1" value="2" data-tree-columns />
|
||||
<output data-tree-columns-value>2</output>
|
||||
</label>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Показывать теги дерева</strong>
|
||||
<small>Теги рядом с элементом, без 2 колонок</small>
|
||||
<strong>Показывать теги</strong>
|
||||
<small>Оформление как на карточках</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree-tags />
|
||||
</label>
|
||||
|
||||
<!-- Элементы внутри папок дерева отображаются в две колонки без тегов. -->
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>2 колонки в дереве</strong>
|
||||
<small>Компактный вид элементов папок</small>
|
||||
<strong>Показывать тип</strong>
|
||||
<small>Подпись типа сервиса</small>
|
||||
</span>
|
||||
<input type="checkbox" data-tree-two-column />
|
||||
<input type="checkbox" data-show-tree-type />
|
||||
</label>
|
||||
|
||||
<!-- Управляет тем, раскрывает ли поиск только ветки с совпадениями. -->
|
||||
<div class="control-group">
|
||||
<span>Теги и тип</span>
|
||||
<button class="segmented is-active" type="button" data-tree-meta-layout="inline">
|
||||
Сбоку
|
||||
</button>
|
||||
<button class="segmented" type="button" data-tree-meta-layout="stacked">
|
||||
Снизу
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Авто раскрывать поиск</strong>
|
||||
|
||||
@ -15,14 +15,38 @@
|
||||
"tags": ["статус", "overview"],
|
||||
"url": "https://status.example.com"
|
||||
},
|
||||
{
|
||||
"id": "social",
|
||||
"number": "1.0",
|
||||
"title": "Соцсеть",
|
||||
"type": "social",
|
||||
"tags": ["соцсеть", "лента"],
|
||||
"url": "https://social.example.com"
|
||||
},
|
||||
{
|
||||
"id": "chat",
|
||||
"number": "1.0",
|
||||
"title": "Беседы",
|
||||
"type": "chat",
|
||||
"tags": ["беседа", "чат"],
|
||||
"url": "https://chat.example.com"
|
||||
},
|
||||
{
|
||||
"id": "docs",
|
||||
"number": "1.0",
|
||||
"title": "Документация",
|
||||
"type": "config",
|
||||
"tags": ["docs", "wiki"],
|
||||
"title": "Wiki",
|
||||
"type": "wiki",
|
||||
"tags": ["wiki", "docs"],
|
||||
"url": "https://docs.example.com"
|
||||
},
|
||||
{
|
||||
"id": "calendar",
|
||||
"number": "1.0",
|
||||
"title": "Календарь",
|
||||
"type": "calendar",
|
||||
"tags": ["календарь", "встречи"],
|
||||
"url": "https://calendar.example.com"
|
||||
},
|
||||
{
|
||||
"id": "support",
|
||||
"number": "1.0",
|
||||
@ -56,8 +80,16 @@
|
||||
"url": "https://prometheus.example.com"
|
||||
},
|
||||
{
|
||||
"id": "logs",
|
||||
"id": "analytics",
|
||||
"number": "1.1.3",
|
||||
"title": "Аналитика",
|
||||
"type": "analytics",
|
||||
"tags": ["аналитика", "отчёты"],
|
||||
"url": "https://analytics.example.com"
|
||||
},
|
||||
{
|
||||
"id": "logs",
|
||||
"number": "1.1.4",
|
||||
"title": "Журнал событий",
|
||||
"type": "opensearch",
|
||||
"tags": ["логи"],
|
||||
@ -66,14 +98,54 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "data",
|
||||
"id": "communications",
|
||||
"number": "1.2",
|
||||
"title": "Данные и очереди",
|
||||
"description": "Базы данных и брокеры сообщений.",
|
||||
"title": "Коммуникации",
|
||||
"description": "Форум, видео и почта.",
|
||||
"items": [
|
||||
{
|
||||
"id": "forum",
|
||||
"number": "1.2.1",
|
||||
"title": "Форум",
|
||||
"type": "forum",
|
||||
"tags": ["форум", "обсуждения"],
|
||||
"url": "https://forum.example.com"
|
||||
},
|
||||
{
|
||||
"id": "video",
|
||||
"number": "1.2.2",
|
||||
"title": "Видеозвонки",
|
||||
"type": "video",
|
||||
"tags": ["видео", "meet"],
|
||||
"url": "https://meet.example.com"
|
||||
},
|
||||
{
|
||||
"id": "mail",
|
||||
"number": "1.2.3",
|
||||
"title": "Почта",
|
||||
"type": "messages",
|
||||
"tags": ["почта"],
|
||||
"url": "https://mail.example.com"
|
||||
},
|
||||
{
|
||||
"id": "xmpp",
|
||||
"number": "1.2.4",
|
||||
"title": "XMPP",
|
||||
"type": "xmpp",
|
||||
"tags": ["xmpp", "чат"],
|
||||
"url": "https://xmpp.example.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "data",
|
||||
"number": "1.3",
|
||||
"title": "Данные и интеграции",
|
||||
"description": "Базы, очереди, API и секреты.",
|
||||
"items": [
|
||||
{
|
||||
"id": "postgres",
|
||||
"number": "1.2.1",
|
||||
"number": "1.3.1",
|
||||
"title": "PostgreSQL",
|
||||
"type": "postgresql",
|
||||
"tags": ["postgresql", "sql"],
|
||||
@ -81,11 +153,43 @@
|
||||
},
|
||||
{
|
||||
"id": "mq",
|
||||
"number": "1.2.2",
|
||||
"number": "1.3.2",
|
||||
"title": "RabbitMQ",
|
||||
"type": "rabbitmq",
|
||||
"tags": ["очереди"],
|
||||
"url": "https://rabbitmq.example.com"
|
||||
},
|
||||
{
|
||||
"id": "drive",
|
||||
"number": "1.3.3",
|
||||
"title": "Корп. диск",
|
||||
"type": "drive",
|
||||
"tags": ["файлы", "диск"],
|
||||
"url": "https://drive.example.com"
|
||||
},
|
||||
{
|
||||
"id": "api",
|
||||
"number": "1.3.4",
|
||||
"title": "API Gateway",
|
||||
"type": "api",
|
||||
"tags": ["api", "gateway"],
|
||||
"url": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"id": "vault",
|
||||
"number": "1.3.5",
|
||||
"title": "Vault",
|
||||
"type": "vault",
|
||||
"tags": ["секреты", "vault"],
|
||||
"url": "https://vault.example.com"
|
||||
},
|
||||
{
|
||||
"id": "vpn",
|
||||
"number": "1.3.6",
|
||||
"title": "VPN",
|
||||
"type": "vpn",
|
||||
"tags": ["vpn", "доступ"],
|
||||
"url": "https://vpn.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,23 +3,60 @@
|
||||
"data": null,
|
||||
"defaults": {
|
||||
"navLayout": "sidebar",
|
||||
"showTree": true,
|
||||
"showResizeHandles": true,
|
||||
"theme": "dark",
|
||||
"view": "cards",
|
||||
"accent": "violet",
|
||||
"customAccentFrom": "#8b5cf6",
|
||||
"customAccentTo": "#22d3ee",
|
||||
"customAccentAngle": 135,
|
||||
"showNumbers": false,
|
||||
"showTreeTags": true,
|
||||
"treeTwoColumn": false,
|
||||
"showTreeType": false,
|
||||
"treeMetaLayout": "inline",
|
||||
"treeColumns": 2,
|
||||
"autoCollapseSearch": true,
|
||||
"sidebarWidth": 390,
|
||||
"topBarHeight": 140,
|
||||
"shellMaxWidth": 2800,
|
||||
"sidebarMinWidth": 260,
|
||||
"sidebarMaxWidth": 800,
|
||||
"sidebarMaxWidthWide": 1400,
|
||||
"treeSidebarBaseWidth": 420,
|
||||
"treeSidebarColumnWidth": 120,
|
||||
"topBarMinHeight": 80,
|
||||
"topBarMaxHeight": 560,
|
||||
"catalogCardsColumns": 3,
|
||||
"catalogListColumns": 1,
|
||||
"catalogCompactCardsColumns": 5,
|
||||
"catalogCompactListColumns": 3,
|
||||
"searchPlaceholder": "Grafana, S3, журнал, сервер..."
|
||||
},
|
||||
"limits": {
|
||||
"shellMaxWidth": { "min": 1200, "max": 3840 },
|
||||
"sidebarMinWidth": { "min": 200, "max": 480 },
|
||||
"sidebarMaxWidth": { "min": 400, "max": 1200 },
|
||||
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
|
||||
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
|
||||
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
|
||||
"topBarMinHeight": { "min": 64, "max": 240 },
|
||||
"topBarMaxHeight": { "min": 160, "max": 800 },
|
||||
"catalogCardsColumns": { "min": 1, "max": 6 },
|
||||
"catalogListColumns": { "min": 1, "max": 4 },
|
||||
"catalogCompactCardsColumns": { "min": 2, "max": 8 },
|
||||
"catalogCompactListColumns": { "min": 1, "max": 6 }
|
||||
},
|
||||
"features": {
|
||||
"allowNavLayoutChange": true,
|
||||
"allowShowTreeChange": true,
|
||||
"allowSearch": true,
|
||||
"allowViewChange": true,
|
||||
"allowCatalogColumnsChange": true,
|
||||
"allowShowNumbers": true,
|
||||
"allowTreeOptions": true,
|
||||
"allowSidebarResize": true,
|
||||
"allowLayoutLimitsChange": true,
|
||||
"allowThemeChange": true,
|
||||
"allowAccentChange": true
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Демо панели: раздел с элементами без подразделов (section → item)."
|
||||
content="Навигационная панель экосистемы с разделами, подразделами, поиском и быстрыми переходами."
|
||||
/>
|
||||
<title>Пример: section → item</title>
|
||||
<title>Панель экосистемы</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
@ -42,7 +42,7 @@
|
||||
⚙
|
||||
</button>
|
||||
|
||||
<p class="eyebrow" id="page-title">Пример: section → item</p>
|
||||
<p class="eyebrow" id="page-title">Панель экосистемы</p>
|
||||
|
||||
<!-- Поиск фильтрует каталог и подсвечивает совпадения в дереве и карточках. -->
|
||||
<label class="search-field" data-search-field>
|
||||
@ -57,6 +57,15 @@
|
||||
<!-- Дерево генерируется из ecosystemSections в script.js. -->
|
||||
<nav class="nav-tree" aria-label="Дерево навигации" data-nav-tree></nav>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="top-bar-resizer"
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label="Изменить высоту верхней панели"
|
||||
data-top-bar-resizer
|
||||
hidden
|
||||
></div>
|
||||
</aside>
|
||||
|
||||
<!-- Вертикальная ручка для изменения ширины бокового меню мышью/указателем. -->
|
||||
@ -82,10 +91,7 @@
|
||||
-->
|
||||
<div class="control-panel" aria-label="Настройки панели" data-settings-panel>
|
||||
<div class="settings-panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">View settings</p>
|
||||
<h2>Настройки</h2>
|
||||
</div>
|
||||
<p class="eyebrow settings-panel-eyebrow">Настройки</p>
|
||||
<button class="settings-close" type="button" aria-label="Закрыть настройки" data-settings-close>
|
||||
×
|
||||
</button>
|
||||
@ -106,8 +112,109 @@
|
||||
<p class="settings-section-hint">
|
||||
Режим «Сверху» — компактная шапка с названием и поиском, без дерева.
|
||||
</p>
|
||||
<label class="toggle-row" data-feature="allowShowTreeChange" data-show-when-sidebar>
|
||||
<span>
|
||||
<strong>Показывать дерево</strong>
|
||||
<small>Только для панели «Сбоку»</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree checked />
|
||||
</label>
|
||||
<label class="toggle-row" data-feature="allowSidebarResize">
|
||||
<span>
|
||||
<strong>Ручки изменения размера</strong>
|
||||
<small>Ширина меню или высота шапки</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-resize-handles checked />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<details class="settings-section settings-section-collapsible" data-settings-section="dimensions">
|
||||
<summary class="settings-section-summary">
|
||||
<span class="settings-section-title">Габариты</span>
|
||||
<span class="collapse-indicator" aria-hidden="true">⌄</span>
|
||||
</summary>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-section-hint">
|
||||
Ограничения ширины страницы, бокового меню и верхней шапки. Значения применяются сразу;
|
||||
текущий размер меню и шапки можно также менять перетаскиванием ручек на странице.
|
||||
Допустимые диапазоны ползунков задаёт администратор в <code>settings.json</code> →
|
||||
<code>limits</code>.
|
||||
</p>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина всей панели (.shell): насколько широко вместе могут располагаться меню и каталог."
|
||||
>
|
||||
<span>Ширина контента (max)</span>
|
||||
<input type="range" data-layout-limit="shellMaxWidth" />
|
||||
<output data-layout-limit-value="shellMaxWidth">2800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная ширина бокового меню при перетаскивании вертикальной ручки между меню и каталогом."
|
||||
>
|
||||
<span>Меню: мин. ширина</span>
|
||||
<input type="range" data-layout-limit="sidebarMinWidth" />
|
||||
<output data-layout-limit-value="sidebarMinWidth">260</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина меню, когда элементы дерева в папках показываются в одну колонку."
|
||||
>
|
||||
<span>Меню: макс. (1 кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidth" />
|
||||
<output data-layout-limit-value="sidebarMaxWidth">800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Верхний предел ширины меню при нескольких колонках в дереве. Фактический максимум также зависит от базы и шага колонки."
|
||||
>
|
||||
<span>Меню: макс. (неск. кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidthWide" />
|
||||
<output data-layout-limit-value="sidebarMaxWidthWide">1400</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="База формулы ширины меню при нескольких колонках: min(макс. неск. кол., база + колонки × шаг)."
|
||||
>
|
||||
<span>Дерево: база ширины</span>
|
||||
<input type="range" data-layout-limit="treeSidebarBaseWidth" />
|
||||
<output data-layout-limit-value="treeSidebarBaseWidth">420</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Сколько пикселей добавляется к базовой ширине меню за каждую колонку элементов в папках дерева."
|
||||
>
|
||||
<span>Дерево: ширина колонки</span>
|
||||
<input type="range" data-layout-limit="treeSidebarColumnWidth" />
|
||||
<output data-layout-limit-value="treeSidebarColumnWidth">120</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная высота верхней шапки в режиме «Сверху» — нижняя граница при перетаскивании горизонтальной ручки."
|
||||
>
|
||||
<span>Шапка: мин. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMinHeight" />
|
||||
<output data-layout-limit-value="topBarMinHeight">80</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная высота верхней шапки в режиме «Сверху». Также ограничена примерно 75% высоты окна браузера."
|
||||
>
|
||||
<span>Шапка: макс. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMaxHeight" />
|
||||
<output data-layout-limit-value="topBarMaxHeight">560</output>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="settings-section" data-settings-section="catalog">
|
||||
<p class="settings-section-title">Каталог</p>
|
||||
|
||||
@ -128,6 +235,34 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="catalog-columns-group" data-feature="allowCatalogColumnsChange">
|
||||
<p class="settings-section-hint">Колонок в строке для каждого вида каталога.</p>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCardsColumns">3</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Список</span>
|
||||
<input type="range" data-catalog-columns="catalogListColumns" />
|
||||
<output data-catalog-columns-value="catalogListColumns">1</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactCardsColumns">5</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт список</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactListColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactListColumns">3</output>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Показывать номера разделов, подразделов и элементов. -->
|
||||
<label class="toggle-row" data-feature="allowShowNumbers">
|
||||
<span>
|
||||
@ -141,25 +276,38 @@
|
||||
<section class="settings-section" data-settings-section="tree">
|
||||
<p class="settings-section-title">Дерево</p>
|
||||
|
||||
<!-- Показывать поисковые теги рядом с файлами в дереве навигации. -->
|
||||
<label class="accent-custom-row">
|
||||
<span>Колонок</span>
|
||||
<input type="range" min="1" max="6" step="1" value="2" data-tree-columns />
|
||||
<output data-tree-columns-value>2</output>
|
||||
</label>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Показывать теги дерева</strong>
|
||||
<small>Теги рядом с элементом, без 2 колонок</small>
|
||||
<strong>Показывать теги</strong>
|
||||
<small>Оформление как на карточках</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree-tags />
|
||||
</label>
|
||||
|
||||
<!-- Элементы внутри папок дерева отображаются в две колонки без тегов. -->
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>2 колонки в дереве</strong>
|
||||
<small>Компактный вид элементов папок</small>
|
||||
<strong>Показывать тип</strong>
|
||||
<small>Подпись типа сервиса</small>
|
||||
</span>
|
||||
<input type="checkbox" data-tree-two-column />
|
||||
<input type="checkbox" data-show-tree-type />
|
||||
</label>
|
||||
|
||||
<!-- Управляет тем, раскрывает ли поиск только ветки с совпадениями. -->
|
||||
<div class="control-group">
|
||||
<span>Теги и тип</span>
|
||||
<button class="segmented is-active" type="button" data-tree-meta-layout="inline">
|
||||
Сбоку
|
||||
</button>
|
||||
<button class="segmented" type="button" data-tree-meta-layout="stacked">
|
||||
Снизу
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Авто раскрывать поиск</strong>
|
||||
|
||||
@ -8,24 +8,56 @@
|
||||
"description": "Часто используемые сервисы без промежуточных подразделов.",
|
||||
"items": [
|
||||
{
|
||||
"id": "mail",
|
||||
"id": "social",
|
||||
"number": "1.1",
|
||||
"title": "Корпоративная соцсеть",
|
||||
"type": "social",
|
||||
"tags": ["соцсеть", "лента", "новости"],
|
||||
"url": "https://social.example.com"
|
||||
},
|
||||
{
|
||||
"id": "chat",
|
||||
"number": "1.2",
|
||||
"title": "Командные беседы",
|
||||
"type": "chat",
|
||||
"tags": ["беседа", "чат", "команда"],
|
||||
"url": "https://chat.example.com"
|
||||
},
|
||||
{
|
||||
"id": "mail",
|
||||
"number": "1.3",
|
||||
"title": "Почта",
|
||||
"type": "messages",
|
||||
"tags": ["почта", "mail"],
|
||||
"url": "https://mail.example.com"
|
||||
},
|
||||
{
|
||||
"id": "chat",
|
||||
"number": "1.2",
|
||||
"title": "Корпоративный чат",
|
||||
"id": "xmpp",
|
||||
"number": "1.4",
|
||||
"title": "Корпоративный XMPP",
|
||||
"type": "xmpp",
|
||||
"tags": ["xmpp", "чат"],
|
||||
"url": "https://chat.example.com"
|
||||
},
|
||||
{
|
||||
"id": "video",
|
||||
"number": "1.5",
|
||||
"title": "Видеоконференции",
|
||||
"type": "video",
|
||||
"tags": ["видео", "meet", "звонки"],
|
||||
"url": "https://meet.example.com"
|
||||
},
|
||||
{
|
||||
"id": "calendar",
|
||||
"number": "1.6",
|
||||
"title": "Календарь",
|
||||
"type": "calendar",
|
||||
"tags": ["календарь", "встречи"],
|
||||
"url": "https://calendar.example.com"
|
||||
},
|
||||
{
|
||||
"id": "tasks",
|
||||
"number": "1.3",
|
||||
"number": "1.7",
|
||||
"title": "Задачи",
|
||||
"type": "redmine",
|
||||
"tags": ["redmine", "задачи"],
|
||||
@ -34,22 +66,78 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dev",
|
||||
"id": "knowledge",
|
||||
"number": "2",
|
||||
"title": "Знания и обсуждения",
|
||||
"description": "Wiki, форум и аналитика.",
|
||||
"items": [
|
||||
{
|
||||
"id": "wiki",
|
||||
"number": "2.1",
|
||||
"title": "Wiki — база знаний",
|
||||
"type": "wiki",
|
||||
"tags": ["wiki", "документация"],
|
||||
"url": "https://wiki.example.com"
|
||||
},
|
||||
{
|
||||
"id": "forum",
|
||||
"number": "2.2",
|
||||
"title": "Форум сотрудников",
|
||||
"type": "forum",
|
||||
"tags": ["форум", "обсуждения", "q&a"],
|
||||
"url": "https://forum.example.com"
|
||||
},
|
||||
{
|
||||
"id": "analytics",
|
||||
"number": "2.3",
|
||||
"title": "Портал аналитики",
|
||||
"type": "analytics",
|
||||
"tags": ["аналитика", "отчёты"],
|
||||
"url": "https://analytics.example.com"
|
||||
},
|
||||
{
|
||||
"id": "drive",
|
||||
"number": "2.4",
|
||||
"title": "Общий диск",
|
||||
"type": "drive",
|
||||
"tags": ["файлы", "диск", "обмен"],
|
||||
"url": "https://drive.example.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dev",
|
||||
"number": "3",
|
||||
"title": "Разработка",
|
||||
"description": "Репозитории, CI и конфигурации.",
|
||||
"items": [
|
||||
{
|
||||
"id": "gitea",
|
||||
"number": "2.1",
|
||||
"number": "3.1",
|
||||
"title": "Gitea",
|
||||
"type": "gitea",
|
||||
"tags": ["git", "repo", "gitea"],
|
||||
"url": "https://gitea.example.com"
|
||||
},
|
||||
{
|
||||
"id": "cicd",
|
||||
"number": "3.2",
|
||||
"title": "CI/CD — пайплайны",
|
||||
"type": "cicd",
|
||||
"tags": ["ci", "cd", "deploy"],
|
||||
"url": "https://ci.example.com"
|
||||
},
|
||||
{
|
||||
"id": "api",
|
||||
"number": "3.3",
|
||||
"title": "API Gateway",
|
||||
"type": "api",
|
||||
"tags": ["api", "gateway", "интеграции"],
|
||||
"url": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"id": "config",
|
||||
"number": "2.2",
|
||||
"number": "3.4",
|
||||
"title": "Конфигурации",
|
||||
"type": "config",
|
||||
"tags": ["config", "настройки"],
|
||||
@ -57,31 +145,55 @@
|
||||
},
|
||||
{
|
||||
"id": "xpath-debug",
|
||||
"number": "2.3",
|
||||
"number": "3.5",
|
||||
"title": "Отладка xPath",
|
||||
"type": "xpath",
|
||||
"tags": ["xpath", "отладка"],
|
||||
"url": "#"
|
||||
},
|
||||
{
|
||||
"id": "vault",
|
||||
"number": "3.6",
|
||||
"title": "Vault — секреты",
|
||||
"type": "vault",
|
||||
"tags": ["vault", "секреты"],
|
||||
"url": "https://vault.example.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "admin",
|
||||
"number": "3",
|
||||
"number": "4",
|
||||
"title": "Администрирование",
|
||||
"description": "Управление пользователями и железом.",
|
||||
"description": "Управление пользователями, доступом и железом.",
|
||||
"items": [
|
||||
{
|
||||
"id": "ldap",
|
||||
"number": "3.1",
|
||||
"number": "4.1",
|
||||
"title": "LDAP Account Manager",
|
||||
"type": "ldap",
|
||||
"tags": ["ldap", "пользователи"],
|
||||
"url": "https://ldap.example.com"
|
||||
},
|
||||
{
|
||||
"id": "vpn",
|
||||
"number": "4.2",
|
||||
"title": "VPN-портал",
|
||||
"type": "vpn",
|
||||
"tags": ["vpn", "удалёнка"],
|
||||
"url": "https://vpn.example.com"
|
||||
},
|
||||
{
|
||||
"id": "grafana",
|
||||
"number": "4.3",
|
||||
"title": "Grafana — мониторинг",
|
||||
"type": "grafana",
|
||||
"tags": ["grafana", "метрики"],
|
||||
"url": "https://grafana.example.com"
|
||||
},
|
||||
{
|
||||
"id": "server-01",
|
||||
"number": "3.2",
|
||||
"number": "4.4",
|
||||
"title": "Сервер 01 — IPMI",
|
||||
"type": "ipmi",
|
||||
"tags": ["ipmi", "сервер", "железо"],
|
||||
@ -89,7 +201,7 @@
|
||||
},
|
||||
{
|
||||
"id": "server-02",
|
||||
"number": "3.3",
|
||||
"number": "4.5",
|
||||
"title": "Сервер 02 — IPMI",
|
||||
"type": "ipmi",
|
||||
"tags": ["ipmi", "сервер"],
|
||||
|
||||
@ -3,23 +3,60 @@
|
||||
"data": null,
|
||||
"defaults": {
|
||||
"navLayout": "sidebar",
|
||||
"showTree": true,
|
||||
"showResizeHandles": true,
|
||||
"theme": "dark",
|
||||
"view": "cards",
|
||||
"accent": "violet",
|
||||
"customAccentFrom": "#8b5cf6",
|
||||
"customAccentTo": "#22d3ee",
|
||||
"customAccentAngle": 135,
|
||||
"showNumbers": false,
|
||||
"showTreeTags": true,
|
||||
"treeTwoColumn": false,
|
||||
"showTreeType": false,
|
||||
"treeMetaLayout": "inline",
|
||||
"treeColumns": 2,
|
||||
"autoCollapseSearch": true,
|
||||
"sidebarWidth": 390,
|
||||
"topBarHeight": 140,
|
||||
"shellMaxWidth": 2800,
|
||||
"sidebarMinWidth": 260,
|
||||
"sidebarMaxWidth": 800,
|
||||
"sidebarMaxWidthWide": 1400,
|
||||
"treeSidebarBaseWidth": 420,
|
||||
"treeSidebarColumnWidth": 120,
|
||||
"topBarMinHeight": 80,
|
||||
"topBarMaxHeight": 560,
|
||||
"catalogCardsColumns": 3,
|
||||
"catalogListColumns": 1,
|
||||
"catalogCompactCardsColumns": 5,
|
||||
"catalogCompactListColumns": 3,
|
||||
"searchPlaceholder": "Grafana, S3, журнал, сервер..."
|
||||
},
|
||||
"limits": {
|
||||
"shellMaxWidth": { "min": 1200, "max": 3840 },
|
||||
"sidebarMinWidth": { "min": 200, "max": 480 },
|
||||
"sidebarMaxWidth": { "min": 400, "max": 1200 },
|
||||
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
|
||||
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
|
||||
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
|
||||
"topBarMinHeight": { "min": 64, "max": 240 },
|
||||
"topBarMaxHeight": { "min": 160, "max": 800 },
|
||||
"catalogCardsColumns": { "min": 1, "max": 6 },
|
||||
"catalogListColumns": { "min": 1, "max": 4 },
|
||||
"catalogCompactCardsColumns": { "min": 2, "max": 8 },
|
||||
"catalogCompactListColumns": { "min": 1, "max": 6 }
|
||||
},
|
||||
"features": {
|
||||
"allowNavLayoutChange": true,
|
||||
"allowShowTreeChange": true,
|
||||
"allowSearch": true,
|
||||
"allowViewChange": true,
|
||||
"allowCatalogColumnsChange": true,
|
||||
"allowShowNumbers": true,
|
||||
"allowTreeOptions": true,
|
||||
"allowSidebarResize": true,
|
||||
"allowLayoutLimitsChange": true,
|
||||
"allowThemeChange": true,
|
||||
"allowAccentChange": true
|
||||
}
|
||||
|
||||
1
examples/shared/assets/analytics.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-analytics)"/><rect x="40" y="92" width="18" height="32" rx="6" fill="white"/><rect x="71" y="72" width="18" height="52" rx="6" fill="white" fill-opacity=".9"/><rect x="102" y="52" width="18" height="72" rx="6" fill="white" fill-opacity=".78"/><path d="M44 60l28 18 28-28 16 12" stroke="white" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><defs><linearGradient id="g-analytics" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#6EE7B7"/><stop offset="1" stop-color="#059669"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 680 B |
1
examples/shared/assets/api.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-api)"/><path d="M52 52h-8v56h8M108 52h8v56h-8M68 80h24" stroke="white" stroke-width="10" stroke-linecap="round"/><circle cx="80" cy="80" r="28" stroke="white" stroke-width="8" fill="none"/><defs><linearGradient id="g-api" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#FDE68A"/><stop offset="1" stop-color="#D97706"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
1
examples/shared/assets/calendar.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-calendar)"/><rect x="36" y="48" width="88" height="76" rx="14" fill="white"/><path d="M52 40v16M108 40v16" stroke="white" stroke-width="8" stroke-linecap="round"/><path d="M36 68h88" stroke="#4F46E5" stroke-width="6"/><rect x="50" y="82" width="14" height="14" rx="4" fill="#4F46E5"/><rect x="73" y="82" width="14" height="14" rx="4" fill="#4F46E5" fill-opacity=".7"/><rect x="96" y="82" width="14" height="14" rx="4" fill="#4F46E5" fill-opacity=".45"/><rect x="50" y="102" width="14" height="14" rx="4" fill="#4F46E5" fill-opacity=".7"/><rect x="73" y="102" width="14" height="14" rx="4" fill="#4F46E5"/><defs><linearGradient id="g-calendar" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#A5B4FC"/><stop offset="1" stop-color="#4F46E5"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 926 B |
1
examples/shared/assets/chat.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-chat)"/><path d="M34 52h52c6 0 11 5 11 11v28c0 6-5 11-11 11H62L42 118V91H34c-6 0-11-5-11-11V63c0-6 5-11 11-11z" fill="white"/><path d="M74 58h52c6 0 11 5 11 11v28c0 6-5 11-11 11h-8l-20 27V98H74c-6 0-11-5-11-11V69c0-6 5-11 11-11z" fill="white" fill-opacity=".78"/><circle cx="52" cy="74" r="4" fill="#7C3AED"/><circle cx="68" cy="74" r="4" fill="#7C3AED"/><circle cx="84" cy="74" r="4" fill="#7C3AED"/><defs><linearGradient id="g-chat" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#C4B5FD"/><stop offset="1" stop-color="#7C3AED"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 718 B |
1
examples/shared/assets/cicd.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-cicd)"/><path d="M80 44a36 36 0 1 1-25 59" stroke="white" stroke-width="10" stroke-linecap="round" fill="none"/><path d="M55 44h25v25" stroke="white" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/><circle cx="80" cy="80" r="10" fill="#1D4ED8"/><path d="M96 96l18 18" stroke="white" stroke-width="8" stroke-linecap="round"/><defs><linearGradient id="g-cicd" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#93C5FD"/><stop offset="1" stop-color="#1D4ED8"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 659 B |
1
examples/shared/assets/drive.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-drive)"/><path d="M44 98l18-36h36l18 36H44z" fill="white"/><path d="M62 62l14-18h18l14 18" stroke="white" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><rect x="48" y="104" width="64" height="14" rx="7" fill="white" fill-opacity=".85"/><defs><linearGradient id="g-drive" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#93C5FD"/><stop offset="1" stop-color="#2563EB"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 573 B |
1
examples/shared/assets/forum.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-forum)"/><rect x="36" y="44" width="88" height="18" rx="9" fill="white"/><rect x="36" y="72" width="72" height="18" rx="9" fill="white" fill-opacity=".88"/><rect x="36" y="100" width="80" height="18" rx="9" fill="white" fill-opacity=".76"/><circle cx="124" cy="53" r="8" fill="#EA580C"/><circle cx="116" cy="81" r="8" fill="#EA580C" fill-opacity=".85"/><circle cx="122" cy="109" r="8" fill="#EA580C" fill-opacity=".7"/><defs><linearGradient id="g-forum" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#FDBA74"/><stop offset="1" stop-color="#EA580C"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 737 B |
1
examples/shared/assets/social.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-social)"/><circle cx="56" cy="68" r="16" fill="white"/><circle cx="104" cy="68" r="16" fill="white"/><circle cx="80" cy="108" r="18" fill="white" fill-opacity=".92"/><path d="M68 118c6 8 18 12 28 12s22-4 28-12" stroke="white" stroke-width="6" stroke-linecap="round"/><defs><linearGradient id="g-social" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#F9A8D4"/><stop offset="1" stop-color="#DB2777"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 586 B |
1
examples/shared/assets/vault.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-vault)"/><rect x="38" y="48" width="84" height="72" rx="16" fill="white"/><circle cx="80" cy="84" r="18" stroke="#6D28D9" stroke-width="8" fill="none"/><circle cx="80" cy="84" r="6" fill="#6D28D9"/><rect x="74" y="84" width="12" height="18" rx="2" fill="#6D28D9"/><defs><linearGradient id="g-vault" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#DDD6FE"/><stop offset="1" stop-color="#6D28D9"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 582 B |
1
examples/shared/assets/video.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-video)"/><rect x="34" y="50" width="92" height="60" rx="14" fill="white"/><path d="M88 68l28-16v56l-28-16V68z" fill="#DC2626"/><circle cx="58" cy="118" r="10" fill="white"/><circle cx="102" cy="118" r="10" fill="white" fill-opacity=".85"/><defs><linearGradient id="g-video" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#FCA5A5"/><stop offset="1" stop-color="#DC2626"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 557 B |
1
examples/shared/assets/vpn.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-vpn)"/><path d="M80 38l44 24v36c0 24-20 44-44 44S36 122 36 98V62l44-24z" fill="white"/><path d="M72 92c0-4 4-8 8-8s8 4 8 8v8H72v-8z" fill="#0F766E"/><rect x="68" y="76" width="24" height="18" rx="8" stroke="#0F766E" stroke-width="6"/><defs><linearGradient id="g-vpn" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#5EEAD4"/><stop offset="1" stop-color="#0F766E"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 550 B |
1
examples/shared/assets/wiki.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="160" height="160" rx="44" fill="url(#g-wiki)"/><path d="M48 44h64l8 92H40l8-92z" fill="white"/><path d="M56 56h48M56 76h40M56 96h44M56 116h32" stroke="#0369A1" stroke-width="6" stroke-linecap="round"/><path d="M80 44v16" stroke="white" stroke-width="8" stroke-linecap="round"/><defs><linearGradient id="g-wiki" x1="18" y1="14" x2="140" y2="150"><stop stop-color="#7DD3FC"/><stop offset="1" stop-color="#0369A1"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 555 B |
@ -32,9 +32,16 @@ let ecosystemSections = [];
|
||||
let panelTitle = "Панель экосистемы";
|
||||
|
||||
const defaultServiceMeta = {
|
||||
analytics: { label: "Аналитика", icon: "assets/analytics.svg" },
|
||||
api: { label: "API", icon: "assets/api.svg" },
|
||||
calendar: { label: "Календарь", icon: "assets/calendar.svg" },
|
||||
ceph: { label: "Ceph", icon: "assets/ceph.svg" },
|
||||
chat: { label: "Беседа", icon: "assets/chat.svg" },
|
||||
cicd: { label: "CI/CD", icon: "assets/cicd.svg" },
|
||||
cloudbeaver: { label: "CloudBeaver", icon: "assets/cloudbeaver.svg" },
|
||||
config: { label: "Config", icon: "assets/config.svg" },
|
||||
drive: { label: "Диск", icon: "assets/drive.svg" },
|
||||
forum: { label: "Форум", icon: "assets/forum.svg" },
|
||||
gitea: { label: "Gitea", icon: "assets/gitea.svg" },
|
||||
grafana: { label: "Grafana", icon: "assets/grafana.svg" },
|
||||
ipmi: { label: "IPMI", icon: "assets/server.svg" },
|
||||
@ -47,6 +54,11 @@ const defaultServiceMeta = {
|
||||
rabbitmq: { label: "RabbitMQ", icon: "assets/rabbitmq.svg" },
|
||||
redmine: { label: "Redmine", icon: "assets/redmine.svg" },
|
||||
rules: { label: "Rules", icon: "assets/rules.svg" },
|
||||
social: { label: "Соцсеть", icon: "assets/social.svg" },
|
||||
vault: { label: "Vault", icon: "assets/vault.svg" },
|
||||
video: { label: "Видео", icon: "assets/video.svg" },
|
||||
vpn: { label: "VPN", icon: "assets/vpn.svg" },
|
||||
wiki: { label: "Wiki", icon: "assets/wiki.svg" },
|
||||
xpath: { label: "xPath", icon: "assets/xpath.svg" },
|
||||
xmpp: { label: "XMPP", icon: "assets/xmpp.svg" },
|
||||
};
|
||||
@ -65,26 +77,84 @@ const defaultPreferences = {
|
||||
customAccentTo: "#22d3ee",
|
||||
customAccentAngle: 135,
|
||||
navLayout: "sidebar",
|
||||
showTree: true,
|
||||
showResizeHandles: true,
|
||||
showNumbers: false,
|
||||
showTreeTags: true,
|
||||
treeTwoColumn: false,
|
||||
showTreeType: false,
|
||||
treeMetaLayout: "inline",
|
||||
treeColumns: 2,
|
||||
autoCollapseSearch: true,
|
||||
sidebarWidth: 390,
|
||||
topBarHeight: 140,
|
||||
shellMaxWidth: 2800,
|
||||
sidebarMinWidth: 260,
|
||||
sidebarMaxWidth: 800,
|
||||
sidebarMaxWidthWide: 1400,
|
||||
treeSidebarBaseWidth: 420,
|
||||
treeSidebarColumnWidth: 120,
|
||||
topBarMinHeight: 80,
|
||||
topBarMaxHeight: 560,
|
||||
catalogCardsColumns: 3,
|
||||
catalogListColumns: 1,
|
||||
catalogCompactCardsColumns: 5,
|
||||
catalogCompactListColumns: 3,
|
||||
searchPlaceholder: "Grafana, S3, журнал, сервер...",
|
||||
};
|
||||
|
||||
const defaultPanelFeatures = {
|
||||
allowNavLayoutChange: true,
|
||||
allowShowTreeChange: true,
|
||||
allowSearch: true,
|
||||
allowViewChange: true,
|
||||
allowCatalogColumnsChange: true,
|
||||
allowShowNumbers: true,
|
||||
allowTreeOptions: true,
|
||||
allowSidebarResize: true,
|
||||
allowLayoutLimitsChange: true,
|
||||
allowThemeChange: true,
|
||||
allowAccentChange: true,
|
||||
};
|
||||
|
||||
const defaultPanelLimits = {
|
||||
shellMaxWidth: { min: 1200, max: 3840 },
|
||||
sidebarMinWidth: { min: 200, max: 480 },
|
||||
sidebarMaxWidth: { min: 400, max: 1200 },
|
||||
sidebarMaxWidthWide: { min: 560, max: 2000 },
|
||||
treeSidebarBaseWidth: { min: 280, max: 720 },
|
||||
treeSidebarColumnWidth: { min: 60, max: 200 },
|
||||
topBarMinHeight: { min: 64, max: 240 },
|
||||
topBarMaxHeight: { min: 160, max: 800 },
|
||||
catalogCardsColumns: { min: 1, max: 6 },
|
||||
catalogListColumns: { min: 1, max: 4 },
|
||||
catalogCompactCardsColumns: { min: 2, max: 8 },
|
||||
catalogCompactListColumns: { min: 1, max: 6 },
|
||||
};
|
||||
|
||||
const layoutLimitKeys = [
|
||||
"shellMaxWidth",
|
||||
"sidebarMinWidth",
|
||||
"sidebarMaxWidth",
|
||||
"sidebarMaxWidthWide",
|
||||
"treeSidebarBaseWidth",
|
||||
"treeSidebarColumnWidth",
|
||||
"topBarMinHeight",
|
||||
"topBarMaxHeight",
|
||||
];
|
||||
|
||||
const catalogColumnKeys = [
|
||||
"catalogCardsColumns",
|
||||
"catalogListColumns",
|
||||
"catalogCompactCardsColumns",
|
||||
"catalogCompactListColumns",
|
||||
];
|
||||
|
||||
const panelLimitKeys = [...layoutLimitKeys, ...catalogColumnKeys];
|
||||
|
||||
const availableNavLayouts = ["sidebar", "top"];
|
||||
const availableTreeMetaLayouts = ["inline", "stacked"];
|
||||
const MIN_TREE_COLUMNS = 1;
|
||||
const MAX_TREE_COLUMNS = 6;
|
||||
const availableViews = ["cards", "list", "compact-cards", "compact-list"];
|
||||
const availableAccents = ["violet", "cyan", "lime", "amber", "custom"];
|
||||
|
||||
@ -94,6 +164,7 @@ const availableAccents = ["violet", "cyan", "lime", "amber", "custom"];
|
||||
// preferences - текущие настройки интерфейса; итоговые значения собираются в bootstrap().
|
||||
let panelDefaults = { ...defaultPreferences };
|
||||
let panelFeatures = { ...defaultPanelFeatures };
|
||||
let panelLimits = structuredClone(defaultPanelLimits);
|
||||
let preferences = { ...defaultPreferences };
|
||||
|
||||
// collapseState - объект вида { [collapseKey]: boolean } для details.
|
||||
@ -104,6 +175,7 @@ const appLayout = document.querySelector(".app-layout");
|
||||
const catalog = document.querySelector("[data-catalog-root]");
|
||||
const navTree = document.querySelector("[data-nav-tree]");
|
||||
const sidebarResizer = document.querySelector("[data-sidebar-resizer]");
|
||||
const topBarResizer = document.querySelector("[data-top-bar-resizer]");
|
||||
const searchField = document.querySelector("[data-search-field]");
|
||||
const searchInput = document.querySelector("[data-search-input]");
|
||||
const settingsToggle = document.querySelector("[data-settings-toggle]");
|
||||
@ -120,10 +192,17 @@ const customAccentToInput = document.querySelector("[data-custom-accent-to]");
|
||||
const customAccentAngleInput = document.querySelector("[data-custom-accent-angle]");
|
||||
const customAccentAngleValue = document.querySelector("[data-custom-accent-angle-value]");
|
||||
const customAccentPreview = document.querySelector("[data-custom-accent-preview]");
|
||||
const showTreeInput = document.querySelector("[data-show-tree]");
|
||||
const showResizeHandlesInput = document.querySelector("[data-show-resize-handles]");
|
||||
const showNumbersInput = document.querySelector("[data-show-numbers]");
|
||||
const showTreeTagsInput = document.querySelector("[data-show-tree-tags]");
|
||||
const treeTwoColumnInput = document.querySelector("[data-tree-two-column]");
|
||||
const showTreeTypeInput = document.querySelector("[data-show-tree-type]");
|
||||
const treeColumnsInput = document.querySelector("[data-tree-columns]");
|
||||
const treeColumnsValue = document.querySelector("[data-tree-columns-value]");
|
||||
const treeMetaLayoutButtons = document.querySelectorAll("[data-tree-meta-layout]");
|
||||
const autoCollapseSearchInput = document.querySelector("[data-auto-collapse-search]");
|
||||
const layoutLimitInputs = document.querySelectorAll("[data-layout-limit]");
|
||||
const catalogColumnInputs = document.querySelectorAll("[data-catalog-columns]");
|
||||
const settingsSections = document.querySelectorAll("[data-settings-section]");
|
||||
const emptyTemplate = document.querySelector("#empty-template");
|
||||
const configErrorTemplate = document.querySelector("#config-error-template");
|
||||
@ -236,14 +315,53 @@ navLayoutButtons.forEach((button) => {
|
||||
preferences.navLayout = button.dataset.navLayout;
|
||||
applyPreferences(preferences);
|
||||
|
||||
if (preferences.navLayout === "sidebar") {
|
||||
if (isTreeVisible(preferences)) {
|
||||
renderNavigationTree();
|
||||
} else {
|
||||
navTree.replaceChildren();
|
||||
}
|
||||
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
function setShowTree(nextValue, { persist = true } = {}) {
|
||||
if (!panelFeatures.allowShowTreeChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.showTree = Boolean(nextValue);
|
||||
applyPreferences(preferences);
|
||||
|
||||
if (preferences.showTree) {
|
||||
renderNavigationTree();
|
||||
} else {
|
||||
navTree.replaceChildren();
|
||||
}
|
||||
|
||||
if (persist) {
|
||||
savePreferences(preferences);
|
||||
}
|
||||
}
|
||||
|
||||
if (showTreeInput) {
|
||||
showTreeInput.addEventListener("change", () => {
|
||||
setShowTree(showTreeInput.checked);
|
||||
});
|
||||
}
|
||||
|
||||
if (showResizeHandlesInput) {
|
||||
showResizeHandlesInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowSidebarResize) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.showResizeHandles = showResizeHandlesInput.checked;
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
}
|
||||
|
||||
// Переключение режима отображения каталога.
|
||||
viewButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
@ -300,41 +418,62 @@ showNumbersInput.addEventListener("change", () => {
|
||||
});
|
||||
|
||||
// Показывает/скрывает поисковые теги в файловом дереве.
|
||||
// Взаимоисключимо с двухколоночным режимом элементов папок.
|
||||
showTreeTagsInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || preferences.navLayout === "top") {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.showTreeTags = showTreeTagsInput.checked;
|
||||
if (preferences.showTreeTags) {
|
||||
preferences.treeTwoColumn = false;
|
||||
}
|
||||
applyPreferences(preferences);
|
||||
renderNavigationTree();
|
||||
savePreferences(preferences);
|
||||
});
|
||||
|
||||
// Компактный двухколоночный режим элементов внутри папок дерева.
|
||||
// Взаимоисключимо с отображением тегов.
|
||||
treeTwoColumnInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || preferences.navLayout === "top") {
|
||||
showTreeTypeInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.treeTwoColumn = treeTwoColumnInput.checked;
|
||||
if (preferences.treeTwoColumn) {
|
||||
preferences.showTreeTags = false;
|
||||
if (preferences.sidebarWidth < 480) {
|
||||
preferences.sidebarWidth = 480;
|
||||
}
|
||||
preferences.showTreeType = showTreeTypeInput.checked;
|
||||
applyPreferences(preferences);
|
||||
renderNavigationTree();
|
||||
savePreferences(preferences);
|
||||
});
|
||||
|
||||
function handleTreeColumnsChange() {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.treeColumns = getTreeColumns({ treeColumns: treeColumnsInput.value });
|
||||
|
||||
if (preferences.treeColumns > 1 && preferences.sidebarWidth < getSidebarMinWidth(preferences) + preferences.treeColumns * 70) {
|
||||
preferences.sidebarWidth = getSidebarMinWidth(preferences) + preferences.treeColumns * 70;
|
||||
}
|
||||
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
}
|
||||
|
||||
if (treeColumnsInput) {
|
||||
treeColumnsInput.addEventListener("input", handleTreeColumnsChange);
|
||||
}
|
||||
|
||||
treeMetaLayoutButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.treeMetaLayout = button.dataset.treeMetaLayout;
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
// Включает/выключает авто-раскрытие веток дерева при поиске.
|
||||
autoCollapseSearchInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || preferences.navLayout === "top") {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -344,10 +483,38 @@ autoCollapseSearchInput.addEventListener("change", () => {
|
||||
savePreferences(preferences);
|
||||
});
|
||||
|
||||
layoutLimitInputs.forEach((input) => {
|
||||
input.addEventListener("input", () => {
|
||||
if (!panelFeatures.allowLayoutLimitsChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = input.dataset.layoutLimit;
|
||||
preferences[key] = clampLayoutPreference(key, input.value);
|
||||
preferences = normalizePreferences(preferences);
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
catalogColumnInputs.forEach((input) => {
|
||||
input.addEventListener("input", () => {
|
||||
if (!panelFeatures.allowCatalogColumnsChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = input.dataset.catalogColumns;
|
||||
preferences[key] = clampCatalogColumn(key, input.value);
|
||||
preferences = normalizePreferences(preferences);
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
// Drag-resize бокового меню.
|
||||
// Ширина сохраняется в preferences.sidebarWidth и применяется через CSS-переменную.
|
||||
sidebarResizer.addEventListener("pointerdown", (event) => {
|
||||
if (preferences.navLayout === "top" || !panelFeatures.allowSidebarResize) {
|
||||
if (preferences.navLayout === "top" || !isResizeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -358,7 +525,7 @@ sidebarResizer.addEventListener("pointerdown", (event) => {
|
||||
const handlePointerMove = (moveEvent) => {
|
||||
const layoutLeft = document.querySelector(".app-layout").getBoundingClientRect().left;
|
||||
preferences.sidebarWidth = Math.round(
|
||||
clamp(moveEvent.clientX - layoutLeft, 260, getSidebarMaxWidth(preferences)),
|
||||
clamp(moveEvent.clientX - layoutLeft, getSidebarMinWidth(preferences), getSidebarMaxWidth(preferences)),
|
||||
);
|
||||
applyPreferences(preferences);
|
||||
};
|
||||
@ -376,6 +543,38 @@ sidebarResizer.addEventListener("pointerdown", (event) => {
|
||||
sidebarResizer.addEventListener("pointercancel", handlePointerUp);
|
||||
});
|
||||
|
||||
// Drag-resize верхней панели в режиме navLayout: top.
|
||||
topBarResizer?.addEventListener("pointerdown", (event) => {
|
||||
if (preferences.navLayout !== "top" || !isResizeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
document.body.classList.add("is-resizing-top-bar");
|
||||
topBarResizer.setPointerCapture(event.pointerId);
|
||||
|
||||
const panelTop = document.querySelector(".side-panel")?.getBoundingClientRect().top ?? 0;
|
||||
|
||||
const handlePointerMove = (moveEvent) => {
|
||||
preferences.topBarHeight = Math.round(
|
||||
clamp(moveEvent.clientY - panelTop, getTopBarMinHeight(preferences), getTopBarMaxHeight(preferences)),
|
||||
);
|
||||
applyPreferences(preferences);
|
||||
};
|
||||
|
||||
const handlePointerUp = () => {
|
||||
document.body.classList.remove("is-resizing-top-bar");
|
||||
savePreferences(preferences);
|
||||
topBarResizer.removeEventListener("pointermove", handlePointerMove);
|
||||
topBarResizer.removeEventListener("pointerup", handlePointerUp);
|
||||
topBarResizer.removeEventListener("pointercancel", handlePointerUp);
|
||||
};
|
||||
|
||||
topBarResizer.addEventListener("pointermove", handlePointerMove);
|
||||
topBarResizer.addEventListener("pointerup", handlePointerUp);
|
||||
topBarResizer.addEventListener("pointercancel", handlePointerUp);
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Panel config loading
|
||||
// ============================================================================
|
||||
@ -395,6 +594,7 @@ async function loadPanelDataFromSettings(settings) {
|
||||
}
|
||||
|
||||
function applyOrganizationConfig(settings) {
|
||||
panelLimits = normalizePanelLimits(settings?.limits);
|
||||
panelDefaults = normalizePreferences({
|
||||
...defaultPreferences,
|
||||
...(settings?.defaults && typeof settings.defaults === "object" ? settings.defaults : {}),
|
||||
@ -438,29 +638,60 @@ function normalizePreferences(raw) {
|
||||
360,
|
||||
);
|
||||
next.showNumbers = Boolean(next.showNumbers);
|
||||
next.showTree = Boolean(next.showTree);
|
||||
next.showResizeHandles = next.showResizeHandles !== false;
|
||||
next.showTreeTags = Boolean(next.showTreeTags);
|
||||
next.treeTwoColumn = Boolean(next.treeTwoColumn);
|
||||
next.showTreeType = Boolean(next.showTreeType);
|
||||
next.treeColumns = getTreeColumns(next);
|
||||
next.treeMetaLayout = availableTreeMetaLayouts.includes(next.treeMetaLayout)
|
||||
? next.treeMetaLayout
|
||||
: defaultPreferences.treeMetaLayout;
|
||||
next.autoCollapseSearch = Boolean(next.autoCollapseSearch);
|
||||
normalizeLayoutPreferences(next);
|
||||
normalizeCatalogColumns(next);
|
||||
next.sidebarWidth = clamp(
|
||||
Number(next.sidebarWidth) || defaultPreferences.sidebarWidth,
|
||||
260,
|
||||
getSidebarMinWidth(next),
|
||||
getSidebarMaxWidth(next),
|
||||
);
|
||||
|
||||
if (next.treeTwoColumn) {
|
||||
next.showTreeTags = false;
|
||||
} else if (next.showTreeTags) {
|
||||
next.treeTwoColumn = false;
|
||||
}
|
||||
|
||||
if (next.navLayout === "top") {
|
||||
next.treeTwoColumn = false;
|
||||
next.showTreeTags = false;
|
||||
}
|
||||
next.topBarHeight = clamp(
|
||||
Number(next.topBarHeight) || defaultPreferences.topBarHeight,
|
||||
getTopBarMinHeight(next),
|
||||
getTopBarMaxHeight(next),
|
||||
);
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function normalizeLayoutPreferences(next) {
|
||||
for (const key of layoutLimitKeys) {
|
||||
next[key] = clampLayoutPreference(key, next[key]);
|
||||
}
|
||||
|
||||
if (next.sidebarMinWidth > next.sidebarMaxWidth) {
|
||||
next.sidebarMinWidth = next.sidebarMaxWidth;
|
||||
}
|
||||
|
||||
if (next.sidebarMaxWidth > next.sidebarMaxWidthWide) {
|
||||
next.sidebarMaxWidth = next.sidebarMaxWidthWide;
|
||||
}
|
||||
|
||||
if (next.topBarMinHeight > next.topBarMaxHeight) {
|
||||
next.topBarMinHeight = next.topBarMaxHeight;
|
||||
}
|
||||
|
||||
const minShellWidth = getSidebarMaxWidth(next) + 320;
|
||||
if (next.shellMaxWidth < minShellWidth) {
|
||||
next.shellMaxWidth = clampLayoutPreference("shellMaxWidth", minShellWidth);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeCatalogColumns(next) {
|
||||
for (const key of catalogColumnKeys) {
|
||||
next[key] = clampCatalogColumn(key, next[key]);
|
||||
}
|
||||
}
|
||||
|
||||
function enforceFeatureLocks(rawPreferences) {
|
||||
const next = normalizePreferences(rawPreferences);
|
||||
|
||||
@ -468,6 +699,10 @@ function enforceFeatureLocks(rawPreferences) {
|
||||
next.navLayout = panelDefaults.navLayout;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowShowTreeChange) {
|
||||
next.showTree = panelDefaults.showTree;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowThemeChange) {
|
||||
next.theme = panelDefaults.theme;
|
||||
}
|
||||
@ -476,6 +711,12 @@ function enforceFeatureLocks(rawPreferences) {
|
||||
next.view = panelDefaults.view;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowCatalogColumnsChange) {
|
||||
for (const key of catalogColumnKeys) {
|
||||
next[key] = panelDefaults[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowAccentChange) {
|
||||
next.accent = panelDefaults.accent;
|
||||
next.customAccentFrom = panelDefaults.customAccentFrom;
|
||||
@ -487,16 +728,33 @@ function enforceFeatureLocks(rawPreferences) {
|
||||
next.showNumbers = panelDefaults.showNumbers;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowTreeOptions || next.navLayout === "top") {
|
||||
next.showTreeTags = false;
|
||||
next.treeTwoColumn = false;
|
||||
if (!panelFeatures.allowTreeOptions || !next.showTree) {
|
||||
next.showTreeTags = panelDefaults.showTreeTags;
|
||||
next.showTreeType = panelDefaults.showTreeType;
|
||||
next.treeColumns = panelDefaults.treeColumns;
|
||||
next.treeMetaLayout = panelDefaults.treeMetaLayout;
|
||||
next.autoCollapseSearch = panelDefaults.autoCollapseSearch;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowSidebarResize) {
|
||||
next.showResizeHandles = panelDefaults.showResizeHandles;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowSidebarResize || next.navLayout === "top") {
|
||||
next.sidebarWidth = panelDefaults.sidebarWidth;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowSidebarResize || next.navLayout !== "top") {
|
||||
next.topBarHeight = panelDefaults.topBarHeight;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowLayoutLimitsChange) {
|
||||
for (const key of layoutLimitKeys) {
|
||||
next[key] = panelDefaults[key];
|
||||
}
|
||||
normalizeLayoutPreferences(next);
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
@ -893,7 +1151,7 @@ function matchesText(values, query) {
|
||||
// Строит левое файловое дерево из той же структуры данных, что и каталог.
|
||||
// Это важно: дерево и карточки всегда остаются синхронизированными.
|
||||
function renderNavigationTree() {
|
||||
if (preferences.navLayout === "top") {
|
||||
if (!isTreeVisible(preferences)) {
|
||||
navTree.replaceChildren();
|
||||
return;
|
||||
}
|
||||
@ -964,22 +1222,39 @@ function createTreeItemList(items, query = "", className = "tree-items") {
|
||||
itemList.className = className;
|
||||
|
||||
items.forEach((item) => {
|
||||
const meta = getItemMeta(item);
|
||||
const itemButton = document.createElement("button");
|
||||
itemButton.type = "button";
|
||||
itemButton.className = "tree-node tree-file";
|
||||
itemButton.dataset.focusTarget = getItemId(item);
|
||||
itemButton.innerHTML = `
|
||||
<img class="tree-node-icon" src="${resolveAssetPath(meta.icon)}" alt="" loading="lazy" />
|
||||
<span>${highlightText(item.title, query)}</span>
|
||||
<small>${highlightText(item.tags.join(", "), query)}</small>
|
||||
`;
|
||||
itemList.append(itemButton);
|
||||
itemList.append(createTreeItemButton(item, query));
|
||||
});
|
||||
|
||||
return itemList;
|
||||
}
|
||||
|
||||
function createTreeItemButton(item, query = "") {
|
||||
const meta = getItemMeta(item);
|
||||
const itemButton = document.createElement("button");
|
||||
itemButton.type = "button";
|
||||
itemButton.className = "tree-node tree-file";
|
||||
itemButton.dataset.focusTarget = getItemId(item);
|
||||
itemButton.innerHTML = `
|
||||
<img class="tree-node-icon" src="${resolveAssetPath(meta.icon)}" alt="" loading="lazy" />
|
||||
<div class="tree-file-body">
|
||||
<span class="tree-file-title">${highlightText(item.title, query)}</span>
|
||||
<div class="tree-file-meta">
|
||||
<span class="tree-file-type">${highlightText(meta.label, query)}</span>
|
||||
<div class="tree-tags tags">${renderTreeTagSpans(item.tags, query)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return itemButton;
|
||||
}
|
||||
|
||||
function renderTreeTagSpans(tags, query = "") {
|
||||
if (!Array.isArray(tags) || !tags.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return tags.map((tag) => `<span>${highlightText(tag, query)}</span>`).join("");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Focusing from tree to content
|
||||
// ============================================================================
|
||||
@ -1097,31 +1372,84 @@ function applyPreferences(nextPreferences) {
|
||||
document.documentElement.dataset.accent = activePreferences.accent;
|
||||
document.documentElement.dataset.navLayout =
|
||||
activePreferences.navLayout === "top" ? "top" : "sidebar";
|
||||
document.documentElement.dataset.showTree = String(isTreeVisible(activePreferences));
|
||||
document.documentElement.dataset.showResizeHandles = String(isResizeVisible(activePreferences));
|
||||
applyAccentVariables(activePreferences);
|
||||
document.documentElement.style.setProperty(
|
||||
"--sidebar-width",
|
||||
`${clamp(
|
||||
Number(activePreferences.sidebarWidth) || defaultPreferences.sidebarWidth,
|
||||
260,
|
||||
getSidebarMinWidth(activePreferences),
|
||||
getSidebarMaxWidth(activePreferences),
|
||||
)}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--top-bar-height",
|
||||
`${clamp(
|
||||
Number(activePreferences.topBarHeight) || defaultPreferences.topBarHeight,
|
||||
getTopBarMinHeight(activePreferences),
|
||||
getTopBarMaxHeight(activePreferences),
|
||||
)}px`,
|
||||
);
|
||||
const treeColumns = getTreeColumns(activePreferences);
|
||||
const sidebarMaxWidth = getSidebarMaxWidth(activePreferences);
|
||||
|
||||
document.documentElement.style.setProperty(
|
||||
"--shell-max-width",
|
||||
activePreferences.treeTwoColumn ? "1920px" : "1560px",
|
||||
`${getShellMaxWidth(activePreferences)}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty("--sidebar-max-width", `${sidebarMaxWidth}px`);
|
||||
document.documentElement.style.setProperty(
|
||||
"--tree-sidebar-base-width",
|
||||
`${activePreferences.treeSidebarBaseWidth}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--sidebar-max-width",
|
||||
`${getSidebarMaxWidth(activePreferences)}px`,
|
||||
"--tree-sidebar-column-width",
|
||||
`${activePreferences.treeSidebarColumnWidth}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-cards-columns",
|
||||
String(activePreferences.catalogCardsColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-list-columns",
|
||||
String(activePreferences.catalogListColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-compact-cards-columns",
|
||||
String(activePreferences.catalogCompactCardsColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-compact-list-columns",
|
||||
String(activePreferences.catalogCompactListColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty("--tree-columns", String(treeColumns));
|
||||
catalog.classList.remove("cards-view", "list-view", "compact-cards-view", "compact-list-view");
|
||||
catalog.classList.add(`${activePreferences.view}-view`);
|
||||
document.documentElement.dataset.showNumbers = String(Boolean(activePreferences.showNumbers));
|
||||
document.documentElement.dataset.showTreeTags = String(Boolean(activePreferences.showTreeTags));
|
||||
document.documentElement.dataset.treeTwoColumn = String(Boolean(activePreferences.treeTwoColumn));
|
||||
document.documentElement.dataset.showTreeType = String(Boolean(activePreferences.showTreeType));
|
||||
document.documentElement.dataset.treeMetaLayout = activePreferences.treeMetaLayout;
|
||||
document.documentElement.dataset.treeColumns = String(treeColumns);
|
||||
if (showTreeInput) {
|
||||
showTreeInput.checked = Boolean(activePreferences.showTree);
|
||||
}
|
||||
|
||||
if (showResizeHandlesInput) {
|
||||
showResizeHandlesInput.checked = Boolean(activePreferences.showResizeHandles);
|
||||
}
|
||||
|
||||
showNumbersInput.checked = Boolean(activePreferences.showNumbers);
|
||||
showTreeTagsInput.checked = Boolean(activePreferences.showTreeTags);
|
||||
treeTwoColumnInput.checked = Boolean(activePreferences.treeTwoColumn);
|
||||
if (showTreeTypeInput) {
|
||||
showTreeTypeInput.checked = Boolean(activePreferences.showTreeType);
|
||||
}
|
||||
if (treeColumnsInput) {
|
||||
treeColumnsInput.value = String(treeColumns);
|
||||
}
|
||||
if (treeColumnsValue) {
|
||||
treeColumnsValue.textContent = String(treeColumns);
|
||||
}
|
||||
autoCollapseSearchInput.checked = Boolean(activePreferences.autoCollapseSearch);
|
||||
customAccentFromInput.value = activePreferences.customAccentFrom;
|
||||
customAccentToInput.value = activePreferences.customAccentTo;
|
||||
@ -1133,6 +1461,9 @@ function applyPreferences(nextPreferences) {
|
||||
setActiveButton(navLayoutButtons, "navLayout", activePreferences.navLayout);
|
||||
setActiveButton(viewButtons, "view", activePreferences.view);
|
||||
setActiveButton(accentButtons, "accent", activePreferences.accent);
|
||||
setActiveButton(treeMetaLayoutButtons, "treeMetaLayout", activePreferences.treeMetaLayout);
|
||||
syncLayoutLimitInputs(activePreferences);
|
||||
syncCatalogColumnInputs(activePreferences);
|
||||
applyPanelFeatures(activePreferences);
|
||||
}
|
||||
|
||||
@ -1147,12 +1478,17 @@ function applyPanelFeatures(activePreferences) {
|
||||
}
|
||||
|
||||
if (navTree) {
|
||||
navTree.hidden = activePreferences.navLayout === "top";
|
||||
navTree.hidden = !isTreeVisible(activePreferences);
|
||||
}
|
||||
|
||||
if (sidebarResizer) {
|
||||
sidebarResizer.hidden =
|
||||
activePreferences.navLayout === "top" || !panelFeatures.allowSidebarResize;
|
||||
activePreferences.navLayout === "top" || !isResizeVisible(activePreferences);
|
||||
}
|
||||
|
||||
if (topBarResizer) {
|
||||
topBarResizer.hidden =
|
||||
activePreferences.navLayout !== "top" || !isResizeVisible(activePreferences);
|
||||
}
|
||||
|
||||
if (appLayout) {
|
||||
@ -1163,16 +1499,30 @@ function applyPanelFeatures(activePreferences) {
|
||||
const featureName = element.dataset.feature;
|
||||
element.hidden = featureName ? !panelFeatures[featureName] : false;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-show-when-sidebar]").forEach((element) => {
|
||||
element.hidden = activePreferences.navLayout === "top";
|
||||
});
|
||||
}
|
||||
|
||||
function isSettingsSectionVisible(sectionName, activePreferences) {
|
||||
switch (sectionName) {
|
||||
case "layout":
|
||||
return panelFeatures.allowNavLayoutChange;
|
||||
return (
|
||||
panelFeatures.allowNavLayoutChange ||
|
||||
panelFeatures.allowShowTreeChange ||
|
||||
panelFeatures.allowSidebarResize
|
||||
);
|
||||
case "dimensions":
|
||||
return panelFeatures.allowLayoutLimitsChange;
|
||||
case "catalog":
|
||||
return panelFeatures.allowViewChange || panelFeatures.allowShowNumbers;
|
||||
return (
|
||||
panelFeatures.allowViewChange ||
|
||||
panelFeatures.allowShowNumbers ||
|
||||
panelFeatures.allowCatalogColumnsChange
|
||||
);
|
||||
case "tree":
|
||||
return panelFeatures.allowTreeOptions && activePreferences.navLayout !== "top";
|
||||
return panelFeatures.allowTreeOptions && activePreferences.showTree;
|
||||
case "appearance":
|
||||
return panelFeatures.allowThemeChange || panelFeatures.allowAccentChange;
|
||||
default:
|
||||
@ -1180,6 +1530,14 @@ function isSettingsSectionVisible(sectionName, activePreferences) {
|
||||
}
|
||||
}
|
||||
|
||||
function isTreeVisible(activePreferences) {
|
||||
return Boolean(activePreferences.showTree);
|
||||
}
|
||||
|
||||
function isResizeVisible(activePreferences) {
|
||||
return panelFeatures.allowSidebarResize && Boolean(activePreferences.showResizeHandles);
|
||||
}
|
||||
|
||||
// Применяет CSS-переменные акцента для пресетов или пользовательского градиента.
|
||||
function applyAccentVariables(nextPreferences) {
|
||||
const root = document.documentElement;
|
||||
@ -1331,9 +1689,162 @@ function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
// Максимальная ширина sidebar зависит от режима дерева.
|
||||
function getTreeColumns(nextPreferences) {
|
||||
let columns = Number(nextPreferences.treeColumns);
|
||||
|
||||
if (!Number.isFinite(columns)) {
|
||||
columns = nextPreferences.treeTwoColumn ? 2 : defaultPreferences.treeColumns;
|
||||
}
|
||||
|
||||
return clamp(Math.round(columns), MIN_TREE_COLUMNS, MAX_TREE_COLUMNS);
|
||||
}
|
||||
|
||||
function getLayoutLimitRange(key) {
|
||||
const limits = panelLimits[key] ?? defaultPanelLimits[key];
|
||||
|
||||
if (!limits || !Number.isFinite(limits.min) || !Number.isFinite(limits.max)) {
|
||||
return defaultPanelLimits[key];
|
||||
}
|
||||
|
||||
return {
|
||||
min: Math.min(limits.min, limits.max),
|
||||
max: Math.max(limits.min, limits.max),
|
||||
};
|
||||
}
|
||||
|
||||
function clampLayoutPreference(key, value) {
|
||||
const limits = getLayoutLimitRange(key);
|
||||
const fallback = panelDefaults[key] ?? defaultPreferences[key];
|
||||
const numeric = Number(value);
|
||||
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return clamp(Math.round(numeric), limits.min, limits.max);
|
||||
}
|
||||
|
||||
function normalizePanelLimits(rawLimits) {
|
||||
const next = structuredClone(defaultPanelLimits);
|
||||
|
||||
if (!rawLimits || typeof rawLimits !== "object") {
|
||||
return next;
|
||||
}
|
||||
|
||||
for (const key of panelLimitKeys) {
|
||||
const source = rawLimits[key];
|
||||
|
||||
if (!source || typeof source !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const min = Math.round(Number(source.min));
|
||||
const max = Math.round(Number(source.max));
|
||||
|
||||
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
next[key] = {
|
||||
min: Math.min(min, max),
|
||||
max: Math.max(min, max),
|
||||
};
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function syncLayoutLimitInputs(activePreferences) {
|
||||
layoutLimitInputs.forEach((input) => {
|
||||
const key = input.dataset.layoutLimit;
|
||||
|
||||
if (!layoutLimitKeys.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const limits = getPanelLimitRange(key);
|
||||
input.min = String(limits.min);
|
||||
input.max = String(limits.max);
|
||||
input.step = key === "treeSidebarColumnWidth" ? "5" : "10";
|
||||
input.value = String(activePreferences[key]);
|
||||
input.disabled = !panelFeatures.allowLayoutLimitsChange;
|
||||
|
||||
const output = document.querySelector(`[data-layout-limit-value="${key}"]`);
|
||||
if (output) {
|
||||
output.textContent = String(activePreferences[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function syncCatalogColumnInputs(activePreferences) {
|
||||
catalogColumnInputs.forEach((input) => {
|
||||
const key = input.dataset.catalogColumns;
|
||||
|
||||
if (!catalogColumnKeys.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const limits = getPanelLimitRange(key);
|
||||
input.min = String(limits.min);
|
||||
input.max = String(limits.max);
|
||||
input.step = "1";
|
||||
input.value = String(activePreferences[key]);
|
||||
input.disabled = !panelFeatures.allowCatalogColumnsChange;
|
||||
|
||||
const output = document.querySelector(`[data-catalog-columns-value="${key}"]`);
|
||||
if (output) {
|
||||
output.textContent = String(activePreferences[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getPanelLimitRange(key) {
|
||||
return getLayoutLimitRange(key);
|
||||
}
|
||||
|
||||
function clampCatalogColumn(key, value) {
|
||||
const limits = getPanelLimitRange(key);
|
||||
const fallback = panelDefaults[key] ?? defaultPreferences[key];
|
||||
const numeric = Number(value);
|
||||
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return clamp(Math.round(numeric), limits.min, limits.max);
|
||||
}
|
||||
|
||||
function getSidebarMinWidth(nextPreferences) {
|
||||
return Number(nextPreferences.sidebarMinWidth);
|
||||
}
|
||||
|
||||
function getSidebarMaxWidth(nextPreferences) {
|
||||
return nextPreferences.treeTwoColumn ? 760 : 560;
|
||||
const columns = getTreeColumns(nextPreferences);
|
||||
|
||||
if (columns <= 1) {
|
||||
return Number(nextPreferences.sidebarMaxWidth);
|
||||
}
|
||||
|
||||
return Math.min(
|
||||
Number(nextPreferences.sidebarMaxWidthWide),
|
||||
Number(nextPreferences.treeSidebarBaseWidth) +
|
||||
columns * Number(nextPreferences.treeSidebarColumnWidth),
|
||||
);
|
||||
}
|
||||
|
||||
function getShellMaxWidth(nextPreferences) {
|
||||
return Number(nextPreferences.shellMaxWidth);
|
||||
}
|
||||
|
||||
function getTopBarMinHeight(nextPreferences) {
|
||||
return Number(nextPreferences.topBarMinHeight);
|
||||
}
|
||||
|
||||
function getTopBarMaxHeight(nextPreferences) {
|
||||
return Math.min(
|
||||
Number(nextPreferences.topBarMaxHeight),
|
||||
Math.round(window.innerHeight * 0.75),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@ -24,8 +24,13 @@
|
||||
--card-x: 50%;
|
||||
--card-y: 50%;
|
||||
--sidebar-width: 390px;
|
||||
--shell-max-width: 1560px;
|
||||
--sidebar-max-width: 560px;
|
||||
--top-bar-height: 140px;
|
||||
--shell-max-width: 1920px;
|
||||
--sidebar-max-width: 800px;
|
||||
--catalog-cards-columns: 3;
|
||||
--catalog-list-columns: 1;
|
||||
--catalog-compact-cards-columns: 5;
|
||||
--catalog-compact-list-columns: 3;
|
||||
}
|
||||
|
||||
/* Светлая тема переопределяет только токены, структура стилей остается общей. */
|
||||
@ -138,11 +143,15 @@ a {
|
||||
/* Левая колонка получает ширину из --sidebar-width, которую меняет JS. */
|
||||
.app-layout {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 8px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
grid-template-columns: var(--sidebar-width) 6px minmax(0, 1fr);
|
||||
gap: 4px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
html[data-nav-layout="sidebar"][data-show-resize-handles="false"] .app-layout {
|
||||
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
/* Компактная шапка: название + поиск, без дерева и resizer. */
|
||||
html[data-nav-layout="top"] .app-layout,
|
||||
.app-layout[data-nav-layout="top"] {
|
||||
@ -155,17 +164,22 @@ html[data-nav-layout="top"] .sidebar-resizer {
|
||||
|
||||
html[data-nav-layout="top"] .side-panel {
|
||||
position: static;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .side-panel .hero-content {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 12px 16px;
|
||||
max-height: none;
|
||||
min-height: auto;
|
||||
overflow: visible;
|
||||
height: var(--top-bar-height);
|
||||
min-height: var(--top-bar-height);
|
||||
max-height: var(--top-bar-height);
|
||||
overflow: auto;
|
||||
padding: 14px 58px 14px 18px;
|
||||
}
|
||||
|
||||
@ -173,6 +187,13 @@ html[data-nav-layout="top"] .side-panel .hero-content > .eyebrow {
|
||||
margin: 0;
|
||||
flex: 0 1 auto;
|
||||
max-width: min(100%, 360px);
|
||||
align-self: center;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.side-panel .hero-content > .eyebrow#page-title {
|
||||
padding-right: 46px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .search-field {
|
||||
@ -181,22 +202,31 @@ html[data-nav-layout="top"] .search-field {
|
||||
max-width: min(100%, 560px);
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .nav-tree {
|
||||
html[data-show-tree="false"] .nav-tree {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
04. Sidebar Resizer
|
||||
--------------------------------------------------------------------------
|
||||
Узкая колонка между меню и каталогом. Pointer Events в script.js двигают
|
||||
CSS-переменную --sidebar-width и сохраняют значение в localStorage.
|
||||
========================================================================== */
|
||||
html[data-nav-layout="top"][data-show-tree="true"] .nav-tree {
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
max-height: none;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
margin-top: 4px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
html[data-show-tree="false"] .side-panel .hero-content {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
width: 8px;
|
||||
width: 6px;
|
||||
height: calc(100vh - 40px);
|
||||
margin: 0 -1px;
|
||||
border-radius: 999px;
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
@ -224,6 +254,49 @@ body.is-resizing-sidebar {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
04b. Top Bar Resizer
|
||||
--------------------------------------------------------------------------
|
||||
Горизонтальная ручка под шапкой в режиме navLayout: top.
|
||||
========================================================================== */
|
||||
|
||||
.top-bar-resizer {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 999px;
|
||||
cursor: row-resize;
|
||||
background: transparent;
|
||||
transition: background 160ms ease;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .top-bar-resizer:not([hidden]) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top-bar-resizer::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
margin: 3px auto;
|
||||
border-radius: inherit;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.top-bar-resizer:hover::before,
|
||||
body.is-resizing-top-bar .top-bar-resizer::before {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
body.is-resizing-top-bar {
|
||||
cursor: row-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
05. Sidebar Panel
|
||||
--------------------------------------------------------------------------
|
||||
@ -308,35 +381,16 @@ html:not([data-show-numbers="true"]) .item-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Теги в дереве управляются toggle-настройкой "Показывать теги дерева". */
|
||||
html[data-show-tree-tags="false"] .tree-node small {
|
||||
/* Теги и тип в дереве управляются настройками showTreeTags / showTreeType. */
|
||||
html[data-show-tree-tags="false"] .tree-tags {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Двухколоночный режим элементов внутри папок дерева. */
|
||||
html[data-tree-two-column="true"] {
|
||||
--shell-max-width: 1920px;
|
||||
--sidebar-max-width: 760px;
|
||||
html[data-show-tree-type="false"] .tree-file-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-items {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 2px 8px;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-node.tree-file {
|
||||
grid-template-columns: 20px minmax(0, 1fr);
|
||||
min-height: 24px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-node-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
html[data-show-tree-tags="false"][data-show-tree-type="false"] .tree-file-meta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -440,13 +494,13 @@ html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settings-panel-head h2 {
|
||||
margin: 4px 0 0;
|
||||
font-size: 1.35rem;
|
||||
letter-spacing: -0.04em;
|
||||
.settings-panel-eyebrow {
|
||||
margin: 0;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-close {
|
||||
@ -463,7 +517,8 @@ html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.settings-section[hidden] {
|
||||
.settings-section[hidden],
|
||||
.settings-section-collapsible[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -483,6 +538,98 @@ html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.settings-section-hint code {
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.catalog-columns-group {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.settings-section-collapsible {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
padding: 10px 12px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.settings-section-collapsible > summary {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.settings-section-collapsible > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-section-summary .settings-section-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-section-collapsible:not([open]) > .settings-section-summary .collapse-indicator {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.settings-section-body {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.layout-limit-row {
|
||||
position: relative;
|
||||
grid-template-columns: minmax(128px, 1.5fr) minmax(0, 2fr) auto;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.layout-limit-row[data-tooltip]::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
bottom: calc(100% + 6px);
|
||||
top: auto;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-strong);
|
||||
color: var(--text);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
box-shadow: var(--shadow);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transform: translateY(4px);
|
||||
transition:
|
||||
opacity 140ms ease,
|
||||
visibility 140ms ease,
|
||||
transform 140ms ease;
|
||||
}
|
||||
|
||||
.layout-limit-row[data-tooltip]:hover::after,
|
||||
.layout-limit-row[data-tooltip]:focus-within::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.layout-limit-row span {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
07. Search Field And Control Inputs
|
||||
--------------------------------------------------------------------------
|
||||
@ -936,13 +1083,91 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
}
|
||||
|
||||
.tree-file {
|
||||
grid-template-columns: 20px minmax(0, 1fr) auto;
|
||||
grid-template-columns: 20px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tree-file::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.tree-file-body {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tree-file-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tree-file-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="inline"] .tree-file-body {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="inline"] .tree-file-title {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="inline"] .tree-file-meta {
|
||||
flex: 0 1 auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="stacked"] .tree-file-body {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tree-file-type {
|
||||
margin: 0;
|
||||
color: var(--accent);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 750;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tree-tags.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tree-tags.tags span {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 2px 6px;
|
||||
background: rgba(var(--accent-rgb), 0.1);
|
||||
color: var(--muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
html:not([data-tree-columns="1"]) .tree-file {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
html:not([data-tree-columns="1"]) .tree-node-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tree-node-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
@ -955,7 +1180,8 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
.tree-items {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
grid-template-columns: repeat(var(--tree-columns, 1), minmax(0, 1fr));
|
||||
gap: 2px 8px;
|
||||
margin-left: 18px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid var(--border);
|
||||
@ -967,17 +1193,6 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
background: rgba(var(--accent-rgb), 0.14);
|
||||
}
|
||||
|
||||
/* Теги дерева усечены ellipsis, чтобы длинные списки не ломали sidebar. */
|
||||
.tree-node small {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--accent);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
09. Catalog Structure
|
||||
--------------------------------------------------------------------------
|
||||
@ -1155,11 +1370,14 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
|
||||
.items-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.catalog.cards-view .items-grid {
|
||||
grid-template-columns: repeat(var(--catalog-cards-columns, 3), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* Базовая карточка с иконкой, контентом, тегами, стрелкой и shine-слоем. */
|
||||
.nav-card {
|
||||
--tilt-x: 0deg;
|
||||
@ -1321,7 +1539,7 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
========================================================================== */
|
||||
|
||||
.catalog.list-view .items-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(var(--catalog-list-columns, 1), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* В list-режимах карточка вытягивается в строку. */
|
||||
@ -1342,12 +1560,12 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
|
||||
/* Compact modes скрывают второстепенную информацию и уменьшают отступы. */
|
||||
.catalog.compact-cards-view .items-grid {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(var(--catalog-compact-cards-columns, 5), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* Compact list на широких экранах использует 3 колонки. */
|
||||
/* Compact list на широких экранах использует настраиваемое число колонок. */
|
||||
.catalog.compact-list-view .items-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(var(--catalog-compact-list-columns, 3), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.catalog.compact-cards-view .section-head,
|
||||
@ -1466,15 +1684,15 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
/* Узкое окно на устройствах с мышью: sidebar и сетка карточек чуть компактнее. */
|
||||
@media (hover: hover) and (pointer: fine) and (max-width: 1120px) {
|
||||
.app-layout {
|
||||
grid-template-columns: minmax(240px, min(var(--sidebar-width), 320px)) 8px minmax(0, 1fr);
|
||||
grid-template-columns: minmax(240px, min(var(--sidebar-width), 320px)) 6px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.items-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
.catalog.cards-view .items-grid {
|
||||
grid-template-columns: repeat(min(var(--catalog-cards-columns, 3), 2), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.catalog.compact-cards-view .items-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(min(var(--catalog-compact-cards-columns, 5), 3), minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1489,7 +1707,8 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
.sidebar-resizer,
|
||||
.top-bar-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -1509,21 +1728,6 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Двухколоночное дерево на touch не отключается полностью:
|
||||
- в portrait — всегда, если включена настройка;
|
||||
- в landscape — начиная с 520px ширины viewport.
|
||||
*/
|
||||
html[data-tree-two-column="true"] .tree-items {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media (min-width: 520px), (orientation: portrait) {
|
||||
html[data-tree-two-column="true"] .tree-items {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.control-panel,
|
||||
.catalog-section,
|
||||
.empty-state {
|
||||
@ -1567,17 +1771,24 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.items-grid {
|
||||
grid-template-columns: 1fr;
|
||||
.catalog.cards-view .items-grid {
|
||||
grid-template-columns: repeat(min(var(--catalog-cards-columns, 3), 1), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.catalog.list-view .items-grid {
|
||||
grid-template-columns: repeat(min(var(--catalog-list-columns, 1), 1), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.catalog.compact-list-view .items-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(min(var(--catalog-compact-list-columns, 3), 1), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.catalog.compact-cards-view .items-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(min(var(--catalog-compact-cards-columns, 5), 2), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.nav-card {
|
||||
|
||||
175
index.html
@ -57,6 +57,16 @@
|
||||
<!-- Дерево генерируется из ecosystemSections в script.js. -->
|
||||
<nav class="nav-tree" aria-label="Дерево навигации" data-nav-tree></nav>
|
||||
</div>
|
||||
|
||||
<!-- Горизонтальная ручка высоты верхней панели (режим «Сверху»). -->
|
||||
<div
|
||||
class="top-bar-resizer"
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label="Изменить высоту верхней панели"
|
||||
data-top-bar-resizer
|
||||
hidden
|
||||
></div>
|
||||
</aside>
|
||||
|
||||
<!-- Вертикальная ручка для изменения ширины бокового меню мышью/указателем. -->
|
||||
@ -82,10 +92,7 @@
|
||||
-->
|
||||
<div class="control-panel" aria-label="Настройки панели" data-settings-panel>
|
||||
<div class="settings-panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">View settings</p>
|
||||
<h2>Настройки</h2>
|
||||
</div>
|
||||
<p class="eyebrow settings-panel-eyebrow">Настройки</p>
|
||||
<button class="settings-close" type="button" aria-label="Закрыть настройки" data-settings-close>
|
||||
×
|
||||
</button>
|
||||
@ -104,10 +111,111 @@
|
||||
</button>
|
||||
</div>
|
||||
<p class="settings-section-hint">
|
||||
Режим «Сверху» — компактная шапка с названием и поиском, без дерева.
|
||||
«Сверху» — компактная шапка. Дерево включается отдельно переключателем ниже.
|
||||
</p>
|
||||
<label class="toggle-row" data-feature="allowShowTreeChange">
|
||||
<span>
|
||||
<strong>Показывать дерево</strong>
|
||||
<small>В режимах «Сбоку» и «Сверху»</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree checked />
|
||||
</label>
|
||||
<label class="toggle-row" data-feature="allowSidebarResize">
|
||||
<span>
|
||||
<strong>Ручки изменения размера</strong>
|
||||
<small>Ширина меню или высота шапки</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-resize-handles checked />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<details class="settings-section settings-section-collapsible" data-settings-section="dimensions">
|
||||
<summary class="settings-section-summary">
|
||||
<span class="settings-section-title">Габариты</span>
|
||||
<span class="collapse-indicator" aria-hidden="true">⌄</span>
|
||||
</summary>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-section-hint">
|
||||
Ограничения ширины страницы, бокового меню и верхней шапки. Значения применяются сразу;
|
||||
текущий размер меню и шапки можно также менять перетаскиванием ручек на странице.
|
||||
Допустимые диапазоны ползунков задаёт администратор в <code>settings.json</code> →
|
||||
<code>limits</code>.
|
||||
</p>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина всей панели (.shell): насколько широко вместе могут располагаться меню и каталог."
|
||||
>
|
||||
<span>Ширина контента (max)</span>
|
||||
<input type="range" data-layout-limit="shellMaxWidth" />
|
||||
<output data-layout-limit-value="shellMaxWidth">2800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная ширина бокового меню при перетаскивании вертикальной ручки между меню и каталогом."
|
||||
>
|
||||
<span>Меню: мин. ширина</span>
|
||||
<input type="range" data-layout-limit="sidebarMinWidth" />
|
||||
<output data-layout-limit-value="sidebarMinWidth">260</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная ширина меню, когда элементы дерева в папках показываются в одну колонку."
|
||||
>
|
||||
<span>Меню: макс. (1 кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidth" />
|
||||
<output data-layout-limit-value="sidebarMaxWidth">800</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Верхний предел ширины меню при нескольких колонках в дереве. Фактический максимум также зависит от базы и шага колонки."
|
||||
>
|
||||
<span>Меню: макс. (неск. кол.)</span>
|
||||
<input type="range" data-layout-limit="sidebarMaxWidthWide" />
|
||||
<output data-layout-limit-value="sidebarMaxWidthWide">1400</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="База формулы ширины меню при нескольких колонках: min(макс. неск. кол., база + колонки × шаг)."
|
||||
>
|
||||
<span>Дерево: база ширины</span>
|
||||
<input type="range" data-layout-limit="treeSidebarBaseWidth" />
|
||||
<output data-layout-limit-value="treeSidebarBaseWidth">420</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Сколько пикселей добавляется к базовой ширине меню за каждую колонку элементов в папках дерева."
|
||||
>
|
||||
<span>Дерево: ширина колонки</span>
|
||||
<input type="range" data-layout-limit="treeSidebarColumnWidth" />
|
||||
<output data-layout-limit-value="treeSidebarColumnWidth">120</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Минимальная высота верхней шапки в режиме «Сверху» — нижняя граница при перетаскивании горизонтальной ручки."
|
||||
>
|
||||
<span>Шапка: мин. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMinHeight" />
|
||||
<output data-layout-limit-value="topBarMinHeight">80</output>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="accent-custom-row layout-limit-row"
|
||||
data-tooltip="Максимальная высота верхней шапки в режиме «Сверху». Также ограничена примерно 75% высоты окна браузера."
|
||||
>
|
||||
<span>Шапка: макс. высота</span>
|
||||
<input type="range" data-layout-limit="topBarMaxHeight" />
|
||||
<output data-layout-limit-value="topBarMaxHeight">560</output>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="settings-section" data-settings-section="catalog">
|
||||
<p class="settings-section-title">Каталог</p>
|
||||
|
||||
@ -128,6 +236,34 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="catalog-columns-group" data-feature="allowCatalogColumnsChange">
|
||||
<p class="settings-section-hint">Колонок в строке для каждого вида каталога.</p>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCardsColumns">3</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Список</span>
|
||||
<input type="range" data-catalog-columns="catalogListColumns" />
|
||||
<output data-catalog-columns-value="catalogListColumns">1</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт плитки</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactCardsColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactCardsColumns">5</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Компакт список</span>
|
||||
<input type="range" data-catalog-columns="catalogCompactListColumns" />
|
||||
<output data-catalog-columns-value="catalogCompactListColumns">3</output>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Показывать номера разделов, подразделов и элементов. -->
|
||||
<label class="toggle-row" data-feature="allowShowNumbers">
|
||||
<span>
|
||||
@ -141,25 +277,38 @@
|
||||
<section class="settings-section" data-settings-section="tree">
|
||||
<p class="settings-section-title">Дерево</p>
|
||||
|
||||
<!-- Показывать поисковые теги рядом с файлами в дереве навигации. -->
|
||||
<label class="accent-custom-row">
|
||||
<span>Колонок</span>
|
||||
<input type="range" min="1" max="6" step="1" value="2" data-tree-columns />
|
||||
<output data-tree-columns-value>2</output>
|
||||
</label>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Показывать теги дерева</strong>
|
||||
<small>Теги рядом с элементом, без 2 колонок</small>
|
||||
<strong>Показывать теги</strong>
|
||||
<small>Оформление как на карточках</small>
|
||||
</span>
|
||||
<input type="checkbox" data-show-tree-tags />
|
||||
</label>
|
||||
|
||||
<!-- Элементы внутри папок дерева отображаются в две колонки без тегов. -->
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>2 колонки в дереве</strong>
|
||||
<small>Компактный вид элементов папок</small>
|
||||
<strong>Показывать тип</strong>
|
||||
<small>Подпись типа сервиса</small>
|
||||
</span>
|
||||
<input type="checkbox" data-tree-two-column />
|
||||
<input type="checkbox" data-show-tree-type />
|
||||
</label>
|
||||
|
||||
<!-- Управляет тем, раскрывает ли поиск только ветки с совпадениями. -->
|
||||
<div class="control-group">
|
||||
<span>Теги и тип</span>
|
||||
<button class="segmented is-active" type="button" data-tree-meta-layout="inline">
|
||||
Сбоку
|
||||
</button>
|
||||
<button class="segmented" type="button" data-tree-meta-layout="stacked">
|
||||
Снизу
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="toggle-row">
|
||||
<span>
|
||||
<strong>Авто раскрывать поиск</strong>
|
||||
|
||||
108
panel-data.json
@ -1,113 +1,7 @@
|
||||
{
|
||||
"title": "Панель экосистемы",
|
||||
"sections": [
|
||||
{
|
||||
"id": "user",
|
||||
"number": "1",
|
||||
"title": "Интерфейс пользователя",
|
||||
"description": "Рабочие инструменты пользователя для фильтрации и обработки сообщений.",
|
||||
"groups": [
|
||||
{
|
||||
"id": "message-filtering",
|
||||
"number": "1.1",
|
||||
"title": "Управление фильтрацией сообщений",
|
||||
"description": "Правила, задания, события и статистика фильтрации.",
|
||||
"items": [
|
||||
{ "number": "1.1.1", "title": "Редактирование правил", "type": "rules", "tags": ["правила", "фильтрация", "редактор"], "url": "#" },
|
||||
{ "number": "1.1.2", "title": "Управление заданиями", "type": "redmine", "tags": ["redmine", "задачи", "задания"], "url": "#" },
|
||||
{ "number": "1.1.3", "title": "Применение правил", "type": "rules", "tags": ["правила", "применение", "фильтрация"], "url": "#" },
|
||||
{ "number": "1.1.4", "title": "Просмотр статистики", "type": "grafana", "tags": ["grafana", "статистика", "дашборд"], "url": "#" },
|
||||
{ "number": "1.1.5", "title": "Управление событиями фильтрации", "type": "grafana", "tags": ["grafana", "события", "алерты"], "url": "#" },
|
||||
{ "number": "1.1.6", "title": "Просмотр журнала событий фильтрации", "type": "grafana", "tags": ["grafana", "журнал", "события"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "message-processing",
|
||||
"number": "1.2",
|
||||
"title": "Обработка сообщений",
|
||||
"description": "Просмотр, оформление и доступ к хранилищам сообщений.",
|
||||
"items": [
|
||||
{ "number": "1.2.1", "title": "Просмотр и оформление", "type": "messages", "tags": ["сообщения", "просмотр", "оформление"], "url": "#" },
|
||||
{ "number": "1.2.2", "title": "Прямой доступ", "type": "ceph", "tags": ["ceph", "доступ", "объекты"], "url": "#" },
|
||||
{ "number": "1.2.3", "title": "S3 Хранилище", "type": "ceph", "tags": ["ceph", "s3", "хранилище"], "url": "#" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "admin",
|
||||
"number": "2",
|
||||
"title": "Интерфейс администратора",
|
||||
"description": "Администрирование сервисов, кластеров, мониторинга и аппаратной части.",
|
||||
"groups": [
|
||||
{
|
||||
"id": "service-monitoring",
|
||||
"number": "2.1",
|
||||
"title": "Управление и мониторинг сервисов",
|
||||
"description": "Кластер, модули, события мониторинга и журналы ошибок.",
|
||||
"items": [
|
||||
{ "number": "2.1.1", "title": "Управление кластером", "type": "kubernetes", "tags": ["kubernetes", "кластер", "k8s"], "url": "#" },
|
||||
{ "number": "2.1.2", "title": "Мониторинг состояния модулей", "type": "grafana", "tags": ["grafana", "модули", "мониторинг"], "url": "#" },
|
||||
{ "number": "2.1.3", "title": "Управление событиями мониторинга", "type": "grafana", "tags": ["grafana", "события", "алерты"], "url": "#" },
|
||||
{ "number": "2.1.4", "title": "Просмотр журнала событий мониторинга", "type": "grafana", "tags": ["grafana", "журнал", "мониторинг"], "url": "#" },
|
||||
{ "number": "2.1.5", "title": "Просмотр журналов ошибок", "type": "opensearch", "tags": ["opensearch", "ошибки", "логи"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "service-configuration",
|
||||
"number": "2.2",
|
||||
"title": "Настройка сервисов",
|
||||
"description": "Пользователи, очереди, xPath, модули и конфигурации.",
|
||||
"items": [
|
||||
{ "number": "2.2.1", "title": "Управление пользователями и очередями", "type": "rabbitmq", "tags": ["пользователи", "очереди", "rabbitmq"], "url": "#" },
|
||||
{ "number": "2.2.2", "title": "Отладка xPath-запросов", "type": "xpath", "tags": ["xpath", "отладка", "запросы"], "url": "#" },
|
||||
{ "number": "2.2.3", "title": "Конфигурирование модулей", "type": "config", "tags": ["конфигурация", "модули", "настройки"], "url": "#" },
|
||||
{ "number": "2.2.4", "title": "Просмотр журнала конфигурирования", "type": "gitea", "tags": ["gitea", "журнал", "конфигурация"], "url": "#" },
|
||||
{ "number": "2.2.5", "title": "Прямой доступ к конфигурации", "type": "gitea", "tags": ["gitea", "конфигурация", "git"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hardware",
|
||||
"number": "2.3",
|
||||
"title": "Управление и мониторинг аппаратной части",
|
||||
"description": "Сенсоры, ресурсы и прямые панели серверов.",
|
||||
"items": [
|
||||
{ "number": "2.3.1", "title": "Мониторинг показаний сенсоров", "type": "grafana", "tags": ["grafana", "сенсоры", "железо"], "url": "#" },
|
||||
{ "number": "2.3.2", "title": "Мониторинг потребления ресурсов", "type": "grafana", "tags": ["grafana", "ресурсы", "метрики"], "url": "#" },
|
||||
{ "number": "2.3.3", "title": "СЕРВЕР01", "type": "ipmi", "tags": ["ipmi", "сервер01", "железо"], "url": "#" },
|
||||
{ "number": "2.3.4", "title": "СЕРВЕР02", "type": "ipmi", "tags": ["ipmi", "сервер02", "железо"], "url": "#" },
|
||||
{ "number": "2.3.5", "title": "СЕРВЕР03", "type": "ipmi", "tags": ["ipmi", "сервер03", "железо"], "url": "#" },
|
||||
{ "number": "2.3.6", "title": "СЕРВЕР04", "type": "ipmi", "tags": ["ipmi", "сервер04", "железо"], "url": "#" },
|
||||
{ "number": "2.3.7", "title": "СЕРВЕР05", "type": "ipmi", "tags": ["ipmi", "сервер05", "железо"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "service-panels",
|
||||
"number": "2.4",
|
||||
"title": "Настройка отдельных сервисов",
|
||||
"description": "Панели управления инфраструктурными сервисами.",
|
||||
"items": [
|
||||
{ "number": "2.4.1", "title": "Панель управления LDAP Account Manager", "type": "ldap", "tags": ["ldap", "accounts", "пользователи"], "url": "#" },
|
||||
{ "number": "2.4.2", "title": "Панель управления кластера RabbitMQ", "type": "rabbitmq", "tags": ["rabbitmq", "кластер", "очереди"], "url": "#" },
|
||||
{ "number": "2.4.3", "title": "Панель управления кластера Ceph", "type": "ceph", "tags": ["ceph", "кластер", "storage"], "url": "#" },
|
||||
{ "number": "2.4.4", "title": "Панель управления Prometheus", "type": "prometheus", "tags": ["prometheus", "метрики", "мониторинг"], "url": "#" },
|
||||
{ "number": "2.4.5", "title": "Панель управления XMPP-сервера", "type": "xmpp", "tags": ["xmpp", "чат", "сервер"], "url": "#" },
|
||||
{ "number": "2.4.6", "title": "Панель управления PostgreSQL", "type": "postgresql", "tags": ["postgresql", "база", "db"], "url": "#" },
|
||||
{ "number": "2.4.7", "title": "Подключение к БД", "type": "cloudbeaver", "tags": ["cloudbeaver", "database", "sql"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"number": "2.5",
|
||||
"title": "Образы и пакеты",
|
||||
"description": "Реестры образов и deb-пакетов.",
|
||||
"items": [
|
||||
{ "number": "2.5.1", "title": "Реестр docker-образов", "type": "gitea", "tags": ["gitea", "docker", "registry"], "url": "#" },
|
||||
{ "number": "2.5.2", "title": "Репозиторий deb-пакетов", "type": "gitea", "tags": ["gitea", "deb", "packages"], "url": "#" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
],
|
||||
"serviceMeta": {}
|
||||
}
|
||||
|
||||
635
script.js
@ -32,9 +32,16 @@ let ecosystemSections = [];
|
||||
let panelTitle = "Панель экосистемы";
|
||||
|
||||
const defaultServiceMeta = {
|
||||
analytics: { label: "Аналитика", icon: "assets/analytics.svg" },
|
||||
api: { label: "API", icon: "assets/api.svg" },
|
||||
calendar: { label: "Календарь", icon: "assets/calendar.svg" },
|
||||
ceph: { label: "Ceph", icon: "assets/ceph.svg" },
|
||||
chat: { label: "Беседа", icon: "assets/chat.svg" },
|
||||
cicd: { label: "CI/CD", icon: "assets/cicd.svg" },
|
||||
cloudbeaver: { label: "CloudBeaver", icon: "assets/cloudbeaver.svg" },
|
||||
config: { label: "Config", icon: "assets/config.svg" },
|
||||
drive: { label: "Диск", icon: "assets/drive.svg" },
|
||||
forum: { label: "Форум", icon: "assets/forum.svg" },
|
||||
gitea: { label: "Gitea", icon: "assets/gitea.svg" },
|
||||
grafana: { label: "Grafana", icon: "assets/grafana.svg" },
|
||||
ipmi: { label: "IPMI", icon: "assets/server.svg" },
|
||||
@ -47,6 +54,11 @@ const defaultServiceMeta = {
|
||||
rabbitmq: { label: "RabbitMQ", icon: "assets/rabbitmq.svg" },
|
||||
redmine: { label: "Redmine", icon: "assets/redmine.svg" },
|
||||
rules: { label: "Rules", icon: "assets/rules.svg" },
|
||||
social: { label: "Соцсеть", icon: "assets/social.svg" },
|
||||
vault: { label: "Vault", icon: "assets/vault.svg" },
|
||||
video: { label: "Видео", icon: "assets/video.svg" },
|
||||
vpn: { label: "VPN", icon: "assets/vpn.svg" },
|
||||
wiki: { label: "Wiki", icon: "assets/wiki.svg" },
|
||||
xpath: { label: "xPath", icon: "assets/xpath.svg" },
|
||||
xmpp: { label: "XMPP", icon: "assets/xmpp.svg" },
|
||||
};
|
||||
@ -65,26 +77,84 @@ const defaultPreferences = {
|
||||
customAccentTo: "#22d3ee",
|
||||
customAccentAngle: 135,
|
||||
navLayout: "sidebar",
|
||||
showTree: true,
|
||||
showResizeHandles: true,
|
||||
showNumbers: false,
|
||||
showTreeTags: true,
|
||||
treeTwoColumn: false,
|
||||
showTreeType: false,
|
||||
treeMetaLayout: "inline",
|
||||
treeColumns: 2,
|
||||
autoCollapseSearch: true,
|
||||
sidebarWidth: 390,
|
||||
topBarHeight: 140,
|
||||
shellMaxWidth: 2800,
|
||||
sidebarMinWidth: 260,
|
||||
sidebarMaxWidth: 800,
|
||||
sidebarMaxWidthWide: 1400,
|
||||
treeSidebarBaseWidth: 420,
|
||||
treeSidebarColumnWidth: 120,
|
||||
topBarMinHeight: 80,
|
||||
topBarMaxHeight: 560,
|
||||
catalogCardsColumns: 3,
|
||||
catalogListColumns: 1,
|
||||
catalogCompactCardsColumns: 5,
|
||||
catalogCompactListColumns: 3,
|
||||
searchPlaceholder: "Grafana, S3, журнал, сервер...",
|
||||
};
|
||||
|
||||
const defaultPanelFeatures = {
|
||||
allowNavLayoutChange: true,
|
||||
allowShowTreeChange: true,
|
||||
allowSearch: true,
|
||||
allowViewChange: true,
|
||||
allowCatalogColumnsChange: true,
|
||||
allowShowNumbers: true,
|
||||
allowTreeOptions: true,
|
||||
allowSidebarResize: true,
|
||||
allowLayoutLimitsChange: true,
|
||||
allowThemeChange: true,
|
||||
allowAccentChange: true,
|
||||
};
|
||||
|
||||
const defaultPanelLimits = {
|
||||
shellMaxWidth: { min: 1200, max: 3840 },
|
||||
sidebarMinWidth: { min: 200, max: 480 },
|
||||
sidebarMaxWidth: { min: 400, max: 1200 },
|
||||
sidebarMaxWidthWide: { min: 560, max: 2000 },
|
||||
treeSidebarBaseWidth: { min: 280, max: 720 },
|
||||
treeSidebarColumnWidth: { min: 60, max: 200 },
|
||||
topBarMinHeight: { min: 64, max: 240 },
|
||||
topBarMaxHeight: { min: 160, max: 800 },
|
||||
catalogCardsColumns: { min: 1, max: 6 },
|
||||
catalogListColumns: { min: 1, max: 4 },
|
||||
catalogCompactCardsColumns: { min: 2, max: 8 },
|
||||
catalogCompactListColumns: { min: 1, max: 6 },
|
||||
};
|
||||
|
||||
const layoutLimitKeys = [
|
||||
"shellMaxWidth",
|
||||
"sidebarMinWidth",
|
||||
"sidebarMaxWidth",
|
||||
"sidebarMaxWidthWide",
|
||||
"treeSidebarBaseWidth",
|
||||
"treeSidebarColumnWidth",
|
||||
"topBarMinHeight",
|
||||
"topBarMaxHeight",
|
||||
];
|
||||
|
||||
const catalogColumnKeys = [
|
||||
"catalogCardsColumns",
|
||||
"catalogListColumns",
|
||||
"catalogCompactCardsColumns",
|
||||
"catalogCompactListColumns",
|
||||
];
|
||||
|
||||
const panelLimitKeys = [...layoutLimitKeys, ...catalogColumnKeys];
|
||||
|
||||
const availableNavLayouts = ["sidebar", "top"];
|
||||
const availableTreeMetaLayouts = ["inline", "stacked"];
|
||||
const MIN_TREE_COLUMNS = 1;
|
||||
const MAX_TREE_COLUMNS = 6;
|
||||
const availableViews = ["cards", "list", "compact-cards", "compact-list"];
|
||||
const availableAccents = ["violet", "cyan", "lime", "amber", "custom"];
|
||||
|
||||
@ -94,6 +164,7 @@ const availableAccents = ["violet", "cyan", "lime", "amber", "custom"];
|
||||
// preferences - текущие настройки интерфейса; итоговые значения собираются в bootstrap().
|
||||
let panelDefaults = { ...defaultPreferences };
|
||||
let panelFeatures = { ...defaultPanelFeatures };
|
||||
let panelLimits = structuredClone(defaultPanelLimits);
|
||||
let preferences = { ...defaultPreferences };
|
||||
|
||||
// collapseState - объект вида { [collapseKey]: boolean } для details.
|
||||
@ -104,6 +175,7 @@ const appLayout = document.querySelector(".app-layout");
|
||||
const catalog = document.querySelector("[data-catalog-root]");
|
||||
const navTree = document.querySelector("[data-nav-tree]");
|
||||
const sidebarResizer = document.querySelector("[data-sidebar-resizer]");
|
||||
const topBarResizer = document.querySelector("[data-top-bar-resizer]");
|
||||
const searchField = document.querySelector("[data-search-field]");
|
||||
const searchInput = document.querySelector("[data-search-input]");
|
||||
const settingsToggle = document.querySelector("[data-settings-toggle]");
|
||||
@ -120,10 +192,17 @@ const customAccentToInput = document.querySelector("[data-custom-accent-to]");
|
||||
const customAccentAngleInput = document.querySelector("[data-custom-accent-angle]");
|
||||
const customAccentAngleValue = document.querySelector("[data-custom-accent-angle-value]");
|
||||
const customAccentPreview = document.querySelector("[data-custom-accent-preview]");
|
||||
const showTreeInput = document.querySelector("[data-show-tree]");
|
||||
const showResizeHandlesInput = document.querySelector("[data-show-resize-handles]");
|
||||
const showNumbersInput = document.querySelector("[data-show-numbers]");
|
||||
const showTreeTagsInput = document.querySelector("[data-show-tree-tags]");
|
||||
const treeTwoColumnInput = document.querySelector("[data-tree-two-column]");
|
||||
const showTreeTypeInput = document.querySelector("[data-show-tree-type]");
|
||||
const treeColumnsInput = document.querySelector("[data-tree-columns]");
|
||||
const treeColumnsValue = document.querySelector("[data-tree-columns-value]");
|
||||
const treeMetaLayoutButtons = document.querySelectorAll("[data-tree-meta-layout]");
|
||||
const autoCollapseSearchInput = document.querySelector("[data-auto-collapse-search]");
|
||||
const layoutLimitInputs = document.querySelectorAll("[data-layout-limit]");
|
||||
const catalogColumnInputs = document.querySelectorAll("[data-catalog-columns]");
|
||||
const settingsSections = document.querySelectorAll("[data-settings-section]");
|
||||
const emptyTemplate = document.querySelector("#empty-template");
|
||||
const configErrorTemplate = document.querySelector("#config-error-template");
|
||||
@ -236,14 +315,53 @@ navLayoutButtons.forEach((button) => {
|
||||
preferences.navLayout = button.dataset.navLayout;
|
||||
applyPreferences(preferences);
|
||||
|
||||
if (preferences.navLayout === "sidebar") {
|
||||
if (isTreeVisible(preferences)) {
|
||||
renderNavigationTree();
|
||||
} else {
|
||||
navTree.replaceChildren();
|
||||
}
|
||||
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
function setShowTree(nextValue, { persist = true } = {}) {
|
||||
if (!panelFeatures.allowShowTreeChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.showTree = Boolean(nextValue);
|
||||
applyPreferences(preferences);
|
||||
|
||||
if (preferences.showTree) {
|
||||
renderNavigationTree();
|
||||
} else {
|
||||
navTree.replaceChildren();
|
||||
}
|
||||
|
||||
if (persist) {
|
||||
savePreferences(preferences);
|
||||
}
|
||||
}
|
||||
|
||||
if (showTreeInput) {
|
||||
showTreeInput.addEventListener("change", () => {
|
||||
setShowTree(showTreeInput.checked);
|
||||
});
|
||||
}
|
||||
|
||||
if (showResizeHandlesInput) {
|
||||
showResizeHandlesInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowSidebarResize) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.showResizeHandles = showResizeHandlesInput.checked;
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
}
|
||||
|
||||
// Переключение режима отображения каталога.
|
||||
viewButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
@ -300,41 +418,62 @@ showNumbersInput.addEventListener("change", () => {
|
||||
});
|
||||
|
||||
// Показывает/скрывает поисковые теги в файловом дереве.
|
||||
// Взаимоисключимо с двухколоночным режимом элементов папок.
|
||||
showTreeTagsInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || preferences.navLayout === "top") {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.showTreeTags = showTreeTagsInput.checked;
|
||||
if (preferences.showTreeTags) {
|
||||
preferences.treeTwoColumn = false;
|
||||
}
|
||||
applyPreferences(preferences);
|
||||
renderNavigationTree();
|
||||
savePreferences(preferences);
|
||||
});
|
||||
|
||||
// Компактный двухколоночный режим элементов внутри папок дерева.
|
||||
// Взаимоисключимо с отображением тегов.
|
||||
treeTwoColumnInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || preferences.navLayout === "top") {
|
||||
showTreeTypeInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.treeTwoColumn = treeTwoColumnInput.checked;
|
||||
if (preferences.treeTwoColumn) {
|
||||
preferences.showTreeTags = false;
|
||||
if (preferences.sidebarWidth < 480) {
|
||||
preferences.sidebarWidth = 480;
|
||||
}
|
||||
preferences.showTreeType = showTreeTypeInput.checked;
|
||||
applyPreferences(preferences);
|
||||
renderNavigationTree();
|
||||
savePreferences(preferences);
|
||||
});
|
||||
|
||||
function handleTreeColumnsChange() {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.treeColumns = getTreeColumns({ treeColumns: treeColumnsInput.value });
|
||||
|
||||
if (preferences.treeColumns > 1 && preferences.sidebarWidth < getSidebarMinWidth(preferences) + preferences.treeColumns * 70) {
|
||||
preferences.sidebarWidth = getSidebarMinWidth(preferences) + preferences.treeColumns * 70;
|
||||
}
|
||||
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
}
|
||||
|
||||
if (treeColumnsInput) {
|
||||
treeColumnsInput.addEventListener("input", handleTreeColumnsChange);
|
||||
}
|
||||
|
||||
treeMetaLayoutButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences.treeMetaLayout = button.dataset.treeMetaLayout;
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
// Включает/выключает авто-раскрытие веток дерева при поиске.
|
||||
autoCollapseSearchInput.addEventListener("change", () => {
|
||||
if (!panelFeatures.allowTreeOptions || preferences.navLayout === "top") {
|
||||
if (!panelFeatures.allowTreeOptions || !isTreeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -344,10 +483,38 @@ autoCollapseSearchInput.addEventListener("change", () => {
|
||||
savePreferences(preferences);
|
||||
});
|
||||
|
||||
layoutLimitInputs.forEach((input) => {
|
||||
input.addEventListener("input", () => {
|
||||
if (!panelFeatures.allowLayoutLimitsChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = input.dataset.layoutLimit;
|
||||
preferences[key] = clampLayoutPreference(key, input.value);
|
||||
preferences = normalizePreferences(preferences);
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
catalogColumnInputs.forEach((input) => {
|
||||
input.addEventListener("input", () => {
|
||||
if (!panelFeatures.allowCatalogColumnsChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = input.dataset.catalogColumns;
|
||||
preferences[key] = clampCatalogColumn(key, input.value);
|
||||
preferences = normalizePreferences(preferences);
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
});
|
||||
});
|
||||
|
||||
// Drag-resize бокового меню.
|
||||
// Ширина сохраняется в preferences.sidebarWidth и применяется через CSS-переменную.
|
||||
sidebarResizer.addEventListener("pointerdown", (event) => {
|
||||
if (preferences.navLayout === "top" || !panelFeatures.allowSidebarResize) {
|
||||
if (preferences.navLayout === "top" || !isResizeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -358,7 +525,7 @@ sidebarResizer.addEventListener("pointerdown", (event) => {
|
||||
const handlePointerMove = (moveEvent) => {
|
||||
const layoutLeft = document.querySelector(".app-layout").getBoundingClientRect().left;
|
||||
preferences.sidebarWidth = Math.round(
|
||||
clamp(moveEvent.clientX - layoutLeft, 260, getSidebarMaxWidth(preferences)),
|
||||
clamp(moveEvent.clientX - layoutLeft, getSidebarMinWidth(preferences), getSidebarMaxWidth(preferences)),
|
||||
);
|
||||
applyPreferences(preferences);
|
||||
};
|
||||
@ -376,6 +543,38 @@ sidebarResizer.addEventListener("pointerdown", (event) => {
|
||||
sidebarResizer.addEventListener("pointercancel", handlePointerUp);
|
||||
});
|
||||
|
||||
// Drag-resize верхней панели в режиме navLayout: top.
|
||||
topBarResizer?.addEventListener("pointerdown", (event) => {
|
||||
if (preferences.navLayout !== "top" || !isResizeVisible(preferences)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
document.body.classList.add("is-resizing-top-bar");
|
||||
topBarResizer.setPointerCapture(event.pointerId);
|
||||
|
||||
const panelTop = document.querySelector(".side-panel")?.getBoundingClientRect().top ?? 0;
|
||||
|
||||
const handlePointerMove = (moveEvent) => {
|
||||
preferences.topBarHeight = Math.round(
|
||||
clamp(moveEvent.clientY - panelTop, getTopBarMinHeight(preferences), getTopBarMaxHeight(preferences)),
|
||||
);
|
||||
applyPreferences(preferences);
|
||||
};
|
||||
|
||||
const handlePointerUp = () => {
|
||||
document.body.classList.remove("is-resizing-top-bar");
|
||||
savePreferences(preferences);
|
||||
topBarResizer.removeEventListener("pointermove", handlePointerMove);
|
||||
topBarResizer.removeEventListener("pointerup", handlePointerUp);
|
||||
topBarResizer.removeEventListener("pointercancel", handlePointerUp);
|
||||
};
|
||||
|
||||
topBarResizer.addEventListener("pointermove", handlePointerMove);
|
||||
topBarResizer.addEventListener("pointerup", handlePointerUp);
|
||||
topBarResizer.addEventListener("pointercancel", handlePointerUp);
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Panel config loading
|
||||
// ============================================================================
|
||||
@ -395,6 +594,7 @@ async function loadPanelDataFromSettings(settings) {
|
||||
}
|
||||
|
||||
function applyOrganizationConfig(settings) {
|
||||
panelLimits = normalizePanelLimits(settings?.limits);
|
||||
panelDefaults = normalizePreferences({
|
||||
...defaultPreferences,
|
||||
...(settings?.defaults && typeof settings.defaults === "object" ? settings.defaults : {}),
|
||||
@ -438,29 +638,60 @@ function normalizePreferences(raw) {
|
||||
360,
|
||||
);
|
||||
next.showNumbers = Boolean(next.showNumbers);
|
||||
next.showTree = Boolean(next.showTree);
|
||||
next.showResizeHandles = next.showResizeHandles !== false;
|
||||
next.showTreeTags = Boolean(next.showTreeTags);
|
||||
next.treeTwoColumn = Boolean(next.treeTwoColumn);
|
||||
next.showTreeType = Boolean(next.showTreeType);
|
||||
next.treeColumns = getTreeColumns(next);
|
||||
next.treeMetaLayout = availableTreeMetaLayouts.includes(next.treeMetaLayout)
|
||||
? next.treeMetaLayout
|
||||
: defaultPreferences.treeMetaLayout;
|
||||
next.autoCollapseSearch = Boolean(next.autoCollapseSearch);
|
||||
normalizeLayoutPreferences(next);
|
||||
normalizeCatalogColumns(next);
|
||||
next.sidebarWidth = clamp(
|
||||
Number(next.sidebarWidth) || defaultPreferences.sidebarWidth,
|
||||
260,
|
||||
getSidebarMinWidth(next),
|
||||
getSidebarMaxWidth(next),
|
||||
);
|
||||
|
||||
if (next.treeTwoColumn) {
|
||||
next.showTreeTags = false;
|
||||
} else if (next.showTreeTags) {
|
||||
next.treeTwoColumn = false;
|
||||
}
|
||||
|
||||
if (next.navLayout === "top") {
|
||||
next.treeTwoColumn = false;
|
||||
next.showTreeTags = false;
|
||||
}
|
||||
next.topBarHeight = clamp(
|
||||
Number(next.topBarHeight) || defaultPreferences.topBarHeight,
|
||||
getTopBarMinHeight(next),
|
||||
getTopBarMaxHeight(next),
|
||||
);
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function normalizeLayoutPreferences(next) {
|
||||
for (const key of layoutLimitKeys) {
|
||||
next[key] = clampLayoutPreference(key, next[key]);
|
||||
}
|
||||
|
||||
if (next.sidebarMinWidth > next.sidebarMaxWidth) {
|
||||
next.sidebarMinWidth = next.sidebarMaxWidth;
|
||||
}
|
||||
|
||||
if (next.sidebarMaxWidth > next.sidebarMaxWidthWide) {
|
||||
next.sidebarMaxWidth = next.sidebarMaxWidthWide;
|
||||
}
|
||||
|
||||
if (next.topBarMinHeight > next.topBarMaxHeight) {
|
||||
next.topBarMinHeight = next.topBarMaxHeight;
|
||||
}
|
||||
|
||||
const minShellWidth = getSidebarMaxWidth(next) + 320;
|
||||
if (next.shellMaxWidth < minShellWidth) {
|
||||
next.shellMaxWidth = clampLayoutPreference("shellMaxWidth", minShellWidth);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeCatalogColumns(next) {
|
||||
for (const key of catalogColumnKeys) {
|
||||
next[key] = clampCatalogColumn(key, next[key]);
|
||||
}
|
||||
}
|
||||
|
||||
function enforceFeatureLocks(rawPreferences) {
|
||||
const next = normalizePreferences(rawPreferences);
|
||||
|
||||
@ -468,6 +699,10 @@ function enforceFeatureLocks(rawPreferences) {
|
||||
next.navLayout = panelDefaults.navLayout;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowShowTreeChange) {
|
||||
next.showTree = panelDefaults.showTree;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowThemeChange) {
|
||||
next.theme = panelDefaults.theme;
|
||||
}
|
||||
@ -476,6 +711,12 @@ function enforceFeatureLocks(rawPreferences) {
|
||||
next.view = panelDefaults.view;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowCatalogColumnsChange) {
|
||||
for (const key of catalogColumnKeys) {
|
||||
next[key] = panelDefaults[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowAccentChange) {
|
||||
next.accent = panelDefaults.accent;
|
||||
next.customAccentFrom = panelDefaults.customAccentFrom;
|
||||
@ -487,16 +728,33 @@ function enforceFeatureLocks(rawPreferences) {
|
||||
next.showNumbers = panelDefaults.showNumbers;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowTreeOptions || next.navLayout === "top") {
|
||||
next.showTreeTags = false;
|
||||
next.treeTwoColumn = false;
|
||||
if (!panelFeatures.allowTreeOptions || !next.showTree) {
|
||||
next.showTreeTags = panelDefaults.showTreeTags;
|
||||
next.showTreeType = panelDefaults.showTreeType;
|
||||
next.treeColumns = panelDefaults.treeColumns;
|
||||
next.treeMetaLayout = panelDefaults.treeMetaLayout;
|
||||
next.autoCollapseSearch = panelDefaults.autoCollapseSearch;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowSidebarResize) {
|
||||
next.showResizeHandles = panelDefaults.showResizeHandles;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowSidebarResize || next.navLayout === "top") {
|
||||
next.sidebarWidth = panelDefaults.sidebarWidth;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowSidebarResize || next.navLayout !== "top") {
|
||||
next.topBarHeight = panelDefaults.topBarHeight;
|
||||
}
|
||||
|
||||
if (!panelFeatures.allowLayoutLimitsChange) {
|
||||
for (const key of layoutLimitKeys) {
|
||||
next[key] = panelDefaults[key];
|
||||
}
|
||||
normalizeLayoutPreferences(next);
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
@ -893,7 +1151,7 @@ function matchesText(values, query) {
|
||||
// Строит левое файловое дерево из той же структуры данных, что и каталог.
|
||||
// Это важно: дерево и карточки всегда остаются синхронизированными.
|
||||
function renderNavigationTree() {
|
||||
if (preferences.navLayout === "top") {
|
||||
if (!isTreeVisible(preferences)) {
|
||||
navTree.replaceChildren();
|
||||
return;
|
||||
}
|
||||
@ -964,22 +1222,39 @@ function createTreeItemList(items, query = "", className = "tree-items") {
|
||||
itemList.className = className;
|
||||
|
||||
items.forEach((item) => {
|
||||
const meta = getItemMeta(item);
|
||||
const itemButton = document.createElement("button");
|
||||
itemButton.type = "button";
|
||||
itemButton.className = "tree-node tree-file";
|
||||
itemButton.dataset.focusTarget = getItemId(item);
|
||||
itemButton.innerHTML = `
|
||||
<img class="tree-node-icon" src="${resolveAssetPath(meta.icon)}" alt="" loading="lazy" />
|
||||
<span>${highlightText(item.title, query)}</span>
|
||||
<small>${highlightText(item.tags.join(", "), query)}</small>
|
||||
`;
|
||||
itemList.append(itemButton);
|
||||
itemList.append(createTreeItemButton(item, query));
|
||||
});
|
||||
|
||||
return itemList;
|
||||
}
|
||||
|
||||
function createTreeItemButton(item, query = "") {
|
||||
const meta = getItemMeta(item);
|
||||
const itemButton = document.createElement("button");
|
||||
itemButton.type = "button";
|
||||
itemButton.className = "tree-node tree-file";
|
||||
itemButton.dataset.focusTarget = getItemId(item);
|
||||
itemButton.innerHTML = `
|
||||
<img class="tree-node-icon" src="${resolveAssetPath(meta.icon)}" alt="" loading="lazy" />
|
||||
<div class="tree-file-body">
|
||||
<span class="tree-file-title">${highlightText(item.title, query)}</span>
|
||||
<div class="tree-file-meta">
|
||||
<span class="tree-file-type">${highlightText(meta.label, query)}</span>
|
||||
<div class="tree-tags tags">${renderTreeTagSpans(item.tags, query)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return itemButton;
|
||||
}
|
||||
|
||||
function renderTreeTagSpans(tags, query = "") {
|
||||
if (!Array.isArray(tags) || !tags.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return tags.map((tag) => `<span>${highlightText(tag, query)}</span>`).join("");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Focusing from tree to content
|
||||
// ============================================================================
|
||||
@ -1097,31 +1372,84 @@ function applyPreferences(nextPreferences) {
|
||||
document.documentElement.dataset.accent = activePreferences.accent;
|
||||
document.documentElement.dataset.navLayout =
|
||||
activePreferences.navLayout === "top" ? "top" : "sidebar";
|
||||
document.documentElement.dataset.showTree = String(isTreeVisible(activePreferences));
|
||||
document.documentElement.dataset.showResizeHandles = String(isResizeVisible(activePreferences));
|
||||
applyAccentVariables(activePreferences);
|
||||
document.documentElement.style.setProperty(
|
||||
"--sidebar-width",
|
||||
`${clamp(
|
||||
Number(activePreferences.sidebarWidth) || defaultPreferences.sidebarWidth,
|
||||
260,
|
||||
getSidebarMinWidth(activePreferences),
|
||||
getSidebarMaxWidth(activePreferences),
|
||||
)}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--top-bar-height",
|
||||
`${clamp(
|
||||
Number(activePreferences.topBarHeight) || defaultPreferences.topBarHeight,
|
||||
getTopBarMinHeight(activePreferences),
|
||||
getTopBarMaxHeight(activePreferences),
|
||||
)}px`,
|
||||
);
|
||||
const treeColumns = getTreeColumns(activePreferences);
|
||||
const sidebarMaxWidth = getSidebarMaxWidth(activePreferences);
|
||||
|
||||
document.documentElement.style.setProperty(
|
||||
"--shell-max-width",
|
||||
activePreferences.treeTwoColumn ? "1920px" : "1560px",
|
||||
`${getShellMaxWidth(activePreferences)}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty("--sidebar-max-width", `${sidebarMaxWidth}px`);
|
||||
document.documentElement.style.setProperty(
|
||||
"--tree-sidebar-base-width",
|
||||
`${activePreferences.treeSidebarBaseWidth}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--sidebar-max-width",
|
||||
`${getSidebarMaxWidth(activePreferences)}px`,
|
||||
"--tree-sidebar-column-width",
|
||||
`${activePreferences.treeSidebarColumnWidth}px`,
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-cards-columns",
|
||||
String(activePreferences.catalogCardsColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-list-columns",
|
||||
String(activePreferences.catalogListColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-compact-cards-columns",
|
||||
String(activePreferences.catalogCompactCardsColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--catalog-compact-list-columns",
|
||||
String(activePreferences.catalogCompactListColumns),
|
||||
);
|
||||
document.documentElement.style.setProperty("--tree-columns", String(treeColumns));
|
||||
catalog.classList.remove("cards-view", "list-view", "compact-cards-view", "compact-list-view");
|
||||
catalog.classList.add(`${activePreferences.view}-view`);
|
||||
document.documentElement.dataset.showNumbers = String(Boolean(activePreferences.showNumbers));
|
||||
document.documentElement.dataset.showTreeTags = String(Boolean(activePreferences.showTreeTags));
|
||||
document.documentElement.dataset.treeTwoColumn = String(Boolean(activePreferences.treeTwoColumn));
|
||||
document.documentElement.dataset.showTreeType = String(Boolean(activePreferences.showTreeType));
|
||||
document.documentElement.dataset.treeMetaLayout = activePreferences.treeMetaLayout;
|
||||
document.documentElement.dataset.treeColumns = String(treeColumns);
|
||||
if (showTreeInput) {
|
||||
showTreeInput.checked = Boolean(activePreferences.showTree);
|
||||
}
|
||||
|
||||
if (showResizeHandlesInput) {
|
||||
showResizeHandlesInput.checked = Boolean(activePreferences.showResizeHandles);
|
||||
}
|
||||
|
||||
showNumbersInput.checked = Boolean(activePreferences.showNumbers);
|
||||
showTreeTagsInput.checked = Boolean(activePreferences.showTreeTags);
|
||||
treeTwoColumnInput.checked = Boolean(activePreferences.treeTwoColumn);
|
||||
if (showTreeTypeInput) {
|
||||
showTreeTypeInput.checked = Boolean(activePreferences.showTreeType);
|
||||
}
|
||||
if (treeColumnsInput) {
|
||||
treeColumnsInput.value = String(treeColumns);
|
||||
}
|
||||
if (treeColumnsValue) {
|
||||
treeColumnsValue.textContent = String(treeColumns);
|
||||
}
|
||||
autoCollapseSearchInput.checked = Boolean(activePreferences.autoCollapseSearch);
|
||||
customAccentFromInput.value = activePreferences.customAccentFrom;
|
||||
customAccentToInput.value = activePreferences.customAccentTo;
|
||||
@ -1133,6 +1461,9 @@ function applyPreferences(nextPreferences) {
|
||||
setActiveButton(navLayoutButtons, "navLayout", activePreferences.navLayout);
|
||||
setActiveButton(viewButtons, "view", activePreferences.view);
|
||||
setActiveButton(accentButtons, "accent", activePreferences.accent);
|
||||
setActiveButton(treeMetaLayoutButtons, "treeMetaLayout", activePreferences.treeMetaLayout);
|
||||
syncLayoutLimitInputs(activePreferences);
|
||||
syncCatalogColumnInputs(activePreferences);
|
||||
applyPanelFeatures(activePreferences);
|
||||
}
|
||||
|
||||
@ -1147,12 +1478,17 @@ function applyPanelFeatures(activePreferences) {
|
||||
}
|
||||
|
||||
if (navTree) {
|
||||
navTree.hidden = activePreferences.navLayout === "top";
|
||||
navTree.hidden = !isTreeVisible(activePreferences);
|
||||
}
|
||||
|
||||
if (sidebarResizer) {
|
||||
sidebarResizer.hidden =
|
||||
activePreferences.navLayout === "top" || !panelFeatures.allowSidebarResize;
|
||||
activePreferences.navLayout === "top" || !isResizeVisible(activePreferences);
|
||||
}
|
||||
|
||||
if (topBarResizer) {
|
||||
topBarResizer.hidden =
|
||||
activePreferences.navLayout !== "top" || !isResizeVisible(activePreferences);
|
||||
}
|
||||
|
||||
if (appLayout) {
|
||||
@ -1163,16 +1499,30 @@ function applyPanelFeatures(activePreferences) {
|
||||
const featureName = element.dataset.feature;
|
||||
element.hidden = featureName ? !panelFeatures[featureName] : false;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-show-when-sidebar]").forEach((element) => {
|
||||
element.hidden = activePreferences.navLayout === "top";
|
||||
});
|
||||
}
|
||||
|
||||
function isSettingsSectionVisible(sectionName, activePreferences) {
|
||||
switch (sectionName) {
|
||||
case "layout":
|
||||
return panelFeatures.allowNavLayoutChange;
|
||||
return (
|
||||
panelFeatures.allowNavLayoutChange ||
|
||||
panelFeatures.allowShowTreeChange ||
|
||||
panelFeatures.allowSidebarResize
|
||||
);
|
||||
case "dimensions":
|
||||
return panelFeatures.allowLayoutLimitsChange;
|
||||
case "catalog":
|
||||
return panelFeatures.allowViewChange || panelFeatures.allowShowNumbers;
|
||||
return (
|
||||
panelFeatures.allowViewChange ||
|
||||
panelFeatures.allowShowNumbers ||
|
||||
panelFeatures.allowCatalogColumnsChange
|
||||
);
|
||||
case "tree":
|
||||
return panelFeatures.allowTreeOptions && activePreferences.navLayout !== "top";
|
||||
return panelFeatures.allowTreeOptions && activePreferences.showTree;
|
||||
case "appearance":
|
||||
return panelFeatures.allowThemeChange || panelFeatures.allowAccentChange;
|
||||
default:
|
||||
@ -1180,6 +1530,14 @@ function isSettingsSectionVisible(sectionName, activePreferences) {
|
||||
}
|
||||
}
|
||||
|
||||
function isTreeVisible(activePreferences) {
|
||||
return Boolean(activePreferences.showTree);
|
||||
}
|
||||
|
||||
function isResizeVisible(activePreferences) {
|
||||
return panelFeatures.allowSidebarResize && Boolean(activePreferences.showResizeHandles);
|
||||
}
|
||||
|
||||
// Применяет CSS-переменные акцента для пресетов или пользовательского градиента.
|
||||
function applyAccentVariables(nextPreferences) {
|
||||
const root = document.documentElement;
|
||||
@ -1331,9 +1689,162 @@ function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
// Максимальная ширина sidebar зависит от режима дерева.
|
||||
function getTreeColumns(nextPreferences) {
|
||||
let columns = Number(nextPreferences.treeColumns);
|
||||
|
||||
if (!Number.isFinite(columns)) {
|
||||
columns = nextPreferences.treeTwoColumn ? 2 : defaultPreferences.treeColumns;
|
||||
}
|
||||
|
||||
return clamp(Math.round(columns), MIN_TREE_COLUMNS, MAX_TREE_COLUMNS);
|
||||
}
|
||||
|
||||
function getLayoutLimitRange(key) {
|
||||
const limits = panelLimits[key] ?? defaultPanelLimits[key];
|
||||
|
||||
if (!limits || !Number.isFinite(limits.min) || !Number.isFinite(limits.max)) {
|
||||
return defaultPanelLimits[key];
|
||||
}
|
||||
|
||||
return {
|
||||
min: Math.min(limits.min, limits.max),
|
||||
max: Math.max(limits.min, limits.max),
|
||||
};
|
||||
}
|
||||
|
||||
function clampLayoutPreference(key, value) {
|
||||
const limits = getLayoutLimitRange(key);
|
||||
const fallback = panelDefaults[key] ?? defaultPreferences[key];
|
||||
const numeric = Number(value);
|
||||
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return clamp(Math.round(numeric), limits.min, limits.max);
|
||||
}
|
||||
|
||||
function normalizePanelLimits(rawLimits) {
|
||||
const next = structuredClone(defaultPanelLimits);
|
||||
|
||||
if (!rawLimits || typeof rawLimits !== "object") {
|
||||
return next;
|
||||
}
|
||||
|
||||
for (const key of panelLimitKeys) {
|
||||
const source = rawLimits[key];
|
||||
|
||||
if (!source || typeof source !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const min = Math.round(Number(source.min));
|
||||
const max = Math.round(Number(source.max));
|
||||
|
||||
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
next[key] = {
|
||||
min: Math.min(min, max),
|
||||
max: Math.max(min, max),
|
||||
};
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function syncLayoutLimitInputs(activePreferences) {
|
||||
layoutLimitInputs.forEach((input) => {
|
||||
const key = input.dataset.layoutLimit;
|
||||
|
||||
if (!layoutLimitKeys.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const limits = getPanelLimitRange(key);
|
||||
input.min = String(limits.min);
|
||||
input.max = String(limits.max);
|
||||
input.step = key === "treeSidebarColumnWidth" ? "5" : "10";
|
||||
input.value = String(activePreferences[key]);
|
||||
input.disabled = !panelFeatures.allowLayoutLimitsChange;
|
||||
|
||||
const output = document.querySelector(`[data-layout-limit-value="${key}"]`);
|
||||
if (output) {
|
||||
output.textContent = String(activePreferences[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function syncCatalogColumnInputs(activePreferences) {
|
||||
catalogColumnInputs.forEach((input) => {
|
||||
const key = input.dataset.catalogColumns;
|
||||
|
||||
if (!catalogColumnKeys.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const limits = getPanelLimitRange(key);
|
||||
input.min = String(limits.min);
|
||||
input.max = String(limits.max);
|
||||
input.step = "1";
|
||||
input.value = String(activePreferences[key]);
|
||||
input.disabled = !panelFeatures.allowCatalogColumnsChange;
|
||||
|
||||
const output = document.querySelector(`[data-catalog-columns-value="${key}"]`);
|
||||
if (output) {
|
||||
output.textContent = String(activePreferences[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getPanelLimitRange(key) {
|
||||
return getLayoutLimitRange(key);
|
||||
}
|
||||
|
||||
function clampCatalogColumn(key, value) {
|
||||
const limits = getPanelLimitRange(key);
|
||||
const fallback = panelDefaults[key] ?? defaultPreferences[key];
|
||||
const numeric = Number(value);
|
||||
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return clamp(Math.round(numeric), limits.min, limits.max);
|
||||
}
|
||||
|
||||
function getSidebarMinWidth(nextPreferences) {
|
||||
return Number(nextPreferences.sidebarMinWidth);
|
||||
}
|
||||
|
||||
function getSidebarMaxWidth(nextPreferences) {
|
||||
return nextPreferences.treeTwoColumn ? 760 : 560;
|
||||
const columns = getTreeColumns(nextPreferences);
|
||||
|
||||
if (columns <= 1) {
|
||||
return Number(nextPreferences.sidebarMaxWidth);
|
||||
}
|
||||
|
||||
return Math.min(
|
||||
Number(nextPreferences.sidebarMaxWidthWide),
|
||||
Number(nextPreferences.treeSidebarBaseWidth) +
|
||||
columns * Number(nextPreferences.treeSidebarColumnWidth),
|
||||
);
|
||||
}
|
||||
|
||||
function getShellMaxWidth(nextPreferences) {
|
||||
return Number(nextPreferences.shellMaxWidth);
|
||||
}
|
||||
|
||||
function getTopBarMinHeight(nextPreferences) {
|
||||
return Number(nextPreferences.topBarMinHeight);
|
||||
}
|
||||
|
||||
function getTopBarMaxHeight(nextPreferences) {
|
||||
return Math.min(
|
||||
Number(nextPreferences.topBarMaxHeight),
|
||||
Math.round(window.innerHeight * 0.75),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@ -3,23 +3,47 @@
|
||||
"data": null,
|
||||
"defaults": {
|
||||
"navLayout": "top",
|
||||
"showTree": false,
|
||||
"theme": "dark",
|
||||
"view": "cards",
|
||||
"accent": "cyan",
|
||||
"showNumbers": false,
|
||||
"showTreeTags": false,
|
||||
"treeTwoColumn": false,
|
||||
"showTreeTags": true,
|
||||
"showTreeType": false,
|
||||
"treeMetaLayout": "inline",
|
||||
"treeColumns": 2,
|
||||
"autoCollapseSearch": true,
|
||||
"sidebarWidth": 390,
|
||||
"topBarHeight": 180,
|
||||
"shellMaxWidth": 2800,
|
||||
"sidebarMinWidth": 260,
|
||||
"sidebarMaxWidth": 800,
|
||||
"sidebarMaxWidthWide": 1400,
|
||||
"treeSidebarBaseWidth": 420,
|
||||
"treeSidebarColumnWidth": 120,
|
||||
"topBarMinHeight": 80,
|
||||
"topBarMaxHeight": 560,
|
||||
"searchPlaceholder": "Поиск сервиса..."
|
||||
},
|
||||
"limits": {
|
||||
"shellMaxWidth": { "min": 1200, "max": 3840 },
|
||||
"sidebarMinWidth": { "min": 200, "max": 480 },
|
||||
"sidebarMaxWidth": { "min": 400, "max": 1200 },
|
||||
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
|
||||
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
|
||||
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
|
||||
"topBarMinHeight": { "min": 64, "max": 240 },
|
||||
"topBarMaxHeight": { "min": 160, "max": 800 }
|
||||
},
|
||||
"features": {
|
||||
"allowNavLayoutChange": true,
|
||||
"allowShowTreeChange": true,
|
||||
"allowSearch": true,
|
||||
"allowViewChange": true,
|
||||
"allowShowNumbers": false,
|
||||
"allowTreeOptions": false,
|
||||
"allowSidebarResize": false,
|
||||
"allowTreeOptions": true,
|
||||
"allowSidebarResize": true,
|
||||
"allowLayoutLimitsChange": true,
|
||||
"allowThemeChange": true,
|
||||
"allowAccentChange": true
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
"data": null,
|
||||
"defaults": {
|
||||
"navLayout": "sidebar",
|
||||
"showTree": true,
|
||||
"showResizeHandles": true,
|
||||
"theme": "dark",
|
||||
"view": "cards",
|
||||
"accent": "violet",
|
||||
@ -11,18 +13,50 @@
|
||||
"customAccentAngle": 135,
|
||||
"showNumbers": false,
|
||||
"showTreeTags": true,
|
||||
"treeTwoColumn": false,
|
||||
"showTreeType": false,
|
||||
"treeMetaLayout": "inline",
|
||||
"treeColumns": 2,
|
||||
"autoCollapseSearch": true,
|
||||
"sidebarWidth": 390,
|
||||
"topBarHeight": 140,
|
||||
"shellMaxWidth": 2800,
|
||||
"sidebarMinWidth": 260,
|
||||
"sidebarMaxWidth": 800,
|
||||
"sidebarMaxWidthWide": 1400,
|
||||
"treeSidebarBaseWidth": 420,
|
||||
"treeSidebarColumnWidth": 120,
|
||||
"topBarMinHeight": 80,
|
||||
"topBarMaxHeight": 560,
|
||||
"catalogCardsColumns": 3,
|
||||
"catalogListColumns": 1,
|
||||
"catalogCompactCardsColumns": 5,
|
||||
"catalogCompactListColumns": 3,
|
||||
"searchPlaceholder": "Grafana, S3, журнал, сервер..."
|
||||
},
|
||||
"limits": {
|
||||
"shellMaxWidth": { "min": 1200, "max": 3840 },
|
||||
"sidebarMinWidth": { "min": 200, "max": 480 },
|
||||
"sidebarMaxWidth": { "min": 400, "max": 1200 },
|
||||
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
|
||||
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
|
||||
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
|
||||
"topBarMinHeight": { "min": 64, "max": 240 },
|
||||
"topBarMaxHeight": { "min": 160, "max": 800 },
|
||||
"catalogCardsColumns": { "min": 1, "max": 6 },
|
||||
"catalogListColumns": { "min": 1, "max": 4 },
|
||||
"catalogCompactCardsColumns": { "min": 2, "max": 8 },
|
||||
"catalogCompactListColumns": { "min": 1, "max": 6 }
|
||||
},
|
||||
"features": {
|
||||
"allowNavLayoutChange": true,
|
||||
"allowShowTreeChange": true,
|
||||
"allowSearch": true,
|
||||
"allowViewChange": true,
|
||||
"allowCatalogColumnsChange": true,
|
||||
"allowShowNumbers": true,
|
||||
"allowTreeOptions": true,
|
||||
"allowSidebarResize": true,
|
||||
"allowLayoutLimitsChange": true,
|
||||
"allowThemeChange": true,
|
||||
"allowAccentChange": true
|
||||
}
|
||||
|
||||
387
styles.css
@ -24,8 +24,13 @@
|
||||
--card-x: 50%;
|
||||
--card-y: 50%;
|
||||
--sidebar-width: 390px;
|
||||
--shell-max-width: 1560px;
|
||||
--sidebar-max-width: 560px;
|
||||
--top-bar-height: 140px;
|
||||
--shell-max-width: 1920px;
|
||||
--sidebar-max-width: 800px;
|
||||
--catalog-cards-columns: 3;
|
||||
--catalog-list-columns: 1;
|
||||
--catalog-compact-cards-columns: 5;
|
||||
--catalog-compact-list-columns: 3;
|
||||
}
|
||||
|
||||
/* Светлая тема переопределяет только токены, структура стилей остается общей. */
|
||||
@ -138,11 +143,15 @@ a {
|
||||
/* Левая колонка получает ширину из --sidebar-width, которую меняет JS. */
|
||||
.app-layout {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 8px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
grid-template-columns: var(--sidebar-width) 6px minmax(0, 1fr);
|
||||
gap: 4px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
html[data-nav-layout="sidebar"][data-show-resize-handles="false"] .app-layout {
|
||||
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
/* Компактная шапка: название + поиск, без дерева и resizer. */
|
||||
html[data-nav-layout="top"] .app-layout,
|
||||
.app-layout[data-nav-layout="top"] {
|
||||
@ -155,17 +164,22 @@ html[data-nav-layout="top"] .sidebar-resizer {
|
||||
|
||||
html[data-nav-layout="top"] .side-panel {
|
||||
position: static;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .side-panel .hero-content {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 12px 16px;
|
||||
max-height: none;
|
||||
min-height: auto;
|
||||
overflow: visible;
|
||||
height: var(--top-bar-height);
|
||||
min-height: var(--top-bar-height);
|
||||
max-height: var(--top-bar-height);
|
||||
overflow: auto;
|
||||
padding: 14px 58px 14px 18px;
|
||||
}
|
||||
|
||||
@ -173,6 +187,13 @@ html[data-nav-layout="top"] .side-panel .hero-content > .eyebrow {
|
||||
margin: 0;
|
||||
flex: 0 1 auto;
|
||||
max-width: min(100%, 360px);
|
||||
align-self: center;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.side-panel .hero-content > .eyebrow#page-title {
|
||||
padding-right: 46px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .search-field {
|
||||
@ -181,22 +202,31 @@ html[data-nav-layout="top"] .search-field {
|
||||
max-width: min(100%, 560px);
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .nav-tree {
|
||||
html[data-show-tree="false"] .nav-tree {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
04. Sidebar Resizer
|
||||
--------------------------------------------------------------------------
|
||||
Узкая колонка между меню и каталогом. Pointer Events в script.js двигают
|
||||
CSS-переменную --sidebar-width и сохраняют значение в localStorage.
|
||||
========================================================================== */
|
||||
html[data-nav-layout="top"][data-show-tree="true"] .nav-tree {
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
max-height: none;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
margin-top: 4px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
html[data-show-tree="false"] .side-panel .hero-content {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
width: 8px;
|
||||
width: 6px;
|
||||
height: calc(100vh - 40px);
|
||||
margin: 0 -1px;
|
||||
border-radius: 999px;
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
@ -224,6 +254,49 @@ body.is-resizing-sidebar {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
04b. Top Bar Resizer
|
||||
--------------------------------------------------------------------------
|
||||
Горизонтальная ручка под шапкой в режиме navLayout: top.
|
||||
========================================================================== */
|
||||
|
||||
.top-bar-resizer {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 999px;
|
||||
cursor: row-resize;
|
||||
background: transparent;
|
||||
transition: background 160ms ease;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
html[data-nav-layout="top"] .top-bar-resizer:not([hidden]) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top-bar-resizer::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
margin: 3px auto;
|
||||
border-radius: inherit;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.top-bar-resizer:hover::before,
|
||||
body.is-resizing-top-bar .top-bar-resizer::before {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
body.is-resizing-top-bar {
|
||||
cursor: row-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
05. Sidebar Panel
|
||||
--------------------------------------------------------------------------
|
||||
@ -308,35 +381,16 @@ html:not([data-show-numbers="true"]) .item-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Теги в дереве управляются toggle-настройкой "Показывать теги дерева". */
|
||||
html[data-show-tree-tags="false"] .tree-node small {
|
||||
/* Теги и тип в дереве управляются настройками showTreeTags / showTreeType. */
|
||||
html[data-show-tree-tags="false"] .tree-tags {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Двухколоночный режим элементов внутри папок дерева. */
|
||||
html[data-tree-two-column="true"] {
|
||||
--shell-max-width: 1920px;
|
||||
--sidebar-max-width: 760px;
|
||||
html[data-show-tree-type="false"] .tree-file-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-items {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 2px 8px;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-node.tree-file {
|
||||
grid-template-columns: 20px minmax(0, 1fr);
|
||||
min-height: 24px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-node-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
html[data-show-tree-tags="false"][data-show-tree-type="false"] .tree-file-meta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -440,13 +494,13 @@ html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settings-panel-head h2 {
|
||||
margin: 4px 0 0;
|
||||
font-size: 1.35rem;
|
||||
letter-spacing: -0.04em;
|
||||
.settings-panel-eyebrow {
|
||||
margin: 0;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-close {
|
||||
@ -463,7 +517,8 @@ html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.settings-section[hidden] {
|
||||
.settings-section[hidden],
|
||||
.settings-section-collapsible[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -483,6 +538,98 @@ html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.settings-section-hint code {
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.catalog-columns-group {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.settings-section-collapsible {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
padding: 10px 12px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.settings-section-collapsible > summary {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.settings-section-collapsible > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-section-summary .settings-section-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-section-collapsible:not([open]) > .settings-section-summary .collapse-indicator {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.settings-section-body {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.layout-limit-row {
|
||||
position: relative;
|
||||
grid-template-columns: minmax(128px, 1.5fr) minmax(0, 2fr) auto;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.layout-limit-row[data-tooltip]::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
bottom: calc(100% + 6px);
|
||||
top: auto;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-strong);
|
||||
color: var(--text);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
box-shadow: var(--shadow);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transform: translateY(4px);
|
||||
transition:
|
||||
opacity 140ms ease,
|
||||
visibility 140ms ease,
|
||||
transform 140ms ease;
|
||||
}
|
||||
|
||||
.layout-limit-row[data-tooltip]:hover::after,
|
||||
.layout-limit-row[data-tooltip]:focus-within::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.layout-limit-row span {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
07. Search Field And Control Inputs
|
||||
--------------------------------------------------------------------------
|
||||
@ -936,13 +1083,91 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
}
|
||||
|
||||
.tree-file {
|
||||
grid-template-columns: 20px minmax(0, 1fr) auto;
|
||||
grid-template-columns: 20px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tree-file::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.tree-file-body {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tree-file-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tree-file-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="inline"] .tree-file-body {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="inline"] .tree-file-title {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="inline"] .tree-file-meta {
|
||||
flex: 0 1 auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
html[data-tree-meta-layout="stacked"] .tree-file-body {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tree-file-type {
|
||||
margin: 0;
|
||||
color: var(--accent);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 750;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tree-tags.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tree-tags.tags span {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 2px 6px;
|
||||
background: rgba(var(--accent-rgb), 0.1);
|
||||
color: var(--muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
html:not([data-tree-columns="1"]) .tree-file {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
html:not([data-tree-columns="1"]) .tree-node-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tree-node-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
@ -955,7 +1180,8 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
.tree-items {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
grid-template-columns: repeat(var(--tree-columns, 1), minmax(0, 1fr));
|
||||
gap: 2px 8px;
|
||||
margin-left: 18px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid var(--border);
|
||||
@ -967,17 +1193,6 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
background: rgba(var(--accent-rgb), 0.14);
|
||||
}
|
||||
|
||||
/* Теги дерева усечены ellipsis, чтобы длинные списки не ломали sidebar. */
|
||||
.tree-node small {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--accent);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
09. Catalog Structure
|
||||
--------------------------------------------------------------------------
|
||||
@ -1155,11 +1370,14 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
|
||||
.items-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.catalog.cards-view .items-grid {
|
||||
grid-template-columns: repeat(var(--catalog-cards-columns, 3), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* Базовая карточка с иконкой, контентом, тегами, стрелкой и shine-слоем. */
|
||||
.nav-card {
|
||||
--tilt-x: 0deg;
|
||||
@ -1321,7 +1539,7 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
========================================================================== */
|
||||
|
||||
.catalog.list-view .items-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(var(--catalog-list-columns, 1), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* В list-режимах карточка вытягивается в строку. */
|
||||
@ -1342,12 +1560,12 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
|
||||
/* Compact modes скрывают второстепенную информацию и уменьшают отступы. */
|
||||
.catalog.compact-cards-view .items-grid {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(var(--catalog-compact-cards-columns, 5), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* Compact list на широких экранах использует 3 колонки. */
|
||||
/* Compact list на широких экранах использует настраиваемое число колонок. */
|
||||
.catalog.compact-list-view .items-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(var(--catalog-compact-list-columns, 3), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.catalog.compact-cards-view .section-head,
|
||||
@ -1466,15 +1684,15 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
/* Узкое окно на устройствах с мышью: sidebar и сетка карточек чуть компактнее. */
|
||||
@media (hover: hover) and (pointer: fine) and (max-width: 1120px) {
|
||||
.app-layout {
|
||||
grid-template-columns: minmax(240px, min(var(--sidebar-width), 320px)) 8px minmax(0, 1fr);
|
||||
grid-template-columns: minmax(240px, min(var(--sidebar-width), 320px)) 6px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.items-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
.catalog.cards-view .items-grid {
|
||||
grid-template-columns: repeat(min(var(--catalog-cards-columns, 3), 2), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.catalog.compact-cards-view .items-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(min(var(--catalog-compact-cards-columns, 5), 3), minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1489,7 +1707,8 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
.sidebar-resizer,
|
||||
.top-bar-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -1509,21 +1728,6 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Двухколоночное дерево на touch не отключается полностью:
|
||||
- в portrait — всегда, если включена настройка;
|
||||
- в landscape — начиная с 520px ширины viewport.
|
||||
*/
|
||||
html[data-tree-two-column="true"] .tree-items {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media (min-width: 520px), (orientation: portrait) {
|
||||
html[data-tree-two-column="true"] .tree-items {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.control-panel,
|
||||
.catalog-section,
|
||||
.empty-state {
|
||||
@ -1567,17 +1771,24 @@ html[data-theme="light"] .accent-swatch-custom {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.items-grid {
|
||||
grid-template-columns: 1fr;
|
||||
.catalog.cards-view .items-grid {
|
||||
grid-template-columns: repeat(min(var(--catalog-cards-columns, 3), 1), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.catalog.list-view .items-grid {
|
||||
grid-template-columns: repeat(min(var(--catalog-list-columns, 1), 1), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.catalog.compact-list-view .items-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(min(var(--catalog-compact-list-columns, 3), 1), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.catalog.compact-cards-view .items-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(min(var(--catalog-compact-cards-columns, 5), 2), minmax(0, 1fr));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.nav-card {
|
||||
|
||||