index.vue 6.49 KB
Newer Older
1
<script>
2
import { mapActions, mapGetters, mapState } from 'vuex';
3 4 5
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import VirtualList from 'vue-virtual-scroll-list';
import Item from './item.vue';
6
import router from '../../ide_router';
Phil Hughes's avatar
Phil Hughes committed
7 8 9 10
import {
  MAX_FILE_FINDER_RESULTS,
  FILE_FINDER_ROW_HEIGHT,
  FILE_FINDER_EMPTY_ROW_HEIGHT,
11 12
} from '../../constants';
import {
Phil Hughes's avatar
Phil Hughes committed
13 14 15 16
  UP_KEY_CODE,
  DOWN_KEY_CODE,
  ENTER_KEY_CODE,
  ESC_KEY_CODE,
17
} from '../../../lib/utils/keycodes';
18 19 20 21 22 23 24 25

export default {
  components: {
    Item,
    VirtualList,
  },
  data() {
    return {
26
      focusedIndex: 0,
27
      searchText: '',
28
      mouseOver: false,
29
      cancelMouseOver: false,
30 31 32 33
    };
  },
  computed: {
    ...mapGetters(['allBlobs']),
34
    ...mapState(['fileFindVisible', 'loading']),
35 36 37
    filteredBlobs() {
      const searchText = this.searchText.trim();

Phil Hughes's avatar
Phil Hughes committed
38 39 40
      if (searchText === '') {
        return this.allBlobs.slice(0, MAX_FILE_FINDER_RESULTS);
      }
41

42 43 44 45
      return fuzzaldrinPlus.filter(this.allBlobs, searchText, {
        key: 'path',
        maxResults: MAX_FILE_FINDER_RESULTS,
      });
46
    },
47 48 49
    filteredBlobsLength() {
      return this.filteredBlobs.length;
    },
50
    listShowCount() {
Phil Hughes's avatar
Phil Hughes committed
51
      return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1;
52 53
    },
    listHeight() {
Phil Hughes's avatar
Phil Hughes committed
54
      return this.filteredBlobsLength ? FILE_FINDER_ROW_HEIGHT : FILE_FINDER_EMPTY_ROW_HEIGHT;
55
    },
Phil Hughes's avatar
Phil Hughes committed
56 57 58
    showClearInputButton() {
      return this.searchText.trim() !== '';
    },
59 60 61
  },
  watch: {
    fileFindVisible() {
62 63 64 65
      this.$nextTick(() => {
        if (!this.fileFindVisible) {
          this.searchText = '';
        } else {
Phil Hughes's avatar
Phil Hughes committed
66
          this.focusedIndex = 0;
Phil Hughes's avatar
Phil Hughes committed
67

Phil Hughes's avatar
Phil Hughes committed
68 69 70
          if (this.$refs.searchInput) {
            this.$refs.searchInput.focus();
          }
71 72
        }
      });
73 74
    },
    searchText() {
Phil Hughes's avatar
Phil Hughes committed
75
      this.focusedIndex = 0;
76
    },
77 78 79 80
    focusedIndex() {
      if (!this.mouseOver) {
        this.$nextTick(() => {
          const el = this.$refs.virtualScrollList.$el;
81 82
          const scrollTop = this.focusedIndex * FILE_FINDER_ROW_HEIGHT;
          const bottom = this.listShowCount * FILE_FINDER_ROW_HEIGHT;
83

84 85 86 87 88 89 90 91 92 93 94 95 96
          if (this.focusedIndex === 0) {
            // if index is the first index, scroll straight to start
            el.scrollTop = 0;
          } else if (this.focusedIndex === this.filteredBlobsLength - 1) {
            // if index is the last index, scroll to the end
            el.scrollTop = this.filteredBlobsLength * FILE_FINDER_ROW_HEIGHT;
          } else if (scrollTop >= bottom + el.scrollTop) {
            // if element is off the bottom of the scroll list, scroll down one item
            el.scrollTop = scrollTop - bottom + FILE_FINDER_ROW_HEIGHT;
          } else if (scrollTop < el.scrollTop) {
            // if element is off the top of the scroll list, scroll up one item
            el.scrollTop = scrollTop;
          }
97 98 99
        });
      }
    },
100
  },
101 102
  methods: {
    ...mapActions(['toggleFileFinder']),
Phil Hughes's avatar
Phil Hughes committed
103 104 105 106 107 108 109
    clearSearchInput() {
      this.searchText = '';

      this.$nextTick(() => {
        this.$refs.searchInput.focus();
      });
    },
110 111
    onKeydown(e) {
      switch (e.keyCode) {
Phil Hughes's avatar
Phil Hughes committed
112
        case UP_KEY_CODE:
113
          e.preventDefault();
114
          this.mouseOver = false;
115
          this.cancelMouseOver = true;
Phil Hughes's avatar
Phil Hughes committed
116 117 118 119 120
          if (this.focusedIndex > 0) {
            this.focusedIndex -= 1;
          } else {
            this.focusedIndex = this.filteredBlobsLength - 1;
          }
121
          break;
Phil Hughes's avatar
Phil Hughes committed
122
        case DOWN_KEY_CODE:
123
          e.preventDefault();
124
          this.mouseOver = false;
125
          this.cancelMouseOver = true;
Phil Hughes's avatar
Phil Hughes committed
126 127 128 129 130
          if (this.focusedIndex < this.filteredBlobsLength - 1) {
            this.focusedIndex += 1;
          } else {
            this.focusedIndex = 0;
          }
131 132 133 134 135 136 137
          break;
        default:
          break;
      }
    },
    onKeyup(e) {
      switch (e.keyCode) {
Phil Hughes's avatar
Phil Hughes committed
138
        case ENTER_KEY_CODE:
139 140
          this.openFile(this.filteredBlobs[this.focusedIndex]);
          break;
Phil Hughes's avatar
Phil Hughes committed
141
        case ESC_KEY_CODE:
142 143
          this.toggleFileFinder(false);
          break;
144 145 146 147 148 149 150 151
        default:
          break;
      }
    },
    openFile(file) {
      this.toggleFileFinder(false);
      router.push(`/project${file.url}`);
    },
152
    onMouseOver(index) {
153 154 155 156 157
      if (!this.cancelMouseOver) {
        this.mouseOver = true;
        this.focusedIndex = index;
      }
    },
158
    onMouseMove(index) {
159
      this.cancelMouseOver = false;
160
      this.onMouseOver(index);
161
    },
162 163 164 165 166
  },
};
</script>

