You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AppOrderSelector.vue 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. <template>
  2. <ol ref="listElement" data-cy-app-order class="order-selector">
  3. <AppOrderSelectorElement v-for="app,index in appList"
  4. :key="`${app.id}${renderCount}`"
  5. :app="app"
  6. :is-first="index === 0 || !!appList[index - 1].default"
  7. :is-last="index === value.length - 1"
  8. v-on="app.default ? {} : {
  9. 'move:up': () => moveUp(index),
  10. 'move:down': () => moveDown(index),
  11. }" />
  12. </ol>
  13. </template>
  14. <script lang="ts">
  15. import { useSortable } from '@vueuse/integrations/useSortable'
  16. import { PropType, computed, defineComponent, ref } from 'vue'
  17. import AppOrderSelectorElement from './AppOrderSelectorElement.vue'
  18. interface IApp {
  19. id: string // app id
  20. icon: string // path to the icon svg
  21. label?: string // display name
  22. default?: boolean // force app as default app
  23. }
  24. export default defineComponent({
  25. name: 'AppOrderSelector',
  26. components: {
  27. AppOrderSelectorElement,
  28. },
  29. props: {
  30. /**
  31. * List of apps to reorder
  32. */
  33. value: {
  34. type: Array as PropType<IApp[]>,
  35. required: true,
  36. },
  37. },
  38. emits: {
  39. /**
  40. * Update the apps list on reorder
  41. * @param value The new value of the app list
  42. */
  43. 'update:value': (value: IApp[]) => Array.isArray(value),
  44. },
  45. setup(props, { emit }) {
  46. /**
  47. * The Element that contains the app list
  48. */
  49. const listElement = ref<HTMLElement | null>(null)
  50. /**
  51. * The app list with setter that will ement the `update:value` event
  52. */
  53. const appList = computed({
  54. get: () => props.value,
  55. // Ensure the sortable.js does not mess with the default attribute
  56. set: (list) => {
  57. const newValue = [...list].sort((a, b) => ((b.default ? 1 : 0) - (a.default ? 1 : 0)) || list.indexOf(a) - list.indexOf(b))
  58. if (newValue.some(({ id }, index) => id !== props.value[index].id)) {
  59. emit('update:value', newValue)
  60. } else {
  61. // forceUpdate as the DOM has changed because of a drag event, but the reactive state has not -> wrong state
  62. renderCount.value += 1
  63. }
  64. },
  65. })
  66. /**
  67. * Helper to force rerender the list in case of a invalid drag event
  68. */
  69. const renderCount = ref(0)
  70. /**
  71. * Handle drag & drop sorting
  72. */
  73. useSortable(listElement, appList, { filter: '.order-selector-element--disabled' })
  74. /**
  75. * Handle element is moved up
  76. * @param index The index of the element that is moved
  77. */
  78. const moveUp = (index: number) => {
  79. const before = index > 1 ? props.value.slice(0, index - 1) : []
  80. // skip if not possible, because of default default app
  81. if (props.value[index - 1]?.default) {
  82. return
  83. }
  84. const after = [props.value[index - 1]]
  85. if (index < props.value.length - 1) {
  86. after.push(...props.value.slice(index + 1))
  87. }
  88. emit('update:value', [...before, props.value[index], ...after])
  89. }
  90. /**
  91. * Handle element is moved down
  92. * @param index The index of the element that is moved
  93. */
  94. const moveDown = (index: number) => {
  95. const before = index > 0 ? props.value.slice(0, index) : []
  96. before.push(props.value[index + 1])
  97. const after = index < (props.value.length - 2) ? props.value.slice(index + 2) : []
  98. emit('update:value', [...before, props.value[index], ...after])
  99. }
  100. return {
  101. appList,
  102. listElement,
  103. moveDown,
  104. moveUp,
  105. renderCount,
  106. }
  107. },
  108. })
  109. </script>
  110. <style scoped lang="scss">
  111. .order-selector {
  112. width: max-content;
  113. min-width: 260px; // align with NcSelect
  114. }
  115. </style>