Adds fuzzy search modal (#1108)
* Adds fuzzy search modal * Fixes modal bug * Updates fuzzy with autocomplete component * Adds better shortcut * Fixes padding * Fixes tests
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<splitpanes @resized="onResized($event)">
|
<splitpanes @resized="onResized($event)">
|
||||||
<pane min-size="10" :size="settings.menuWidth" v-if="!isMobile && !collapseNav">
|
<pane min-size="10" :size="settings.menuWidth" v-if="!isMobile && !collapseNav">
|
||||||
<side-menu></side-menu>
|
<side-menu @search="showFuzzySearch"></side-menu>
|
||||||
</pane>
|
</pane>
|
||||||
<pane min-size="10">
|
<pane min-size="10">
|
||||||
<splitpanes>
|
<splitpanes>
|
||||||
@@ -42,11 +42,15 @@
|
|||||||
import { mapActions, mapGetters, mapState } from "vuex";
|
import { mapActions, mapGetters, mapState } from "vuex";
|
||||||
import { Splitpanes, Pane } from "splitpanes";
|
import { Splitpanes, Pane } from "splitpanes";
|
||||||
|
|
||||||
|
import hotkeys from "hotkeys-js";
|
||||||
|
|
||||||
import LogContainer from "./components/LogContainer";
|
import LogContainer from "./components/LogContainer";
|
||||||
import SideMenu from "./components/SideMenu";
|
import SideMenu from "./components/SideMenu";
|
||||||
import MobileMenu from "./components/MobileMenu";
|
import MobileMenu from "./components/MobileMenu";
|
||||||
import Search from "./components/Search";
|
import Search from "./components/Search";
|
||||||
|
import PastTime from "./components/PastTime";
|
||||||
import Icon from "./components/Icon";
|
import Icon from "./components/Icon";
|
||||||
|
import FuzzySearchModal from "./components/FuzzySearchModal";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
@@ -56,6 +60,7 @@ export default {
|
|||||||
LogContainer,
|
LogContainer,
|
||||||
MobileMenu,
|
MobileMenu,
|
||||||
Splitpanes,
|
Splitpanes,
|
||||||
|
PastTime,
|
||||||
Pane,
|
Pane,
|
||||||
Search,
|
Search,
|
||||||
},
|
},
|
||||||
@@ -79,6 +84,9 @@ export default {
|
|||||||
document.documentElement.setAttribute("data-theme", "light");
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
}
|
}
|
||||||
this.menuWidth = this.settings.menuWidth;
|
this.menuWidth = this.settings.menuWidth;
|
||||||
|
hotkeys("command+k, ctrl+k", (event, handler) => {
|
||||||
|
this.showFuzzySearch();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
hasSmallerScrollbars(newValue, oldValue) {
|
hasSmallerScrollbars(newValue, oldValue) {
|
||||||
@@ -100,7 +108,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["isMobile", "settings"]),
|
...mapState(["isMobile", "settings", "containers"]),
|
||||||
...mapGetters(["visibleContainers", "activeContainers"]),
|
...mapGetters(["visibleContainers", "activeContainers"]),
|
||||||
hasSmallerScrollbars() {
|
hasSmallerScrollbars() {
|
||||||
return this.settings.smallerScrollbars;
|
return this.settings.smallerScrollbars;
|
||||||
@@ -120,6 +128,14 @@ export default {
|
|||||||
this.updateSetting({ menuWidth });
|
this.updateSetting({ menuWidth });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
showFuzzySearch() {
|
||||||
|
this.$buefy.modal.open({
|
||||||
|
parent: this,
|
||||||
|
component: FuzzySearchModal,
|
||||||
|
animation: "false",
|
||||||
|
width: 600,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
80
assets/components/FuzzySearchModal.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="panel">
|
||||||
|
<b-autocomplete
|
||||||
|
ref="autocomplete"
|
||||||
|
v-model="query"
|
||||||
|
placeholder="Search containers using ⌘ + k, ⌃k"
|
||||||
|
field="name"
|
||||||
|
open-on-focus
|
||||||
|
keep-first
|
||||||
|
expanded
|
||||||
|
:data="results"
|
||||||
|
@select="selected"
|
||||||
|
>
|
||||||
|
</b-autocomplete>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import fuzzysort from "fuzzysort";
|
||||||
|
|
||||||
|
import PastTime from "./PastTime";
|
||||||
|
import Icon from "./Icon";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
maxResults: {
|
||||||
|
default: 20,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
query: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
name: "FuzzySearchModal",
|
||||||
|
components: {
|
||||||
|
Icon,
|
||||||
|
PastTime,
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => this.$refs.autocomplete.focus());
|
||||||
|
},
|
||||||
|
watch: {},
|
||||||
|
methods: {
|
||||||
|
selected(item) {
|
||||||
|
this.$router.push({ name: "container", params: { id: item.id, name: item.name } });
|
||||||
|
this.$emit("close");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["containers"]),
|
||||||
|
preparedContainers() {
|
||||||
|
return this.containers.map((c) => ({
|
||||||
|
name: c.name,
|
||||||
|
id: c.id,
|
||||||
|
created: c.created,
|
||||||
|
preparedName: fuzzysort.prepare(c.name),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
results() {
|
||||||
|
const options = {
|
||||||
|
limit: this.maxResults,
|
||||||
|
key: "preparedName",
|
||||||
|
};
|
||||||
|
if (this.query) return fuzzysort.go(this.query, this.preparedContainers, options).map((i) => i.obj);
|
||||||
|
else {
|
||||||
|
return [...this.containers].sort((a, b) => b.created - a.created);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.panel {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow has-text-right x">
|
<div class="column is-narrow has-text-right px-1">
|
||||||
|
<button
|
||||||
|
class="button is-small is-rounded is-settings-control"
|
||||||
|
@click="$emit('search')"
|
||||||
|
title="Search containers (⌘ + k, ⌃k)"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<icon name="search"></icon>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow has-text-right px-0">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'settings' }"
|
:to="{ name: 'settings' }"
|
||||||
active-class="is-active"
|
active-class="is-active"
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import Meta from "vue-meta";
|
|||||||
import Switch from "buefy/dist/esm/switch";
|
import Switch from "buefy/dist/esm/switch";
|
||||||
import Radio from "buefy/dist/esm/radio";
|
import Radio from "buefy/dist/esm/radio";
|
||||||
import Field from "buefy/dist/esm/field";
|
import Field from "buefy/dist/esm/field";
|
||||||
|
import Modal from "buefy/dist/esm/modal";
|
||||||
|
import Autocomplete from "buefy/dist/esm/autocomplete";
|
||||||
|
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import config from "./store/config";
|
import config from "./store/config";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
@@ -14,6 +17,8 @@ Vue.use(Meta);
|
|||||||
Vue.use(Switch);
|
Vue.use(Switch);
|
||||||
Vue.use(Radio);
|
Vue.use(Radio);
|
||||||
Vue.use(Field);
|
Vue.use(Field);
|
||||||
|
Vue.use(Modal);
|
||||||
|
Vue.use(Autocomplete);
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import { mapActions, mapGetters, mapState } from "vuex";
|
import { mapActions, mapGetters, mapState } from "vuex";
|
||||||
|
|
||||||
import LogContainer from "../components/LogContainer";
|
import LogContainer from "../components/LogContainer";
|
||||||
import store from "../store";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapGetters, mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import PastTime from "../components/PastTime";
|
import PastTime from "../components/PastTime";
|
||||||
import config from "../store/config";
|
import config from "../store/config";
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ $link-active: $grey-dark;
|
|||||||
@import "~buefy/src/scss/utils/_all";
|
@import "~buefy/src/scss/utils/_all";
|
||||||
@import "~buefy/src/scss/components/_switch";
|
@import "~buefy/src/scss/components/_switch";
|
||||||
@import "~buefy/src/scss/components/_radio";
|
@import "~buefy/src/scss/components/_radio";
|
||||||
|
@import "~buefy/src/scss/components/_modal";
|
||||||
|
@import "~buefy/src/scss/components/_autocomplete";
|
||||||
|
|
||||||
html {
|
html {
|
||||||
--scheme-main: #{$black};
|
--scheme-main: #{$black};
|
||||||
@@ -141,7 +143,6 @@ html.has-custom-scrollbars {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.splitpanes__splitter {
|
.splitpanes__splitter {
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |