feat(UI): search filter in explore bar

This commit is contained in:
Fabio Di Stasio 2021-02-20 11:55:34 +01:00
parent 9b60bfff8d
commit 2f58007af4
4 changed files with 116 additions and 21 deletions

View File

@ -27,6 +27,17 @@
/> />
</span> </span>
</div> </div>
<div class="workspace-explorebar-search">
<div v-if="workspace.connected" class="has-icon-right">
<input
v-model="searchTerm"
class="form-input input-sm"
type="text"
:placeholder="$t('message.searchForElements')"
>
<i class="form-icon mdi mdi-magnify mdi-18px" />
</div>
</div>
<WorkspaceConnectPanel <WorkspaceConnectPanel
v-if="!workspace.connected" v-if="!workspace.connected"
class="workspace-explorebar-body" class="workspace-explorebar-body"
@ -171,7 +182,8 @@ export default {
isNewSchedulerModal: false, isNewSchedulerModal: false,
localWidth: null, localWidth: null,
debounceInterval: null, explorebarWidthInterval: null,
searchTermInterval: null,
isDatabaseContext: false, isDatabaseContext: false,
isTableContext: false, isTableContext: false,
isMiscContext: false, isMiscContext: false,
@ -182,7 +194,8 @@ export default {
selectedDatabase: '', selectedDatabase: '',
selectedTable: null, selectedTable: null,
selectedMisc: null selectedMisc: null,
searchTerm: ''
}; };
}, },
computed: { computed: {
@ -200,14 +213,21 @@ export default {
}, },
watch: { watch: {
localWidth (val) { localWidth (val) {
clearTimeout(this.debounceInterval); clearTimeout(this.explorebarWidthInterval);
this.debounceInterval = setTimeout(() => { this.explorebarWidthInterval = setTimeout(() => {
this.changeExplorebarSize(val); this.changeExplorebarSize(val);
}, 500); }, 500);
}, },
isSelected (val) { isSelected (val) {
if (val) this.localWidth = this.explorebarSize; if (val) this.localWidth = this.explorebarSize;
},
searchTerm () {
clearTimeout(this.searchTermInterval);
this.searchTermInterval = setTimeout(() => {
this.setSearchTerm(this.searchTerm);
}, 200);
} }
}, },
created () { created () {
@ -229,6 +249,7 @@ export default {
refreshStructure: 'workspaces/refreshStructure', refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs', changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
selectTab: 'workspaces/selectTab', selectTab: 'workspaces/selectTab',
setSearchTerm: 'workspaces/setSearchTerm',
addNotification: 'notifications/addNotification', addNotification: 'notifications/addNotification',
changeExplorebarSize: 'settings/changeExplorebarSize' changeExplorebarSize: 'settings/changeExplorebarSize'
}), }),
@ -481,9 +502,40 @@ export default {
} }
} }
.workspace-explorebar-search {
width: 100%;
display: flex;
justify-content: space-between;
font-size: 0.6rem;
height: 28px;
.has-icon-right {
width: 100%;
padding: 0.1rem;
.form-icon {
opacity: 0.5;
transition: opacity 0.2s;
}
.form-input {
height: 1.2rem;
padding-left: 0.2rem;
&:focus + .form-icon {
opacity: 0.9;
}
&::placeholder {
opacity: 0.6;
}
}
}
}
.workspace-explorebar-body { .workspace-explorebar-body {
width: 100%; width: 100%;
height: calc((100vh - 30px) - #{$excluding-size}); height: calc((100vh - 58px) - #{$excluding-size});
overflow: overlay; overflow: overlay;
padding: 0 0.1rem; padding: 0 0.1rem;
} }

View File

@ -15,7 +15,7 @@
<div class="database-tables"> <div class="database-tables">
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="table of database.tables" v-for="table of filteredTables"
:key="table.name" :key="table.name"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}" :class="{'text-bold': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}"
@ -24,7 +24,7 @@
> >
<a class="table-name"> <a class="table-name">
<i class="table-icon mdi mdi-18px mr-1" :class="table.type === 'view' ? 'mdi-table-eye' : 'mdi-table'" /> <i class="table-icon mdi mdi-18px mr-1" :class="table.type === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span>{{ table.name }}</span> <span v-html="highlightWord(table.name)" />
</a> </a>
<div <div
v-if="table.type === 'table'" v-if="table.type === 'table'"
@ -37,7 +37,7 @@
</ul> </ul>
</div> </div>
<div v-if="database.triggers.length" class="database-misc"> <div v-if="filteredTriggers.length" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"> <summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}">
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
@ -47,7 +47,7 @@
<div> <div>
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="trigger of database.triggers" v-for="trigger of filteredTriggers"
:key="trigger.name" :key="trigger.name"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
@ -56,7 +56,7 @@
> >
<a class="table-name"> <a class="table-name">
<i class="table-icon mdi mdi-table-cog mdi-18px mr-1" /> <i class="table-icon mdi mdi-table-cog mdi-18px mr-1" />
<span>{{ trigger.name }}</span> <span v-html="highlightWord(trigger.name)" />
</a> </a>
</li> </li>
</ul> </ul>
@ -65,7 +65,7 @@
</details> </details>
</div> </div>
<div v-if="database.procedures.length" class="database-misc"> <div v-if="filteredProcedures.length" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure}"> <summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure}">
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
@ -75,7 +75,7 @@
<div> <div>
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="procedure of database.procedures" v-for="procedure of filteredProcedures"
:key="procedure.name" :key="procedure.name"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}"
@ -84,7 +84,7 @@
> >
<a class="table-name"> <a class="table-name">
<i class="table-icon mdi mdi-sync-circle mdi-18px mr-1" /> <i class="table-icon mdi mdi-sync-circle mdi-18px mr-1" />
<span>{{ procedure.name }}</span> <span v-html="highlightWord(procedure.name)" />
</a> </a>
</li> </li>
</ul> </ul>
@ -93,7 +93,7 @@
</details> </details>
</div> </div>
<div v-if="database.functions.length" class="database-misc"> <div v-if="filteredFunctions.length" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}"> <summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}">
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
@ -103,7 +103,7 @@
<div> <div>
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="func of database.functions" v-for="func of filteredFunctions"
:key="func.name" :key="func.name"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
@ -112,7 +112,7 @@
> >
<a class="table-name"> <a class="table-name">
<i class="table-icon mdi mdi-arrow-right-bold-box mdi-18px mr-1" /> <i class="table-icon mdi mdi-arrow-right-bold-box mdi-18px mr-1" />
<span>{{ func.name }}</span> <span v-html="highlightWord(func.name)" />
</a> </a>
</li> </li>
</ul> </ul>
@ -121,7 +121,7 @@
</details> </details>
</div> </div>
<div v-if="database.schedulers.length" class="database-misc"> <div v-if="filteredSchedulers.length" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}"> <summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}">
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
@ -131,7 +131,7 @@
<div> <div>
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="scheduler of database.schedulers" v-for="scheduler of filteredSchedulers"
:key="scheduler.name" :key="scheduler.name"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
@ -140,7 +140,7 @@
> >
<a class="table-name"> <a class="table-name">
<i class="table-icon mdi mdi-calendar-clock mdi-18px mr-1" /> <i class="table-icon mdi mdi-calendar-clock mdi-18px mr-1" />
<span>{{ scheduler.name }}</span> <span v-html="highlightWord(scheduler.name)" />
</a> </a>
</li> </li>
</ul> </ul>
@ -170,8 +170,27 @@ export default {
computed: { computed: {
...mapGetters({ ...mapGetters({
getLoadedSchemas: 'workspaces/getLoadedSchemas', getLoadedSchemas: 'workspaces/getLoadedSchemas',
getWorkspace: 'workspaces/getWorkspace' getWorkspace: 'workspaces/getWorkspace',
getSearchTerm: 'workspaces/getSearchTerm'
}), }),
searchTerm () {
return this.getSearchTerm(this.connection.uid);
},
filteredTables () {
return this.database.tables.filter(table => table.name.search(this.searchTerm) >= 0);
},
filteredTriggers () {
return this.database.triggers.filter(trigger => trigger.name.search(this.searchTerm) >= 0);
},
filteredProcedures () {
return this.database.procedures.filter(procedure => procedure.name.search(this.searchTerm) >= 0);
},
filteredFunctions () {
return this.database.functions.filter(func => func.name.search(this.searchTerm) >= 0);
},
filteredSchedulers () {
return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0);
},
breadcrumbs () { breadcrumbs () {
return this.getWorkspace(this.connection.uid).breadcrumbs; return this.getWorkspace(this.connection.uid).breadcrumbs;
}, },
@ -222,6 +241,14 @@ export default {
setBreadcrumbs (payload) { setBreadcrumbs (payload) {
if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return; if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return;
this.changeBreadcrumbs(payload); this.changeBreadcrumbs(payload);
},
highlightWord (string) {
if (this.searchTerm) {
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return string.replace(regexp, '<span class="text-primary">$1</span>');
}
else
return string;
} }
} }
}; };

