feature: drag support in sort panel.

This commit is contained in:
Chen Lily 2024-05-13 16:46:55 +08:00
parent f676ec76db
commit a946012a33
14 changed files with 777 additions and 566 deletions

View File

@ -1,3 +1,21 @@
# [ui-lib].Grid # [ui-lib].Grid
UI Mordern Gridview Library UI Mordern Gridview Library
## 1.0.3
* 调整: [showSortPanel(callback?: Function, layout?: boolean)](Grid.html#showSortPanel) 现支持输入搜索列,已添加的列不会重复显示在下拉数据源中,增加回调函数与 layout 更新复选框。
* 新增: [onRowChanged(action: "update" | "add" | "remove", items: GridRowItem[], indexes: number | number[])](Grid.html#onRowChanged) - 行发生变化时触发的事件
### bugs
* 修复: 清空多列排序后列头箭头没有清除的异常。
## 1.0.2
* 新增: [export(compressed: string | boolean, module?: string) : Promise<GridExportData>](Grid.html#export) - 导出已压缩的数据源
## 1.0.1
* 新增: [total: GridRowItem](Grid.html#total) - 获取或设置合计行数据
* 新增: [showSortPanel()](Grid.html#showSortPanel) - 显示多列排序设置面板
* 新增: [setItem(index: number, item: GridRowItem)](Grid.html#setItem) - 设置单行数据
* 新增: [addItem(item: GridRowItem, index?: number): GridRowItem](Grid.html#addItem) - 添加行数据
* 新增: [addItems(array: GridRowItem[], index?: number) : GridRowItem[]](Grid.html#addItems) - 批量添加行数据
* 新增: [removeItem(index: number) : GridRowItem](Grid.html#removeItem) - 删除行数据
* 新增: [removeItems(indexes?: number[]): GridRowItem[]](Grid.html#removeItems) - 批量删除行数据

View File

@ -2,9 +2,11 @@ import "./app/communications/style.scss";
import CustomerCommunication from "./app/communications/customer"; import CustomerCommunication from "./app/communications/customer";
import InternalComment from "./app/communications/internal"; import InternalComment from "./app/communications/internal";
import CustomerRecordComment from "./app/communications/comments"; import CustomerRecordComment from "./app/communications/comments";
import { createHideMessageTitleButton } from "./app/communications/lib";
export { export {
CustomerCommunication, CustomerCommunication,
InternalComment, InternalComment,
CustomerRecordComment CustomerRecordComment,
createHideMessageTitleButton
} }

View File

@ -1,6 +1,6 @@
import { createElement, setTooltip, createIcon } from "../../ui"; import { createElement, setTooltip, createIcon } from "../../ui";
import { r as lang, nullOrEmpty, escapeHtml, escapeEmoji } from "../../utility"; import { r as lang, nullOrEmpty, escapeHtml, escapeEmoji } from "../../utility";
import { createBox, appendMedia } from "./lib"; import { createBox, appendMedia, createHideMessageTitleButton, createHideMessageCommentTail } from "./lib";
let r = lang; let r = lang;
@ -25,7 +25,9 @@ export default class CustomerRecordComment {
} }
} }
get messageHidden() { return this._var.option.showCommentHidden } /**
* @param {boolean} flag
*/
set messageHidden(flag) { set messageHidden(flag) {
const el = this._var.container.querySelector('.msgadminsetting'); const el = this._var.container.querySelector('.msgadminsetting');
if (el == null) { if (el == null) {
@ -62,34 +64,14 @@ export default class CustomerRecordComment {
} }
create() { create() {
const option = this._var.option;
const readonly = this._var.option.readonly; const readonly = this._var.option.readonly;
const spanv = createElement('span');
if (option.userIsAdmin) {
spanv.className = 'msgadminsetting sbutton iconnotview';
spanv.style.padding = '0px 0px 0px 5px';
spanv.addEventListener('click', function () {
if (!option.showCommentHidden) {
this.classList.remove('iconnotview');
this.classList.add('iconview');
option.showCommentHidden = true;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = '');
} else {
this.classList.remove('iconview');
this.classList.add('iconnotview');
option.showCommentHidden = false;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = 'none');
}
});
}
const container = createBox( const container = createBox(
createElement('div', null, createElement('div', null,
createElement('div', div => { createElement('div', div => {
div.className = 'title-module'; div.className = 'title-module';
div.innerText = r('P_CR_COMMENTS', 'Comments'); div.innerText = r('P_CR_COMMENTS', 'Comments');
}, },
spanv createHideMessageTitleButton(this, 'showCommentHidden')
) )
), ),
[ [
@ -154,11 +136,18 @@ export default class CustomerRecordComment {
return this._var.container = container; return this._var.container = container;
} }
load(data, func) { load(data, func, hisFunc, keep) {
const children = []; const children = [];
if (data?.length > 0) { if (data?.length > 0) {
const lastVisible = this._var.option.showCommentHidden;
for (let comment of data) { for (let comment of data) {
const div = createElement('div', 'item-div'); const div = createElement('div', 'item-div');
if (comment.Hidden) {
div.classList.add('hidden-content');
if (!lastVisible) {
div.style.display = 'none';
}
}
// if (sendto !== '') { // if (sendto !== '') {
// sendto = r('P_CU_SENDTO_COLON', 'Sent To :') + `\n${sendto}`; // sendto = r('P_CU_SENDTO_COLON', 'Sent To :') + `\n${sendto}`;
// } // }
@ -177,37 +166,19 @@ export default class CustomerRecordComment {
} }
div.append( div.append(
content, content,
createElement('div', div => { createHideMessageCommentTail(
div.className = 'item-time'; this, 'showCommentHidden',
}, comment, 'SubmitLocalDateStr',
createElement('span', span => { func, hisFunc)
span.className = 'msgsetting sbutton ' + (comment.Hidden ? 'iconnotview' : 'iconview');
span.style.padding = '0px 0px 0px 5px';
span.style.fontSize = '12px';
span.style.display = this._var.option.showCommentHidden ? '' : 'none';
span.addEventListener('click', function () {
if (this.classList.contains('iconview')) {
func(comment.Id, true);
this.classList.remove('iconview');
this.classList.add('iconnotview');
} else {
func(comment.Id, false);
this.classList.remove('iconnotview');
this.classList.add('iconview');
}
});
}),
createElement('span', span => {
span.innerText = comment.SubmitLocalDateStr;
})
)
); );
children.push(div); children.push(div);
} }
children[0].style.marginTop = '0'; children[0].style.marginTop = '0';
} }
if (this._var.message.children.length > 0) {
this._var.lastTop = this._var.message.scrollTop;
}
this._var.message.replaceChildren(...children); this._var.message.replaceChildren(...children);
this._var.message.scrollTop = this._var.message.scrollHeight setTimeout(() => this._var.message.scrollTop = keep ? this._var.lastTop : this._var.message.scrollHeight, 0);
// setTimeout(() => this._var.message.scrollTop = this._var.message.scrollHeight, 0);
} }
} }

View File

@ -1,6 +1,6 @@
import { Grid, GridColumn, createElement, setTooltip, createIcon, createCheckbox, createRadiobox, showAlert, showConfirm, Popup } from "../../ui"; import { Grid, GridColumn, createElement, setTooltip, createIcon, createCheckbox, createRadiobox, showAlert, showConfirm, Popup } from "../../ui";
import { r as lang, nullOrEmpty, formatUrl, escapeEmoji, isEmail, isPhone } from "../../utility"; import { r as lang, nullOrEmpty, formatUrl, escapeEmoji, isEmail, isPhone } from "../../utility";
import { createBox, appendMedia, fileSupported, insertFile, getMessageSendTo, getMessageStatus, updateCustomerName } from "./lib"; import { createBox, appendMedia, fileSupported, insertFile, getMessageSendTo, getMessageStatus, updateCustomerName, createHideMessageTitleButton, createHideMessageCommentTail } from "./lib";
import { Contact, CustomerRecordContact } from "./contact"; import { Contact, CustomerRecordContact } from "./contact";
import Follower from "./follower"; import Follower from "./follower";
@ -69,7 +69,9 @@ export default class CustomerCommunication {
element.dispatchEvent(new Event('change')); element.dispatchEvent(new Event('change'));
} }
get messageHidden() { return this._var.option.showMessageHidden } /**
* @param {boolean} flag
*/
set messageHidden(flag) { set messageHidden(flag) {
const el = this._var.container.querySelector('.msgadminsetting'); const el = this._var.container.querySelector('.msgadminsetting');
if (el == null) { if (el == null) {
@ -425,24 +427,6 @@ export default class CustomerCommunication {
if (option.statusLinkVisible === false) { if (option.statusLinkVisible === false) {
checkLink.style.display = 'none'; checkLink.style.display = 'none';
} }
const spanv = createElement('span');
if (option.userIsAdmin) {
spanv.className = 'msgadminsetting sbutton iconnotview';
spanv.style.padding = '0px 0px 0px 5px';
spanv.addEventListener('click', function () {
if (!option.showMessageHidden) {
this.classList.remove('iconnotview');
this.classList.add('iconview');
option.showMessageHidden = true;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = '');
} else {
this.classList.remove('iconview');
this.classList.add('iconnotview');
option.showMessageHidden = false;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = 'none');
}
});
}
const container = createBox( const container = createBox(
createElement('div', null, createElement('div', null,
@ -450,7 +434,7 @@ export default class CustomerCommunication {
div.className = 'title-module'; div.className = 'title-module';
div.innerText = option.title ?? r('P_WO_CUSTOMERCOMMUNICATION', 'Customer Communication'); div.innerText = option.title ?? r('P_WO_CUSTOMERCOMMUNICATION', 'Customer Communication');
}, },
spanv createHideMessageTitleButton(this, 'showMessageHidden')
), ),
createElement('div', div => { createElement('div', div => {
div.className = 'title-company'; div.className = 'title-company';
@ -1179,7 +1163,7 @@ export default class CustomerCommunication {
return followers; return followers;
} }
load(data, contacts, followers, func) { load(data, contacts, followers, func, hisFunc, keep) {
const children = []; const children = [];
if (data?.length > 0) { if (data?.length > 0) {
contacts ??= this._var.data.contacts; contacts ??= this._var.data.contacts;
@ -1192,45 +1176,52 @@ export default class CustomerCommunication {
this._var.contactsUpdated = true; this._var.contactsUpdated = true;
} }
} }
for (let comm of data) { const lastVisible = this._var.option.showMessageHidden;
for (let comment of data) {
const div = createElement('div', 'item-div'); const div = createElement('div', 'item-div');
if (comment.Hidden) {
div.classList.add('hidden-content');
if (!lastVisible) {
div.style.display = 'none';
}
}
let name; let name;
if (comm.IsReply && contacts?.length > 0) { if (comment.IsReply && contacts?.length > 0) {
const c = isEmail(comm.Sender) ? const c = isEmail(comment.Sender) ?
contacts.find(c => c.Email === comm.Sender) : contacts.find(c => c.Email === comment.Sender) :
contacts.find(c => c.MobilePhone === comm.Sender); contacts.find(c => c.MobilePhone === comment.Sender);
name = c?.Name; name = c?.Name;
} }
name ??= comm.IsReply && String(comm.FormatSender) !== '' ? comm.FormatSender : comm.Sender; name ??= comment.IsReply && String(comment.FormatSender) !== '' ? comment.FormatSender : comment.Sender;
const sendto = getMessageSendTo(comm, contacts, followers, r) const sendto = getMessageSendTo(comment, contacts, followers, r)
div.appendChild(createElement('div', div => { div.appendChild(createElement('div', div => {
div.className = 'item-poster'; div.className = 'item-poster';
div.innerText = name; div.innerText = name;
if (!comm.IsReply && sendto?.length > 0) { if (!comment.IsReply && sendto?.length > 0) {
setTooltip(div, sendto); setTooltip(div, sendto);
} }
})); }));
const content = createElement('div', 'item-content'); const content = createElement('div', 'item-content');
const mmsParts = createElement('div', div => div.style.display = 'none'); const mmsParts = createElement('div', div => div.style.display = 'none');
content.appendChild(createElement('span', span => { content.appendChild(createElement('span', span => {
if (/https?:\/\//i.test(comm.Message)) { if (/https?:\/\//i.test(comment.Message)) {
span.innerHTML = formatUrl(escapeEmoji(comm.Message)); span.innerHTML = formatUrl(escapeEmoji(comment.Message));
} else { } else {
span.innerText = escapeEmoji(comm.Message); span.innerText = escapeEmoji(comment.Message);
} }
span.appendChild(mmsParts); span.appendChild(mmsParts);
})); }));
if (comm.MMSParts?.length > 0) { if (comment.MMSParts?.length > 0) {
mmsParts.style.display = ''; mmsParts.style.display = '';
for (let kv of comm.MMSParts) { for (let kv of comment.MMSParts) {
appendMedia(mmsParts, kv.Key, kv.Value); appendMedia(mmsParts, kv.Key, kv.Value);
} }
} }
if (comm.IsReply) { if (comment.IsReply) {
div.classList.add('item-other'); div.classList.add('item-other');
} else { } else {
div.classList.add('item-self'); div.classList.add('item-self');
const [status, text, color, tips] = getMessageStatus(comm, r, this._var); const [status, text, color, tips] = getMessageStatus(comment, r, this._var);
if (status !== -100) { if (status !== -100) {
if (color != null) { if (color != null) {
content.style.backgroundColor = color; content.style.backgroundColor = color;
@ -1247,37 +1238,19 @@ export default class CustomerCommunication {
} }
div.append( div.append(
content, content,
createElement('div', div => { createHideMessageCommentTail(
div.className = 'item-time'; this, 'showMessageHidden',
}, comment, 'TimeStr',
createElement('span', span => { func, hisFunc)
span.className = 'msgsetting sbutton ' + (comm.Hidden ? 'iconnotview' : 'iconview');
span.style.padding = '0px 0px 0px 5px';
span.style.fontSize = '12px';
span.style.display = this._var.option.showMessageHidden ? '' : 'none';
span.addEventListener('click', function () {
if (this.classList.contains('iconview')) {
func(comm.Id, true);
this.classList.remove('iconview');
this.classList.add('iconnotview');
} else {
func(comm.Id, false);
this.classList.remove('iconnotview');
this.classList.add('iconview');
}
});
}),
createElement('span', span => {
span.innerText = comm.TimeStr;
})
)
); );
children.push(div); children.push(div);
} }
children[0].style.marginTop = '0'; children[0].style.marginTop = '0';
} }
if (this._var.message.children.length > 0) {
this._var.lastTop = this._var.message.scrollTop;
}
this._var.message.replaceChildren(...children); this._var.message.replaceChildren(...children);
this._var.message.scrollTop = this._var.message.scrollHeight setTimeout(() => this._var.message.scrollTop = keep ? this._var.lastTop : this._var.message.scrollHeight, 0);
// setTimeout(() => this._var.message.scrollTop = this._var.message.scrollHeight, 0);
} }
} }

View File

@ -1,7 +1,7 @@
import { createElement, setTooltip, createIcon } from "../../ui"; import { createElement, setTooltip, createIcon } from "../../ui";
import { r as lang, nullOrEmpty, escapeHtml, escapeEmoji } from "../../utility"; import { r as lang, nullOrEmpty, escapeHtml, escapeEmoji } from "../../utility";
import { createBox, appendMedia } from "./lib"; import { createBox, appendMedia } from "./lib";
import { fileSupported, insertFile, getMessageSendTo, getMessageStatus, updateCustomerName } from "./lib"; import { fileSupported, insertFile, getMessageSendTo, getMessageStatus, updateCustomerName, createHideMessageTitleButton, createHideMessageCommentTail } from "./lib";
let r = lang; let r = lang;
@ -32,7 +32,9 @@ export default class InternalComment {
} }
} }
get messageHidden() { return this._var.option.showCommentHidden } /**
* @param {boolean} flag
*/
set messageHidden(flag) { set messageHidden(flag) {
const el = this._var.container.querySelector('.msgadminsetting'); const el = this._var.container.querySelector('.msgadminsetting');
if (el == null) { if (el == null) {
@ -123,26 +125,7 @@ export default class InternalComment {
div.className = 'title-module'; div.className = 'title-module';
div.innerText = r('P_WO_INTERNALCOMMENTS', 'Internal Comments'); div.innerText = r('P_WO_INTERNALCOMMENTS', 'Internal Comments');
}, },
createElement('span', span => { createHideMessageTitleButton(this, 'showMessageHidden')
if (option.userIsAdmin) {
span.className = 'msgadminsetting sbutton iconnotview';
span.style.padding = '0px 0px 0px 5px';
span.addEventListener('click', function () {
if (!option.showMessageHidden) {
this.classList.remove('iconnotview');
this.classList.add('iconview');
option.showMessageHidden = true;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = '');
}
else {
this.classList.remove('iconview');
this.classList.add('iconnotview');
option.showMessageHidden = false;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = 'none');
}
});
}
})
) )
), [] ), []
); );
@ -278,7 +261,7 @@ export default class InternalComment {
return this._var.container = container; return this._var.container = container;
} }
load(data, func) { load(data, func, hisFunc, keep) {
const children = []; const children = [];
if (data?.length > 0) { if (data?.length > 0) {
this._var.comments = data; this._var.comments = data;
@ -289,8 +272,15 @@ export default class InternalComment {
this._var.contactsUpdated = true; this._var.contactsUpdated = true;
} }
} }
const lastVisible = this._var.option.showMessageHidden;
for (let comment of data) { for (let comment of data) {
const div = createElement('div', 'item-div'); const div = createElement('div', 'item-div');
if (comment.Hidden) {
div.classList.add('hidden-content');
if (!lastVisible) {
div.style.display = 'none';
}
}
const sendto = getMessageSendTo(comment, null, null, r) const sendto = getMessageSendTo(comment, null, null, r)
div.appendChild(createElement('div', div => { div.appendChild(createElement('div', div => {
div.className = 'item-poster'; div.className = 'item-poster';
@ -333,37 +323,19 @@ export default class InternalComment {
} }
div.append( div.append(
content, content,
createElement('div', div => { createHideMessageCommentTail(
div.className = 'item-time'; this, 'showMessageHidden',
}, comment, 'TimeStr',
createElement('span', span => { func, hisFunc)
span.className = 'msgsetting sbutton ' + (comment.Hidden ? 'iconnotview' : 'iconview');
span.style.padding = '0px 0px 0px 5px';
span.style.fontSize = '12px';
span.style.display = this._var.option.showMessageHidden ? '' : 'none';
span.addEventListener('click', function () {
if (this.classList.contains('iconview')) {
func(comment.Id, true);
this.classList.remove('iconview');
this.classList.add('iconnotview');
} else {
func(comment.Id, false);
this.classList.remove('iconnotview');
this.classList.add('iconview');
}
});
}),
createElement('span', span => {
span.innerText = comment.TimeStr;
})
)
); );
children.push(div); children.push(div);
} }
children[0].style.marginTop = '0'; children[0].style.marginTop = '0';
} }
if (this._var.message.children.length > 0) {
this._var.lastTop = this._var.message.scrollTop;
}
this._var.message.replaceChildren(...children); this._var.message.replaceChildren(...children);
this._var.message.scrollTop = this._var.message.scrollHeight setTimeout(() => this._var.message.scrollTop = keep ? this._var.lastTop : this._var.message.scrollHeight, 0);
// setTimeout(() => this._var.message.scrollTop = this._var.message.scrollHeight, 0);
} }
} }

View File

@ -421,3 +421,98 @@ export function updateCustomerName(messages, contacts) {
} }
} }
} }
export function createHideMessageTitleButton(This, optionName) {
const option = This._var.option;
return createElement('span', span => {
if (option.userIsAdmin) {
if (option[optionName]) {
span.className = 'msgadminsetting sbutton iconview';
} else {
span.className = 'msgadminsetting sbutton iconnotview';
}
span.style.padding = '0px 0px 0px 5px';
setTooltip(span, option?.getText('P_WO_MESSAGEHISTORY_MANAGE', 'Manage Messages'));
span.addEventListener('click', function () {
const container = This._var.container;
if (!option[optionName]) {
this.classList.remove('iconnotview');
this.classList.add('iconview');
option[optionName] = true;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = '');
container.querySelectorAll('.msgHistory').forEach(h => h.style.display = h.getAttribute('ModifyCount') > 0 ? '' : 'none');
container.querySelectorAll('.hidden-content').forEach(c => c.style.display = '');
} else {
this.classList.remove('iconview');
this.classList.add('iconnotview');
option[optionName] = false;
container.querySelectorAll('.msgsetting').forEach(x => x.style.display = 'none');
container.querySelectorAll('.msgHistory').forEach(h => h.style.display = 'none');
container.querySelectorAll('.hidden-content').forEach(c => c.style.display = 'none');
}
});
}
});
}
export function createHideMessageCommentTail(This, optionName, comment, commentTime, func, hisFunc) {
const option = This._var.option;
const showTooltip = option?.getText('P_WO_MESSAGEHISTORY_VISIBLE', 'Visible');
const notShowTooltip = option?.getText('P_WO_MESSAGEHISTORY_NOTVISIBLE', 'Not Visible');
return createElement('div', div => {
div.className = 'item-time';
div.style.display = 'flex';
div.style.alignItems = 'center';
},
createElement('span', span => {
span.className = 'msgsetting sbutton ' + (comment.Hidden ? 'iconnotview' : 'iconview');
span.style.padding = '0';
span.style.fontSize = '12px';
setTooltip(span, comment.Hidden ? notShowTooltip : showTooltip);
span.style.display = option[optionName] ? '' : 'none';
span.addEventListener('click', function () {
if (this.classList.contains('iconview')) {
func(comment.Id, true);
this.classList.remove('iconview');
this.classList.add('iconnotview');
setTooltip(this, notShowTooltip);
} else {
func(comment.Id, false);
this.classList.remove('iconnotview');
this.classList.add('iconview');
setTooltip(this, showTooltip);
}
if (isNaN(comment.ModifyCount)) {
comment.ModifyCount = 1;
} else {
comment.ModifyCount += 1;
}
const x = This._var.container.querySelector('.history-span-' + comment.Id);
if (x != null) {
x.setAttribute('ModifyCount', comment.ModifyCount);
x.style.display = (option[optionName] && comment.ModifyCount > 0) ? '' : 'none';
}
});
}),
createElement('span', span => {
span.className = 'msgHistory history-span-' + comment.Id;
span.setAttribute('ModifyCount', comment.ModifyCount ?? 0);
span.style.display = (option[optionName] && comment.ModifyCount > 0) ? '' : 'none';
setTooltip(span, option?.getText('P_WO_MESSAGEHISTORY_HEADER', 'Hidden History'));
const icon = createIcon('fa-light', 'wave-sine');
icon.style.height = '12px';
icon.style.width = '12px';
icon.style.margin = '0 5px 0 0';
icon.style.cursor = 'pointer';
icon.style.border = '1px solid';
icon.style.borderRadius = '6px';
icon.style.borderColor = '#000';
icon.style.display = 'block';
span.appendChild(icon);
span.addEventListener('click', () => hisFunc(comment.Id));
}),
createElement('span', span => {
span.innerText = comment[commentTime];
})
);
}

