let index_notif = 0; const Notification = { _container: null, _timeout: 5000, _maxVisible: 5, init({ timeout = 4000, maxVisible = 5, position = 'top-right' } = {}) { this._timeout = timeout; this._maxVisible = maxVisible; // если уже инициализировано — обновляем настройки if (!this._container) { this._container = document.createElement('div'); this._container.className = 'app-notification-container'; this._container.setAttribute('data-position', position); document.body.appendChild(this._container); // стили, вставляем один раз const style = document.createElement('style'); style.textContent = ` .app-notification-container { position: fixed; z-index: 9999; pointer-events: none; display: flex; flex-direction: column; gap: 10px; padding: 12px; } .app-notification-container[data-position="top-right"] { top: 8px; right: 8px; align-items: flex-end; } .app-notification-container[data-position="top-left"] { top: 8px; left: 8px; align-items: flex-start; } .app-notification-container[data-position="bottom-right"] { bottom: 8px; right: 8px; align-items: flex-end; } .app-notification { pointer-events: auto; min-width: 220px; max-width: 360px; background: #111; color: #fff; padding: 10px 12px 10px 12px; border-radius: var(--border-radius); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25); font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; font-size: var(--FontSize2); line-height: 1.2; display: flex; gap: 10px; align-items: center; opacity: 0; transform: translateY(-6px) scale(0.995); transition: opacity .18s ease, transform .18s ease; } .app-notification.show { opacity: 0.95; transform: translateY(0) scale(1); } .app-notification .icon { font-size: 18px; width: 22px; height: 22px; display:flex; align-items:center; justify-content:center; border-radius: calc(var(--border-radius) - 5px); padding: 8px; } .app-notification .icon svg{ width: 20px; height: 20px; fill: #fff; } .app-notification .body { flex:1; } .app-notification .title { font-weight: 600; margin-bottom: 4px; font-size: 13px; } .app-notification .blockClose { width: 20px; } .app-notification .blockClose .close { position: absolute; right: 10px; top: 10px; margin-left: 8px; background: transparent; border: none; color: inherit; cursor: pointer; } .app-notification .blockClose .close svg { width: 15px; height: 15px; fill: #fff; opacity: 0.8; } .app-notification.info { background: var(--ColorThemes3); color: var(--ColorThemes0); } .app-notification.info .icon { background: var(--ColorThemes0); } .app-notification.info .icon svg{fill: var(--ColorThemes3);} .app-notification.info .close svg{fill: var(--ColorThemes0);} .app-notification.success { background: #52ac56; } .app-notification.success .icon { background: #6dc450; } .app-notification.warn { background: #d18515; } .app-notification.warn .icon { background: #eaad57; } .app-notification.error { background: #9c2424; } .app-notification.error .icon { background: #c45050; } @media (max-width: 700px) { .app-notification-container { width: calc(100% - 33px); } .app-notification { width: calc(100% - 30px); border-radius: var(--border-radius); } } `; document.head.appendChild(style); } else { this._container.setAttribute('data-position', position); } }, /** * show(message, options) * message: string or { title, text } * options: { type: 'info'|'success'|'warn'|'error', timeout: ms, title: string } */ // show(message, options = {}) { // if (!this._container) this.init(); // const { type = 'info', timeout = this._timeout, title } = options; // const content = typeof message === 'string' // ? { title: title || '', text: message } // : message; // // 🔥 УДАЛЯЕМ ЛИШНИЕ СРАЗУ // while (this._container.children.length >= this._maxVisible) { // const first = this._container.firstElementChild; // if (first) first.remove(); // ← никаких переходов и setTimeout // else break; // } // const node = document.createElement('div'); // node.className = `app-notification ${type}`; // // иконка (можно менять на svg) // const icons = { info: 'ℹ️', success: '✅', warn: '⚠️', error: '❌' }; // const icon = document.createElement('div'); // icon.className = 'icon'; // icon.innerHTML = `
${icons[type] || icons.info}
`; // const body = document.createElement('div'); // body.className = 'body'; // if (content.title) { // const t = document.createElement('div'); // t.className = 'title'; // t.textContent = content.title; // body.appendChild(t); // } // const txt = document.createElement('div'); // txt.className = 'text'; // txt.textContent = content.text || ''; // body.appendChild(txt); // const closeBtn = document.createElement('button'); // closeBtn.className = 'close'; // closeBtn.setAttribute('aria-label', 'Закрыть уведомление'); // closeBtn.innerHTML = ''; // node.appendChild(icon); // node.appendChild(body); // node.appendChild(closeBtn); // // вставляем в контейнер в конец (новые сверху — ставим в конец, контейнер flex column) // this._container.appendChild(node); // requestAnimationFrame(() => node.classList.add('show')); // let timer = null; // const startTimer = () => { // if (timeout === 0) return; // timer = setTimeout(() => this._removeNode(node), timeout); // }; // const clearTimer = () => { if (timer) { clearTimeout(timer); timer = null; } }; // node.addEventListener('mouseenter', clearTimer); // node.addEventListener('mouseleave', startTimer); // node.querySelector('.close').addEventListener('click', () => { // this._removeNode(node); // }); // startTimer(); // return node; // }, // удалить ноду с анимацией show(message, options = {}) { if (!this._container) this.init(); const { type = 'info', timeout = this._timeout, title, onClick, lock } = options; const content = typeof message === 'string' ? { title: title || '', text: message } : message; // Удаляем лишние уведомления while (this._container.children.length >= this._maxVisible) { const first = this._container.firstElementChild; if (first) first.remove(); else break; } const node = document.createElement('div'); node.className = `app-notification ${type}`; if (onClick) node.style.cursor = "pointer" // Иконка const icons = { info: ``, success: ``, warn: ` `, error: `` }; const icon = document.createElement('div'); icon.className = 'icon'; icon.innerHTML = icons[type] || icons.info; // Тело уведомления const body = document.createElement('div'); body.className = 'body'; if (content.title) { const t = document.createElement('div'); t.className = 'title'; t.textContent = content.title; body.appendChild(t); } const txt = document.createElement('div'); txt.className = 'text'; txt.textContent = content.text || ''; body.appendChild(txt); node.appendChild(icon); node.appendChild(body); // Кнопка закрытия, только если onClick не задан if (!onClick && !lock) { const closeDiv = document.createElement('div'); closeDiv.className = 'blockClose'; node.appendChild(closeDiv); const closeBtn = document.createElement('button'); closeBtn.className = 'close'; closeBtn.setAttribute('aria-label', 'Закрыть уведомление'); closeBtn.innerHTML = ''; closeDiv.appendChild(closeBtn); closeBtn.addEventListener('click', () => this._removeNode(node)); } this._container.appendChild(node); requestAnimationFrame(() => node.classList.add('show')); let timer = null; const startTimer = () => { if (timeout === 0) return; timer = setTimeout(() => this._removeNode(node), timeout); }; const clearTimer = () => { if (timer) { clearTimeout(timer); timer = null; } }; node.addEventListener('mouseenter', clearTimer); node.addEventListener('mouseleave', startTimer); // Обработчик клика для onClick if (typeof onClick === 'function') { node.addEventListener('click', () => { try { onClick(); } catch (e) { } this._removeNode(node); }); } startTimer(); return node; }, _removeNode(node) { if (!node || !node.parentElement) return; node.classList.remove('show'); // дождемся конца transition (примерно 180ms), потом удалим setTimeout(() => { if (node && node.parentElement) node.parentElement.removeChild(node); }, 200); }, // очистить все уведомления clearAll() { if (!this._container) return; Array.from(this._container.children).forEach(n => this._removeNode(n)); }, // удобные обёртки info(message, opts = {}) { return this.show(message, { ...opts, type: 'info' }); }, success(message, opts = {}) { return this.show(message, { ...opts, type: 'success' }); }, warn(message, opts = {}) { return this.show(message, { ...opts, type: 'warn' }); }, error(message, opts = {}) { return this.show(message, { ...opts, type: 'error' }); }, click(message, opts = {}) { return this.show(message, { ...opts, onClick: opts.f }); }, // изменить глобальные настройки во время работы configure({ timeout, maxVisible, position } = {}) { if (typeof timeout === 'number') this._timeout = timeout; if (typeof maxVisible === 'number') this._maxVisible = maxVisible; if (position && this._container) this._container.setAttribute('data-position', position); } }; // Пример использования: // Notification.init({ timeout: 4000, maxVisible: 4 }); // Notification.success('Готово!', { title: 'Успех' }, { timeout: 8000 }); // Notification.error({ title: 'Ошибка', text: 'Не удалось сохранить' }, { timeout: 8000 }); // Notification.info('Информационное сообщение'); // Notification.clearAll(); // Notification.success({ title: `Metrics`, text: `З'єднання встановлено` }, { timeout: 0 }); // Notification.error({ title: `Metrics`, text: `З'єднання встановлено` }, { timeout: 0 }); // Notification.info({ title: `Metrics`, text: `lore` }, { timeout: 0 }); // Notification.warn({ title: `Metrics`, text: `З'єднання встановлено` }, { timeout: 0 }); // Notification.click({ title: `Test`, text: `Натисни, щоб перезавантажити` }, { type: 'info', f: () => Notification.clearAll(), timeout: 0 });