<template>
167 168
  <div
    class="ide-file-finder-overlay"
Phil Hughes's avatar
Phil Hughes committed
169
    @mousedown.self="toggleFileFinder(false)"
170 171 172 173 174 175 176 177
  >
    <div
      class="dropdown-menu diff-file-changes ide-file-finder show"
    >
      <div class="dropdown-input">
        <input
          type="search"
          class="dropdown-input-field"
Phil Hughes's avatar
Phil Hughes committed
178
          :placeholder="__('Search files')"
179 180 181 182 183 184 185 186 187
          autocomplete="off"
          v-model="searchText"
          ref="searchInput"
          @keydown="onKeydown($event)"
          @keyup="onKeyup($event)"
        />
        <i
          aria-hidden="true"
          class="fa fa-search dropdown-input-search"
Phil Hughes's avatar
Phil Hughes committed
188 189 190 191 192 193
          :class="{
            hidden: showClearInputButton
          }"
        ></i>
        <i
          role="button"
194
          :aria-label="__('Clear search input')"
Phil Hughes's avatar
Phil Hughes committed
195 196 197 198 199
          class="fa fa-times dropdown-input-clear"
          :class="{
            show: showClearInputButton
          }"
          @click="clearSearchInput"
200 201
        ></i>
      </div>
202
      <div>
203 204 205 206
        <virtual-list
          :size="listHeight"
          :remain="listShowCount"
          wtag="ul"
207
          ref="virtualScrollList"
208
        >
209
          <template v-if="filteredBlobsLength">
210 211 212 213 214
            <li
              v-for="(file, index) in filteredBlobs"
              :key="file.key"
            >
              <item
215
                class="disable-hover"
216 217 218
                :file="file"
                :search-text="searchText"
                :focused="index === focusedIndex"
219
                :index="index"
220
                @click="openFile"
221
                @mouseover="onMouseOver"
222
                @mousemove="onMouseMove"
223 224 225
              />
            </li>
          </template>
226
          <li
227
            v-else
228
            class="dropdown-menu-empty-item"
229
          >
230
            <div class="append-right-default prepend-left-default prepend-top-8 append-bottom-8">
231
              <template v-if="loading">
Phil Hughes's avatar
Phil Hughes committed
232
                {{ __('Loading...') }}
233 234
              </template>
              <template v-else>
Phil Hughes's avatar
Phil Hughes committed
235
                {{ __('No files found.') }}
236
              </template>
237
            </div>
238
          </li>
239 240
        </virtual-list>
      </div>
241 242 243
    </div>
  </div>
</template>