diff --git a/VinFontApp/Controllers/TypeFontsController.cs b/VinFontApp/Controllers/TypeFontsController.cs index 1b524ec..6ffbf52 100644 --- a/VinFontApp/Controllers/TypeFontsController.cs +++ b/VinFontApp/Controllers/TypeFontsController.cs @@ -5,7 +5,7 @@ namespace VinFontApp.Controllers { public class TypeFontsController : Controller { - [PageInfor("2001", "Type Font")] + [PageInfor("2001", "Type Font", "/TypeFont/*")] [Layout(LayoutOptions.Custom, "Async1")] public async Task Index(string? id) { diff --git a/VinFontApp/Views/Shared/_Layout.cshtml b/VinFontApp/Views/Shared/_Layout.cshtml index 7c4208d..d4113ce 100644 --- a/VinFontApp/Views/Shared/_Layout.cshtml +++ b/VinFontApp/Views/Shared/_Layout.cshtml @@ -7,7 +7,7 @@ - + diff --git a/VinFontApp/Views/Shared/_LayoutAsync1.cshtml b/VinFontApp/Views/Shared/_LayoutAsync1.cshtml index c09197a..ae249f2 100644 --- a/VinFontApp/Views/Shared/_LayoutAsync1.cshtml +++ b/VinFontApp/Views/Shared/_LayoutAsync1.cshtml @@ -49,9 +49,9 @@ -
- -
+
+ +
diff --git a/VinFontApp/VinFontApp.csproj b/VinFontApp/VinFontApp.csproj index abb40dd..66104dd 100644 --- a/VinFontApp/VinFontApp.csproj +++ b/VinFontApp/VinFontApp.csproj @@ -68,7 +68,7 @@ - PreserveNewest + Always Always diff --git a/VinFontApp/wwwroot/js/js-page/asyncLayout1.js b/VinFontApp/wwwroot/js/js-page/asyncLayout1.js index fb24dc7..3c72894 100644 --- a/VinFontApp/wwwroot/js/js-page/asyncLayout1.js +++ b/VinFontApp/wwwroot/js/js-page/asyncLayout1.js @@ -64,9 +64,6 @@ class Async1Layout extends ALayout { -
- -
`; } _createFHeader() { diff --git a/VinFontApp/wwwroot/js/libs/CacheManager.js b/VinFontApp/wwwroot/js/libs/CacheManager.js new file mode 100644 index 0000000..e69de29 diff --git a/VinFontApp/wwwroot/js/libs/js-core.js b/VinFontApp/wwwroot/js/libs/js-core.js index a040f0e..fd0e32e 100644 --- a/VinFontApp/wwwroot/js/libs/js-core.js +++ b/VinFontApp/wwwroot/js/libs/js-core.js @@ -104,13 +104,18 @@ window.getOS = function () { return os; } -window.GetAbsoluteURL = function (relativeURL) { +window.GetAbsoluteURL = function(relativeURL) { if (relativeURL.startsWith(window.location.origin)) { return 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 () { if (isTouchAvailable) { return "touchend"; @@ -656,15 +661,23 @@ class AApp extends window.AObject { this.isRedirectPage = true; this.callLoadPage(window.GetAbsoluteURL(t)); } - callLoadPage(url) { - this.cachePage.get(url, ((result) => { - if (result) { - this.setContentPage(result, url); // Set content page từ cache - } else { - console.log("connect new"); - this.getPage(url); // Load mới nếu chưa có trong cache - } - }).bind(this)); + 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) => { + if (result) { + this.setContentPage(result, url); // Set content page từ cache + } else { + this.getPage(url); // Load mới nếu chưa có trong cache + } + }).bind(this)); + } } loadContentPage(content) { const tpl = document.createElement('template'); @@ -721,6 +734,7 @@ class AApp extends window.AObject { title: title, idPage: idPage, lName: o.LayoutName, + flexPageID: o.FlexPageId, doc: doc, dynamicF: dF ? dF.getAttribute("dynamic") : null }; @@ -827,8 +841,12 @@ class CacheManager { this.layoutMap = new Map(); this.readyCallbacks = []; this.isReady = false; - + this.flexPages = new FlexPageTrie(); + this.countFP = 0; this._loadIndex(); + window.addEventListener("beforeunload", (function (e) { + + }).bind(this)); } @@ -845,25 +863,29 @@ class CacheManager { } _loadIndex() { - this.storage.getAllKeys(((keys) => { - let pending = 0; - keys.forEach(((key) => { - if (!key.startsWith(`${this.storage.prefix}|`)) return; - - const shortKey = key.replace(`${this.storage.prefix}|`, ""); - const [type, id] = shortKey.split("|"); - - if (type === "pI") { - pending++; - this.storage.get(key, (data) => { - if (data) this.pageMap.set(id, data); - if (--pending === 0) this._finishInit(); - }, true); - } else if (type === "layout") { - this.layoutMap.set(id, true); - } - }).bind(this)); - if (pending === 0) this._finishInit(); // Trường hợp không có key nào là "pI" + this.storage.getAllKeys(((keys, type) => { + if (type == "cache") { + let pending = 0; + keys.forEach(((key) => { + if (!key.startsWith(`${this.storage.prefix}|`)) return; + + const shortKey = key.replace(`${this.storage.prefix}|`, ""); + const [type, id] = shortKey.split("|"); + + if (type === "pI") { + pending++; + this.storage.get(key, (data) => { + if (data) this.pageMap.set(id, data); + if (--pending === 0) this._finishInit(); + }, true); + } else if (type === "layout") { + this.layoutMap.set(id, true); + } + }).bind(this)); + if (pending === 0) this._finishInit(); // Trường hợp không có key nào là "pI" + } else { + + } }).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 set(id, obj, type = "page", callback) { this._onReady(() => { @@ -915,20 +944,35 @@ class CacheManager { html: obj.html, doc: obj.doc.innerHTML }; - - // Lưu dữ liệu htmlData vào storage - this.storage.set(key, htmlData, function () { }); const info = { title: obj.title, idPage: obj.idPage, lName: obj.lName, 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 - this.pageMap.set(id, info); - this.storage.set(`pI|${id}`, info, function () { }); - } else { + + // Lưu thông tin trang vào pageMap và storage + this.pageMap.set(id, info); + this.storage.set(`pI|${id}`, info, function () { }); + } + + + } else if (type == "flex") { + + } else { const layout = { Content: obj.Content, Scripts: obj.Scripts @@ -962,7 +1006,13 @@ class CacheStorage { _initDB() { const req = indexedDB.open(`${this.prefix}_db`, 1); 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 { store.add({ id: 1, value: "test data" }); store.delete("1"); @@ -971,6 +1021,7 @@ class CacheStorage { this.db = null; this.useIndexedDB = false; } + }; req.onsuccess = (event) => { this.db = req.result; @@ -996,14 +1047,19 @@ class CacheStorage { else this.queue.push(fn); } - get(key, callback, f=false) { + get(key, callback, f = false, type = "cache") { this._onReady(() => { if (this.db) { - const tx = this.db.transaction("cache", "readonly"); - const store = tx.objectStore("cache"); - const req = store.get(f ? key : this._key(key)); + let tx, store, req; + if (type === "flex") { + 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.onerror = () => callback(null); } else { try { const raw = localStorage.getItem(this._key(key)); @@ -1018,21 +1074,35 @@ class CacheStorage { getAllKeys(callback) { this._onReady(() => { if (this.db) { - const tx = this.db.transaction("cache", "readonly"); - const store = tx.objectStore("cache"); - const req = store.getAllKeys(); - req.onsuccess = () => { - callback(req.result); + let tx = this.db.transaction("cache", "readonly"); + let store = tx.objectStore("cache"); + let req = store.getAllKeys(); + req.onsuccess = (evt) => { + 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(() => { if (this.db) { - const tx = this.db.transaction("cache", "readwrite"); - const store = tx.objectStore("cache"); - store.put({ key: this._key(key), value: data, ts: Date.now() }); + let tx, store, req; + if (type === "flex") { + tx = this.db.transaction("fpCache", "readwrite"); + store = tx.objectStore("fpCache"); + 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.onerror = (e) => callback(e); } else { @@ -1048,9 +1118,8 @@ class CacheStorage { }); } - remove(key) { + remove(key, type = "cache") { const fullKey = this._key(key); - localStorage.removeItem(fullKey); if (this.db) { try { 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 () { if (window.dropdown != null && window.dropdown.currentE != null) {