Мапа: підпис ребра більше не друкує «? → ?» і «— з 10.0 Гбіт/с» — невідоме краще не обіцяти. Вузол став карткою з градієнтом, кольоровою смугою стану зліва й крапкою стану праворуч; смуга несе стан навіть тоді, коли рамку перефарбували під майданчик. Пульсація лише за обривом, порти — на наведення. Фон: радіальна підсвітка й дві сітки. Вбудовані шаблони редагуються: правка створює власну копію тенанта з тим самим ключем, вона замінює оригінал у списку, оригінал лишається недоторканим для решти. Плюс окрема кнопка «Копія» з суфіксом у ключі. Ключі елементів і графіків тепер виводяться на сервері — прямий виклик API без key давав 500 від core.slug. Вибір шаблонів у формі хоста став картками з описом і лічильником метрик; обрані спливають угору. Мобільна сітка: min-w-0 на картці. Елемент грід-сітки типово має min-width:auto й не стискається нижче ширини вмісту — картка на 410 px обрізалась у 375 px екрані. Межа помилок навколо полотна: цикл рендеру в React Flow одного разу вже поклав увесь застосунок, тепер падатиме лише мапа. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.4 KiB
TypeScript
53 lines
2.4 KiB
TypeScript
import { Component, type ErrorInfo, type ReactNode } from 'react'
|
||
import { Button } from './ui'
|
||
|
||
/**
|
||
* Межа помилок навколо полотна мапи.
|
||
*
|
||
* Причина конкретна: збій усередині React Flow одного разу вже поклав
|
||
* увесь застосунок — обробник виділення зациклив оновлення, цикл зʼїв
|
||
* головний потік, і мертвими стали ВСІ сторінки, не лише мапа. Людині
|
||
* лишалося перезавантажувати вкладку.
|
||
*
|
||
* Межа не лікує причину — вона обмежує наслідки: полотно падає саме, а
|
||
* навігація й решта інтерфейсу лишаються живими.
|
||
*
|
||
* Класовий компонент, бо іншого способу зловити помилку рендеру в React
|
||
* немає: хуки цього не вміють.
|
||
*/
|
||
export class CanvasBoundary extends Component<
|
||
{ children: ReactNode },
|
||
{ error: Error | null }
|
||
> {
|
||
state: { error: Error | null } = { error: null }
|
||
|
||
static getDerivedStateFromError(error: Error) {
|
||
return { error }
|
||
}
|
||
|
||
componentDidCatch(error: Error, info: ErrorInfo) {
|
||
// У консоль, а не в інтерфейс: стек потрібен тому, хто лагодить, а
|
||
// не тому, хто дивиться на схему мережі.
|
||
console.error('полотно мапи впало', error, info.componentStack)
|
||
}
|
||
|
||
render() {
|
||
if (!this.state.error) return this.props.children
|
||
|
||
return (
|
||
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center">
|
||
<p className="text-sm text-slate-300">Полотно не намалювалося</p>
|
||
<p className="max-w-md text-xs leading-relaxed text-slate-600">
|
||
Решта інтерфейсу працює — можна перейти на іншу сторінку. Подробиці помилки в консолі
|
||
браузера.
|
||
</p>
|
||
<code className="max-w-md truncate rounded border border-slate-800 bg-slate-900 px-2 py-1 text-[11px] text-slate-500">
|
||
{this.state.error.message}
|
||
</code>
|
||
<Button kind="primary" onClick={() => this.setState({ error: null })}>
|
||
Спробувати ще раз
|
||
</Button>
|
||
</div>
|
||
)
|
||
}
|
||
}
|