PowerSearch.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <template>
  2. <SmartModal
  3. v-if="show"
  4. max-width="sm:max-w-md"
  5. full-width
  6. @close="$emit('hide-modal')"
  7. >
  8. <template #body>
  9. <div class="flex flex-col border-b transition border-dividerLight">
  10. <input
  11. id="command"
  12. v-model="search"
  13. v-focus
  14. type="text"
  15. autocomplete="off"
  16. name="command"
  17. :placeholder="`${t('app.type_a_command_search')}`"
  18. class="flex flex-shrink-0 p-6 text-base bg-transparent text-secondaryDark"
  19. />
  20. <div
  21. class="flex flex-shrink-0 text-tiny text-secondaryLight px-4 pb-4 justify-between whitespace-nowrap overflow-auto hide-scrollbar <sm:hidden"
  22. >
  23. <div class="flex items-center">
  24. <kbd class="shortcut-key">↑</kbd>
  25. <kbd class="shortcut-key">↓</kbd>
  26. <span class="ml-2 truncate">
  27. {{ t("action.to_navigate") }}
  28. </span>
  29. <kbd class="shortcut-key">↩</kbd>
  30. <span class="ml-2 truncate">
  31. {{ t("action.to_select") }}
  32. </span>
  33. </div>
  34. <div class="flex items-center">
  35. <kbd class="shortcut-key">ESC</kbd>
  36. <span class="ml-2 truncate">
  37. {{ t("action.to_close") }}
  38. </span>
  39. </div>
  40. </div>
  41. </div>
  42. <AppFuse
  43. v-if="search && show"
  44. :input="fuse"
  45. :search="search"
  46. @action="runAction"
  47. />
  48. <div
  49. v-else
  50. class="flex flex-col flex-1 overflow-auto space-y-4 divide-y divide-dividerLight hide-scrollbar"
  51. >
  52. <div
  53. v-for="(map, mapIndex) in mappings"
  54. :key="`map-${mapIndex}`"
  55. class="flex flex-col"
  56. >
  57. <h5 class="px-6 py-2 my-2 text-secondaryLight">
  58. {{ t(map.section) }}
  59. </h5>
  60. <AppPowerSearchEntry
  61. v-for="(shortcut, shortcutIndex) in map.shortcuts"
  62. :key="`map-${mapIndex}-shortcut-${shortcutIndex}`"
  63. :shortcut="shortcut"
  64. :active="shortcutsItems.indexOf(shortcut) === selectedEntry"
  65. @action="runAction"
  66. @mouseover.native="selectedEntry = shortcutsItems.indexOf(shortcut)"
  67. />
  68. </div>
  69. </div>
  70. </template>
  71. </SmartModal>
  72. </template>
  73. <script setup lang="ts">
  74. import { ref, computed, watch } from "@nuxtjs/composition-api"
  75. import { HoppAction, invokeAction } from "~/helpers/actions"
  76. import { spotlight as mappings, fuse } from "~/helpers/shortcuts"
  77. import { useArrowKeysNavigation } from "~/helpers/powerSearchNavigation"
  78. import { useI18n } from "~/helpers/utils/composables"
  79. const t = useI18n()
  80. const props = defineProps<{
  81. show: boolean
  82. }>()
  83. const emit = defineEmits<{
  84. (e: "hide-modal"): void
  85. }>()
  86. const search = ref("")
  87. const hideModal = () => {
  88. search.value = ""
  89. emit("hide-modal")
  90. }
  91. const runAction = (command: HoppAction) => {
  92. invokeAction(command)
  93. hideModal()
  94. }
  95. const shortcutsItems = computed(() =>
  96. mappings.reduce(
  97. (shortcuts, section) => [...shortcuts, ...section.shortcuts],
  98. []
  99. )
  100. )
  101. const { bindArrowKeysListeners, unbindArrowKeysListeners, selectedEntry } =
  102. useArrowKeysNavigation(shortcutsItems, {
  103. onEnter: runAction,
  104. })
  105. watch(
  106. () => props.show,
  107. (show) => {
  108. if (show) bindArrowKeysListeners()
  109. else unbindArrowKeysListeners()
  110. }
  111. )
  112. </script>