Commit d32eaee2 authored by Phil Hughes's avatar Phil Hughes

correctly show the dropdown with `t` keypress

added arrow key navigation in the dropdown
enter & click open the file
highlight occurrences of the searched text in the drppdown item
fixed some performance issues when rendering
limit the dropdown items to a maximum of 20 - this may change to more
depending on other performance changes
parent 748a2f2b
<script>
import { mapGetters, mapState } from 'vuex';
import { mapActions, mapGetters, mapState } from 'vuex';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import VirtualList from 'vue-virtual-scroll-list';
import Item from './item.vue';
import router from '../../ide_router';
const MAX_RESULTS = 20;
export default {
components: {
......@@ -11,89 +14,153 @@ export default {
},
data() {
return {
focusedIndex: 0,
searchText: '',
};
},
computed: {
...mapGetters(['allBlobs']),
...mapState(['loading']),
...mapState(['fileFindVisible', 'loading']),
filteredBlobs() {
const searchText = this.searchText.trim();
if (searchText === '') return this.allBlobs;
if (searchText === '') return this.allBlobs.slice(0, MAX_RESULTS);
return fuzzaldrinPlus.filter(this.allBlobs, searchText, {
key: 'path',
maxResults: MAX_RESULTS,
});
},
listShowCount() {
if (this.filteredBlobs.length === 0) return 1;
if (!this.filteredBlobs.length) return 1;
return this.filteredBlobs.length > 5 ? 5 : this.filteredBlobs.length;
},
listHeight() {
return this.listShowCount > 1 ? 55 : 33;
return this.filteredBlobs.length ? 55 : 33;
},
},
watch: {
fileFindVisible() {
this.$nextTick(() => this.$refs.searchInput.focus());
},
searchText() {
if (this.searchText.trim() !== '') {
this.focusedIndex = 0;
}
},
},
mounted() {
this.$refs.searchInput.focus();
methods: {
...mapActions(['toggleFileFinder']),
onKeydown(e) {
switch (e.keyCode) {
case 38:
// UP
e.preventDefault();
if (this.focusedIndex > 0) this.focusedIndex -= 1;
break;
case 40:
// DOWN
e.preventDefault();
if (this.focusedIndex < this.filteredBlobs.length - 1) this.focusedIndex += 1;
break;
default:
break;
}
},
onKeyup(e) {
switch (e.keyCode) {
case 13:
// ENTER
this.openFile(this.filteredBlobs[this.focusedIndex]);
break;
default:
break;
}
},
openFile(file) {
this.toggleFileFinder(false);
router.push(`/project${file.url}`);
},
},
};
</script>
<template>
<div class="dropdown-menu diff-file-changes ide-file-finder" style="display: block;">
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search files"
autocomplete="off"
v-model="searchText"
ref="searchInput"
/>
<i
aria-hidden="true"
class="fa fa-search dropdown-input-search"
></i>
</div>
<div>
<virtual-list
:size="listHeight"
:remain="listShowCount"
wtag="ul"
>
<template v-if="filteredBlobs.length">
<div
class="ide-file-finder-overlay"
@click.self="toggleFileFinder(false)"
>
<div
class="dropdown-menu diff-file-changes ide-file-finder show"
>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search files"
autocomplete="off"
v-model="searchText"
ref="searchInput"
@keydown="onKeydown($event)"
@keyup="onKeyup($event)"
/>
<i
aria-hidden="true"
class="fa fa-search dropdown-input-search"
></i>
</div>
<div>
<virtual-list
:size="listHeight"
:remain="listShowCount"
:start="focusedIndex"
wtag="ul"
>
<template v-if="filteredBlobs.length">
<li
v-for="(file, index) in filteredBlobs"
:key="file.key"
>
<item
:file="file"
:search-text="searchText"
:focused="index === focusedIndex"
@click="openFile"
/>
</li>
</template>
<li
v-for="file in filteredBlobs"
:key="file.key"
v-else
class="dropdown-menu-empty-itemhidden"
>
<item
:file="file"
/>
<a href="">
<template v-if="loading">
Loading...
</template>
<template v-else>
No files found.
</template>
</a>
</li>
</template>
<li
v-else
class="dropdown-menu-empty-itemhidden"
>
<a href="">
<template v-if="loading">
Loading...
</template>
<template v-else>
No files found.
</template>
</a>
</li>
</virtual-list>
</virtual-list>
</div>
</div>
</div>
</template>
<style>
.ide-file-finder-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
}
.ide-file-finder {
top: 100px;
top: 10px;
left: 50%;
transform: translateX(-50%);
}
......
<script>
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import FileIcon from '../../../vue_shared/components/file_icon.vue';
export default {
......@@ -10,14 +11,42 @@ export default {
type: Object,
required: true,
},
focused: {
type: Boolean,
required: true,
},
searchText: {
type: String,
required: true,
},
},
methods: {
clickRow() {
this.$emit('click', this.file);
},
highlightText(text) {
const occurrences = fuzzaldrinPlus.match(text, this.searchText);
return text
.split('')
.map(
(char, i) =>
`<span class="${occurrences.indexOf(i) >= 0 ? 'highlighted' : ''}">${char}</span>`,
)
.join('');
},
},
};
</script>
<template>
<a
href=""
href="#"
class="diff-changed-file"
:class="{
'is-focused': focused,
}"
@click.prevent="clickRow"
>
<file-icon
:file-name="file.name"
......@@ -25,12 +54,23 @@ export default {
css-classes="diff-file-changed-icon append-right-8"
/>
<span class="diff-changed-file-content append-right-8">
<strong class="diff-changed-file-name">
{{ file.name }}
<strong
class="diff-changed-file-name"
v-html="highlightText(this.file.name)"
>
</strong>
<span class="diff-changed-file-path prepend-top-5">
{{ file.path }}
<span
class="diff-changed-file-path prepend-top-5"
v-html="highlightText(this.file.path)"
>
</span>
</span>
</a>
</template>
<style>
.highlighted {
color: #1b69b6;
font-weight: 600;
}
</style>
<script>
import { mapState, mapGetters } from 'vuex';
import { mapActions, mapState, mapGetters } from 'vuex';
import Mousetrap from 'mousetrap';
import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue';
......@@ -50,6 +51,14 @@ export default {
});
return returnValue;
};
Mousetrap.bind('t', e => {
e.preventDefault();
this.toggleFileFinder(true);
});
},
methods: {
...mapActions(['toggleFileFinder']),
},
};
</script>
......@@ -59,7 +68,7 @@ export default {
class="ide-view"
>
<find-file
v-if="fileFindVisible"
v-show="fileFindVisible"
/>
<ide-sidebar />
<div
......
......@@ -112,6 +112,9 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
};
export const toggleFileFinder = ({ commit }, fileFindVisible) =>
commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
......
......@@ -37,4 +37,12 @@ export const hasChanges = state => !!state.changedFiles.length;
export const hasMergeRequest = state => !!state.currentMergeRequestId;
export const allBlobs = state =>
Object.keys(state.entries).reduce((acc, key) => acc.concat(state.entries[key]), []);
Object.keys(state.entries).reduce((acc, key) => {
const entry = state.entries[key];
if (entry.type === 'blob') {
acc.push(entry);
}
return acc;
}, []);
......@@ -53,3 +53,5 @@ export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
export const TOGGLE_FILE_FINDER = 'SHOW_FILE_FINDER';
......@@ -95,6 +95,11 @@ export default {
delayViewerUpdated,
});
},
[types.TOGGLE_FILE_FINDER](state, fileFindVisible) {
Object.assign(state, {
fileFindVisible,
});
},
...projectMutations,
...mergeRequestMutation,
...fileMutations,
......
......@@ -17,5 +17,5 @@ export default () => ({
entries: {},
viewer: 'editor',
delayViewerUpdated: false,
fileFindVisible: true,
fileFindVisible: false,
});
......@@ -17,6 +17,7 @@
}
.ide-view {
position: relative;
display: flex;
height: calc(100vh - #{$header-height});
margin-top: 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment