Додані повідомлення та перепрацьована структура застосунку та api

This commit is contained in:
2026-03-15 00:25:10 +02:00
parent 85483b85bb
commit 4bc9c11512
101 changed files with 5763 additions and 2546 deletions

View File

@@ -0,0 +1,301 @@
class PwaInstallBanner extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.deferredPrompt = null;
this.STORAGE_KEY = 'PwaInstallBanner'; // Визначаємо ключ localStorage
this.isInStandaloneMode = () => ('standalone' in window.navigator && window.navigator.standalone === true);
this.os = this.detectOS();
}
connectedCallback() {
// Додаємо стилі та розмітку до Shadow DOM
this.shadowRoot.innerHTML = this.getStyles() + this.getTemplate();
this.elements = {
backdrop: this.shadowRoot.getElementById('blur-backdrop'),
installOverlay: this.shadowRoot.getElementById('pwa-install-overlay'),
iosOverlay: this.shadowRoot.getElementById('pwa-ios-overlay'),
installButton: this.shadowRoot.getElementById('pwa-install-button'),
closeButton: this.shadowRoot.getElementById('pwa-close-button'),
iosCloseButton: this.shadowRoot.getElementById('pwa-ios-close-button'),
};
this.setupListeners();
this.checkInitialDisplay();
}
// --- Утиліти ---
detectOS() {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return 'iOS';
}
// ... (можна додати Android, Windows, але для PWA нас цікавить в першу чергу iOS)
return 'Other';
}
shouldShowBanner() {
return localStorage.getItem(this.STORAGE_KEY) !== 'false';
}
checkInitialDisplay() {
if (!this.shouldShowBanner()) {
return; // Не показуємо, якщо localStorage = 'false'
}
// Логіка для iOS
if (this.os === 'iOS' && !this.isInStandaloneMode()) {
// Затримка відображення, як у вихідному коді
setTimeout(() => {
this.openPopup(this.elements.iosOverlay);
}, 1000);
}
}
openPopup(overlayElement) {
this.elements.backdrop.classList.remove('pwa-hidden');
overlayElement.classList.remove('pwa-hidden');
document.body.classList.add('modal-open');
}
closePopup = () => {
this.elements.installOverlay.classList.add('pwa-hidden');
this.elements.iosOverlay.classList.add('pwa-hidden');
this.elements.backdrop.classList.add('pwa-hidden');
document.body.classList.remove('modal-open');
this.deferredPrompt = null;
}
// --- Обробники подій ---
setupListeners() {
window.addEventListener("beforeinstallprompt", this.handleBeforeInstallPrompt);
// Обробники кнопок
this.elements.installButton.addEventListener("click", this.handleInstallClick);
this.elements.closeButton.addEventListener("click", this.closePopup);
this.elements.iosCloseButton.addEventListener('click', this.closePopup);
}
handleBeforeInstallPrompt = (e) => {
// Вихідний код перевіряв localStorage, але для простоти прикладу я її пропускаю.
if (!this.shouldShowBanner()) {
return; // Не показуємо, якщо localStorage = 'false'
}
e.preventDefault();
this.deferredPrompt = e;
// Показуємо стандартний банер, якщо доступно і не в режимі iOS
if (this.os !== 'iOS') {
this.openPopup(this.elements.installOverlay);
}
}
handleInstallClick = async () => {
if (!this.deferredPrompt) return;
this.deferredPrompt.prompt();
const { outcome } = await this.deferredPrompt.userChoice;
console.log(`[APP] Результат встановлення PWA: ${outcome}`);
this.closePopup();
}
// --- Шаблон (Template) та Стилі (Styles) ---
getTemplate() {
// HTML розмітка з вихідного коду
return `
<div id="blur-backdrop" class="pwa-hidden"></div>
<div id="pwa-install-overlay" class="pwa-overlay pwa-hidden">
<div class="popup">
<h2>Встановити застосунок?</h2>
<p>Додайте його на головний екран для швидкого доступу.</p>
<div>
<button id="pwa-install-button">Встановити</button>
<button id="pwa-close-button">Пізніше</button>
</div>
</div>
</div>
<div id="pwa-ios-overlay" class="pwa-overlay pwa-hidden">
<div class="popup">
<h2>Встановлення застосунку</h2>
<p>Щоб встановити застосунок, виконайте наступні кроки:</p>
<ol>
<li>1. Відкрийте посилання в браузері Safari.</li>
<li>
2. Натисніть кнопку
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path
d="M 14.984375 1 A 1.0001 1.0001 0 0 0 14.292969 1.2929688 L 10.292969 5.2929688 A 1.0001 1.0001 0 1 0 11.707031 6.7070312 L 14 4.4140625 L 14 17 A 1.0001 1.0001 0 1 0 16 17 L 16 4.4140625 L 18.292969 6.7070312 A 1.0001 1.0001 0 1 0 19.707031 5.2929688 L 15.707031 1.2929688 A 1.0001 1.0001 0 0 0 14.984375 1 z M 9 9 C 7.3550302 9 6 10.35503 6 12 L 6 24 C 6 25.64497 7.3550302 27 9 27 L 21 27 C 22.64497 27 24 25.64497 24 24 L 24 12 C 24 10.35503 22.64497 9 21 9 L 19 9 L 19 11 L 21 11 C 21.56503 11 22 11.43497 22 12 L 22 24 C 22 24.56503 21.56503 25 21 25 L 9 25 C 8.4349698 25 8 24.56503 8 24 L 8 12 C 8 11.43497 8.4349698 11 9 11 L 11 11 L 11 9 L 9 9 z"
/>
</svg>
</span>
в нижній частині екрана Safari.
</li>
<li>
3. У меню, що з’явиться, виберіть
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M 6 3 C 4.3550302 3 3 4.3550302 3 6 L 3 18 C 3 19.64497 4.3550302 21 6 21 L 18 21 C 19.64497 21 21 19.64497 21 18 L 21 6 C 21 4.3550302 19.64497 3 18 3 L 6 3 z M 6 5 L 18 5 C 18.56503 5 19 5.4349698 19 6 L 19 18 C 19 18.56503 18.56503 19 18 19 L 6 19 C 5.4349698 19 5 18.56503 5 18 L 5 6 C 5 5.4349698 5.4349698 5 6 5 z M 11.984375 6.9863281 A 1.0001 1.0001 0 0 0 11 8 L 11 11 L 8 11 A 1.0001 1.0001 0 1 0 8 13 L 11 13 L 11 16 A 1.0001 1.0001 0 1 0 13 16 L 13 13 L 16 13 A 1.0001 1.0001 0 1 0 16 11 L 13 11 L 13 8 A 1.0001 1.0001 0 0 0 11.984375 6.9863281 z"
/>
</svg>
</span>
«На Початковий екран».
</li>
</ol>
<div>
<button id="pwa-ios-close-button">Зрозуміло</button>
</div>
</div>
</div>
`;
}
getStyles() {
// CSS стилі, які були у вихідному коді, але адаптовані для Shadow DOM
// Примітки:
// 1. Змінні CSS (наприклад, --ColorThemes0) мають бути визначені в основному документі
// або передані через властивості, інакше вони не працюватимуть в Shadow DOM.
// Я залишаю їх як є, припускаючи, що вони глобально доступні.
// 2. Стилі для body.modal-open потрібно додати в основний CSS.
return `
<style>
#blur-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(8px);
z-index: 9998;
}
.pwa-overlay {
position: fixed;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.pwa-overlay>.popup {
background: var(--ColorThemes0, #ffffff); /* Fallback */
padding: 24px 32px;
border-radius: var(--border-radius, 15px);
max-width: 90%;
width: 320px;
text-align: center;
font-family: sans-serif;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
animation: fadeIn 0.3s ease-out;
display: flex;
flex-direction: column;
align-items: center;
}
.pwa-overlay>.popup h2 {
margin-bottom: 12px;
color: var(--ColorThemes3, #333);
opacity: 0.8;
}
.pwa-overlay>.popup p {
margin-bottom: 10px;
color: var(--ColorThemes3, #333);
opacity: 0.6;
}
.pwa-overlay>.popup ol {
text-align: justify;
font-size: var(--FontSize4, 15px);
margin-bottom: 10px;
max-width: 290px;
padding-left: 0; /* Виправлення відступу списку */
}
.pwa-overlay>.popup li {
list-style-type: none;
font-size: var(--FontSize3, 14px);
margin-bottom: 8px;
}
.pwa-overlay>.popup li span {
vertical-align: middle;
display: inline-block;
width: 22px;
height: 22px;
}
.pwa-overlay>.popup li span svg {
fill: var(--PrimaryColor, #007bff);
}
.pwa-overlay>.popup>div {
margin-top: 10px;
display: flex;
justify-content: center;
gap: 10px;
}
.pwa-overlay>.popup>div>button {
padding: 8px 16px;
border: none;
border-radius: calc(var(--border-radius, 15px) - 8px);
cursor: pointer;
font-size: var(--FontSize3, 14px);
}
#pwa-install-button {
background-color: var(--PrimaryColor, #007bff);
color: var(--PrimaryColorText, #ffffff);
}
#pwa-close-button,
#pwa-ios-close-button {
background-color: #ccc;
color: #333;
}
.pwa-hidden {
display: none !important; /* Важливо для скриптів */
}
@media (max-width: 450px) {
.pwa-overlay>.popup {
padding: 17px 10px;
}
.pwa-overlay>.popup h2 {
font-size: 22px;
}
.pwa-overlay>.popup p {
font-size: var(--FontSize4, 15px);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
</style>
`;
}
}
// Реєстрація веб-компонента
customElements.define('pwa-install-banner', PwaInstallBanner);