import { customElement, html, DeesElement, property, type TemplateResult, cssManager, css, unsafeCSS, type CSSResult, state, resolveExec, } from '@design.estate/dees-element'; import { DeesContextmenu } from './dees-contextmenu.js' import * as domtools from '@design.estate/dees-domtools'; import { type TIconKey } from './dees-icon.js'; declare global { interface HTMLElementTagNameMap { 'dees-table': DeesTable; } } // interfaces export interface IDataAction { name: string; iconName: TIconKey; /** * the table behaviour to use for this action * e.g. upload: allows to upload files to the table */ useTableBehaviour?: 'upload' | 'cancelUpload' | 'none'; /** * the type of the action */ type: 'inRow' | 'rightClick' | 'footer' | 'header' | 'preview' | 'keyCombination'; /** * allows to check if the action is relevant for the given item * @param itemArg * @returns */ actionRelevancyCheckFunc?: (itemArg: T) => boolean; /** * the actual action function implementation * @param itemArg * @returns */ actionFunc: (itemArg: T) => Promise; } export type TDisplayFunction = (itemArg: T) => object; // the table implementation @customElement('dees-table') export class DeesTable extends DeesElement { public static demo = () => html`
{ return { ...itemArg, onlyDisplayProp: 'onlyDisplay', }; }} >This is a slotted Text
`; // INSTANCE @property({ type: String, }) public heading1: string = 'heading 1'; @property({ type: String, }) public heading2: string = 'heading 2'; @property({ type: Array, }) public data: T[] = []; @property({ type: String, reflect: true, }) public dataName: string; @property({ type: Array, }) public dataActions: IDataAction[] = []; @property({ attribute: false, }) public displayFunction: TDisplayFunction = (itemArg: T) => itemArg as any; @property({ type: Object, }) public selectedDataRow: T; public files: File[] = []; public fileWeakMap = new WeakMap(); constructor() { super(); } public static styles = [ cssManager.defaultStyles, css` .mainbox { color: ${cssManager.bdTheme('#333', '#fff')}; font-family: 'Mona Sans', 'Inter', sans-serif; font-weight: 400; font-size: 14px; padding: 16px; display: block; width: 100%; min-height: 50px; background: ${cssManager.bdTheme('#ffffff', '#333333')}; border-radius: 3px; border-top: 1px solid ${cssManager.bdTheme('#fff', '#444')}; box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); overflow-x: auto; } .header { display: flex; justify-content: flex-end; align-items: center; } .headingContainer { } .heading { font-family: 'Hubot Sans', 'Inter', sans-serif; } .heading1 { font-weight: 600; } .heading2 { opacity: 0.6; } .headingSeparation { margin-top: 7px; border-bottom: 1px solid ${cssManager.bdTheme('#bcbcbc', '#bcbcbc')}; } .headerActions { margin-left: auto; cursor: pointer; } .headerAction { display: flex; color: ${cssManager.bdTheme('#333', '#ccc')}; } .headerAction:hover { color: ${cssManager.bdTheme('#555', '#fff')}; } .headerAction dees-icon { margin-right: 8px; } table, .noDataSet { margin-top: 16px; color: ${cssManager.bdTheme('#333', '#fff')}; border-collapse: collapse; width: 100%; } .noDataSet { text-align: center; } tr { border-bottom: 1px dashed ${cssManager.bdTheme('#999', '#808080')}; text-align: left; } tr:last-child { border-bottom: none; text-align: left; } tr:hover { cursor: pointer; } tr:hover .innerCellContainer { background: ${cssManager.bdTheme('#22222210', '#ffffff20')}; } tr:first-child:hover { cursor: auto; } tr:first-child:hover .innerCellContainer { background: none; } tr.selected .innerCellContainer { background: ${cssManager.bdTheme('#22222220', '#ffffff40')}; } th { text-transform: uppercase; } th, td { padding: 3px 0px; border-right: 1px dashed ${cssManager.bdTheme('#999', '#808080')}; } .innerCellContainer { padding: 6px 8px; } th:first-child .innerCellContainer, td:first-child .innerCellContainer { padding-left: 0px; } th:last-child .innerCellContainer, td:last-child .innerCellContainer { padding-right: 0px; } th:last-child, td:last-child { border-right: none; } .action { margin: -8px 0px; padding: 8px; line-height: 16px; display: inline-block; } .action:first-child { margin-left: -8px; width: min-content; } .action:hover { background: ${cssManager.bdTheme('#CCC', '#111')}; } .footer { font-size: 14px; color: ${cssManager.bdTheme('#111', '#ffffff90')}; background: ${cssManager.bdTheme('#eeeeeb', '#00000050')}; margin: 16px -16px -16px -16px; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; display: flex; } .tableStatistics { padding: 8px 16px; } .footerActions { margin-left: auto; } .footerActions .footerAction { cursor: pointer; padding: 8px 16px; display: flex; } .footerActions .footerAction:hover { background: ${cssManager.bdTheme('#CCC', '#111')}; } .footerActions dees-icon { display: flex; margin-right: 8px; } `, ]; public render(): TemplateResult { return html`
${this.heading1}
${this.heading2}
${resolveExec(async () => { const resultArray: TemplateResult[] = []; for (const action of this.dataActions) { if (action.type !== 'header') continue; resultArray.push( html`
{ action.actionFunc(this.selectedDataRow); }} > ${action.iconName ? html` ${action.name}` : action.name}
` ); } return resultArray; })}
${this.data.length > 0 ? (() => { // Only pick up the keys from the first transformed data object // as all data objects are assumed to have the same structure const firstTransformedItem = this.displayFunction(this.data[0]); const headings: string[] = Object.keys(firstTransformedItem); return html` ${headings.map( (headingArg) => html` ` )} ${(() => { if (this.dataActions && this.dataActions.length > 0) { return html` `; } })()} ${this.data.map((itemArg) => { const transformedItem = this.displayFunction(itemArg); const getTr = (elementArg: HTMLElement): HTMLElement => { if (elementArg.tagName === 'TR') { return elementArg; } else { return getTr(elementArg.parentElement); } }; return html` { this.selectedDataRow = itemArg; }} @dragenter=${async (eventArg: DragEvent) => { eventArg.preventDefault(); eventArg.stopPropagation(); const realTarget = getTr(eventArg.target as HTMLElement); console.log('dragenter'); console.log(realTarget); setTimeout(() => { realTarget.style.background = 'green'; }, 0); }} @dragleave=${async (eventArg: DragEvent) => { eventArg.preventDefault(); eventArg.stopPropagation(); const realTarget = getTr(eventArg.target as HTMLElement); realTarget.style.background = 'none'; }} @dragover=${async (eventArg: DragEvent) => { eventArg.preventDefault(); }} @drop=${async (eventArg: DragEvent) => { eventArg.preventDefault(); const newFiles = []; for (const file of Array.from(eventArg.dataTransfer.files)) { this.files.push(file); newFiles.push(file); this.requestUpdate(); } const result: File[] = this.fileWeakMap.get(itemArg as object); if (!result) { this.fileWeakMap.set(itemArg as object, newFiles); } else { result.push(...newFiles); } }} @contextmenu=${async (eventArg: MouseEvent) => { DeesContextmenu.openContextMenuWithOptions(eventArg, [ { name: 'copy', iconName: 'copySolid', action: async () => { return null; }, }, { name: 'edit', iconName: 'penToSquare', action: async () => { return null; }, },{ name: 'paste', iconName: 'pasteSolid', action: async () => { return null; }, }, ]) }} class="${itemArg === this.selectedDataRow ? 'selected' : ''}" > ${headings.map( (headingArg) => html` ` )} ${(() => { if (this.dataActions && this.dataActions.length > 0) { return html` `; } })()} `; })}
${headingArg}
Actions
${transformedItem[headingArg]}
${(() => { const actions: TemplateResult[] = []; for (const action of this.dataActions) { if (action.type !== 'inRow') continue; actions.push( html`
action.actionFunc(itemArg)} > ${action.iconName ? html` ` : action.name}
` ); } return actions; })()}
`; })() : html`
No data set!
`}
`; } public async firstUpdated() {} }