AdvancedTable.vue 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099
  1. <template>
  2. <div>
  3. <div
  4. class="table-outer-container"
  5. @mousemove="columnResizingMouseMove($event)"
  6. >
  7. <div class="table-header">
  8. <tippy
  9. v-if="filters.length > 0"
  10. :touch="true"
  11. :interactive="true"
  12. placement="bottom"
  13. theme="search"
  14. ref="search"
  15. trigger="click"
  16. >
  17. <a class="button is-info" @click.prevent="true">
  18. <i class="material-icons icon-with-button">search</i>
  19. Search
  20. </a>
  21. <template #content>
  22. <div
  23. v-for="(query, index) in advancedQuery"
  24. :key="`query-${index}`"
  25. class="advanced-query"
  26. >
  27. <div class="control select">
  28. <select
  29. v-model="query.filter"
  30. @change="changeQueryFilter(index)"
  31. >
  32. <option
  33. v-for="filter in filters"
  34. :key="filter.name"
  35. :value="filter"
  36. >
  37. {{ filter.displayName }}
  38. </option>
  39. </select>
  40. </div>
  41. <div class="control select">
  42. <select
  43. v-model="query.filterType"
  44. :disabled="!query.filterType"
  45. >
  46. <option
  47. v-for="filterType in filterTypes(
  48. query.filter
  49. )"
  50. :key="filterType.name"
  51. :value="filterType.name"
  52. :selected="
  53. query.filter.defaultFilterType ===
  54. filterType.name
  55. "
  56. >
  57. {{ filterType.displayName }}
  58. </option>
  59. </select>
  60. </div>
  61. <p class="control is-expanded">
  62. <input
  63. v-model="query.data"
  64. class="input"
  65. type="text"
  66. placeholder="Search value"
  67. @keyup.enter="getData()"
  68. :disabled="!query.filterType"
  69. />
  70. </p>
  71. <div class="control">
  72. <button
  73. class="button material-icons is-success"
  74. @click="addQueryItem()"
  75. >
  76. control_point
  77. </button>
  78. </div>
  79. <div
  80. v-if="advancedQuery.length > 1"
  81. class="control"
  82. >
  83. <button
  84. class="button material-icons is-danger"
  85. @click="removeQueryItem(index)"
  86. >
  87. remove_circle_outline
  88. </button>
  89. </div>
  90. </div>
  91. <div class="advanced-query-bottom">
  92. <div class="control select">
  93. <select v-model="queryOperator">
  94. <option
  95. v-for="operator in queryOperators"
  96. :key="operator.name"
  97. :value="operator.name"
  98. >
  99. {{ operator.displayName }}
  100. </option>
  101. </select>
  102. </div>
  103. <div class="control is-expanded">
  104. <button
  105. class="button is-info"
  106. @click="getData()"
  107. >
  108. <i class="material-icons icon-with-button"
  109. >search</i
  110. >
  111. Search
  112. </button>
  113. </div>
  114. <div class="control">
  115. <button
  116. class="button is-warning material-icons"
  117. @click="resetQuery()"
  118. content="Reset query"
  119. v-tippy="{ theme: 'info' }"
  120. >
  121. refresh
  122. </button>
  123. </div>
  124. </div>
  125. </template>
  126. </tippy>
  127. <tippy
  128. v-if="hidableSortedColumns.length > 0"
  129. :touch="true"
  130. :interactive="true"
  131. placement="bottom"
  132. theme="dropdown"
  133. ref="editColumns"
  134. trigger="click"
  135. >
  136. <a class="button is-info" @click.prevent="true">
  137. <i class="material-icons icon-with-button">tune</i>
  138. Columns
  139. </a>
  140. <template #content>
  141. <draggable
  142. item-key="name"
  143. v-model="orderedColumns"
  144. v-bind="columnDragOptions"
  145. tag="div"
  146. draggable=".item-draggable"
  147. class="nav-dropdown-items"
  148. >
  149. <template #item="{ element: column }">
  150. <button
  151. v-if="column.name !== 'select'"
  152. :class="{
  153. sortable: column.sortable,
  154. 'item-draggable': column.draggable,
  155. 'nav-item': true
  156. }"
  157. @click.prevent="
  158. toggleColumnVisibility(column)
  159. "
  160. >
  161. <p
  162. class="
  163. control
  164. is-expanded
  165. checkbox-control
  166. "
  167. >
  168. <label class="switch">
  169. <input
  170. v-if="column.hidable"
  171. type="checkbox"
  172. :id="index"
  173. :checked="
  174. shownColumns.indexOf(
  175. column.name
  176. ) !== -1
  177. "
  178. @click="
  179. toggleColumnVisibility(
  180. column
  181. )
  182. "
  183. />
  184. <span
  185. :class="{
  186. slider: true,
  187. round: true,
  188. disabled: !column.hidable
  189. }"
  190. ></span>
  191. </label>
  192. <label :for="index">
  193. <span></span>
  194. <p>{{ column.displayName }}</p>
  195. </label>
  196. </p>
  197. </button>
  198. </template>
  199. </draggable>
  200. </template>
  201. </tippy>
  202. </div>
  203. <div class="table-container">
  204. <table class="table">
  205. <thead>
  206. <draggable
  207. item-key="name"
  208. v-model="orderedColumns"
  209. v-bind="columnDragOptions"
  210. tag="tr"
  211. draggable=".item-draggable"
  212. >
  213. <template #item="{ element: column }">
  214. <th
  215. :class="{
  216. sortable: column.sortable,
  217. 'item-draggable': column.draggable
  218. }"
  219. :style="{
  220. minWidth: `${column.minWidth}px`,
  221. width: `${column.width}px`,
  222. maxWidth: `${column.maxWidth}px`
  223. }"
  224. v-if="
  225. shownColumns.indexOf(column.name) !== -1
  226. "
  227. >
  228. <div>
  229. <span>
  230. {{ column.displayName }}
  231. </span>
  232. <span
  233. v-if="column.pinable"
  234. content="Toggle Pinned Column"
  235. v-tippy
  236. @click="togglePinnedColumn(column)"
  237. >
  238. <span
  239. :class="{
  240. 'material-icons': true,
  241. active:
  242. pinnedColumns.indexOf(
  243. column.name
  244. ) !== -1
  245. }"
  246. >
  247. push_pin
  248. </span>
  249. </span>
  250. <span
  251. v-if="column.sortable"
  252. :content="`Sort by ${column.displayName}`"
  253. v-tippy
  254. >
  255. <span
  256. v-if="
  257. !sort[column.sortProperty]
  258. "
  259. class="material-icons"
  260. @click="changeSort(column)"
  261. >
  262. unfold_more
  263. </span>
  264. <span
  265. v-if="
  266. sort[
  267. column.sortProperty
  268. ] === 'ascending'
  269. "
  270. class="material-icons active"
  271. @click="changeSort(column)"
  272. >
  273. expand_more
  274. </span>
  275. <span
  276. v-if="
  277. sort[
  278. column.sortProperty
  279. ] === 'descending'
  280. "
  281. class="material-icons active"
  282. @click="changeSort(column)"
  283. >
  284. expand_less
  285. </span>
  286. </span>
  287. </div>
  288. <div
  289. class="resizer"
  290. v-if="column.resizable"
  291. @mousedown.prevent.stop="
  292. columnResizingMouseDown(
  293. column,
  294. $event
  295. )
  296. "
  297. @mouseup="columnResizingMouseUp()"
  298. @dblclick="columnResetWidth(column)"
  299. ></div>
  300. </th>
  301. </template>
  302. </draggable>
  303. </thead>
  304. <tbody>
  305. <tr
  306. v-for="(item, itemIndex) in data"
  307. :key="item._id"
  308. :class="{
  309. selected: item.selected,
  310. highlighted: item.highlighted
  311. }"
  312. @click="clickItem(itemIndex, $event)"
  313. >
  314. <td
  315. v-for="column in sortedFilteredColumns"
  316. :key="`${item._id}-${column.name}`"
  317. >
  318. <slot
  319. :name="`column-${column.name}`"
  320. :item="item"
  321. v-if="
  322. column.properties.length === 0 ||
  323. column.properties.every(
  324. property =>
  325. item[property] !== undefined
  326. )
  327. "
  328. ></slot>
  329. <div
  330. class="resizer"
  331. v-if="column.resizable"
  332. @mousedown.prevent.stop="
  333. columnResizingMouseDown(column, $event)
  334. "
  335. @mouseup="columnResizingMouseUp()"
  336. @dblclick="columnResetWidth(column)"
  337. ></div>
  338. </td>
  339. </tr>
  340. </tbody>
  341. </table>
  342. </div>
  343. <div class="table-footer">
  344. <div class="page-controls">
  345. <button
  346. :class="{ disabled: page === 1 }"
  347. class="button is-primary material-icons"
  348. :disabled="page === 1"
  349. @click="changePage(1)"
  350. content="First Page"
  351. v-tippy
  352. >
  353. skip_previous
  354. </button>
  355. <button
  356. :class="{ disabled: page === 1 }"
  357. class="button is-primary material-icons"
  358. :disabled="page === 1"
  359. @click="changePage(page - 1)"
  360. content="Previous Page"
  361. v-tippy
  362. >
  363. fast_rewind
  364. </button>
  365. <p>Page {{ page }} / {{ lastPage }}</p>
  366. <button
  367. :class="{ disabled: page === lastPage }"
  368. class="button is-primary material-icons"
  369. :disabled="page === lastPage"
  370. @click="changePage(page + 1)"
  371. content="Next Page"
  372. v-tippy
  373. >
  374. fast_forward
  375. </button>
  376. <button
  377. :class="{ disabled: page === lastPage }"
  378. class="button is-primary material-icons"
  379. :disabled="page === lastPage"
  380. @click="changePage(lastPage)"
  381. content="Last Page"
  382. v-tippy
  383. >
  384. skip_next
  385. </button>
  386. </div>
  387. <div class="page-size">
  388. <div class="control">
  389. <label class="label">Items per page</label>
  390. <p class="control select">
  391. <select
  392. v-model.number="pageSize"
  393. @change="changePageSize()"
  394. >
  395. <option value="10">10</option>
  396. <option value="25">25</option>
  397. <option value="50">50</option>
  398. <option value="100">100</option>
  399. <option value="250">250</option>
  400. <option value="500">500</option>
  401. <option value="1000">1000</option>
  402. </select>
  403. </p>
  404. </div>
  405. </div>
  406. </div>
  407. </div>
  408. <div
  409. v-if="selectedRows.length > 0"
  410. class="bulk-popup"
  411. :style="{
  412. top: bulkPopup.top + 'px',
  413. left: bulkPopup.left + 'px'
  414. }"
  415. >
  416. <button
  417. class="button is-primary"
  418. :content="
  419. selectedRows.length === 1
  420. ? `${selectedRows.length} row selected`
  421. : `${selectedRows.length} rows selected`
  422. "
  423. v-tippy
  424. >
  425. {{ selectedRows.length }}
  426. </button>
  427. <slot name="bulk-actions" :item="selectedRows" />
  428. <div class="right">
  429. <slot name="bulk-actions-right" :item="selectedRows" />
  430. <span
  431. class="material-icons drag-icon"
  432. @mousedown.left="onDragBox"
  433. @dblclick="resetBulkActionsPosition()"
  434. >
  435. drag_indicator
  436. </span>
  437. </div>
  438. </div>
  439. </div>
  440. </template>
  441. <script>
  442. import { mapGetters } from "vuex";
  443. import draggable from "vuedraggable";
  444. import Toast from "toasters";
  445. import ws from "@/ws";
  446. export default {
  447. components: {
  448. draggable
  449. },
  450. props: {
  451. /*
  452. Column properties:
  453. name: Unique lowercase name
  454. displayName: Nice name for the column header
  455. properties: The properties this column needs to show data
  456. sortable: Boolean for whether the order of a particular column can be changed
  457. sortProperty: The property the backend will sort on if this column gets sorted, e.g. title
  458. hidable: Boolean for whether a column can be hidden
  459. defaultVisibility: Default visibility for a column, either "shown" or "hidden"
  460. draggable: Boolean for whether a column can be dragged/reordered,
  461. resizable: Boolean for whether a column can be resized
  462. minWidth: Minimum width of column, e.g. 50px
  463. width: Width of column, e.g. 100px
  464. maxWidth: Maximum width of column, e.g. 150px
  465. */
  466. columnDefault: { type: Object, default: () => {} },
  467. columns: { type: Array, default: null },
  468. filters: { type: Array, default: null },
  469. dataAction: { type: String, default: null }
  470. },
  471. data() {
  472. return {
  473. page: 1,
  474. pageSize: 10,
  475. data: [],
  476. count: 0, // TODO Rename
  477. sort: {},
  478. orderedColumns: [],
  479. shownColumns: [],
  480. pinnedColumns: ["select"],
  481. columnDragOptions() {
  482. return {
  483. animation: 200,
  484. group: "columns",
  485. disabled: false,
  486. ghostClass: "draggable-list-ghost",
  487. filter: ".ignore-elements",
  488. fallbackTolerance: 50
  489. };
  490. },
  491. advancedQuery: [],
  492. queryOperator: "or",
  493. queryOperators: [
  494. {
  495. name: "or",
  496. displayName: "OR"
  497. },
  498. {
  499. name: "and",
  500. displayName: "AND"
  501. }
  502. ],
  503. resizing: {},
  504. allFilterTypes: {
  505. contains: {
  506. name: "contains",
  507. displayName: "Contains"
  508. },
  509. exact: {
  510. name: "exact",
  511. displayName: "Exact"
  512. },
  513. regex: {
  514. name: "regex",
  515. displayName: "Regex"
  516. }
  517. },
  518. bulkPopup: {
  519. top: 0,
  520. left: 0,
  521. pos1: 0,
  522. pos2: 0,
  523. pos3: 0,
  524. pos4: 0
  525. }
  526. };
  527. },
  528. computed: {
  529. properties() {
  530. return Array.from(
  531. new Set(
  532. this.sortedFilteredColumns.flatMap(
  533. column => column.properties
  534. )
  535. )
  536. );
  537. },
  538. lastPage() {
  539. return Math.ceil(this.count / this.pageSize);
  540. },
  541. sortedFilteredColumns() {
  542. return this.orderedColumns.filter(
  543. column => this.shownColumns.indexOf(column.name) !== -1
  544. );
  545. },
  546. hidableSortedColumns() {
  547. return this.orderedColumns.filter(column => column.hidable);
  548. },
  549. lastSelectedItemIndex() {
  550. return this.data.findIndex(item => item.highlighted);
  551. },
  552. selectedRows() {
  553. return this.data.filter(data => data.selected);
  554. },
  555. ...mapGetters({
  556. socket: "websockets/getSocket"
  557. })
  558. },
  559. mounted() {
  560. const columns = [
  561. {
  562. name: "select",
  563. displayName: "",
  564. properties: [],
  565. sortable: false,
  566. hidable: false,
  567. draggable: false,
  568. resizable: false,
  569. minWidth: 5,
  570. width: 5,
  571. maxWidth: 5
  572. },
  573. ...this.columns
  574. ];
  575. this.orderedColumns = columns.map(column => ({
  576. ...this.columnDefault,
  577. ...column
  578. }));
  579. // A column will be shown if the defaultVisibility is set to shown, OR if the defaultVisibility is not set to shown and hidable is false
  580. this.shownColumns = columns
  581. .filter(column => column.defaultVisibility !== "hidden")
  582. .map(column => column.name);
  583. const pageSize = parseInt(localStorage.getItem("adminPageSize"));
  584. if (!Number.isNaN(pageSize)) this.pageSize = pageSize;
  585. this.addQueryItem();
  586. this.resetBulkActionsPosition();
  587. ws.onConnect(this.init);
  588. },
  589. methods: {
  590. init() {
  591. this.getData();
  592. },
  593. getData() {
  594. this.socket.dispatch(
  595. this.dataAction,
  596. this.page,
  597. this.pageSize,
  598. this.properties,
  599. this.sort,
  600. this.advancedQuery,
  601. this.queryOperator,
  602. res => {
  603. console.log(111, res);
  604. if (res.status === "success") {
  605. const { data, count } = res.data;
  606. this.data = data;
  607. this.count = count;
  608. } else {
  609. new Toast(res.message);
  610. }
  611. }
  612. );
  613. },
  614. changePageSize() {
  615. this.getData();
  616. localStorage.setItem("adminPageSize", this.pageSize);
  617. },
  618. changePage(page) {
  619. if (page < 1) return;
  620. if (page > this.lastPage) return;
  621. if (page === this.page) return;
  622. this.page = page;
  623. this.getData();
  624. },
  625. changeSort(column) {
  626. if (column.sortable) {
  627. const { sortProperty } = column;
  628. if (this.sort[sortProperty] === undefined)
  629. this.sort[sortProperty] = "ascending";
  630. else if (this.sort[sortProperty] === "ascending")
  631. this.sort[sortProperty] = "descending";
  632. else if (this.sort[sortProperty] === "descending")
  633. delete this.sort[sortProperty];
  634. this.getData();
  635. }
  636. },
  637. toggleColumnVisibility(column) {
  638. if (this.shownColumns.indexOf(column.name) !== -1) {
  639. if (this.shownColumns.length <= 2)
  640. return new Toast(
  641. `Unable to hide column ${column.displayName}, there must be at least 1 visibile column`
  642. );
  643. this.shownColumns.splice(
  644. this.shownColumns.indexOf(column.name),
  645. 1
  646. );
  647. } else {
  648. this.shownColumns.push(column.name);
  649. }
  650. return this.getData();
  651. },
  652. togglePinnedColumn(column) {
  653. if (this.pinnedColumns.indexOf(column.name) !== -1) {
  654. this.pinnedColumns.splice(
  655. this.pinnedColumns.indexOf(column.name),
  656. 1
  657. );
  658. } else {
  659. this.pinnedColumns.push(column.name);
  660. }
  661. },
  662. clickItem(itemIndex, event) {
  663. const { shiftKey, ctrlKey } = event;
  664. // Shift was pressed, so attempt to select all items between the clicked item and last clicked item
  665. if (shiftKey) {
  666. // If there is a last clicked item
  667. if (this.lastSelectedItemIndex >= 0) {
  668. // Clicked item is lower than last item, so select upwards until it reaches the last selected item
  669. if (itemIndex > this.lastSelectedItemIndex) {
  670. for (
  671. let itemIndexUp = itemIndex;
  672. itemIndexUp > this.lastSelectedItemIndex;
  673. itemIndexUp -= 1
  674. ) {
  675. this.data[itemIndexUp].selected = true;
  676. }
  677. }
  678. // Clicked item is higher than last item, so select downwards until it reaches the last selected item
  679. else if (itemIndex < this.lastSelectedItemIndex) {
  680. for (
  681. let itemIndexDown = itemIndex;
  682. itemIndexDown < this.lastSelectedItemIndex;
  683. itemIndexDown += 1
  684. ) {
  685. this.data[itemIndexDown].selected = true;
  686. }
  687. }
  688. }
  689. }
  690. // Ctrl was pressed, so toggle selected on the clicked item
  691. else if (ctrlKey) {
  692. this.data[itemIndex].selected = !this.data[itemIndex].selected;
  693. }
  694. // Neither ctrl nor shift were pressed, so unselect all items and set the clicked item to selected
  695. else {
  696. this.data = this.data.map(item => ({
  697. ...item,
  698. selected: false
  699. }));
  700. this.data[itemIndex].selected = true;
  701. }
  702. // Set the last clicked item to no longer be highlighted, if it exists
  703. if (this.lastSelectedItemIndex >= 0)
  704. this.data[this.lastSelectedItemIndex].highlighted = false;
  705. // Set the clicked item to be highlighted
  706. this.data[itemIndex].highlighted = true;
  707. },
  708. addQueryItem() {
  709. this.advancedQuery.push({
  710. data: "",
  711. filter: {},
  712. filterType: ""
  713. });
  714. },
  715. removeQueryItem(index) {
  716. if (this.advancedQuery.length > 1)
  717. this.advancedQuery.splice(index, 1);
  718. },
  719. resetQuery() {
  720. this.advancedQuery = [];
  721. this.queryOperator = "or";
  722. this.addQueryItem();
  723. this.getData();
  724. },
  725. columnResizingMouseDown(column, event) {
  726. this.resizing.resizing = true;
  727. this.resizing.resizingColumn = column;
  728. this.resizing.width = event.target.parentElement.offsetWidth;
  729. this.resizing.lastX = event.x;
  730. },
  731. columnResizingMouseMove(event) {
  732. if (this.resizing.resizing) {
  733. if (event.buttons !== 1) {
  734. this.resizing.resizing = false;
  735. }
  736. this.resizing.width -= this.resizing.lastX - event.x;
  737. this.resizing.lastX = event.x;
  738. if (
  739. this.resizing.resizingColumn.minWidth &&
  740. this.resizing.resizingColumn.maxWidth
  741. ) {
  742. this.resizing.resizingColumn.width = Math.max(
  743. Math.min(
  744. this.resizing.resizingColumn.maxWidth,
  745. this.resizing.width
  746. ),
  747. this.resizing.resizingColumn.minWidth
  748. );
  749. } else if (this.resizing.resizingColumn.minWidth) {
  750. this.resizing.resizingColumn.width = Math.max(
  751. this.resizing.width,
  752. this.resizing.resizingColumn.minWidth
  753. );
  754. } else if (this.resizing.resizingColumn.maxWidth) {
  755. this.resizing.resizingColumn.width = Math.min(
  756. this.resizing.resizingColumn.maxWidth,
  757. this.resizing.width
  758. );
  759. } else {
  760. this.resizing.resizingColumn.width = this.resizing.width;
  761. }
  762. console.log(`New width: ${this.resizing.width}px`);
  763. }
  764. },
  765. columnResizingMouseUp() {
  766. this.resizing.resizing = false;
  767. },
  768. columnResetWidth(column) {
  769. // eslint-disable-next-line no-param-reassign
  770. column.minWidth = column.maxWidth = "";
  771. },
  772. filterTypes(filter) {
  773. if (!filter || !filter.filterTypes) return [];
  774. return filter.filterTypes.map(
  775. filterType => this.allFilterTypes[filterType]
  776. );
  777. },
  778. changeQueryFilter(index) {
  779. this.advancedQuery[index].filterType =
  780. this.advancedQuery[index].filter.defaultFilterType;
  781. },
  782. onDragBox(e) {
  783. const e1 = e || window.event;
  784. e1.preventDefault();
  785. this.bulkPopup.pos3 = e1.clientX;
  786. this.bulkPopup.pos4 = e1.clientY;
  787. document.onmousemove = e => {
  788. const e2 = e || window.event;
  789. e2.preventDefault();
  790. // calculate the new cursor position:
  791. this.bulkPopup.pos1 = this.bulkPopup.pos3 - e.clientX;
  792. this.bulkPopup.pos2 = this.bulkPopup.pos4 - e.clientY;
  793. this.bulkPopup.pos3 = e.clientX;
  794. this.bulkPopup.pos4 = e.clientY;
  795. // set the element's new position:
  796. this.bulkPopup.top -= this.bulkPopup.pos2;
  797. this.bulkPopup.left -= this.bulkPopup.pos1;
  798. if (this.bulkPopup.top < 0) this.bulkPopup.top = 0;
  799. if (this.bulkPopup.top > document.body.clientHeight - 50)
  800. this.bulkPopup.top = document.body.clientHeight - 50;
  801. if (this.bulkPopup.left < 0) this.bulkPopup.left = 0;
  802. if (this.bulkPopup.left > document.body.clientWidth - 400)
  803. this.bulkPopup.left = document.body.clientWidth - 400;
  804. };
  805. document.onmouseup = () => {
  806. document.onmouseup = null;
  807. document.onmousemove = null;
  808. };
  809. },
  810. resetBulkActionsPosition() {
  811. this.bulkPopup.top = document.body.clientHeight - 56;
  812. this.bulkPopup.left = document.body.clientWidth / 2 - 200;
  813. }
  814. }
  815. };
  816. </script>
  817. <style lang="scss" scoped>
  818. .night-mode {
  819. .table-outer-container {
  820. .table-container .table {
  821. &,
  822. thead th {
  823. background-color: var(--dark-grey-3);
  824. color: var(--light-grey-2);
  825. }
  826. tr {
  827. &:nth-child(even) {
  828. background-color: var(--dark-grey-2);
  829. }
  830. &:hover,
  831. &:focus,
  832. &.highlighted {
  833. background-color: var(--dark-grey-4);
  834. }
  835. }
  836. th,
  837. td {
  838. border-color: var(--dark-grey) !important;
  839. }
  840. }
  841. .table-header,
  842. .table-footer {
  843. background-color: var(--dark-grey-3);
  844. color: var(--light-grey-2);
  845. }
  846. }
  847. .bulk-popup {
  848. border: 0;
  849. background-color: var(--dark-grey-2);
  850. color: var(--white);
  851. .material-icons {
  852. color: var(--white);
  853. }
  854. }
  855. }
  856. .table-outer-container {
  857. border-radius: 5px;
  858. box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
  859. margin: 10px 0;
  860. overflow: hidden;
  861. .table-container {
  862. overflow-x: auto;
  863. table {
  864. border-collapse: separate;
  865. table-layout: fixed;
  866. thead {
  867. tr {
  868. th {
  869. height: 40px;
  870. line-height: 40px;
  871. border: 1px solid var(--light-grey-2);
  872. border-width: 1px 1px 1px 0;
  873. &:first-child,
  874. &:last-child {
  875. border-width: 1px 0 1px;
  876. }
  877. &.sortable {
  878. cursor: pointer;
  879. }
  880. & > div {
  881. display: flex;
  882. white-space: nowrap;
  883. & > span {
  884. margin-left: 5px;
  885. &:first-child {
  886. margin-left: 0;
  887. margin-right: auto;
  888. }
  889. & > .material-icons {
  890. font-size: 22px;
  891. position: relative;
  892. top: 6px;
  893. cursor: pointer;
  894. &.active {
  895. color: var(--primary-color);
  896. }
  897. &:hover,
  898. &:focus {
  899. filter: brightness(90%);
  900. }
  901. }
  902. }
  903. }
  904. }
  905. }
  906. }
  907. tbody {
  908. tr {
  909. &.selected td:first-child {
  910. border-left: 5px solid var(--primary-color);
  911. padding-left: 0;
  912. }
  913. &.highlighted {
  914. background-color: var(--light-grey);
  915. }
  916. td {
  917. border: 1px solid var(--light-grey-2);
  918. border-width: 0 1px 1px 0;
  919. &:first-child,
  920. &:last-child {
  921. border-width: 0 0 1px;
  922. }
  923. }
  924. }
  925. }
  926. }
  927. table thead tr th,
  928. table tbody tr td {
  929. position: relative;
  930. white-space: nowrap;
  931. text-overflow: ellipsis;
  932. overflow: hidden;
  933. &:first-child {
  934. position: sticky;
  935. left: 0;
  936. z-index: 2;
  937. padding: 0;
  938. padding-left: 5px;
  939. }
  940. .resizer {
  941. height: 100%;
  942. width: 5px;
  943. background-color: transparent;
  944. cursor: col-resize;
  945. position: absolute;
  946. right: 0;
  947. top: 0;
  948. }
  949. }
  950. }
  951. .table-header,
  952. .table-footer {
  953. display: flex;
  954. flex-direction: row;
  955. flex-wrap: wrap;
  956. justify-content: space-between;
  957. line-height: 36px;
  958. background-color: var(--white);
  959. }
  960. .table-header > span > .button {
  961. margin: 5px;
  962. }
  963. .table-footer {
  964. .page-controls,
  965. .page-size > .control {
  966. display: flex;
  967. flex-direction: row;
  968. margin-bottom: 0 !important;
  969. button {
  970. margin: 5px;
  971. font-size: 20px;
  972. }
  973. p,
  974. label {
  975. margin: 5px;
  976. font-size: 14px;
  977. font-weight: 600;
  978. }
  979. &.select::after {
  980. top: 18px;
  981. }
  982. }
  983. }
  984. }
  985. .advanced-query {
  986. margin-bottom: 5px;
  987. }
  988. .advanced-query,
  989. .advanced-query-bottom {
  990. display: flex;
  991. & > .control {
  992. & > input,
  993. & > select,
  994. & > .button {
  995. border-radius: 0;
  996. }
  997. &:first-child {
  998. & > input,
  999. & > select,
  1000. & > .button {
  1001. border-radius: 5px 0 0 5px;
  1002. }
  1003. }
  1004. &:last-child {
  1005. & > input,
  1006. & > select,
  1007. & > .button {
  1008. border-radius: 0 5px 5px 0;
  1009. }
  1010. }
  1011. & > .button {
  1012. font-size: 22px;
  1013. }
  1014. }
  1015. }
  1016. .advanced-query-bottom .button {
  1017. font-size: 16px !important;
  1018. width: 100%;
  1019. }
  1020. .bulk-popup {
  1021. display: flex;
  1022. position: fixed;
  1023. flex-direction: row;
  1024. width: 100%;
  1025. max-width: 400px;
  1026. line-height: 36px;
  1027. z-index: 5;
  1028. border: 1px solid var(--light-grey-3);
  1029. border-radius: 5px;
  1030. box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
  1031. background-color: var(--white);
  1032. color: var(--dark-grey);
  1033. padding: 5px;
  1034. .right {
  1035. display: flex;
  1036. flex-direction: row;
  1037. margin-left: auto;
  1038. }
  1039. .drag-icon {
  1040. position: relative;
  1041. top: 6px;
  1042. color: var(--dark-grey);
  1043. cursor: move;
  1044. }
  1045. }
  1046. </style>