30/05/2025 commit

This commit is contained in:
2025-05-30 13:04:37 +07:00
parent c75d9b72cf
commit 23f5f51329
7 changed files with 248 additions and 64 deletions

View File

@ -5,7 +5,7 @@ namespace VinFontApp.Controllers
{ {
public class TypeFontsController : Controller public class TypeFontsController : Controller
{ {
[PageInfor("2001", "Type Font")] [PageInfor("2001", "Type Font", "/TypeFont/*")]
[Layout(LayoutOptions.Custom, "Async1")] [Layout(LayoutOptions.Custom, "Async1")]
public async Task<IActionResult> Index(string? id) public async Task<IActionResult> Index(string? id)
{ {

View File

@ -7,7 +7,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="idPage" content="@ViewData["PageID"]" pageName="Vin Font" layName="@ViewData["LayoutName"]" /> <meta name="idPage" content="@ViewData["PageID"]" pageName="Vin Font" layName="@ViewData["LayoutName"]" flexPage="@ViewData["FlexPage"]"/>
<title></title> <title></title>
<link rel="icon" type="image/x-icon" href="@Url.AbsoluteContent("~/images/logo/icon.png")" /> <link rel="icon" type="image/x-icon" href="@Url.AbsoluteContent("~/images/logo/icon.png")" />
<link rel="preload" as="script" href="@Url.AbsoluteContent("~/js/libs/js-core.js")"> <link rel="preload" as="script" href="@Url.AbsoluteContent("~/js/libs/js-core.js")">

View File

@ -68,7 +68,7 @@
<ItemGroup> <ItemGroup>
<None Update="Keys\app\VFApp.pfx"> <None Update="Keys\app\VFApp.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Keys\db\Certificate.pfx"> <None Update="Keys\db\Certificate.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View File

@ -64,9 +64,6 @@ class Async1Layout extends ALayout {
</div> </div>
</div> </div>
<div class="cfull">
</div>
</div>`; </div>`;
} }
_createFHeader() { _createFHeader() {

View File

@ -111,6 +111,11 @@ window.GetAbsoluteURL = function (relativeURL) {
return window.location.origin + relativeURL; return window.location.origin + relativeURL;
} }
window.getPathFromUrl = function(url) {
const parsed = new URL(url);
return parsed.pathname + parsed.search + parsed.hash;
}
window.GetEventType = function () { window.GetEventType = function () {
if (isTouchAvailable) { if (isTouchAvailable) {
return "touchend"; return "touchend";
@ -656,16 +661,24 @@ class AApp extends window.AObject {
this.isRedirectPage = true; this.isRedirectPage = true;
this.callLoadPage(window.GetAbsoluteURL(t)); this.callLoadPage(window.GetAbsoluteURL(t));
} }
callLoadPage(url) { callLoadPage(url, flex = false) {
if (flex) {
url = window.getPathFromUrl(url);
this.cachePage.searchFlexPage(url, ((result) => {
if (result) {
}
}).bind(this));
} else {
this.cachePage.get(url, ((result) => { this.cachePage.get(url, ((result) => {
if (result) { if (result) {
this.setContentPage(result, url); // Set content page từ cache this.setContentPage(result, url); // Set content page từ cache
} else { } else {
console.log("connect new");
this.getPage(url); // Load mới nếu chưa có trong cache this.getPage(url); // Load mới nếu chưa có trong cache
} }
}).bind(this)); }).bind(this));
} }
}
loadContentPage(content) { loadContentPage(content) {
const tpl = document.createElement('template'); const tpl = document.createElement('template');
tpl.innerHTML = content; tpl.innerHTML = content;
@ -721,6 +734,7 @@ class AApp extends window.AObject {
title: title, title: title,
idPage: idPage, idPage: idPage,
lName: o.LayoutName, lName: o.LayoutName,
flexPageID: o.FlexPageId,
doc: doc, doc: doc,
dynamicF: dF ? dF.getAttribute("dynamic") : null dynamicF: dF ? dF.getAttribute("dynamic") : null
}; };
@ -827,8 +841,12 @@ class CacheManager {
this.layoutMap = new Map(); this.layoutMap = new Map();
this.readyCallbacks = []; this.readyCallbacks = [];
this.isReady = false; this.isReady = false;
this.flexPages = new FlexPageTrie();
this.countFP = 0;
this._loadIndex(); this._loadIndex();
window.addEventListener("beforeunload", (function (e) {
}).bind(this));
} }
@ -845,7 +863,8 @@ class CacheManager {
} }
_loadIndex() { _loadIndex() {
this.storage.getAllKeys(((keys) => { this.storage.getAllKeys(((keys, type) => {
if (type == "cache") {
let pending = 0; let pending = 0;
keys.forEach(((key) => { keys.forEach(((key) => {
if (!key.startsWith(`${this.storage.prefix}|`)) return; if (!key.startsWith(`${this.storage.prefix}|`)) return;
@ -864,6 +883,9 @@ class CacheManager {
} }
}).bind(this)); }).bind(this));
if (pending === 0) this._finishInit(); // Trường hợp không có key nào là "pI" if (pending === 0) this._finishInit(); // Trường hợp không có key nào là "pI"
} else {
}
}).bind(this)); }).bind(this));
} }
@ -904,7 +926,14 @@ class CacheManager {
}); });
}); });
} }
searchFlexPage(url, callback) {
this._onReady(() => {
callback(this.flexPages.search(url));
});
}
genShortID() {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 5);
}
// Lưu dữ liệu page hoặc layout vào cache // Lưu dữ liệu page hoặc layout vào cache
set(id, obj, type = "page", callback) { set(id, obj, type = "page", callback) {
this._onReady(() => { this._onReady(() => {
@ -915,19 +944,34 @@ class CacheManager {
html: obj.html, html: obj.html,
doc: obj.doc.innerHTML doc: obj.doc.innerHTML
}; };
// Lưu dữ liệu htmlData vào storage
this.storage.set(key, htmlData, function () { });
const info = { const info = {
title: obj.title, title: obj.title,
idPage: obj.idPage, idPage: obj.idPage,
lName: obj.lName, lName: obj.lName,
dynamicF: obj.dynamicF dynamicF: obj.dynamicF
}; };
// Lưu dữ liệu htmlData vào storage
if (obj.flexPageID !== "None") {
const idN = this.genShortID();
this.flexPages.insert(obj.flexPageID, { "id": idN, "info": info });
this.storage.set(idN, htmlData, function () { }, type = "flex");
if (this.countFP > 5) {
this.countFP = 0
}
console.log(this.flexPages);
} else {
this.storage.set(key, htmlData, function () { });
// Lưu thông tin trang vào pageMap và storage // Lưu thông tin trang vào pageMap và storage
this.pageMap.set(id, info); this.pageMap.set(id, info);
this.storage.set(`pI|${id}`, info, function () { }); this.storage.set(`pI|${id}`, info, function () { });
}
} else if (type == "flex") {
} else { } else {
const layout = { const layout = {
Content: obj.Content, Content: obj.Content,
@ -962,7 +1006,13 @@ class CacheStorage {
_initDB() { _initDB() {
const req = indexedDB.open(`${this.prefix}_db`, 1); const req = indexedDB.open(`${this.prefix}_db`, 1);
req.onupgradeneeded = (e) => { req.onupgradeneeded = (e) => {
const store = e.target.result.createObjectStore("cache", { keyPath: "key" }); let store;
if (!e.target.result.objectStoreNames.contains("cache")) {
store = e.target.result.createObjectStore("cache", { keyPath: "key" });
}
if (!e.target.result.objectStoreNames.contains("fpCache")) {
e.target.result.createObjectStore("fpCache", { keyPath: "key" });
}
try { try {
store.add({ id: 1, value: "test data" }); store.add({ id: 1, value: "test data" });
store.delete("1"); store.delete("1");
@ -971,6 +1021,7 @@ class CacheStorage {
this.db = null; this.db = null;
this.useIndexedDB = false; this.useIndexedDB = false;
} }
}; };
req.onsuccess = (event) => { req.onsuccess = (event) => {
this.db = req.result; this.db = req.result;
@ -996,14 +1047,19 @@ class CacheStorage {
else this.queue.push(fn); else this.queue.push(fn);
} }
get(key, callback, f=false) { get(key, callback, f = false, type = "cache") {
this._onReady(() => { this._onReady(() => {
if (this.db) { if (this.db) {
const tx = this.db.transaction("cache", "readonly"); let tx, store, req;
const store = tx.objectStore("cache"); if (type === "flex") {
const req = store.get(f ? key : this._key(key)); tx = this.db.transaction("fpCache", "readonly");
store = tx.objectStore("fpCache");
} else {
tx = this.db.transaction("cache", "readonly");
store = tx.objectStore("cache");
}
req = store.get(f ? key : this._key(key));
req.onsuccess = () => callback(req.result ? req.result.value : null); req.onsuccess = () => callback(req.result ? req.result.value : null);
req.onerror = () => callback(null);
} else { } else {
try { try {
const raw = localStorage.getItem(this._key(key)); const raw = localStorage.getItem(this._key(key));
@ -1018,21 +1074,35 @@ class CacheStorage {
getAllKeys(callback) { getAllKeys(callback) {
this._onReady(() => { this._onReady(() => {
if (this.db) { if (this.db) {
const tx = this.db.transaction("cache", "readonly"); let tx = this.db.transaction("cache", "readonly");
const store = tx.objectStore("cache"); let store = tx.objectStore("cache");
const req = store.getAllKeys(); let req = store.getAllKeys();
req.onsuccess = () => { req.onsuccess = (evt) => {
callback(req.result); callback(evt.target.result, "cache");
};
tx = this.db.transaction("fpCache", "readonly");
store = tx.objectStore("fpCache");
req = store.getAllKeys();
req.onsuccess = (evt) => {
callback(evt.target.result, "flex");
}; };
} }
}); });
} }
set(key, data, callback = () => { }) { set(key, data, callback = () => { }, type="cache") {
this._onReady(() => { this._onReady(() => {
if (this.db) { if (this.db) {
const tx = this.db.transaction("cache", "readwrite"); let tx, store, req;
const store = tx.objectStore("cache"); if (type === "flex") {
tx = this.db.transaction("fpCache", "readwrite");
store = tx.objectStore("fpCache");
store.put({ key: this._key(key), value: data, ts: Date.now() }); store.put({ key: this._key(key), value: data, ts: Date.now() });
} else {
tx = this.db.transaction("cache", "readwrite");
store = tx.objectStore("cache");
store.put({ key: this._key(key), value: data, ts: Date.now() });
}
tx.oncomplete = () => callback(null); tx.oncomplete = () => callback(null);
tx.onerror = (e) => callback(e); tx.onerror = (e) => callback(e);
} else { } else {
@ -1048,9 +1118,8 @@ class CacheStorage {
}); });
} }
remove(key) { remove(key, type = "cache") {
const fullKey = this._key(key); const fullKey = this._key(key);
localStorage.removeItem(fullKey);
if (this.db) { if (this.db) {
try { try {
const tx = this.db.transaction("cache", "readwrite"); const tx = this.db.transaction("cache", "readwrite");
@ -1076,6 +1145,124 @@ class CacheStorage {
} }
} }
} }
class TrieNode {
constructor() {
this.children = new Map(); // segment -> TrieNode
this.paramChild = null; // node chứa ':param'
this.wildcardChild = null; // node chứa '*'
this.paramName = null; // tên param nếu là param node
this.layout = null;
}
}
class FlexPageTrie {
constructor() {
this.root = new TrieNode();
}
insert(path, layout) {
const segments = path.split('/').filter(Boolean);
let node = this.root;
for (const seg of segments) {
if (seg === '*') {
if (!node.wildcardChild) node.wildcardChild = new TrieNode();
node = node.wildcardChild;
} else if (seg.startsWith(':')) {
if (!node.paramChild) node.paramChild = new TrieNode();
node.paramChild.paramName = seg.slice(1);
node = node.paramChild;
} else {
if (!node.children.has(seg)) node.children.set(seg, new TrieNode());
node = node.children.get(seg);
}
}
node.layout = layout;
}
search(url) {
const segments = url.split('/').filter(Boolean);
const result = this._searchRecursive(this.root, segments, 0, {});
return result || null;
}
_searchRecursive(node, segments, index, params) {
if (!node) return null;
if (index === segments.length) {
if (node.layout) {
return { layout: node.layout, params };
}
return null;
}
const seg = segments[index];
// Ưu tiên match chính xác
if (node.children.has(seg)) {
const res = this._searchRecursive(node.children.get(seg), segments, index + 1, { ...params });
if (res) return res;
}
// Match dynamic :param
if (node.paramChild) {
const newParams = { ...params };
newParams[node.paramChild.paramName] = seg;
const res = this._searchRecursive(node.paramChild, segments, index + 1, newParams);
if (res) return res;
}
// Match wildcard *
if (node.wildcardChild) {
const res = this._searchRecursive(node.wildcardChild, segments, index + 1, { ...params });
if (res) return res;
}
return null;
}
_toJSON(node) {
const obj = {
layout: node.layout,
paramName: node.paramName,
children: {},
paramChild: node.paramChild ? this._toJSON(node.paramChild) : null,
wildcardChild: node.wildcardChild ? this._toJSON(node.wildcardChild) : null
};
for (const [key, child] of node.children.entries()) {
obj.children[key] = this._toJSON(child);
}
return obj;
}
toJSON() {
return this._toJSON(this.root);
}
_fromJSON(data) {
const node = new TrieNode();
node.layout = data.layout;
node.paramName = data.paramName;
for (const key in data.children) {
node.children.set(key, this._fromJSON(data.children[key]));
}
if (data.paramChild) {
node.paramChild = this._fromJSON(data.paramChild);
}
if (data.wildcardChild) {
node.wildcardChild = this._fromJSON(data.wildcardChild);
}
return node;
}
fromJSON(data) {
this.root = this._fromJSON(data);
}
}
window.removeStopCollapsed = function () { window.removeStopCollapsed = function () {
if (window.dropdown != null && window.dropdown.currentE != null) { if (window.dropdown != null && window.dropdown.currentE != null) {