2 lines
36 KiB
JavaScript
2 lines
36 KiB
JavaScript
const MODE_MAP={text:["contains","equals","starts","ends"],textarea:["contains","equals","starts","ends"],number:["=",">","<","between"],date:["=",">","<","between"],select:["contains","equals"],TagsInput:["contains","equals","starts","ends"]},MODE_ICONS={contains:"bi-search",equals:"bi-distribute-vertical",starts:"bi-align-start",ends:"bi-align-end","=":"bi-calculator",">":"bi-chevron-right","<":"bi-chevron-left",between:"bi-arrows-expand"},DataGridConfig={_fieldTypes:new Map,registerFieldType:e=>{DataGridConfig._fieldTypes.set(e.value,e)},getFieldTypes:()=>Array.from(DataGridConfig._fieldTypes.values())};DataGridConfig.registerFieldType({value:"text",label:"{#Text#}",typeForDB:"v4096",schema:[{name:"placeholder",label:"Placeholder",type:"text",if:'this.data.user_type=="text"'}]}),DataGridConfig.registerFieldType({value:"number",label:"{#Number#}",typeForDB:"ff",schema:[{name:"decimals",label:"Decimals",type:"number",setting:{min:0,max:10},if:'this.data.user_type=="number"'},{name:"prefix",label:"Prefix (e.g. $)",type:"text",if:'this.data.user_type=="number"'},{name:"suffix",label:"Suffix (e.g. %)",type:"text",if:'this.data.user_type=="number"'},{name:"thousandSep",label:"Thousand Sep",type:"switch",if:'this.data.user_type=="number"'}],formatter:(e,t)=>{if(null==e||""===e)return"";let i=Number(e);if(isNaN(i))return e;const n=t.settings||{};void 0!==n.decimals&&(i=i.toFixed(n.decimals));let l=String(i);if(n.thousandSep){const e=l.split(".");e[0]=e[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),l=e.join(".")}return(n.prefix||"")+l+(n.suffix||"")}}),DataGridConfig.registerFieldType({value:"select",label:"{#Single Select#}",typeForDB:"v1024",schema:[{name:"options_str",label:"Options",type:"textarea",setting:{rows:3,placeholder:"Label:Value per line"},if:'this.data.user_type=="select"'}],formatter:(e,t)=>{if(null==e||""===e)return"";const i=(t.settings?.options||[]).find(t=>"object"==typeof t?t.value==e:t==e);return i?"object"==typeof i?i.label:i:e}}),DataGridConfig.registerFieldType({value:"checkbox",label:"{#Multi Select#}",typeForDB:"v4096",schema:[{name:"options_str",label:"Options",type:"textarea",setting:{rows:3,placeholder:"Label:Value per line"},if:'this.data.user_type=="checkbox"'}],formatter:(e,t)=>{if(!Array.isArray(e))return null==e?"":String(e);const i=t.settings?.options||[];return e.map(e=>{const t=i.find(t=>"object"==typeof t?t.value==e:t==e);return t?"object"==typeof t?t.label:t:e}).join(", ")}}),DataGridConfig.registerFieldType({value:"switch",label:"{#Switch#}",typeForDB:"b",schema:[{name:"labelOn",label:"Label On",type:"text",if:'this.data.user_type=="switch"'},{name:"labelOff",label:"Label Off",type:"text",if:'this.data.user_type=="switch"'}],formatter:(e,t)=>{const i=t.settings||{};return e?i.labelOn||"Yes":i.labelOff||"No"}}),DataGridConfig.registerFieldType({value:"datetime",label:"{#DateTime#}",typeForDB:"dt",schema:[{name:"format",label:"Format",type:"text",setting:{placeholder:"YYYY-MM-DD"},if:'this.data.user_type=="datetime"'}]}),DataGridConfig.registerFieldType({value:"textarea",label:"{#Long Text#}",typeForDB:"t",schema:[{name:"placeholder",label:"Placeholder",type:"text",if:'this.data.user_type=="textarea"'}]});const createPerfMonitor=()=>{let e=!!globalThis.__DT_PERF_MODE__;const t={refreshTime:0,refreshCount:0,scrollCount:0,totalNodes:0};return e&&!globalThis.__statePerformanceTelemetry&&(globalThis.__statePerformanceTelemetry={scanCount:0,reuseCount:0,moveCount:0}),{get stats(){return t},enable:()=>{e=!0},disable:()=>{e=!1},onScroll:()=>{e&&t.scrollCount++},startFrame:()=>e?{start:performance.now(),scan:globalThis.__statePerformanceTelemetry?.scanCount||0,move:globalThis.__statePerformanceTelemetry?.moveCount||0,reuse:globalThis.__statePerformanceTelemetry?.reuseCount||0}:null,endFrame:(i,n)=>{if(!e||!i)return;t.refreshCount++,t.totalNodes+=n;const l=performance.now()-i.start;t.refreshTime+=l;const s=globalThis.__statePerformanceTelemetry;if(s){const e=s.scanCount-i.scan,t=s.moveCount-i.move,o=s.reuseCount-i.reuse;(e>0||l>2)&&console.log(`[DataGrid Frame] Time: ${l.toFixed(2)}ms, Scans: ${e}, Moves: ${t}, Reuses: ${o}, Rows: ${n}`)}}}},createScrollManager=(e,t,i)=>{const n=globalThis.VirtualScroll({itemHeight:40});let l=null;const s=(e=!1)=>{if(!l)return;const s=n.calc(l,t.list);if(s){if(!e&&t.prevHeight===s.prevHeight&&t.postHeight===s.postHeight&&t._listStartIndex===s.listStartIndex&&t._renderedList.length===s.renderedList.length)return;Object.assign(t,{prevHeight:s.prevHeight,postHeight:s.postHeight,_listStartIndex:s.listStartIndex,_renderedList:s.renderedList}),i?.(s.renderedList.length,e)}};return{init:()=>{l=e.querySelector(".dt-main")},reset:i=>{t._listStartIndex=0,n.reset(i,l||e),t.list===i&&n.init(i,()=>s(!0))},refresh:s,onScroll:()=>s(!1)}},createSelectionManager=(e,t)=>{let i=null,n=null,l=[];const s=(e,t)=>!!(i&&e>=i.minRow&&e<=i.maxRow&&t>=i.minCol&&t<=i.maxCol)||l.some(i=>e>=i.minRow&&e<=i.maxRow&&t>=i.minCol&&t<=i.maxCol);let o=!1;const a=()=>{if(globalThis.__DT_FEATURES__&&!globalThis.__DT_FEATURES__.selection)return;let n=1/0,a=-1/0;i&&(n=Math.min(n,i.minRow),a=Math.max(a,i.maxRow)),l.forEach(e=>{n=Math.min(n,e.minRow),a=Math.max(a,e.maxRow)});const r=n!==1/0;if(!r&&!o)return;o=r;const d=e.querySelector(".dt-body");if(!d)return;d.querySelectorAll(".dt-body-row").forEach(e=>{const i=(e._ref?.rIdx??-1)+t._listStartIndex,l=e.querySelectorAll(".dt-cell");!r||i<n||i>a?l.forEach(e=>e.classList.remove("dt-cell-selected")):l.forEach((e,t)=>{s(i,t)?e.classList.add("dt-cell-selected"):e.classList.remove("dt-cell-selected")})})},r=()=>{let e=0;i&&(e+=i.maxRow-i.minRow+1),l.forEach(t=>e+=t.maxRow-t.minRow+1),t.selectedRowCount=e},d=(e=!1)=>{e||(i=null,n=null,l=[],a(),r())},c=()=>{if(!i)return null;let e=i.minRow,t=i.maxRow,n=i.minCol,s=i.maxCol;return l.forEach(i=>{e=Math.min(e,i.minRow),t=Math.max(t,i.maxRow),n=Math.min(n,i.minCol),s=Math.max(s,i.maxCol)}),{minRow:e,maxRow:t,minCol:n,maxCol:s}};return{applySelectionUI:a,clearAllActive:d,startSelect:(o,c,m)=>{const p=s(o,c),u=i&&(i.minRow!==i.maxRow||i.minCol!==i.maxCol)||l.length>0;m.shiftKey&&n?i={minRow:Math.min(n.row,o),maxRow:Math.max(n.row,o),minCol:Math.min(n.col,c),maxCol:Math.max(n.col,c)}:(!p||m.ctrlKey||m.metaKey?(m.ctrlKey||m.metaKey?i&&!p&&l.push(i):d(),n={row:o,col:c},i={minRow:o,maxRow:o,minCol:c,maxCol:c}):u||(e._potentialCancel={row:o,col:c}),t.isSelecting=!0),a(),r(),e.focus()},updateSelect:(l,s)=>{t.isSelecting&&n&&(i={minRow:Math.min(n.row,l),maxRow:Math.max(n.row,l),minCol:Math.min(n.col,s),maxCol:Math.max(n.col,s)},e._potentialCancel=null,a(),r())},endSelect:()=>{if(e._potentialCancel){const{row:t,col:i}=e._potentialCancel;s(t,i)&&d(),e._potentialCancel=null}t.isSelecting=!1},getSelectionBounds:c,copy:async()=>{const e=c();if(!e)return;const i=t.list.slice(e.minRow,e.maxRow+1).map(i=>t.fields.slice(e.minCol,e.maxCol+1).map(e=>{let t=String(i[e.id]??"");return(t.includes("\t")||t.includes("\n")||t.includes('"'))&&(t='"'+t.replace(/"/g,'""')+'"'),t}).join("\t")).join("\n");await navigator.clipboard.writeText(i)},paste:async()=>{try{const i=await navigator.clipboard.readText();if(!i)return;const n=c();if(!n)return;const l=i.split(/\r?\n/).filter(e=>e.length>0).map(e=>{const t=[];let i="",n=!1;for(let l=0;l<e.length;l++){const s=e[l];'"'===s?n&&'"'===e[l+1]?(i+='"',l++):n=!n:"\t"!==s||n?i+=s:(t.push(i),i="")}return t.push(i),t});let{minRow:s,minCol:o,maxRow:a,maxCol:r}=n;minRow===a&&minCol===r&&(a=1/0,r=1/0);const d=e.querySelector(".dt-body");d&&Array.from(d.childNodes).filter(e=>e.classList?.contains("dt-body-row"));let m=!1;l.forEach((e,i)=>{const n=s+i;if(n>a||n>=t.list.length)return;const l=t.list[n];let d=!1;e.forEach((e,i)=>{const n=o+i;if(n>r||n>=t.fields.length)return;const s=t.fields[n];l[s.id]=e,d=!0}),d&&(m=!0)}),m&&(t.list=[...t.list])}catch(e){console.error("Paste Error:",e)}}}};globalThis.Component.register("DataGrid",e=>{e.state||(e.state=globalThis.NewState({}));const t=e.state;e.editable=e.hasAttribute("editable"),Object.assign(t,{list:[],fields:[],_renderedList:[],prevHeight:0,postHeight:0,_listStartIndex:0,selectedRowCount:0,_originalList:[],sortConfig:{fieldId:null,direction:null},filterConfig:{},activeFieldId:null,activeField:null,activeModes:[],_columnStats:{},_internalUpdate:!1,_appliedHash:"",_fieldsDirty:!1,_masterCellNodes:null,isDirty:!1,isBulkEdit:null,editable:e.editable});const i=createPerfMonitor();t.perf=i.stats;const n=createSelectionManager(e,t),l=createScrollManager(e,t,()=>setTimeout(()=>n.applySelectionUI(),50)),s=e.querySelector(".dt-column-menu");s&&(s._thisObj=e),e.onColumnResizing=(t,i)=>e.style.setProperty(`--w-${t.id}`,i.detail.newSize+"px"),e.onColumnResize=(e,i)=>{const n=t.fields.findIndex(t=>t.id===e.id);-1!==n&&(t.fields[n].width=i.detail.newSize,t.fields=[...t.fields])};let o,a=null;e.format=(e,t)=>{if(t.formatter)return t.formatter(e,t);const i=DataGridConfig._fieldTypes.get(t.settings?.formType||t.type||"text");return i&&i.formatter?i.formatter(e,t):null==e?"":"object"==typeof e?JSON.stringify(e):String(e)},e.onScroll=()=>{i.onScroll(),l.refresh(),e.hideColumnMenu(),e.hideEditor(!0);const n=e.querySelector(".dt-spacer-prev"),s=e.querySelector(".dt-spacer-post");n&&(n.style.height=(t.prevHeight||0)+"px",n.style.display=t.prevHeight>0?"block":"none"),s&&(s.style.height=(t.postHeight||0)+"px",s.style.display=t.postHeight>0?"block":"none")},e.applySortFilter=(e={})=>{if(t._internalUpdate)return;const i={...t.filterConfig,...e.filters||{}},n=void 0!==e.sort?e.sort?{fieldId:t.activeFieldId,direction:e.sort}:{fieldId:null,direction:null}:t.sortConfig;let l=[...t._originalList];if(Object.entries(i).forEach(([e,t])=>{(t.value||t.selectedValues&&0!==t.selectedValues.length)&&(l=l.filter(i=>{const n=i[e];if(t.selectedValues?.length>0)return t.selectedValues.includes(String(n));const l=String(t.value).toLowerCase(),s=String(n??"").toLowerCase();switch(t.mode){case"contains":return s.includes(l);case"equals":return s===l;case"starts":return s.startsWith(l);case"ends":return s.endsWith(l);case"=":return Number(n)===Number(t.value);case">":return Number(n)>Number(t.value);case"<":return Number(n)<Number(t.value);case"between":return Number(n)>=Number(t.value)&&Number(n)<=Number(t.value2);default:return!0}}))}),n.fieldId&&n.direction){const e=n.fieldId,t="asc"===n.direction?1:-1;l.sort((i,n)=>i[e]==n[e]?0:i[e]>n[e]?t:-t)}t._internalUpdate=!0,t.filterConfig=i,t.sortConfig=n,t.list=l,t._internalUpdate=!1},e.showColumnMenu=(i,n)=>{const l=n.currentTarget,s=e.querySelector(".dt-column-menu");if("block"===s.style.display&&t.activeFieldId===i.id)return e.hideColumnMenu(),void e.applySortFilter();const o=i.settings?.formType||i.type||"text";t.activeModes=MODE_MAP[o]||(["boolean","switch","checkbox","radio"].includes(o)?[]:MODE_MAP.text),t.filterConfig[i.id]||(t.filterConfig[i.id]={mode:t.activeModes[0]||"contains",value:"",selectedValues:[]}),t.activeField=i,t.activeFieldId=i.id,s.style.display="block";const a=l.closest(".dt-cell").getBoundingClientRect(),r=e.getBoundingClientRect(),d=s.offsetWidth||260;let c=a.right-r.left-d;c<0&&(c=Math.max(0,a.left-r.left)),s.style.left=c+"px",s.style.top=a.bottom-r.top+5+"px";const m=t=>{s.contains(t.target)||l.contains(t.target)||(e.hideColumnMenu(),e.applySortFilter(),document.removeEventListener("mousedown",m))};document.addEventListener("mousedown",m),setTimeout(()=>s.querySelector("input")?.focus(),50)},e.toggleSelectedValue=i=>{const n=t.filterConfig[t.activeFieldId];if(!n)return;const l=n.selectedValues.indexOf(i);-1===l?n.selectedValues.push(i):n.selectedValues.splice(l,1),t.filterConfig={...t.filterConfig},e.applySortFilter()},e.filterOnlyThis=i=>{t.filterConfig[t.activeFieldId]={mode:"contains",value:"",selectedValues:[String(i)]},t.filterConfig={...t.filterConfig},e.applySortFilter()},e.hideColumnMenu=()=>{const t=e.querySelector(".dt-column-menu");t&&(t.style.display="none")},e.setSort=i=>{const n=t.sortConfig.direction===i&&t.sortConfig.fieldId===t.activeFieldId?null:i;e.applySortFilter({sort:n})},e.clearColumnSettings=()=>{t.activeFieldId&&(t.filterConfig={...t.filterConfig,[t.activeFieldId]:{mode:t.activeModes[0]||"contains",value:"",value2:"",selectedValues:[]}},e.applySortFilter())},e._initRow=i=>{const n=i._ref?.item;n&&void 0===n._editingF&&Object.defineProperty(n,"_editingF",{set:t=>{null===t&&e.hideEditor(!0)},configurable:!0}),Array.from(i.children).forEach(e=>{const n=parseInt(e.dataset.fidx);isNaN(n)||(e._ref={...e._ref||i._ref,f:t.fields[n],fIdx:n})})},t.__watch("fields",i=>{if(!i)return;t._fieldsDirty=!0,t._masterCellNodes=null,e.style.setProperty("--dt-grid-template",i.map(e=>`var(--w-${e.id}, ${e.settings?.width||e.width||150}px)`).join(" ")),e.style.setProperty("--dt-row-width",i.reduce((e,t)=>e+(t.settings?.width||t.width||150),0)+"px");let n=0;i.forEach(t=>{"left"===(t.settings?.pinned||t.pinned)&&(e.style.setProperty(`--l-${t.id}`,n+"px"),n+=t.settings?.width||t.width||150)});let l=0;[...i].reverse().forEach(t=>{"right"===(t.settings?.pinned||t.pinned)&&(e.style.setProperty(`--r-${t.id}`,l+"px"),l+=t.settings?.width||t.width||150)})}),t.__watch("list",e=>{t._internalUpdate||(t._originalList=[...e||[]],setTimeout(()=>{const e={};t.fields.forEach(i=>{const n={};t._originalList.forEach(e=>{const t=e[i.id],l=null==t||""===t?"":String(t);n[l]=(n[l]||0)+1}),e[i.id]=Object.entries(n).sort((e,t)=>t[1]-e[1]).slice(0,20).map(([e,t])=>({val:e,count:t}))}),t._columnStats=e},200)),l.init(),l.reset(e)}),e.editCell=(t,i,n)=>{const l=e.querySelector(".dt-main"),s=e.querySelector(".dt-editor-overlay"),o=n.getBoundingClientRect(),r=l.getBoundingClientRect();a=n;const d=i.settings?.formType||i.type||"text",c=s.querySelector("AutoForm");c&&(c.state.data=globalThis.NewState(globalThis.Util.clone(t)),c.state.schema=[{...i,type:d,options:i.settings?.options||i.options,name:i.id,label:""}]);const m=["textarea","TagsInput","checkbox","radio"].includes(d);let p=o.top-r.top+l.scrollTop-1,u=o.left-r.left+l.scrollLeft-1,f=Math.max(o.width+2,m?300:0);const h=l.scrollWidth-f-5;u>h&&(u=Math.max(0,h)),Object.assign(s.style,{display:"flex",left:u+"px",top:p+"px",width:f+"px",height:"auto",minHeight:o.height+2+"px",maxHeight:Math.max(100,r.height-(o.top-r.top)-5)+"px",overflow:"auto",padding:"0"}),setTimeout(()=>s.querySelector("input, textarea, select, .form-control")?.focus(),30)},e.hideEditor=(i=!0)=>{if(o||(o=e.querySelector(".dt-editor-overlay")),!o||"none"===o.style.display)return;const n=o.querySelector("AutoForm");if(i&&n&&n.data){const e=o.querySelector("input:focus, select:focus, textarea:focus");e&&e.dispatchEvent(new Event("number"===e.type||"SELECT"===e.tagName?"change":"input",{bubbles:!0}));let i=!1;if((n.state.schema||[]).forEach(e=>{const t=a?.closest(".dt-row")?._ref?.item;t&&JSON.stringify(t[e.name])!==JSON.stringify(n.data[e.name])&&(t[e.name]=n.data[e.name],i=!0)}),t.isBulkEdit){const{minRow:e,maxRow:l,fIdx:s}=t.isBulkEdit,o=t.fields[s],a=n.data[o.id];for(let n=e;n<=l;n++)t.list[n]&&t.list[n][o.id]!==a&&(t.list[n][o.id]=a,i=!0)}i&&(t.list=[...t.list],t.isDirty=!0)}o.style.display="none",n&&(n.state.schema=[],n.data=null),a=null,t.isBulkEdit=null,e.focus()},e.onMainMouseDown=i=>{const l=i.target.closest(".dt-cell"),s=l?.closest(".dt-row");if(!s||s.classList.contains("dt-header-row"))return;const o=l.dataset.fidx?parseInt(l.dataset.fidx):Array.from(s.querySelectorAll(".dt-cell")).indexOf(l),a=s._ref?.rIdx??Array.from(e.querySelectorAll(".dt-body-row")).indexOf(s);n.startSelect(a+t._listStartIndex,o,i)},e.onMainMouseOver=i=>{if(t.isSelecting){const l=i.target.closest(".dt-cell"),s=l?.closest(".dt-row");if(s&&!s.classList.contains("dt-header-row")){const i=l.dataset.fidx?parseInt(l.dataset.fidx):Array.from(s.querySelectorAll(".dt-cell")).indexOf(l),o=s._ref?.rIdx??Array.from(e.querySelectorAll(".dt-body-row")).indexOf(s);n.updateSelect(o+t._listStartIndex,i)}}},e.onMainDblClick=i=>{if(!e.editable)return;const l=i.target.closest(".dt-cell"),s=l?.closest(".dt-row");if(s&&!s.classList.contains("dt-header-row")){const i=s._ref?.item,o=l.dataset.fidx?parseInt(l.dataset.fidx):Array.from(s.querySelectorAll(".dt-cell")).indexOf(l),a=(s._ref?.rIdx??Array.from(e.querySelectorAll(".dt-body-row")).indexOf(s))+t._listStartIndex;if(i&&t.fields[o]){const s=n.getSelectionBounds();if(s&&a>=s.minRow&&a<=s.maxRow&&o>=s.minCol&&o<=s.maxCol){const e=s.maxRow-s.minRow+1;e>1&&(t.isBulkEdit={...s,fIdx:o},globalThis.UI?.toast&&globalThis.UI.toast(`Bulk Edit: Updating ${e} rows in column "${t.fields[o].name}"`,{type:"warning"}))}e.editCell(i,t.fields[o],l)}}},e.addRow=()=>{const i={};t.fields.forEach(e=>i[e.id]=""),t._originalList.push(i),t.list=[...t._originalList],t.isDirty=!0,setTimeout(()=>{l.reset(t.list),e.querySelector(".dt-main").scrollTop=e.querySelector(".dt-main").scrollHeight},50)},e.deleteSelectedRow=async()=>{const i=n.getSelectionBounds();if(!i)return;const l=i.maxRow-i.minRow+1;if(await globalThis.Dialog.confirm(`Are you sure you want to delete ${l} row(s)?`)){const l=i.minRow,s=i.maxRow,o=t.list.slice(l,s+1);t.list=t.list.filter((e,t)=>!(t>=l&&t<=s)),t._originalList=t._originalList.filter(e=>!o.includes(e)),t.isDirty=!0,n.clearAllActive(),e.dispatchEvent(new CustomEvent("remove",{detail:{items:o}}))}},e.saveChanges=()=>{e.dispatchEvent(new CustomEvent("save",{detail:{list:t._originalList,fields:t.fields}})),t.isDirty=!1};const r=()=>{const e=globalThis.DataGrid.getFieldTypes(),t=[{name:"id",label:"Field ID",type:"text",setting:{required:!0,placeholder:"e.g. user_name"}},{name:"name",label:"Display Name",type:"text",setting:{required:!0,placeholder:"e.g. 用户名"}},{name:"user_type",label:"Field Type",type:"select",options:e.map(e=>({label:e.label,value:e.value}))}],i=e.reduce((e,t)=>e.concat(t.schema||[]),[]);return t.concat(i,[{name:"isIndex",label:"Index",type:"switch"},{name:"memo",label:"Memo",type:"text"}])},d=e=>{if(e)return e.split("\n").map(e=>e.trim()).filter(Boolean).map(e=>{const t=e.indexOf(":");return t>-1?{label:e.slice(0,t).trim(),value:e.slice(t+1).trim()}:e})};e.addField=async()=>{e.hideColumnMenu();const i=globalThis.NewState({id:"c"+Date.now().toString().slice(-4),name:"New Field",user_type:"text",decimals:0,isIndex:!1,memo:"",options_str:""}),n=e.querySelector(`Modal[id="${e.id}_field_modal"]`);if(!n)return;Object.assign(n.state,{title:"Add Field",buttons:["Cancel","Save"]});const l=n.querySelector("AutoForm");l&&(l.state.data=i,l.state.schema=r()),n.show();if(2===await new Promise(e=>n.addEventListener("change",t=>e(n.result),{once:!0}))){const n=globalThis.DataGrid.getFieldTypes().find(e=>e.value===i.user_type);let l=n?.typeForDB||"v1024";"number"===i.user_type&&(l=i.decimals>0?"ff":"bi");const s={id:i.id,name:i.name,memo:i.memo,isIndex:!!i.isIndex,type:l,settings:{formType:i.user_type,decimals:i.decimals,prefix:i.prefix,suffix:i.suffix,thousandSep:i.thousandSep,labelOn:i.labelOn,labelOff:i.labelOff,format:i.format,placeholder:i.placeholder,options:d(i.options_str)}};t.fields=[...t.fields,s],t.isDirty=!0,e.dispatchEvent(new CustomEvent("savefields",{detail:t.fields})),t.list=[...t.list]}},e.editField=async()=>{if(!t.activeField)return;e.hideColumnMenu();const i=t.activeField,n=i.settings||{},l=globalThis.NewState({id:i.id,name:i.name,memo:i.memo||"",isIndex:!!i.isIndex,user_type:n.formType||"text",decimals:n.decimals||0,prefix:n.prefix||"",suffix:n.suffix||"",thousandSep:!!n.thousandSep,labelOn:n.labelOn||"",labelOff:n.labelOff||"",format:n.format||"",placeholder:n.placeholder||"",options_str:(s=n.options,s?s.map(e=>"object"==typeof e?`${e.label}:${e.value}`:e).join("\n"):"")});var s;const o=e.querySelector(`Modal[id="${e.id}_field_modal"]`);if(!o)return;Object.assign(o.state,{title:"Edit Field",buttons:["Cancel","Save"]});const a=o.querySelector("AutoForm");a&&(a.state.data=l,a.state.schema=r()),o.show();if(2===await new Promise(e=>o.addEventListener("change",t=>e(o.result),{once:!0}))){const n=t.fields.findIndex(e=>e.id===i.id);if(-1!==n){const s=globalThis.DataGrid.getFieldTypes().find(e=>e.value===l.user_type);let o=s?.typeForDB||"v1024";"number"===l.user_type&&(o=l.decimals>0?"ff":"bi");const a={...i,id:l.id,name:l.name,memo:l.memo,isIndex:!!l.isIndex,type:o,settings:{...i.settings,formType:l.user_type,decimals:l.decimals,prefix:l.prefix,suffix:l.suffix,thousandSep:l.thousandSep,labelOn:l.labelOn,labelOff:l.labelOff,format:l.format,placeholder:l.placeholder,options:d(l.options_str)}};t.fields[n]=a,t.fields=[...t.fields],t.isDirty=!0,e.dispatchEvent(new CustomEvent("savefields",{detail:t.fields})),t.list=[...t.list]}}},e.deleteField=async()=>{if(t.activeField&&(e.hideColumnMenu(),await globalThis.Dialog.confirm(`Are you sure you want to delete field "${t.activeField.name}"?`))){const i=t.fields.findIndex(e=>e.id===t.activeField.id);-1!==i&&(t.fields.splice(i,1),t.fields=[...t.fields],t.isDirty=!0,e.dispatchEvent(new CustomEvent("savefields",{detail:t.fields})),t.list=[...t.list])}},window.addEventListener("mouseup",n.endSelect),document.addEventListener("mousedown",t=>{const i=e.querySelector(".dt-editor-overlay"),l=e.querySelector(".dt-column-menu");"none"===i?.style.display||i.contains(t.target)||setTimeout(()=>e.hideEditor(!0)),e.contains(t.target)||i?.contains(t.target)||l?.contains(t.target)||n.clearAllActive()}),e.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&"c"===e.key.toLowerCase()&&(e.preventDefault(),n.copy()),(e.ctrlKey||e.metaKey)&&"v"===e.key.toLowerCase()&&(e.preventDefault(),n.paste())}),t._MODE_ICONS=MODE_ICONS},globalThis.Util.makeDom('\n<div class="dt-root d-flex flex-column h-100 border bg-body text-body overflow-visible" style="position:relative; user-select:none; outline: none; min-height: 0" tabindex="0">\n <div class="dt-main flex-grow-1 overflow-auto" $onscroll="this.onScroll()" \n $onmousedown="this.onMainMouseDown(event)" $onmouseover="this.onMainMouseOver(event)" $ondblclick="this.onMainDblClick(event)"\n style="overflow-anchor:none; min-height: 0">\n <div class="dt-header border-bottom bg-light sticky-top" style="z-index:20">\n <div class="dt-header-row fw-bold text-muted small">\n <template $each="this.state?.fields || []">\n <div $data-id="item.id" $class="dt-cell dt-col border-end d-flex align-items-center header-cell ${(item.settings?.pinned || item.pinned) ? \'pinned-\' + (item.settings?.pinned || item.pinned) : \'\'}" $style="((item.settings?.pinned || item.pinned) ? \'position: sticky; z-index: 11; background-color: inherit; \' : \'position:relative; \') + \'padding: 0; \' + ((item.settings?.pinned || item.pinned) === \'left\' ? \'left: var(--l-\' + item.id + \'); border-right: 1px solid var(--bs-border-color); box-shadow: 2px 0 5px -2px rgba(0,0,0,0.1);\' : ((item.settings?.pinned || item.pinned) === \'right\' ? \'right: var(--r-\' + item.id + \'); border-left: 1px solid var(--bs-border-color); box-shadow: -2px 0 5px -2px rgba(0,0,0,0.1);\' : \'\'))">\n <div class="d-flex align-items-center overflow-hidden flex-grow-1 h-100 px-2 cursor-pointer" $onclick="this.showColumnMenu(item, event)">\n <i $if="this.state?.filterConfig?.[item.id] && (this.state.filterConfig[item.id].value || this.state.filterConfig[item.id].selectedValues?.length)" class="bi bi-filter me-1 text-primary"></i>\n <i $if="this.state?.sortConfig?.fieldId === item.id && this.state.sortConfig.direction" $class="bi bi-sort-${this.state.sortConfig.direction === \'asc\' ? \'down\' : \'up-alt\'} me-1 text-primary"></i>\n <span $text="item.name" class="text-truncate flex-grow-1"></span>\n </div>\n <button class="btn btn-xs btn-link text-muted p-0 border-0 me-1 header-menu-btn" $onclick="this.showColumnMenu(item, event)"><i class="bi bi-chevron-down"></i></button>\n <Resizer $.target="thisNode.parentElement" style="position:absolute; right:0; top:0; bottom:0; width:4px; z-index:10" min="50" max="1000" $onresizing="this.onColumnResizing(item, event)" $onresize="this.onColumnResize(item, event)"></Resizer>\n </div>\n </template>\n </div>\n </div>\n <div class="dt-body" style="position:relative">\n <div class="dt-spacer-prev flex-shrink-0" style="display:none"></div>\n <template $each="this.state?._renderedList || []" key="id" index="rIdx">\n <div class="dt-row dt-body-row border-bottom bg-white" $.="this._initRow(thisNode)">\n <template $each="this.state?.fields || []" as="f" index="fIdx" key="id"><div $data-fidx="fIdx" $class="dt-cell border-end px-2 d-flex align-items-center ${(f.settings?.pinned || f.pinned) ? \'pinned-\' + (f.settings?.pinned || f.pinned) : \'\'}" $style="((f.settings?.pinned || f.pinned) ? \'position: sticky; z-index: 1; background-color: inherit; \' : \'\') + ((f.settings?.pinned || f.pinned) === \'left\' ? \'left: var(--l-\' + f.id + \'); border-right: 1px solid var(--bs-border-color); box-shadow: 2px 0 5px -2px rgba(0,0,0,0.1);\' : ((f.settings?.pinned || f.pinned) === \'right\' ? \'right: var(--r-\' + f.id + \'); border-left: 1px solid var(--bs-border-color); box-shadow: -2px 0 5px -2px rgba(0,0,0,0.1);\' : \'\'))"><span $text="this.format(item[f.id], f)" class="text-truncate"></span></div></template>\n </div>\n </template>\n <div class="dt-spacer-post flex-shrink-0" style="display:none"></div>\n </div>\n <div class="dt-editor-overlay dt-editor-container" style="display: none; position: absolute; z-index: 1000; background: var(--bs-body-bg); box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: 1px solid var(--bs-primary); padding: 0;"><AutoForm nobutton inline class="h-100 w-100" $onsubmit="event.preventDefault(); thisNode.closest(\'DataGrid\').hideEditor(true)"></AutoForm></div>\n </div>\n\n <div class="dt-column-menu bg-body shadow-lg rounded p-2" style="display:none; position:absolute; z-index:2000; min-width:240px; max-width:300px; border: 1px solid var(--bs-primary)">\n <template $if="this.state?.activeFieldId">\n <div class="d-flex gap-1 mb-2">\n <button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center ${this.state?.sortConfig?.direction === \'asc\' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? \'btn-primary\' : \'btn-outline-secondary border\'}" $onclick="this.setSort(\'asc\')"><i class="bi bi-sort-alpha-down me-1"></i> ASC</button>\n <button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center ${this.state?.sortConfig?.direction === \'desc\' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? \'btn-primary\' : \'btn-outline-secondary border\'}" $onclick="this.setSort(\'desc\')"><i class="bi bi-sort-alpha-up-alt me-1"></i> DESC</button>\n </div>\n\n <template $if="this.state?.activeModes?.length">\n <div class="py-2 border-bottom" style="min-height: 48px">\n <div class="input-group input-group-sm mb-1">\n <input type="text" class="form-control" $placeholder="(this.state?.filterConfig?.[this.state?.activeFieldId]?.mode || \'Search\').toUpperCase() + \'...\'" $bind="this.state.filterConfig[this.state.activeFieldId].value" $oninput="this.applySortFilter()" $onkeydown="if(event.key===\'Enter\'){this.applySortFilter();this.hideColumnMenu();}">\n <button class="btn btn-outline-secondary border dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Search Mode">\n <i $class="bi ${this.state?._MODE_ICONS?.[this.state?.filterConfig?.[this.state?.activeFieldId]?.mode] || \'bi-filter\'}"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end shadow">\n <template $each="this.state?.activeModes || []" as="m">\n <li><button class="dropdown-item d-flex align-items-center" type="button" $onclick="this.state.filterConfig[this.state.activeFieldId].mode = m; this.state.filterConfig = {...this.state.filterConfig}">\n <i $class="bi ${this.state?._MODE_ICONS?.[m] || \'bi-filter\'} me-2"></i> <span $text="m.toUpperCase()"></span>\n </button></li>\n </template>\n </ul>\n </div>\n <input $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === \'between\'" type="text" class="form-control form-control-sm" placeholder="And..." $bind="this.state.filterConfig[this.state.activeFieldId].value2" $oninput="this.applySortFilter()" $onkeydown="if(event.key===\'Enter\'){this.applySortFilter();this.hideColumnMenu();}">\n </div>\n </template>\n\n <div class="mt-2" style="max-height: 180px; overflow-y: auto;">\n <template $each="this.state?._columnStats?.[this.state?.activeFieldId] || []">\n <label class="d-flex align-items-center mb-1 small cursor-pointer p-1 rounded-1 menu-item-row" onmouseover="this.style.background=\'var(--bs-light)\'" onmouseout="this.style.background=\'transparent\'">\n <input type="checkbox" class="form-check-input me-2" $checked="this.state?.filterConfig?.[this.state?.activeFieldId]?.selectedValues?.includes(String(item.val))" $onclick="this.toggleSelectedValue(String(item.val))">\n <span class="text-truncate flex-grow-1"><span $text="item.val || \'(Empty)\'"></span> <span class="text-muted ms-1" style="font-size: 0.7rem" $text="\'(\' + item.count + \')\'"></span></span>\n <button class="btn btn-xs btn-link p-0 text-primary only-btn" style="font-size: 10px; text-decoration: none" $onclick="this.filterOnlyThis(item.val); event.preventDefault(); event.stopPropagation();">Only</button>\n </label>\n </template>\n </div>\n\n <div $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.value || this.state?.filterConfig?.[this.state?.activeFieldId]?.selectedValues?.length" class="mt-2 pt-1 border-top text-center">\n <span class="cursor-pointer text-primary small fw-bold" $onclick="this.clearColumnSettings()"><i class="bi bi-x-circle me-1"></i> Clear Filter</span>\n </div>\n\n <div $if="this.state?.editable" class="mt-3 pt-2 border-top d-flex gap-1 justify-content-between">\n <button class="btn btn-sm btn-light border-0 flex-grow-1" title="Edit Field" $onclick="this.editField()"><i class="bi bi-pencil"></i></button>\n <button class="btn btn-sm btn-light border-0 flex-grow-1" title="Add Field" $onclick="this.addField()"><i class="bi bi-plus-lg"></i></button>\n <button class="btn btn-sm btn-light border-0 flex-grow-1 text-danger" title="Delete Field" $onclick="this.deleteField()"><i class="bi bi-trash"></i></button>\n </div>\n </template>\n </div>\n\n <Modal $.id="this.id + \'_field_modal\'">\n <div slot="body"><AutoForm nobutton class="p-3"></AutoForm></div>\n <div slot="footer">\n <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" $onclick="thisNode.closest(\'Modal\').result=1">Cancel</button>\n <button type="button" class="btn btn-primary" data-bs-dismiss="modal" $onclick="thisNode.closest(\'Modal\').result=2">Save</button>\n </div>\n </Modal>\n\n <div class="dt-footer border-top bg-light d-flex align-items-center px-3 py-1 shadow-sm" style="height:40px; z-index: 10">\n <div class="d-flex align-items-center gap-3 flex-grow-1">\n <div $if="this.state?.editable" class="d-flex align-items-center gap-1">\n <button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.addRow()" title="Add Row"><i class="bi bi-plus-lg text-primary me-1"></i> Add</button>\n <button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.deleteSelectedRow()" $disabled="!this.state?.selectedRowCount" title="Delete Selected Rows"><i class="bi bi-trash text-danger me-1"></i> Delete</button>\n </div>\n <div $if="this.state?.editable" class="vr h-50 my-auto text-muted opacity-25"></div>\n <div class="d-flex align-items-center gap-2 text-muted" style="font-size: 0.75rem">\n <i class="bi bi-check-all fs-6"></i>\n <span $text="(this.state?.selectedRowCount || 0) + \' selected / \' + (this.state?.list?.length || 0) + \' total\'"></span>\n </div>\n </div>\n <div $if="this.state?.editable" class="d-flex align-items-center gap-2">\n <button $if="this.state?.isDirty" class="btn btn-sm btn-primary border-0 px-3 shadow-sm d-flex align-items-center fw-bold" $onclick="this.saveChanges()"><i class="bi bi-cloud-upload me-1"></i> Save</button>\n <button $if="!this.state?.isDirty" class="btn btn-sm btn-light border-0 px-3 text-muted disabled d-flex align-items-center" disabled><i class="bi bi-cloud-check me-1"></i> Saved</button>\n </div>\n </div>\n</div>\n'),globalThis.Util.makeDom("\n<style>\n DataGrid { display: block; }\n .dt-root { font-size: 0.875rem; }\n .dt-row, .dt-header-row { display: grid; grid-template-columns: var(--dt-grid-template); width: var(--dt-row-width, max-content); min-width: 100%; height: 40px; contain: paint layout; }\n .dt-main { position: relative; }\n .dt-header-row { background-color: var(--bs-tertiary-bg); border-bottom: 1px solid var(--bs-border-color); }\n .dt-cell { background: inherit; white-space: nowrap; flex-shrink: 0; contain: content; }\n .dt-cell-selected { background-color: rgba(var(--bs-primary-rgb), 0.15) !important; outline: 1px solid var(--bs-primary); outline-offset: -1px; }\n .dt-body-row:hover { background-color: var(--bs-secondary-bg) !important; }\n .header-cell .header-menu-btn { opacity: 0; transition: opacity 0.2s; }\n .header-cell:hover .header-menu-btn { opacity: 1; }\n .dt-column-menu { background-color: var(--bs-body-bg); border: 1px solid var(--bs-primary); box-shadow: 0 10px 40px rgba(0,0,0,0.2) !important; z-index: 2100 !important; }\n .btn-xs { padding: 1px 5px; line-height: 1.5; }\n .cursor-pointer { cursor: pointer; }\n .dt-filter-tabs i { font-size: 1.1rem; }\n .dt-filter-tabs div:hover i { color: var(--bs-primary); }\n .menu-item-row .only-btn { opacity: 0; }\n .menu-item-row:hover .only-btn { opacity: 1; }\n .dt-editor-overlay .auto-form-root form { gap: 0 !important; margin: 0 !important; height: 100%; }\n .dt-editor-overlay [control-wrapper] { width: 100%; margin: 0 !important; min-height: 100% !important; align-items: stretch !important; }\n .dt-editor-overlay [control-wrapper] > .d-flex { padding: 0.375rem 0.5rem; justify-content: flex-start !important; align-items: center !important; }\n .dt-editor-overlay [control-wrapper] > .form-switch { padding-left: 2.5rem !important; }\n .dt-editor-overlay [control-wrapper] > textarea { min-height: 100px; resize: vertical; }\n</style>\n")),globalThis.DataGrid=DataGridConfig;
|