30/05/2025 commit
This commit is contained in:
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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")">
|
||||||
|
@ -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>
|
||||||
|
@ -64,9 +64,6 @@ class Async1Layout extends ALayout {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cfull">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
_createFHeader() {
|
_createFHeader() {
|
||||||
|
0
VinFontApp/wwwroot/js/libs/CacheManager.js
Normal file
0
VinFontApp/wwwroot/js/libs/CacheManager.js
Normal file
@ -104,13 +104,18 @@ window.getOS = function () {
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.GetAbsoluteURL = function (relativeURL) {
|
window.GetAbsoluteURL = function(relativeURL) {
|
||||||
if (relativeURL.startsWith(window.location.origin)) {
|
if (relativeURL.startsWith(window.location.origin)) {
|
||||||
return relativeURL;
|
return 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) {
|
||||||
|
Reference in New Issue
Block a user