View File

@ -184,7 +184,8 @@ module.exports = {
enableSsl: 'Enable SSL', enableSsl: 'Enable SSL',
manualValue: 'Manual value', manualValue: 'Manual value',
tableFiller: 'Table Filler', tableFiller: 'Table Filler',
fakeDataLanguage: 'Fake data language' fakeDataLanguage: 'Fake data language',
searchForElements: 'Search for elements'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

@ -43,6 +43,9 @@ export default {
getLoadedSchemas: state => uid => { getLoadedSchemas: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid).loaded_schemas; return state.workspaces.find(workspace => workspace.uid === uid).loaded_schemas;
}, },
getSearchTerm: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid).search_term;
},
isUnsavedDiscardModal: state => { isUnsavedDiscardModal: state => {
return state.is_unsaved_discard_modal; return state.is_unsaved_discard_modal;
} }
@ -141,6 +144,14 @@ export default {
} }
: workspace); : workspace);
}, },
SET_SEARCH_TERM (state, { uid, term }) {
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
? {
...workspace,
search_term: term
}
: workspace);
},
NEW_TAB (state, { uid, tab }) { NEW_TAB (state, { uid, tab }) {
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1; tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
const newTab = { const newTab = {
@ -368,6 +379,7 @@ export default {
uid, uid,
connected: false, connected: false,
selected_tab: 0, selected_tab: 0,
search_term: '',
tabs: [], tabs: [],
structure: {}, structure: {},
variables: [], variables: [],
@ -415,6 +427,9 @@ export default {
if (payload.schema) if (payload.schema)
commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema: payload.schema }); commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema: payload.schema });
}, },
setSearchTerm ({ commit, getters }, term) {
commit('SET_SEARCH_TERM', { uid: getters.getSelected, term });
},
newTab ({ commit }, uid) { newTab ({ commit }, uid) {
const tab = uidGen('T'); const tab = uidGen('T');