You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/oaer/lib/v2dom/proxy.ts

162 lines
4.1 KiB
TypeScript

/*
* proxy.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-23 17:20
* Distributed under terms of the GPL license.
*/
type voidFn = () => void
const callbackCache: ([boolean, voidFn])[] = []
const cacheUpdateList: number[] = []
// 界面更新响应频率40hz
setInterval(() => {
let list = new Set(cacheUpdateList.splice(0))
for (let l of list) {
if (callbackCache[l][0]) {
callbackCache[l][1]()
}
}
}, 25)
// TODO: 已删除的元素有可能会重新添加回来,
// // 绑定事件删除
//
// setInterval(() => {
// let exists = new Set()
// let check = (dom: Element) => {
// let ids = dom.getAttribute('vbind-fns')?.split(',')
// if (ids?.length) {
// for (let i of ids) {
// exists.add(i)
// if (!callbackCache[Number(i)][0]) {
// callbackCache[Number(i)][0] = true
// }
// }
// }
// for (let child of dom.children) {
// check(child)
// }
// }
// check(document.body)
// for (let i in callbackCache) {
// if (!exists.has(i)) {
// // 只使能在页面上的更新
// callbackCache[i][0] = false
// // console.log('remove ' + i)
// }
// }
// }, 1000)
function GenUniqueID() {
const timestamp = performance.now().toString(36);
const random = Math.random().toString(36).substring(2, 5);
return `${timestamp}-${random}`;
}
function ForceUpdate() {
for (let c of callbackCache) {
if (c[0]) {
c[1]()
}
}
}
var listen_tags: number[] = []
function Listen(callback: voidFn): number {
let idx = callbackCache.length
listen_tags.push(idx)
callbackCache.push([true, callback])
callback()
listen_tags.pop()
return idx
}
function DomListen(dom: HTMLElement, fn: () => void) {
let fns = dom.getAttribute('vbind-fns')
let fnid = Listen(fn)
if (fns) {
fns += ',' + fnid
} else {
fns = String(fnid)
}
dom.setAttribute('vbind-fns', fns)
}
const isProxy = Symbol("isProxy")
const DataID = Symbol("DataID")
function Watch<T extends Object>(data: T) {
const did = GenUniqueID()
let isArray = false
if (Object.prototype.toString.call(data) === '[object Array]') {
isArray = true
}
// console.log(`watch ${did} ${isArray}`, data)
const listeners: { [key: string | symbol]: number[] } = {}
const handler = {
get(target: Object, key: string | symbol, receiver: any) {
if (key === isProxy) {
return true
}
if (key === DataID) {
return did
}
const value = Reflect.get(target, key, receiver)
if (typeof value === 'object' && value !== null) {
if (value[isProxy]) {
return value
} else {
let newValue = Watch(value)
Reflect.set(target, key, newValue, receiver)
return newValue
}
}
let idx = -1
if (listen_tags.length > 0) {
let lkey = key
idx = listen_tags[listen_tags.length - 1]
if (isArray) {
lkey = ''
}
if (!listeners[lkey]) {
listeners[lkey] = [idx]
} else if (listeners[lkey].indexOf(idx) == -1) {
listeners[lkey].push(idx)
}
}
// console.log(`${did} get ${key.toString()}:${value} ${idx}`)
return value;
},
set(target: Object, key: string | symbol, newValue: any, receiver: any) {
// console.log(`${did} set ${key.toString()} ${newValue}`)
const result = Reflect.set(target, key, newValue, receiver);
if (result) {
let lkey = key
if (isArray) {
lkey = ''
}
if (listeners[lkey]) {
for (let cb of listeners[lkey]) {
cacheUpdateList.push(cb)
}
}
}
return result;
},
deleteProperty(target: Object, key: string) {
// console.log(`del ${key}`)
const result = Reflect.deleteProperty(target, key);
if (result) {
}
return result
}
};
let res = new Proxy<T>(data, handler);
// Symbol(Symbol.toStringTag)
// res[Symbol.toStringTag] = 'Proxy'
return res
}
export default { Watch, Listen, DomListen, ForceUpdate, DataID, GenUniqueID, }