fix: tooltip issue about customer follower

change: Grid.export optimized (compression with `deflate`)
feature: ui-tab
feature: Popup.closable
This commit is contained in:
2024-03-12 13:36:54 +08:00
parent f54eb3ac24
commit e07342a257
10 changed files with 160 additions and 43 deletions

View File

@ -70,6 +70,10 @@ $buttonHeight: 28px;
cursor: move;
}
>.ui-popup-header-title.no-move {
cursor: default;
}
>.ui-popup-header-icons {
flex: 0 0 auto;
padding: 10px 12px 6px 0;

47
lib/ui/css/tab.scss Normal file
View File

@ -0,0 +1,47 @@
.ui-tab-container {
display: flex;
flex-direction: column;
--header-line-height: 24px;
--header-padding: 6px 6px 0;
--header-hover-color: #b9b9b9;
--header-selected-color: rgb(173, 84, 12);
>.ui-tab-header {
flex: 0 0 auto;
display: flex;
flex-wrap: wrap;
line-height: var(--header-line-height);
padding: var(--header-padding);
border-bottom: 1px solid var(--border-color);
user-select: none;
>.ui-tab-title {
border: 1px solid transparent;
background-color: var(--bg-color);
cursor: pointer;
padding: 0 12px;
&:hover {
color: var(--header-hover-color);
}
&.selected {
border-color: var(--border-color);
border-bottom-color: var(--bg-color);
color: var(--header-selected-color);
}
}
}
>.ui-tab-page {
flex: 1 1 auto;
padding: 6px;
overflow: auto;
width: 100%;
height: 100%;
box-sizing: border-box;
position: relative;
display: none;
}
}

View File

@ -16,6 +16,8 @@ import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCh
* @version 1.0.2
*/
const ScriptPath = (self.document == null ? self.location.href : self.document.currentScript.src).replace(/\?.+$/, '');
const ColumnChangedType = {
Reorder: 'reorder',
Resize: 'resize',
@ -2092,7 +2094,7 @@ export class Grid {
* }>
* }
* ```
* @param {boolean} compressed - 是否压缩
* @param {boolean} [compressed=true] - 是否采用 deflate 压缩
* @returns {Promise<Uint8Array>} 返回 `Uint8Array` 数据对象
* @since 1.0.2
*/
@ -2150,15 +2152,14 @@ export class Grid {
sortKey: this.sortKey,
sortArray: this.sortArray
};
const json = JSON.stringify(data);
const uncompressed = json => new TextEncoder('utf-8').encode(json);
if (compressed === false) {
const encoder = new TextEncoder();
const uncompressed = encoder.encode(JSON.stringify(data));
return Promise.resolve(uncompressed);
return Promise.resolve(uncompressed(json));
}
const js = new Blob(['function h(e,t,o){if(null==e)return"";let r;const l={},h={};let s="",n="",p="",a=2,f=3,c=2;const u=[];let i=0,d=0;for(let w=0;w<e.length;w+=1)if(s=e.charAt(w),Object.prototype.hasOwnProperty.call(l,s)||(l[s]=f++,h[s]=!0),n=p+s,Object.prototype.hasOwnProperty.call(l,n))p=n;else{if(Object.prototype.hasOwnProperty.call(h,p)){if(p.charCodeAt(0)<256){for(let e=0;e<c;e++)i<<=1,d==t-1?(d=0,u.push(o(i)),i=0):d++;r=p.charCodeAt(0);for(let e=0;e<8;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}else{r=1;for(let e=0;e<c;e++)i=i<<1|r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r=0;r=p.charCodeAt(0);for(let e=0;e<16;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++),delete h[p]}else{r=l[p];for(let e=0;e<c;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++),l[n]=f++,p=String(s)}if(""!==p){if(Object.prototype.hasOwnProperty.call(h,p)){if(p.charCodeAt(0)<256){for(let e=0;e<c;e++)i<<=1,d==t-1?(d=0,u.push(o(i)),i=0):d++;r=p.charCodeAt(0);for(let e=0;e<8;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}else{r=1;for(let e=0;e<c;e++)i=i<<1|r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r=0;r=p.charCodeAt(0);for(let e=0;e<16;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++),delete h[p]}else{r=l[p];for(let e=0;e<c;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++)}r=2;for(let e=0;e<c;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1;let w=!0;do{i<<=1,d==t-1?(u.push(o(i)),w=!1):d++}while(w);return u.join("")}function s(e){return null==e?"":h(e,16,e=>String.fromCharCode(e))}function i(e){const t=s(e),o=new Uint8Array(2*t.length);for(let e=0,r=t.length;e<r;e++){const r=t.charCodeAt(e);o[2*e]=r>>>8,o[2*e+1]=r%256}return o}self.addEventListener("message",function(e){this.self.postMessage(i(e.data))},!1);']);
return new Promise((resolve, reject) => {
return new Promise(resolve => {
let working;
const url = URL.createObjectURL(js);
const url = URL.createObjectURL(new Blob([`let wasm,WASM_VECTOR_LEN=0,encoder=new TextEncoder("utf-8"),cachegetUint8Memory0=null;function getUint8Memory0(){return null!==cachegetUint8Memory0&&cachegetUint8Memory0.buffer===wasm.memory.buffer||(cachegetUint8Memory0=new Uint8Array(wasm.memory.buffer)),cachegetUint8Memory0}let cachegetInt32Memory0=null;function getInt32Memory0(){return null!==cachegetInt32Memory0&&cachegetInt32Memory0.buffer===wasm.memory.buffer||(cachegetInt32Memory0=new Int32Array(wasm.memory.buffer)),cachegetInt32Memory0}function passArray8ToWasm0(e,t){const n=t(1*e.length);return getUint8Memory0().set(e,n/1),WASM_VECTOR_LEN=e.length,n}function getArrayU8FromWasm0(e,t){return getUint8Memory0().subarray(e/1,e/1+t)}function deflate_encode_raw(e){var t=passArray8ToWasm0(e,wasm.__wbindgen_malloc),n=WASM_VECTOR_LEN;wasm.deflate_encode_raw(8,t,n);var r=getInt32Memory0()[2],a=getInt32Memory0()[3],s=getArrayU8FromWasm0(r,a).slice();return wasm.__wbindgen_free(r,1*a),s}self.addEventListener("message",e=>{const t=e.data.type;if("init"===t)if("function"==typeof WebAssembly.instantiateStreaming){const t={},n=fetch(e.data.module);WebAssembly.instantiateStreaming(n,t).then(({instance:e})=>{wasm=e.exports,self.postMessage({type:"init",result:0})}).catch(e=>n.then(t=>{"application/wasm"!==t.headers.get("Content-Type")?self.postMessage({type:"init",error:"\`WebAssembly.instantiateStreaming\` failed because your server does not serve wasm with \`application/wasm\` MIME type. Original error: "+e.message}):self.postMessage({type:"init",error:e.message})}))}else self.postMessage({type:"init",error:"no \`WebAssembly.instantiateStreaming\`"});else if("compress"===t)if(null==wasm)self.postMessage({error:"no \`wasm\` instance"});else{let t=deflate_encode_raw(encoder.encode(e.data.text));self.postMessage(t,[t.buffer])}});`]));
const worker = new Worker(url);
/**
* @private
@ -2171,26 +2172,37 @@ export class Grid {
URL.revokeObjectURL(url);
next(data);
}
// 超过 60 秒则拒绝
// 超过 30 秒则返回无压缩数据
const timer = setTimeout(() => {
if (working) {
terminate(reject, { message: 'timeout' });
// terminate(reject, { message: 'timeout' });
terminate(resolve, { data: uncompressed(json), error: 'timeout' });
}
}, 60000);
}, 30000);
worker.addEventListener('message', e => {
if (working) {
clearTimeout(timer);
terminate(resolve, e.data);
if (e.data.error != null) {
// terminate(reject, { message: e.data.error });
terminate(resolve, { data: uncompressed(json), error: e.data.error });
} else {
if (e.data.type === 'init') {
worker.postMessage({ type: 'compress', text: json });
} else {
clearTimeout(timer);
terminate(resolve, { type: 'compressed', data: e.data });
}
}
}
})
worker.addEventListener('error', e => {
if (working) {
clearTimeout(timer);
terminate(reject, e);
// terminate(reject, e);
terminate(resolve, { data: uncompressed(json), error: e.message });
}
})
working = true;
worker.postMessage(JSON.stringify(data));
worker.postMessage({ type: 'init', module: ScriptPath.replace(/ui\.min\.js$/, 'wasm_flate_bg.wasm') });
});
}

2
lib/ui/popup.d.ts vendored
View File

@ -13,6 +13,8 @@ interface PopupOptions {
zIndex?: number;
/** 是否在获取焦点时修改 z-index */
changeZIndex?: boolean;
/** 是否允许关闭 */
closable?: boolean;
/** 是否允许移动 */
movable?: boolean;
/** 是否允许修改大小 */

View File

@ -172,7 +172,11 @@ export class Popup {
let title = option.title;
if (!(title instanceof HTMLElement)) {
title = createElement('div', t => {
t.className = 'ui-popup-header-title';
if (option.movable === false) {
t.className = 'ui-popup-header-title no-move';
} else {
t.className = 'ui-popup-header-title';
}
t.innerText = title;
});
}
@ -244,15 +248,17 @@ export class Popup {
});
icons.appendChild(collapse);
}
const cancel = createIcon('fa-regular', 'times');
cancel.tabIndex = tabIndex + 3;
cancel.addEventListener('keypress', e => {
if (e.key === ' ' || e.key === 'Enter') {
this.close();
}
});
cancel.addEventListener('click', () => this.close());
icons.appendChild(cancel);
if (option.closable !== false) {
const cancel = createIcon('fa-regular', 'times');
cancel.tabIndex = tabIndex + 3;
cancel.addEventListener('keypress', e => {
if (e.key === ' ' || e.key === 'Enter') {
this.close();
}
});
cancel.addEventListener('click', () => this.close());
icons.appendChild(cancel);
}
});
header.appendChild(icons);
}),

43
lib/ui/tab.js Normal file
View File

@ -0,0 +1,43 @@
import "./css/tab.scss";
/**
* Tab 页创建参数类
* @typedef TabOption
* @property {HTMLElement} container - 父容器
*/
import { createElement } from "../functions";
/**
* 创建 Tab 页
* @param {TabOption | HTMLElement} options - 创建选项
*/
export function createTab(options) {
if (options instanceof HTMLElement) {
options = { container: options };
}
let container;
if (options?.container instanceof HTMLElement) {
container = options.container;
if (!container.classList.contains('ui-tab-container')) {
container.classList.add('ui-tab-container');
}
} else {
container = createElement('div', 'ui-tab-container');
}
container.replaceChildren(
createElement('div', header => {
header.className = 'ui-tab-header';
header.addEventListener('click', e => {
const title = e.target;
if (title.classList.contains('ui-tab-title')) {
// title
header.querySelectorAll('.ui-tab-title').forEach(t => t === title ? t.classList.add('selected') : t.classList.remove('selected'));
// pages
const page = title.dataset.for;
container.querySelectorAll('[data-page]').forEach(p => p.style.display = p.dataset.page === page ? 'block' : '');
}
});
})
);
}