View File

@ -26,7 +26,9 @@ function fillCheckbox(container, type = 'fa-regular', label, tabindex = -1, char
container.appendChild( container.appendChild(
createElement('span', span => { createElement('span', span => {
span.innerText = label; span.innerText = label;
if (title != null) {
span.title = title; span.title = title;
}
}) })
); );
} }

View File

@ -705,6 +705,12 @@
} }
} }
.ui-popup-mask .ui-popup-container .ui-popup-footer {
>.ui-sort-layout {
flex: 1 1 auto;
}
}
/*@media (prefers-color-scheme: dark) { /*@media (prefers-color-scheme: dark) {
.ui-grid { .ui-grid {
--cell-hover-bg-color: yellow; --cell-hover-bg-color: yellow;

View File

@ -76,6 +76,9 @@
--icon-size: 16px; --icon-size: 16px;
>.ui-video-content {
height: 100%;
>.ui-video-wrapper { >.ui-video-wrapper {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -117,15 +120,17 @@
} }
} }
} }
}
>.ui-video-control { >.ui-video-control {
position: absolute; position: absolute;
top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 70px;
bottom: 0;
opacity: 0; opacity: 0;
background: linear-gradient(transparent calc(100% - 100px), rgba(50, 50, 50, .7) calc(100% - 65px), #000); background: linear-gradient(transparent, #000);
// calc(100% - 100px), rgba(50, 50, 50, .7) calc(100% - 65px)
transition: opacity .5s; transition: opacity .5s;
&:hover, &:hover,

View File

@ -204,7 +204,8 @@ export class Dropdown {
let label; let label;
if (options.input) { if (options.input) {
label = createElement('input', 'ui-drop-text'); label = createElement('input', 'ui-drop-text');
label.setAttribute('type', 'text'); label.type = 'text';
label.draggable = false;
options.placeholder && label.setAttribute('placeholder', options.placeholder); options.placeholder && label.setAttribute('placeholder', options.placeholder);
isPositive(options.maxLength) && label.setAttribute('maxlength', options.maxLength); isPositive(options.maxLength) && label.setAttribute('maxlength', options.maxLength);
isPositive(options.tabIndex) && label.setAttribute('tabindex', options.tabIndex); isPositive(options.tabIndex) && label.setAttribute('tabindex', options.tabIndex);
@ -383,7 +384,7 @@ export class Dropdown {
if (!options.input && options.search) { if (!options.input && options.search) {
const search = createElement('div', 'ui-drop-search'); const search = createElement('div', 'ui-drop-search');
const input = createElement('input'); const input = createElement('input');
input.setAttribute('type', 'text'); input.type = 'text';
isPositive(options.tabIndex) && input.setAttribute('tabindex', options.tabIndex); isPositive(options.tabIndex) && input.setAttribute('tabindex', options.tabIndex);
!nullOrEmpty(options.searchPlaceholder) && input.setAttribute('placeholder', options.searchPlaceholder); !nullOrEmpty(options.searchPlaceholder) && input.setAttribute('placeholder', options.searchPlaceholder);
input.addEventListener('input', e => { input.addEventListener('input', e => {
@ -503,7 +504,7 @@ export class Dropdown {
} }
const li = createElement('li'); const li = createElement('li');
li.dataset.value = val; li.dataset.value = val;
li.setAttribute('title', item[textkey]); li.title = item[textkey];
let label; let label;
const html = item[htmlkey]; const html = item[htmlkey];
if (html instanceof HTMLElement) { if (html instanceof HTMLElement) {

View File

@ -71,13 +71,13 @@ let r = lang;
/** /**
* 键值字典 * 键值字典
* @template T * @template T
* @typedef {Map<string, T>} KeyMap * @typedef {{[key: string]: T}} KeyMap
*/ */
/** /**
* 索引字典 * 索引字典
* @template T * @template T
* @typedef {Map<number, T>} IndexMap * @typedef {{[index: number]: T}} IndexMap
*/ */
/** /**
@ -134,6 +134,7 @@ let r = lang;
* @param {GridRowItem} item - 行数据对象 * @param {GridRowItem} item - 行数据对象
* @param {boolean} editing - 是否处于编辑状态 * @param {boolean} editing - 是否处于编辑状态
* @param {HTMLElement} [body] - Grid 控件的 `<tbody>` 部分 * @param {HTMLElement} [body] - Grid 控件的 `<tbody>` 部分
* @param {number} [index] - 所在行索引不可依赖此值除了呈现时其他时候该值不会传递
* @returns {ValueItem} - 返回过滤后的显示或编辑值 * @returns {ValueItem} - 返回过滤后的显示或编辑值
* @this GridColumnDefinition * @this GridColumnDefinition
*/ */
@ -455,6 +456,9 @@ const GridColumnDirection = {
* @property {string} [addLevel] - Add Level * @property {string} [addLevel] - Add Level
* @property {string} [deleteLevel] - Delete Level * @property {string} [deleteLevel] - Delete Level
* @property {string} [copyLevel] - Copy Level * @property {string} [copyLevel] - Copy Level
* @property {string} [sortBy] - Sort by
* @property {string} [thenBy] - Then by
* @property {string} [updateLayout] - Update Layout
* @property {string} [asc] - Ascending * @property {string} [asc] - Ascending
* @property {string} [desc] - Descending * @property {string} [desc] - Descending
* @property {string} [column] - Column * @property {string} [column] - Column
@ -939,6 +943,13 @@ export class Grid {
* @ignore * @ignore
*/ */
headerWrap = true; headerWrap = true;
/**
* 是否允许行间拖拽
* @type {boolean}
* @default false
* @ignore
*/
rowDraggable = false;
/** /**
* 监听事件的窗口载体 * 监听事件的窗口载体
* @type {(Window | HTMLElement)} * @type {(Window | HTMLElement)}
@ -1060,6 +1071,16 @@ export class Grid {
* @this Grid * @this Grid
*/ */
onRowCollapsed; onRowCollapsed;
/**
* 行发生变化时触发的事件
* @event
* @param {("update" | "add" | "remove")} action - 变动类型
* @param {GridRowItem[]} items - 发生变动的行对象
* @param {(number | number[])} indexes - 变动的索引集合
* @this Grid
* @since 1.0.3
*/
onRowChanged;
/** /**
* 列类型枚举 * 列类型枚举
@ -1099,6 +1120,7 @@ export class Grid {
* @property {boolean} [tooltipDisabled=false] - 单元格 tooltip 是否禁用 * @property {boolean} [tooltipDisabled=false] - 单元格 tooltip 是否禁用
* @property {boolean} [headerVisible=true] - 列头是否显示 * @property {boolean} [headerVisible=true] - 列头是否显示
* @property {boolean} [headerWrap=true] - 列头是否允许换行 * @property {boolean} [headerWrap=true] - 列头是否允许换行
* @property {boolean} [rowDraggable=false] - 是否允许行间拖拽 @since 1.0.3
* @property {(Window | HTMLElement)} [window=global] - 监听事件的窗口载体 * @property {(Window | HTMLElement)} [window=global] - 监听事件的窗口载体
* @property {number} [sortIndex=-1] - 排序列的索引 * @property {number} [sortIndex=-1] - 排序列的索引
* @property {GridColumnDirection} [sortDirection=GridColumnDirection.Ascending] - 排序方式正数升序负数倒序 * @property {GridColumnDirection} [sortDirection=GridColumnDirection.Ascending] - 排序方式正数升序负数倒序
@ -1332,6 +1354,9 @@ export class Grid {
addLevel: r('addLevel', 'Add level'), addLevel: r('addLevel', 'Add level'),
deleteLevel: r('deleteLevel', 'Delete level'), deleteLevel: r('deleteLevel', 'Delete level'),
copyLevel: r('copyLevel', 'Copy level'), copyLevel: r('copyLevel', 'Copy level'),
sortBy: r('sortBy', 'Sort by'),
thenBy: r('thenBy', 'Then by'),
updateLayout: r('updateLayout', 'Update Layout'),
asc: r('asc', 'Ascending'), asc: r('asc', 'Ascending'),
desc: r('desc', 'Descending'), desc: r('desc', 'Descending'),
column: r('column', 'Column'), column: r('column', 'Column'),
@ -1408,6 +1433,30 @@ export class Grid {
} }
this._onRowClicked(e, rowIndex, colIndex); this._onRowClicked(e, rowIndex, colIndex);
}); });
if (this.rowDraggable) {
grid.addEventListener('dragover', e => {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = e.ctrlKey ? 'copy' : 'move';
});
grid.addEventListener('drop', e => {
e.preventDefault();
e.stopPropagation();
const src = Number(e.dataTransfer.getData('text'));
if (isNaN(src) || src < 0) {
return;
}
const target = this._var.currentSource?.length ?? 0;
if (target == null || src === target) {
return;
}
const row = e.ctrlKey ?
Object.assign({}, this._var.currentSource[src]?.values) :
this.removeItem(src);
this.addItem(row, target);
this.selectedIndexes = [e.ctrlKey ? target : target - 1];
});
}
container.replaceChildren(grid); container.replaceChildren(grid);
const sizer = createElement('span', 'ui-grid-sizer'); const sizer = createElement('span', 'ui-grid-sizer');
grid.appendChild(sizer); grid.appendChild(sizer);
@ -1719,24 +1768,35 @@ export class Grid {
/** /**
* 显示多列排序设置面板 * 显示多列排序设置面板
* @param {Function} [callback] - 更新回调函数 @since 1.0.3
* @param {boolean} [layout] - 是否显示更新 layout 复选框 @since 1.0.3
* @since 1.0.1 * @since 1.0.1
*/ */
showSortPanel() { showSortPanel(callback, layout) {
const content = createElement('div', 'ui-sort-panel-content'); const content = createElement('div', 'ui-sort-panel-content');
const buttonWrapper = createElement('div', 'ui-sort-panel-buttons'); const buttonWrapper = createElement('div', 'ui-sort-panel-buttons');
const grid = new Grid(null, r); const grid = new Grid(null, r);
grid.rowDraggable = true;
grid.langs = this.langs; grid.langs = this.langs;
const rowChanged = index => { const rowChanged = index => {
buttonWrapper.querySelector('.ui-button-delete').disabled = index < 0; buttonWrapper.querySelector('.ui-button-delete').disabled = index < 0;
buttonWrapper.querySelector('.ui-button-copy').disabled = index < 0; buttonWrapper.querySelector('.ui-button-copy').disabled = index < 0;
buttonWrapper.querySelector('.ui-button-move-up').disabled = index < 1; // buttonWrapper.querySelector('.ui-button-move-up').disabled = index < 1;
buttonWrapper.querySelector('.ui-button-move-down').disabled = index >= grid.source.length - 1; // buttonWrapper.querySelector('.ui-button-move-down').disabled = index >= grid.source.length - 1;
}; };
grid.onSelectedRowChanged = rowChanged; grid.onSelectedRowChanged = rowChanged;
grid.onRowChanged = () => {
const layout = pop.container.querySelector('.ui-sort-layout');
if (layout != null) {
layout.classList.remove('disabled');
layout.querySelector('input').disabled = false;
}
};
const reload = index => { const reload = index => {
grid.selectedIndexes = [index]; grid.selectedIndexes = [index];
grid.scrollTop = index * (grid.rowHeight + 1); grid.scrollTop = index * (grid.rowHeight + 1);
rowChanged(index); rowChanged(index);
grid.onRowChanged();
} }
buttonWrapper.append( buttonWrapper.append(
createElement('span', 'button', createElement('span', 'button',
@ -1793,6 +1853,7 @@ export class Grid {
}); });
}) })
), ),
/*
createElement('span', button => { createElement('span', button => {
button.className = 'button ui-button-move-up'; button.className = 'button ui-button-move-up';
const icon = createIcon('fa-light', 'chevron-up'); const icon = createIcon('fa-light', 'chevron-up');
@ -1829,23 +1890,43 @@ export class Grid {
}); });
button.appendChild(icon); button.appendChild(icon);
}) })
//*/
); );
const gridWrapper = createElement('div', 'ui-sort-panel-grid'); const gridWrapper = createElement('div', 'ui-sort-panel-grid');
content.append(buttonWrapper, gridWrapper); content.append(buttonWrapper, gridWrapper);
const columnSource = this.columns.filter(c => c.sortable !== false && c.visible !== false); const columnSource = this.columns.filter(c => c.sortable !== false); // ticket 56389, && c.visible !== false
columnSource.sort((a, b) => a.caption > b.caption ? 1 : -1);
grid.columns = [ grid.columns = [
{ {
key: 'column', width: 80,
sortable: false,
orderable: false,
resizable: false,
filter: (_item, _editing, _body, index) => index === 0 ? this.langs.sortBy : this.langs.thenBy
},
{
key: 'caption',
caption: this.langs.column, caption: this.langs.column,
width: 270, width: 270,
type: GridColumnTypeEnum.Dropdown, type: GridColumnTypeEnum.Dropdown,
dropOptions: { dropOptions: {
textKey: 'caption', textKey: 'caption',
valueKey: 'key' valueKey: 'caption',
input: true
}, },
source: columnSource, dropRestrictCase: true,
sourceCache: false,
source: () => columnSource.filter(c => grid.source?.find(s => s.column === c.key) == null),
sortable: false, sortable: false,
orderable: false orderable: false,
onChanged: (item, _value, _oldValue, e) => {
item.column = e.key;
const layout = pop.container.querySelector('.ui-sort-layout');
if (layout != null) {
layout.classList.remove('disabled');
layout.querySelector('input').disabled = false;
}
}
}, },
{ {
key: 'order', key: 'order',
@ -1857,7 +1938,14 @@ export class Grid {
{ value: 'desc', text: this.langs.desc } { value: 'desc', text: this.langs.desc }
], ],
sortable: false, sortable: false,
orderable: false orderable: false,
onChanged: () => {
const layout = pop.container.querySelector('.ui-sort-layout');
if (layout != null) {
layout.classList.remove('disabled');
layout.querySelector('input').disabled = false;
}
}
} }
]; ];
const pop = new Popup({ const pop = new Popup({
@ -1871,6 +1959,14 @@ export class Grid {
const source = grid.source; const source = grid.source;
if (source == null || source.length === 0) { if (source == null || source.length === 0) {
this.sortArray = null; this.sortArray = null;
// arrow icon
[...this._headerCells].forEach((th, i) => {
const arrow = th.querySelector('.arrow');
if (arrow == null) {
return;
}
arrow.className = 'arrow';
});
} else { } else {
const dict = {}; const dict = {};
for (let i = 0; i < source.length; ++i) { for (let i = 0; i < source.length; ++i) {
@ -1895,12 +1991,12 @@ export class Grid {
} }
dict[it.column] = true; dict[it.column] = true;
} }
this.sortArray = source; this.sortArray = source.map(s => ({ column: s.column, order: s.order }));
this.sortDirection = 1; this.sortDirection = 1;
this.sort(); this.sort();
} }
if (typeof this.onSorted === 'function') { if (typeof callback === 'function') {
this.onSorted(this.sortArray); callback.call(this, this.sortArray, pop.container.querySelector('.ui-sort-layout>input')?.checked);
} }
return true; return true;
} }
@ -1909,11 +2005,29 @@ export class Grid {
], ],
onResizeEnded: () => grid.resize() onResizeEnded: () => grid.resize()
}); });
const source = this.sortArray || [{ column: '', order: 'asc' }]; let source = this.sortArray;
if (source == null && this.sortIndex >= 0) {
const col = this.columns[this.sortIndex];
if (col != null) {
source = [{ column: col.key, order: this.sortDirection > 0 ? 'asc' : 'desc' }];
}
}
source ??= [{ column: '', order: 'asc' }];
pop.show(this._var.el).then(() => { pop.show(this._var.el).then(() => {
pop.container.style.cssText += 'width: 520px; height: 400px'; pop.container.style.cssText += 'width: 600px; height: 460px';
if (layout) {
const footer = pop.container.querySelector('.ui-popup-footer');
footer.insertBefore(createCheckbox({
label: this.langs.updateLayout,
className: 'ui-sort-layout',
enabled: false
}), footer.children[0]);
}
grid.init(gridWrapper); grid.init(gridWrapper);
grid.source = source.filter(s => s.column === '' || columnSource.find(c => c.key === s.column) != null); grid.source = source
.map(s => (s.c = columnSource.find(c => c.key === s.column), s))
.filter(s => s.column === '' || s.c != null)
.map(s => ({ column: s.column, caption: s.c?.caption ?? '', order: s.order }));
grid.selectedIndexes = [0]; grid.selectedIndexes = [0];
grid.refresh(); grid.refresh();
rowChanged(0); rowChanged(0);
@ -1942,6 +2056,9 @@ export class Grid {
} else { } else {
this.refresh(); this.refresh();
} }
if (typeof this.onRowChanged === 'function') {
this.onRowChanged('update', [item], index);
}
} }
/** /**
@ -1980,6 +2097,9 @@ export class Grid {
} else { } else {
this.reload(); this.reload();
} }
if (typeof this.onRowChanged === 'function') {
this.onRowChanged('add', [item], index);
}
return item; return item;
} }
@ -2024,6 +2144,9 @@ export class Grid {
} else { } else {
this.reload(); this.reload();
} }
if (typeof this.onRowChanged === 'function') {
this.onRowChanged('add', array, index);
}
return array; return array;
} }
@ -2053,6 +2176,9 @@ export class Grid {
this._var.selectedIndexes = []; this._var.selectedIndexes = [];
} }
this.reload(); this.reload();
if (typeof this.onRowChanged === 'function') {
this.onRowChanged('remove', [it.values], index);
}
return it.values; return it.values;
} }
@ -2098,11 +2224,14 @@ export class Grid {
it.__index -= offset; it.__index -= offset;
} }
const index = indexes[0]; const index = indexes[0];
if (index < 1) { if (index > 0) {
this._var.selectedIndexes = [index - 1]; this._var.selectedIndexes = [index - 1];
} else { } else {
this._var.selectedIndexes = []; this._var.selectedIndexes = [];
} }
if (typeof this.onRowChanged === 'function') {
this.onRowChanged('remove', array, indexes);
}
this.reload(); this.reload();
return array; return array;
} }
@ -2144,7 +2273,7 @@ export class Grid {
align: c.align, align: c.align,
visible: c.visible visible: c.visible
})), })),
source: this.source?.map(s => { source: this.source?.map((s, i) => {
const item = Object.create(null); const item = Object.create(null);
for (let c of this.columns) { for (let c of this.columns) {
if (c.key == null) { if (c.key == null) {
@ -2154,7 +2283,7 @@ export class Grid {
if (c.text != null) { if (c.text != null) {
val = c.text; val = c.text;
} else if (typeof c.filter === 'function') { } else if (typeof c.filter === 'function') {
val = c.filter(s, false, this._var.refs.body); val = c.filter(s, false, this._var.refs.body, i);
} else { } else {
val = s[c.key]; val = s[c.key];
if (val != null) { if (val != null) {
@ -2676,6 +2805,34 @@ export class Grid {
const readonly = this.readonly; const readonly = this.readonly;
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
const row = createElement('tr', 'ui-grid-row'); const row = createElement('tr', 'ui-grid-row');
if (this.rowDraggable) {
row.draggable = true;
row.addEventListener('dragstart', e => {
e.dataTransfer.setData('text', String(this._var.startIndex + exists + i));
});
row.addEventListener('dragover', e => {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = e.ctrlKey ? 'copy' : 'move';
});
row.addEventListener('drop', e => {
e.preventDefault();
e.stopPropagation();
const src = Number(e.dataTransfer.getData('text'));
if (isNaN(src) || src < 0) {
return;
}
const target = this._var.startIndex + exists + i;
if (src === target) {
return;
}
const row = e.ctrlKey ?
Object.assign({}, this._var.currentSource[src]?.values) :
this.removeItem(src);
this.addItem(row, target);
this.selectedIndexes = [target];
});
}
const virtualRow = { cells: {} }; const virtualRow = { cells: {} };
this._var.virtualRows[exists + i] = virtualRow; this._var.virtualRows[exists + i] = virtualRow;
let left = this.expandable ? ExpandableWidth : 0; let left = this.expandable ? ExpandableWidth : 0;
@ -2852,7 +3009,7 @@ export class Grid {
if (col.text != null) { if (col.text != null) {
val = col.text; val = col.text;
} else if (typeof col.filter === 'function') { } else if (typeof col.filter === 'function') {
val = col.filter(item, selected, this._var.refs.body); val = col.filter(item, selected, this._var.refs.body, startIndex + i);
} else { } else {
val = item[col.key]; val = item[col.key];
if (val != null) { if (val != null) {

View File

@ -158,6 +158,7 @@ export function createFile(url, icon = 'file-alt') {
* @param {boolean} options.[autoPlay] - 是否自动播放 * @param {boolean} options.[autoPlay] - 是否自动播放
* @param {boolean} options.[autoFullScreen] - 是否自动全屏 * @param {boolean} options.[autoFullScreen] - 是否自动全屏
* @param {boolean} options.[autoLoop] - 是否循环播放 * @param {boolean} options.[autoLoop] - 是否循环播放
* @param {Function} options.[onLoaded] - 视频加载完成回调
* @param {Function} [callback] - 视频元素处理回调函数 * @param {Function} [callback] - 视频元素处理回调函数
* @returns {HTMLDivElement} 返回联动视频元素 * @returns {HTMLDivElement} 返回联动视频元素
*/ */
@ -330,6 +331,9 @@ export function createVideoList(urls, options, callback) {
controller.classList.add('no-fullscreen'); controller.classList.add('no-fullscreen');
} }
const content = createElement('div', 'ui-video-content');
container.append(content, controller);
urls.forEach((url, i) => { urls.forEach((url, i) => {
const video = createElement('video'); const video = createElement('video');
videos[i] = video; videos[i] = video;
@ -349,17 +353,22 @@ export function createVideoList(urls, options, callback) {
video.volume = 0; video.volume = 0;
} }
prepared += 1; prepared += 1;
if (options?.autoPlay && prepared >= length) { if (prepared >= length) {
if (options?.autoPlay) {
// auto play // auto play
videos.forEach(v => v.play().catch(() => { })); videos.forEach(v => v.play().catch(() => { }));
if (options?.autoFullScreen && length === 1 && document.fullscreenElement == null) { if (options?.autoFullScreen && length === 1 && document.fullscreenElement == null) {
video.requestFullscreen().catch(() => { }); video.requestFullscreen().catch(() => { });
} }
} }
if (typeof options?.onLoaded === 'function') {
options.onLoaded();
}
}
}); });
video.addEventListener('progress', () => { video.addEventListener('progress', () => {
const buffered = video.buffered; const buffered = video.buffered;
for (let i = 0; i < buffered.length; i += 1) { for (let i = 0; i < buffered.length; ++i) {
let buffer = seekBufferBar.children[i]; let buffer = seekBufferBar.children[i];
if (buffer == null) { if (buffer == null) {
seekBufferBar.append(buffer = createElement('div', 'ui-video-buffer')); seekBufferBar.append(buffer = createElement('div', 'ui-video-buffer'));
@ -435,9 +444,8 @@ export function createVideoList(urls, options, callback) {
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback(wrapper); callback(wrapper);
} }
container.append(wrapper); content.append(wrapper);
}); });
container.append(controller);
return container; return container;
} }

661
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -29,13 +29,13 @@
}, },
"devDependencies": { "devDependencies": {
"@mxssfd/typedoc-theme": "^1.1.3", "@mxssfd/typedoc-theme": "^1.1.3",
"clean-jsdoc-theme": "^4.2.18", "clean-jsdoc-theme": "^4.3.0",
"docdash": "^2.0.2", "docdash": "^2.0.2",
"jsdoc": "^4.0.2", "jsdoc": "^4.0.3",
"postcss-preset-env": "^9.5.9", "postcss-preset-env": "^9.5.12",
"sass": "^1.75.0", "sass": "^1.77.1",
"typedoc": "^0.25.13", "typedoc": "^0.25.13",
"vite": "^5.2.10", "vite": "^5.2.11",
"vite-plugin-externals": "^0.6.2" "vite-plugin-externals": "^0.6.2"
} }
} }