Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixUI/hesabix_ui/web/index.html

550 lines
20 KiB
HTML
Executable file

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<script>
/* بلافاصله lang/dir را از تنظیمات ذخیره‌شده اعمال کن (پیش‌فرض: فارسی RTL) */
(function () {
function parseStored(raw) {
if (raw == null || raw === '') return null;
try { return JSON.parse(raw); } catch (e) { return raw; }
}
function langFromStorage() {
var keys = ['hesabix_locale', 'flutter.app_locale_code'];
for (var i = 0; i < keys.length; i++) {
var raw = null;
try { raw = localStorage.getItem(keys[i]); } catch (e) {}
if (!raw) continue;
var v = parseStored(raw);
var s = String(v != null ? v : raw).trim();
var code = s.split('-')[0].toLowerCase().replace(/^["']|["']$/g, '');
if (code === 'en') return 'en';
if (code === 'fa') return 'fa';
}
return 'fa';
}
var loc = langFromStorage();
var html = document.documentElement;
html.setAttribute('lang', loc === 'en' ? 'en' : 'fa');
html.setAttribute('dir', loc === 'en' ? 'ltr' : 'rtl');
})();
</script>
<!-- Purge legacy Flutter service workers once per deploy (old SW + cache can mismatch CanvasKit). -->
<script>
(function () {
if (!('serviceWorker' in navigator)) return;
var purgeKey = 'hesabix_sw_purged_for';
fetch('version.json?t=' + Date.now(), { cache: 'no-store', credentials: 'same-origin' })
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (j) {
var build = j && typeof j.build === 'string' ? j.build : '';
if (build && localStorage.getItem(purgeKey) === build) return;
return navigator.serviceWorker.getRegistrations().then(function (regs) {
var hadSw = regs.length > 0;
var tasks = regs.map(function (reg) { return reg.unregister(); });
return Promise.all(tasks).then(function () {
if ('caches' in window) {
return caches.keys().then(function (keys) {
return Promise.all(keys.map(function (k) { return caches.delete(k); }));
});
}
}).then(function () {
if (build) {
try { localStorage.setItem(purgeKey, build); } catch (e) {}
}
if (hadSw) window.location.reload();
});
});
})
.catch(function () {});
})();
</script>
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="Hesabix - سیستم مدیریت مالی">
<!-- viewport-fit=cover برای مدیریت بهتر فضای پایین صفحه روی موبایل (خصوصاً iOS) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Hesabix">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="icon" type="image/png" href="favicon.png"/>
<title>Hesabix</title>
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#0F4C81" id="hesabix-theme-color">
<script>
/* اعمال فوری رنگ برند ذخیره‌شده (قبل از لودر) برای theme-color و CSS vars */
(function () {
function parseStored(raw) {
if (raw == null || raw === '') return null;
try { return JSON.parse(raw); } catch (e) { return raw; }
}
function readBrand() {
try {
var raw = localStorage.getItem('hesabix_theme_brand');
var v = parseStored(raw);
var s = String(v != null ? v : (raw || '')).trim();
if (/^#[0-9A-Fa-f]{6}$/.test(s)) return s.toUpperCase();
} catch (e) {}
return null;
}
var brand = readBrand();
if (!brand) return;
var meta = document.getElementById('hesabix-theme-color');
if (meta) meta.setAttribute('content', brand);
var root = document.documentElement;
root.style.setProperty('--brand', brand);
try {
var r = parseInt(brand.slice(1, 3), 16);
var g = parseInt(brand.slice(3, 5), 16);
var b = parseInt(brand.slice(5, 7), 16);
root.style.setProperty('--brand-soft', 'rgba(' + r + ',' + g + ',' + b + ',0.14)');
root.style.setProperty('--loader-fill', brand);
} catch (e) {}
})();
</script>
<link rel="preload" href="fonts/YekanBakhFaNum-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="fonts/YekanBakhFaNum-SemiBold.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="hesabix_web_loader.css" as="style">
<link rel="preload" href="hesabix_web_loader.js" as="script">
<link rel="preconnect" href="https://hsxn.hesabix.ir" crossorigin>
<link rel="dns-prefetch" href="https://hsxn.hesabix.ir">
<link rel="preload" href="flutter_bootstrap.js" as="script">
<link rel="preload" href="flutter_web_preload.js" as="script">
<link rel="preload" href="images/logo-light.png" as="image" type="image/png">
<link rel="stylesheet" href="hesabix_web_loader.css">
<!-- Suppress Intl.v8BreakIterator deprecation warning -->
<script>
// Override console.warn to filter out Intl.v8BreakIterator deprecation warnings
const originalWarn = console.warn;
console.warn = function(...args) {
const message = args.join(' ');
if (message.includes('Intl.v8BreakIterator is deprecated') ||
message.includes('Please use Intl.Segmenter instead')) {
return; // Suppress this specific warning
}
originalWarn.apply(console, args);
};
</script>
<!-- pdf.js برای پیش‌نمایش چاپ (بستهٔ printing) در وب؛ بدون آن canRaster غیرفعال است -->
<script>
var dartPdfJsVersion = "3.2.146";
</script>
</head>
<body>
<script src="hesabix_web_loader.js"></script>
<div id="flutter-loading-screen" role="status" aria-live="polite" aria-busy="true">
<div class="loader-center">
<div class="loader-logo-wrap">
<img
class="loader-logo"
src="images/logo-light.png"
width="44"
height="44"
alt="Hesabix"
decoding="async"
fetchpriority="high" />
</div>
<h1 class="loader-title" id="loader-title">حسابیکس</h1>
<p class="loader-tagline" id="loader-tagline">حسابداری ابری هوشمند برای کسب‌وکار شما</p>
<div class="loader-progress-block">
<div class="loader-progress-meta">
<span class="loader-step" id="loading-step"></span>
<span class="loader-percent" id="loading-percent"></span>
</div>
<div class="loader-progress-track is-indeterminate" id="loading-progress-wrap">
<div class="loader-progress-bar" id="loading-progress-bar"></div>
</div>
</div>
<p class="loader-status" id="loading-status">در حال بارگذاری…</p>
<div class="loader-spinner" id="loader-spinner" hidden aria-hidden="true"></div>
<p class="loader-quote" id="loading-quote" aria-hidden="true">
<span id="loading-quote-text"></span>
</p>
<div id="loader-retry-wrap" hidden>
<button type="button" id="loader-retry-btn" onclick="window.location.reload()">تلاش مجدد</button>
</div>
</div>
</div>
<script>
// همگام‌سازی ارتفاع صفحه با visual viewport برای جلوگیری از فضای سفید زیرین هنگام نمایش کیبورد
(function() {
function updateAppHeight() {
try {
var height;
if (window.visualViewport && window.visualViewport.height) {
height = window.visualViewport.height;
} else {
height = window.innerHeight || document.documentElement.clientHeight;
}
document.documentElement.style.setProperty('--app-height', height + 'px');
} catch (e) {
// در صورت بروز خطا، تغییری اعمال نکنیم تا رفتار پیش‌فرض حفظ شود
console.warn('[Hesabix] Failed to update app height', e);
}
}
// اجرای اولیه
window.addEventListener('load', updateAppHeight);
// تغییر اندازه (چرخش، باز و بسته شدن کیبورد و غیره)
window.addEventListener('resize', updateAppHeight);
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', updateAppHeight);
window.visualViewport.addEventListener('scroll', updateAppHeight);
}
})();
// Loader hide: توسط Flutter با __hesabixSignalAppReady() — نه flutter-first-frame
// پیکربندی Flutter — intercept loader قبل از اجرای flutter_bootstrap
if (!window._flutter) {
window._flutter = {};
}
// ذخیره تابع اصلی load برای override کردن بعداً
window._flutterConfig = {
canvasKitBaseUrl: "canvaskit/",
renderer: "canvaskit",
useLocalCanvasKit: true,
// فونت‌های fallback موتور (و Roboto اضطراری) از همین مبدأ؛ نه fonts.gstatic.com
fontFallbackBaseUrl: (function () {
try {
return new URL("fonts/gstatic/s/", document.baseURI).href;
} catch (e) {
return "fonts/gstatic/s/";
}
})()
};
// Intercept loader.load() برای اضافه کردن configuration
Object.defineProperty(window._flutter, 'loader', {
set: function(loader) {
this._loaderInstance = loader;
// Override load method
if (loader && typeof loader.load === 'function') {
const originalLoad = loader.load;
loader.load = function(options) {
options = options || {};
options.config = options.config || {};
// اعمال configuration محلی
Object.assign(options.config, window._flutterConfig);
console.log('[Flutter] Local CanvasKit + local fontFallbackBaseUrl (no fonts.gstatic.com)');
return originalLoad.call(loader, options);
};
}
},
get: function() {
return this._loaderInstance;
},
configurable: true
});
</script>
<!-- به‌روزرسانی: نسخهٔ سرور (version.json) + Service Worker (آفلاین حفظ می‌شود) -->
<script>
(function() {
var STORAGE_BUILD = 'hesabix_web_build';
var SESSION_PENDING = 'hesabix_pending_build';
var SESSION_NO_SW_RELOAD = 'hesabix_no_sw_build_reload';
var UPDATE_BANNER_ID = 'flutter-update-banner';
var hasReloadedForUpdate = false;
var VERSION_POLL_MS = 5 * 60 * 1000;
function storageGet(key) {
try {
return localStorage.getItem(key);
} catch (e) {
return null;
}
}
function storageSet(key, val) {
try {
localStorage.setItem(key, val);
} catch (e) {}
}
function sessionGet(key) {
try {
return sessionStorage.getItem(key);
} catch (e) {
return null;
}
}
function sessionSet(key, val) {
try {
sessionStorage.setItem(key, val);
} catch (e) {}
}
function sessionRemove(key) {
try {
sessionStorage.removeItem(key);
} catch (e) {}
}
function commitPendingBuildBeforeReload() {
var pb = sessionGet(SESSION_PENDING);
if (pb) {
storageSet(STORAGE_BUILD, pb);
sessionRemove(SESSION_PENDING);
}
}
function createUpdateBanner() {
if (document.getElementById(UPDATE_BANNER_ID)) {
return;
}
var defaultFont = (window.getComputedStyle && window.getComputedStyle(document.body).fontFamily) || "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif";
var banner = document.createElement('div');
banner.id = UPDATE_BANNER_ID;
banner.style.position = 'fixed';
banner.style.bottom = '16px';
banner.style.left = '50%';
banner.style.transform = 'translateX(-50%)';
banner.style.background = '#0175C2';
banner.style.color = '#ffffff';
banner.style.padding = '12px 16px';
banner.style.borderRadius = '14px';
banner.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.25)';
banner.style.zIndex = '10000';
banner.style.display = 'flex';
banner.style.alignItems = 'center';
banner.style.gap = '12px';
banner.style.fontFamily = defaultFont;
banner.style.fontSize = '14px';
banner.style.direction = 'rtl';
banner.style.width = 'calc(100% - 24px)';
banner.style.maxWidth = '520px';
banner.style.flexWrap = 'wrap';
banner.style.justifyContent = 'space-between';
var text = document.createElement('span');
text.textContent = 'نسخه جدید حسابیکس در حال اعمال است، صفحه به زودی به‌روزرسانی می‌شود.';
text.style.flex = '1';
text.style.fontFamily = 'inherit';
var closeBtn = document.createElement('button');
closeBtn.textContent = 'بستن';
closeBtn.style.background = 'transparent';
closeBtn.style.color = '#ffffff';
closeBtn.style.border = '1px solid rgba(255, 255, 255, 0.6)';
closeBtn.style.borderRadius = '10px';
closeBtn.style.padding = '6px 12px';
closeBtn.style.cursor = 'pointer';
closeBtn.style.fontWeight = '600';
closeBtn.style.fontSize = '12px';
closeBtn.style.opacity = '0.9';
closeBtn.style.fontFamily = 'inherit';
closeBtn.addEventListener('click', function() {
try {
document.body.removeChild(banner);
} catch (e) {}
});
banner.appendChild(text);
banner.appendChild(closeBtn);
document.body.appendChild(banner);
}
function fetchServerBuild() {
var url = 'version.json?t=' + Date.now();
return fetch(url, { cache: 'no-store', credentials: 'same-origin' })
.then(function(r) {
if (!r.ok) return null;
return r.json();
})
.then(function(j) {
return j && typeof j.build === 'string' ? j.build : null;
})
.catch(function() {
return null;
});
}
function tryActivateWaitingWorker() {
return navigator.serviceWorker.getRegistration().then(function(reg) {
if (reg && reg.waiting) {
reg.waiting.postMessage('skipWaiting');
return true;
}
return false;
});
}
/** اگر waiting باشد skipWaiting؛ در غیر این صورت فقط در مسیر قدیمی SW به reload برگردیم تا از حلقه جلوگیری شود */
function requestAppUpdate(allowReloadFallback) {
navigator.serviceWorker.getRegistration().then(function(reg) {
if (reg && reg.waiting) {
reg.waiting.postMessage('skipWaiting');
} else if (allowReloadFallback) {
window.location.reload();
}
}).catch(function() {
if (allowReloadFallback) {
window.location.reload();
}
});
}
function onServerNewerBuild(reg, serverBuild) {
sessionSet(SESSION_PENDING, serverBuild);
createUpdateBanner();
if (reg) {
reg.update()
.then(function() {
return tryActivateWaitingWorker();
})
.then(function(activated) {
if (activated) return;
return new Promise(function(resolve) {
setTimeout(resolve, 400);
}).then(function() {
return tryActivateWaitingWorker();
});
})
.then(function(activated) {
if (activated) return;
return new Promise(function(resolve) {
setTimeout(resolve, 1600);
}).then(function() {
return tryActivateWaitingWorker();
});
})
.catch(function() {});
} else {
if (!sessionGet(SESSION_NO_SW_RELOAD)) {
sessionSet(SESSION_NO_SW_RELOAD, '1');
storageSet(STORAGE_BUILD, serverBuild);
window.location.reload();
}
}
}
function runVersionCheck(reg) {
fetchServerBuild().then(function(serverBuild) {
if (!serverBuild) return;
var stored = storageGet(STORAGE_BUILD);
if (stored === null || stored === '') {
storageSet(STORAGE_BUILD, serverBuild);
return;
}
if (stored === serverBuild) {
sessionRemove(SESSION_NO_SW_RELOAD);
return;
}
onServerNewerBuild(reg, serverBuild);
});
}
function startVersionPolling(reg) {
runVersionCheck(reg);
setInterval(function() {
if (document.visibilityState === 'visible') {
runVersionCheck(reg);
}
}, VERSION_POLL_MS);
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') {
runVersionCheck(reg);
}
});
}
function setupServiceWorkerUpdateFlow() {
navigator.serviceWorker.addEventListener('controllerchange', function() {
if (hasReloadedForUpdate) return;
hasReloadedForUpdate = true;
commitPendingBuildBeforeReload();
window.location.reload();
});
navigator.serviceWorker.ready
.then(function(reg) {
if (!reg) return;
startVersionPolling(reg);
function handleWaiting(worker) {
if (!worker) return;
if (navigator.serviceWorker.controller) {
createUpdateBanner();
requestAppUpdate(true);
}
}
if (reg.waiting) {
handleWaiting(reg.waiting);
}
reg.addEventListener('updatefound', function() {
var newWorker = reg.installing;
if (!newWorker) return;
newWorker.addEventListener('statechange', function() {
if (newWorker.state === 'installed') {
handleWaiting(reg.waiting || newWorker);
}
});
});
})
.catch(function() {});
}
window.addEventListener('load', function() {
if ('serviceWorker' in navigator) {
setupServiceWorkerUpdateFlow();
} else {
startVersionPolling(null);
}
});
})();
</script>
<script src="hesabix_voice_capture.js"></script>
<script src="flutter_web_preload.js" defer></script>
<script src="flutter_bootstrap.js" defer></script>
</body>
</html>