mirror of
https://github.com/frappe/books.git
synced 2024-11-08 14:50:56 +00:00
refactor: use typed injections
- use contextual shortcuts - type Sidebar.vue, App.vue, ListView.vue - improve types for search.ts
This commit is contained in:
parent
1259420098
commit
06f981163d
148
src/App.vue
148
src/App.vue
@ -36,13 +36,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { ConfigKeys } from 'fyo/core/types';
|
||||
import { RTL_LANGUAGES } from 'fyo/utils/consts';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { systemLanguageRef } from 'src/utils/refs';
|
||||
import { computed } from 'vue';
|
||||
import { defineComponent, provide, ref, Ref } from 'vue';
|
||||
import WindowsTitleBar from './components/WindowsTitleBar.vue';
|
||||
import { handleErrorWithDialog } from './errorHandling';
|
||||
import { fyo } from './initFyo';
|
||||
@ -50,8 +49,10 @@ import DatabaseSelector from './pages/DatabaseSelector.vue';
|
||||
import Desk from './pages/Desk.vue';
|
||||
import SetupWizard from './pages/SetupWizard/SetupWizard.vue';
|
||||
import setupInstance from './setup/setupInstance';
|
||||
import { SetupWizardOptions } from './setup/types';
|
||||
import './styles/index.css';
|
||||
import { initializeInstance } from './utils/initialization';
|
||||
import * as injectionKeys from './utils/injectionKeys';
|
||||
import { checkForUpdates } from './utils/ipcCalls';
|
||||
import { updateConfigFiles } from './utils/misc';
|
||||
import { updatePrintTemplates } from './utils/printTemplates';
|
||||
@ -60,26 +61,38 @@ import { setGlobalShortcuts } from './utils/shortcuts';
|
||||
import { routeTo } from './utils/ui';
|
||||
import { Shortcuts, useKeys } from './utils/vueUtils';
|
||||
|
||||
export default {
|
||||
enum Screen {
|
||||
Desk = 'Desk',
|
||||
DatabaseSelector = 'DatabaseSelector',
|
||||
SetupWizard = 'SetupWizard',
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
setup() {
|
||||
return { keys: useKeys() };
|
||||
const keys = useKeys();
|
||||
const searcher: Ref<null | Search> = ref(null);
|
||||
const shortcuts = new Shortcuts(keys);
|
||||
const languageDirection = ref(
|
||||
getLanguageDirection(systemLanguageRef.value)
|
||||
);
|
||||
|
||||
provide(injectionKeys.keysKey, keys);
|
||||
provide(injectionKeys.searcherKey, searcher);
|
||||
provide(injectionKeys.shortcutsKey, shortcuts);
|
||||
provide(injectionKeys.languageDirectionKey, languageDirection);
|
||||
|
||||
return { keys, searcher, shortcuts, languageDirection };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeScreen: null,
|
||||
dbPath: '',
|
||||
companyName: '',
|
||||
searcher: null,
|
||||
shortcuts: null,
|
||||
};
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
languageDirection: computed(() => this.languageDirection),
|
||||
searcher: computed(() => this.searcher),
|
||||
shortcuts: computed(() => this.shortcuts),
|
||||
keys: computed(() => this.keys),
|
||||
} as {
|
||||
activeScreen: null | Screen;
|
||||
dbPath: string;
|
||||
companyName: string;
|
||||
};
|
||||
},
|
||||
components: {
|
||||
@ -89,66 +102,77 @@ export default {
|
||||
WindowsTitleBar,
|
||||
},
|
||||
async mounted() {
|
||||
const shortcuts = new Shortcuts(this.keys);
|
||||
this.shortcuts = shortcuts;
|
||||
const lastSelectedFilePath = fyo.config.get(
|
||||
ConfigKeys.LastSelectedFilePath,
|
||||
null
|
||||
);
|
||||
|
||||
if (!lastSelectedFilePath) {
|
||||
return (this.activeScreen = 'DatabaseSelector');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.fileSelected(lastSelectedFilePath, false);
|
||||
} catch (err) {
|
||||
await handleErrorWithDialog(err, undefined, true, true);
|
||||
await this.showDbSelector();
|
||||
}
|
||||
|
||||
setGlobalShortcuts(shortcuts);
|
||||
setGlobalShortcuts(this.shortcuts as Shortcuts);
|
||||
this.setInitialScreen();
|
||||
},
|
||||
watch: {
|
||||
language(value) {
|
||||
this.languageDirection = getLanguageDirection(value);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
language() {
|
||||
language(): string {
|
||||
return systemLanguageRef.value;
|
||||
},
|
||||
languageDirection() {
|
||||
return RTL_LANGUAGES.includes(this.language) ? 'rtl' : 'ltr';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async setDesk(filePath) {
|
||||
this.activeScreen = 'Desk';
|
||||
await this.setDeskRoute();
|
||||
await fyo.telemetry.start(true);
|
||||
await checkForUpdates(false);
|
||||
this.dbPath = filePath;
|
||||
this.companyName = await fyo.getValue(
|
||||
ModelNameEnum.AccountingSettings,
|
||||
'companyName'
|
||||
async setInitialScreen(): Promise<void> {
|
||||
const lastSelectedFilePath = fyo.config.get(
|
||||
ConfigKeys.LastSelectedFilePath,
|
||||
null
|
||||
);
|
||||
await this.setSearcher();
|
||||
updateConfigFiles(fyo);
|
||||
|
||||
if (
|
||||
typeof lastSelectedFilePath !== 'string' ||
|
||||
!lastSelectedFilePath.length
|
||||
) {
|
||||
this.activeScreen = Screen.DatabaseSelector;
|
||||
return;
|
||||
}
|
||||
|
||||
await this.fileSelected(lastSelectedFilePath, false);
|
||||
},
|
||||
async setSearcher() {
|
||||
async setSearcher(): Promise<void> {
|
||||
this.searcher = new Search(fyo);
|
||||
await this.searcher.initializeKeywords();
|
||||
},
|
||||
async fileSelected(filePath, isNew) {
|
||||
async setDesk(filePath: string): Promise<void> {
|
||||
this.activeScreen = Screen.Desk;
|
||||
await this.setDeskRoute();
|
||||
await fyo.telemetry.start(true);
|
||||
await checkForUpdates();
|
||||
this.dbPath = filePath;
|
||||
this.companyName = (await fyo.getValue(
|
||||
ModelNameEnum.AccountingSettings,
|
||||
'companyName'
|
||||
)) as string;
|
||||
await this.setSearcher();
|
||||
updateConfigFiles(fyo);
|
||||
},
|
||||
async fileSelected(filePath: string, isNew?: boolean): Promise<void> {
|
||||
fyo.config.set(ConfigKeys.LastSelectedFilePath, filePath);
|
||||
if (isNew) {
|
||||
this.activeScreen = 'SetupWizard';
|
||||
this.activeScreen = Screen.SetupWizard;
|
||||
return;
|
||||
}
|
||||
await this.showSetupWizardOrDesk(filePath);
|
||||
|
||||
try {
|
||||
await this.showSetupWizardOrDesk(filePath);
|
||||
} catch (error) {
|
||||
await handleErrorWithDialog(error, undefined, true, true);
|
||||
await this.showDbSelector();
|
||||
}
|
||||
},
|
||||
async setupComplete(setupWizardOptions) {
|
||||
async setupComplete(setupWizardOptions: SetupWizardOptions): Promise<void> {
|
||||
const filePath = fyo.config.get(ConfigKeys.LastSelectedFilePath);
|
||||
if (typeof filePath !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
await setupInstance(filePath, setupWizardOptions, fyo);
|
||||
await this.setDesk(filePath);
|
||||
},
|
||||
async showSetupWizardOrDesk(filePath) {
|
||||
async showSetupWizardOrDesk(filePath: string): Promise<void> {
|
||||
const countryCode = await fyo.db.connectToDatabase(filePath);
|
||||
const setupComplete = await fyo.getValue(
|
||||
ModelNameEnum.AccountingSettings,
|
||||
@ -156,7 +180,7 @@ export default {
|
||||
);
|
||||
|
||||
if (!setupComplete) {
|
||||
this.activeScreen = 'SetupWizard';
|
||||
this.activeScreen = Screen.SetupWizard;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -164,7 +188,7 @@ export default {
|
||||
await updatePrintTemplates(fyo);
|
||||
await this.setDesk(filePath);
|
||||
},
|
||||
async setDeskRoute() {
|
||||
async setDeskRoute(): Promise<void> {
|
||||
const { onboardingComplete } = await fyo.doc.getDoc('GetStarted');
|
||||
const { hideGetStarted } = await fyo.doc.getDoc('SystemSettings');
|
||||
|
||||
@ -174,15 +198,19 @@ export default {
|
||||
routeTo('/get-started');
|
||||
}
|
||||
},
|
||||
async showDbSelector() {
|
||||
async showDbSelector(): Promise<void> {
|
||||
fyo.config.set('lastSelectedFilePath', null);
|
||||
fyo.telemetry.stop();
|
||||
await fyo.purgeCache();
|
||||
this.activeScreen = 'DatabaseSelector';
|
||||
this.activeScreen = Screen.DatabaseSelector;
|
||||
this.dbPath = '';
|
||||
this.searcher = null;
|
||||
this.companyName = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function getLanguageDirection(language: string): 'rtl' | 'ltr' {
|
||||
return RTL_LANGUAGES.includes(language) ? 'rtl' : 'ltr';
|
||||
}
|
||||
</script>
|
||||
|
@ -15,4 +15,7 @@
|
||||
<feather-icon name="chevron-left" class="w-4 h-4" />
|
||||
</a>
|
||||
</template>
|
||||
<script></script>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({});
|
||||
</script>
|
||||
|
@ -14,8 +14,10 @@
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Button',
|
||||
props: {
|
||||
type: {
|
||||
@ -53,7 +55,7 @@ export default {
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
button:focus {
|
||||
|
@ -30,14 +30,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { languageDirectionKey } from 'src/utils/injectionKeys';
|
||||
import { showSidebar } from 'src/utils/refs';
|
||||
import { Transition } from 'vue';
|
||||
import { defineComponent, inject, Transition } from 'vue';
|
||||
import BackLink from './BackLink.vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
|
||||
export default {
|
||||
inject: ['languageDirection'],
|
||||
export default defineComponent({
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
backLink: { type: Boolean, default: true },
|
||||
@ -45,16 +45,16 @@ export default {
|
||||
border: { type: Boolean, default: true },
|
||||
searchborder: { type: Boolean, default: true },
|
||||
},
|
||||
components: { SearchBar, BackLink, Transition },
|
||||
components: { BackLink, SearchBar, Transition },
|
||||
setup() {
|
||||
return { showSidebar };
|
||||
return { showSidebar, languageDirection: inject(languageDirectionKey) };
|
||||
},
|
||||
computed: {
|
||||
showBorder() {
|
||||
return !!this.$slots.default && this.searchborder;
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.w-tl {
|
||||
|
@ -26,8 +26,6 @@
|
||||
spellcheck="false"
|
||||
:placeholder="t`Type to search...`"
|
||||
v-model="inputValue"
|
||||
@focus="search"
|
||||
@input="search"
|
||||
@keydown.up="up"
|
||||
@keydown.down="down"
|
||||
@keydown.enter="() => select()"
|
||||
@ -50,7 +48,7 @@
|
||||
<div :style="`max-height: ${49 * 6 - 1}px`" class="overflow-auto">
|
||||
<div
|
||||
v-for="(si, i) in suggestions"
|
||||
:key="`${i}-${si.key}`"
|
||||
:key="`${i}-${si.label}`"
|
||||
ref="suggestions"
|
||||
class="hover:bg-gray-50 cursor-pointer"
|
||||
:class="idx === i ? 'border-blue-500 bg-gray-50 border-s-4' : ''"
|
||||
@ -97,7 +95,7 @@
|
||||
:key="g"
|
||||
class="border px-1 py-0.5 rounded-lg"
|
||||
:class="getGroupFilterButtonClass(g)"
|
||||
@click="searcher.set(g, !searcher.filters.groupFilters[g])"
|
||||
@click="searcher!.set(g, !searcher!.filters.groupFilters[g])"
|
||||
>
|
||||
{{ groupLabelMap[g] }}
|
||||
</button>
|
||||
@ -115,11 +113,11 @@
|
||||
<!-- Group Skip Filters -->
|
||||
<div class="flex gap-1 text-gray-800">
|
||||
<button
|
||||
v-for="s in ['skipTables', 'skipTransactions']"
|
||||
v-for="s in ['skipTables', 'skipTransactions'] as const"
|
||||
:key="s"
|
||||
class="border px-1 py-0.5 rounded-lg"
|
||||
:class="{ 'bg-gray-200': searcher.filters[s] }"
|
||||
@click="searcher.set(s, !searcher.filters[s])"
|
||||
:class="{ 'bg-gray-200': searcher?.filters[s] }"
|
||||
@click="searcher?.set(s, !searcher?.filters[s])"
|
||||
>
|
||||
{{
|
||||
s === 'skipTables' ? t`Skip Child Tables` : t`Skip Transactions`
|
||||
@ -141,12 +139,12 @@
|
||||
whitespace-nowrap
|
||||
"
|
||||
:class="{
|
||||
'bg-blue-100': searcher.filters.schemaFilters[sf.value],
|
||||
'bg-blue-100': searcher?.filters.schemaFilters[sf.value],
|
||||
}"
|
||||
@click="
|
||||
searcher.set(
|
||||
searcher?.set(
|
||||
sf.value,
|
||||
!searcher.filters.schemaFilters[sf.value]
|
||||
!searcher?.filters.schemaFilters[sf.value]
|
||||
)
|
||||
"
|
||||
>
|
||||
@ -180,12 +178,12 @@
|
||||
>
|
||||
<template
|
||||
v-for="c in allowedLimits.filter(
|
||||
(c) => c < searcher.numSearches || c === -1
|
||||
(c) => c < (searcher?.numSearches ?? 0) || c === -1
|
||||
)"
|
||||
:key="c + '-count'"
|
||||
>
|
||||
<button
|
||||
@click="limit = parseInt(c)"
|
||||
@click="limit = Number(c)"
|
||||
class="w-9"
|
||||
:class="limit === c ? 'bg-gray-100' : ''"
|
||||
>
|
||||
@ -198,17 +196,32 @@
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
<script lang="ts">
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { getBgTextColorClass } from 'src/utils/colors';
|
||||
import { searcherKey, shortcutsKey } from 'src/utils/injectionKeys';
|
||||
import { openLink } from 'src/utils/ipcCalls';
|
||||
import { docsPathMap } from 'src/utils/misc';
|
||||
import { getGroupLabelMap, searchGroups } from 'src/utils/search';
|
||||
import { nextTick } from 'vue';
|
||||
import {
|
||||
getGroupLabelMap,
|
||||
SearchGroup,
|
||||
searchGroups,
|
||||
SearchItems,
|
||||
} from 'src/utils/search';
|
||||
import { defineComponent, inject, nextTick } from 'vue';
|
||||
import Button from './Button.vue';
|
||||
import Modal from './Modal.vue';
|
||||
|
||||
export default {
|
||||
type SchemaFilters = { value: string; label: string; index: number }[];
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
searcher: inject(searcherKey),
|
||||
shortcuts: inject(shortcutsKey),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
idx: 0,
|
||||
@ -220,10 +233,10 @@ export default {
|
||||
allowedLimits: [50, 100, 500, -1],
|
||||
};
|
||||
},
|
||||
inject: ['searcher', 'shortcuts'],
|
||||
components: { Modal, Button },
|
||||
async mounted() {
|
||||
if (fyo.store.isDevelopment) {
|
||||
// @ts-ignore
|
||||
window.search = this;
|
||||
}
|
||||
|
||||
@ -234,15 +247,15 @@ export default {
|
||||
this.openModal = false;
|
||||
},
|
||||
deactivated() {
|
||||
this.deleteShortcuts();
|
||||
this.shortcuts!.delete(this);
|
||||
},
|
||||
methods: {
|
||||
openDocs() {
|
||||
openLink('https://docs.frappebooks.com/' + docsPathMap.Search);
|
||||
},
|
||||
getShortcuts() {
|
||||
const ifOpen = (cb) => () => this.openModal && cb();
|
||||
const ifClose = (cb) => () => !this.openModal && cb();
|
||||
const ifOpen = (cb: Function) => () => this.openModal && cb();
|
||||
const ifClose = (cb: Function) => () => !this.openModal && cb();
|
||||
|
||||
const shortcuts = [
|
||||
{
|
||||
@ -256,12 +269,16 @@ export default {
|
||||
shortcut: `Digit${Number(i) + 1}`,
|
||||
callback: ifOpen(() => {
|
||||
const group = searchGroups[i];
|
||||
if (!this.searcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = this.searcher.filters.groupFilters[group];
|
||||
if (typeof value !== 'boolean') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.searcher.set(group, !value);
|
||||
this.searcher!.set(group, !value);
|
||||
}),
|
||||
});
|
||||
}
|
||||
@ -270,15 +287,10 @@ export default {
|
||||
},
|
||||
setShortcuts() {
|
||||
for (const { shortcut, callback } of this.getShortcuts()) {
|
||||
this.shortcuts.pmod.set([shortcut], callback);
|
||||
this.shortcuts!.pmod.set(this, [shortcut], callback);
|
||||
}
|
||||
},
|
||||
deleteShortcuts() {
|
||||
for (const { shortcut } of this.getShortcuts()) {
|
||||
this.shortcuts.pmod.delete([shortcut]);
|
||||
}
|
||||
},
|
||||
modKeyText(key) {
|
||||
modKeyText(key: string): string {
|
||||
key = key.toUpperCase();
|
||||
if (this.platform === 'Mac') {
|
||||
return `⌘ ${key}`;
|
||||
@ -286,41 +298,48 @@ export default {
|
||||
|
||||
return `Ctrl ${key}`;
|
||||
},
|
||||
open() {
|
||||
open(): void {
|
||||
this.openModal = true;
|
||||
this.searcher?.updateKeywords();
|
||||
|
||||
nextTick(() => {
|
||||
this.$refs.input.focus();
|
||||
(this.$refs.input as HTMLInputElement).focus();
|
||||
});
|
||||
},
|
||||
close() {
|
||||
close(): void {
|
||||
this.openModal = false;
|
||||
this.reset();
|
||||
},
|
||||
reset() {
|
||||
reset(): void {
|
||||
this.inputValue = '';
|
||||
},
|
||||
up() {
|
||||
up(): void {
|
||||
this.idx = Math.max(this.idx - 1, 0);
|
||||
this.scrollToHighlighted();
|
||||
},
|
||||
down() {
|
||||
down(): void {
|
||||
this.idx = Math.max(
|
||||
Math.min(this.idx + 1, this.suggestions.length - 1),
|
||||
0
|
||||
);
|
||||
this.scrollToHighlighted();
|
||||
},
|
||||
select(idx) {
|
||||
select(idx?: number): void {
|
||||
this.idx = idx ?? this.idx;
|
||||
this.suggestions[this.idx]?.action();
|
||||
this.suggestions[this.idx]?.action?.();
|
||||
this.close();
|
||||
},
|
||||
scrollToHighlighted() {
|
||||
const ref = this.$refs.suggestions[this.idx];
|
||||
ref.scrollIntoView({ block: 'nearest' });
|
||||
scrollToHighlighted(): void {
|
||||
const suggestionRefs = this.$refs.suggestions;
|
||||
if (!Array.isArray(suggestionRefs)) {
|
||||
return;
|
||||
}
|
||||
const el = suggestionRefs[this.idx];
|
||||
if (el instanceof HTMLElement) {
|
||||
el.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
},
|
||||
getGroupFilterButtonClass(g) {
|
||||
getGroupFilterButtonClass(g: SearchGroup): string {
|
||||
if (!this.searcher) {
|
||||
return '';
|
||||
}
|
||||
@ -335,27 +354,34 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
groupLabelMap() {
|
||||
groupLabelMap(): Record<SearchGroup, string> {
|
||||
return getGroupLabelMap();
|
||||
},
|
||||
schemaFilters() {
|
||||
const schemaNames = Object.keys(this.searcher?.searchables) ?? [];
|
||||
return schemaNames
|
||||
.map((sn) => {
|
||||
const schema = fyo.schemaMap[sn];
|
||||
const value = sn;
|
||||
const label = schema.label;
|
||||
schemaFilters(): SchemaFilters {
|
||||
const searchables = this.searcher?.searchables ?? {};
|
||||
|
||||
const schemaNames = Object.keys(searchables);
|
||||
const filters = schemaNames
|
||||
.map((value) => {
|
||||
const schema = fyo.schemaMap[value];
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = 1;
|
||||
if (schema.isSubmittable) {
|
||||
index = 0;
|
||||
} else if (schema.isChild) {
|
||||
index = 2;
|
||||
}
|
||||
return { value, label, index };
|
||||
|
||||
return { value, label: schema.label, index };
|
||||
})
|
||||
.sort((a, b) => a.index - b.index);
|
||||
.filter(Boolean) as SchemaFilters;
|
||||
|
||||
return filters.sort((a, b) => a.index - b.index);
|
||||
},
|
||||
groupColorMap() {
|
||||
groupColorMap(): Record<SearchGroup, string> {
|
||||
return {
|
||||
Docs: 'blue',
|
||||
Create: 'green',
|
||||
@ -364,13 +390,13 @@ export default {
|
||||
Page: 'orange',
|
||||
};
|
||||
},
|
||||
groupColorClassMap() {
|
||||
groupColorClassMap(): Record<SearchGroup, string> {
|
||||
return searchGroups.reduce((map, g) => {
|
||||
map[g] = getBgTextColorClass(this.groupColorMap[g]);
|
||||
return map;
|
||||
}, {});
|
||||
}, {} as Record<SearchGroup, string>);
|
||||
},
|
||||
suggestions() {
|
||||
suggestions(): SearchItems {
|
||||
if (!this.searcher) {
|
||||
return [];
|
||||
}
|
||||
@ -383,7 +409,7 @@ export default {
|
||||
return suggestions.slice(0, this.limit);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
input[type='search']::-webkit-search-decoration,
|
||||
|
@ -35,7 +35,7 @@
|
||||
? 'bg-gray-100 border-s-4 border-blue-500'
|
||||
: ''
|
||||
"
|
||||
@click="onGroupClick(group)"
|
||||
@click="routeToSidebarItem(group)"
|
||||
>
|
||||
<Icon
|
||||
class="flex-shrink-0"
|
||||
@ -72,7 +72,7 @@
|
||||
? 'bg-gray-100 text-blue-600 border-s-4 border-blue-500'
|
||||
: ''
|
||||
"
|
||||
@click="onItemClick(item)"
|
||||
@click="routeToSidebarItem(item)"
|
||||
>
|
||||
<p :style="isItemActive(item) ? 'margin-left: -4px' : ''">
|
||||
{{ item.label }}
|
||||
@ -175,29 +175,40 @@
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Button from 'src/components/Button.vue';
|
||||
<script lang="ts">
|
||||
import { reportIssue } from 'src/errorHandling';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { languageDirectionKey, shortcutsKey } from 'src/utils/injectionKeys';
|
||||
import { openLink } from 'src/utils/ipcCalls';
|
||||
import { docsPathRef } from 'src/utils/refs';
|
||||
import { getSidebarConfig } from 'src/utils/sidebarConfig';
|
||||
import { SidebarConfig, SidebarItem, SidebarRoot } from 'src/utils/types';
|
||||
import { routeTo, toggleSidebar } from 'src/utils/ui';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import router from '../router';
|
||||
import Icon from './Icon.vue';
|
||||
import Modal from './Modal.vue';
|
||||
import ShortcutsHelper from './ShortcutsHelper.vue';
|
||||
|
||||
export default {
|
||||
components: [Button],
|
||||
inject: ['languageDirection', 'shortcuts'],
|
||||
export default defineComponent({
|
||||
emits: ['change-db-file'],
|
||||
setup() {
|
||||
return {
|
||||
languageDirection: inject(languageDirectionKey),
|
||||
shortcuts: inject(shortcutsKey),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
companyName: '',
|
||||
groups: [],
|
||||
viewShortcuts: false,
|
||||
activeGroup: null,
|
||||
} as {
|
||||
companyName: string;
|
||||
groups: SidebarConfig;
|
||||
viewShortcuts: boolean;
|
||||
activeGroup: null | SidebarRoot;
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -212,7 +223,7 @@ export default {
|
||||
},
|
||||
async mounted() {
|
||||
const { companyName } = await fyo.doc.getDoc('AccountingSettings');
|
||||
this.companyName = companyName;
|
||||
this.companyName = companyName as string;
|
||||
this.groups = await getSidebarConfig();
|
||||
|
||||
this.setActiveGroup();
|
||||
@ -220,16 +231,15 @@ export default {
|
||||
this.setActiveGroup();
|
||||
});
|
||||
|
||||
this.shortcuts.shift.set(['KeyH'], () => {
|
||||
this.shortcuts?.shift.set(this, ['KeyH'], () => {
|
||||
if (document.body === document.activeElement) {
|
||||
this.toggleSidebar();
|
||||
}
|
||||
});
|
||||
this.shortcuts.set(['F1'], () => this.openDocumentation());
|
||||
this.shortcuts?.set(this, ['F1'], () => this.openDocumentation());
|
||||
},
|
||||
unmounted() {
|
||||
this.shortcuts.alt.delete(['KeyH']);
|
||||
this.shortcuts.delete(['F1']);
|
||||
this.shortcuts?.delete(this);
|
||||
},
|
||||
methods: {
|
||||
routeTo,
|
||||
@ -241,31 +251,30 @@ export default {
|
||||
setActiveGroup() {
|
||||
const { fullPath } = this.$router.currentRoute.value;
|
||||
const fallBackGroup = this.activeGroup;
|
||||
this.activeGroup = this.groups.find((g) => {
|
||||
if (fullPath.startsWith(g.route) && g.route !== '/') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (g.route === fullPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (g.items) {
|
||||
let activeItem = g.items.filter(
|
||||
({ route }) => route === fullPath || fullPath.startsWith(route)
|
||||
);
|
||||
|
||||
if (activeItem.length) {
|
||||
this.activeGroup =
|
||||
this.groups.find((g) => {
|
||||
if (fullPath.startsWith(g.route) && g.route !== '/') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.activeGroup) {
|
||||
this.activeGroup = fallBackGroup || this.groups[0];
|
||||
}
|
||||
if (g.route === fullPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (g.items) {
|
||||
let activeItem = g.items.filter(
|
||||
({ route }) => route === fullPath || fullPath.startsWith(route)
|
||||
);
|
||||
|
||||
if (activeItem.length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}) ??
|
||||
fallBackGroup ??
|
||||
this.groups[0];
|
||||
},
|
||||
isItemActive(item) {
|
||||
isItemActive(item: SidebarItem) {
|
||||
const { path: currentRoute, params } = this.$route;
|
||||
const routeMatch = currentRoute === item.route;
|
||||
|
||||
@ -279,28 +288,13 @@ export default {
|
||||
|
||||
return isMatch;
|
||||
},
|
||||
isGroupActive(group) {
|
||||
isGroupActive(group: SidebarRoot) {
|
||||
return this.activeGroup && group.label === this.activeGroup.label;
|
||||
},
|
||||
onGroupClick(group) {
|
||||
if (group.action) {
|
||||
group.action();
|
||||
}
|
||||
|
||||
if (group.route) {
|
||||
routeTo(this.getPath(group));
|
||||
}
|
||||
routeToSidebarItem(item: SidebarItem | SidebarRoot) {
|
||||
routeTo(this.getPath(item));
|
||||
},
|
||||
onItemClick(item) {
|
||||
if (item.action) {
|
||||
item.action();
|
||||
}
|
||||
|
||||
if (item.route) {
|
||||
routeTo(this.getPath(item));
|
||||
}
|
||||
},
|
||||
getPath(item) {
|
||||
getPath(item: SidebarItem | SidebarRoot) {
|
||||
const { route: path, filters } = item;
|
||||
if (!filters) {
|
||||
return path;
|
||||
@ -309,5 +303,5 @@ export default {
|
||||
return { path, query: { filters: JSON.stringify(filters) } };
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -41,7 +41,9 @@
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { Field } from 'schemas/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import ExportWizard from 'src/components/ExportWizard.vue';
|
||||
import FilterDropdown from 'src/components/FilterDropdown.vue';
|
||||
@ -49,22 +51,34 @@ import Modal from 'src/components/Modal.vue';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { getRouteData } from 'src/utils/filters';
|
||||
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||
import {
|
||||
docsPathMap,
|
||||
getCreateFiltersFromListViewFilters,
|
||||
} from 'src/utils/misc';
|
||||
import { docsPathRef } from 'src/utils/refs';
|
||||
import { openQuickEdit, routeTo } from 'src/utils/ui';
|
||||
import { Shortcuts } from 'src/utils/vueUtils';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { defineComponent, inject, ref } from 'vue';
|
||||
import { RouteLocationRaw } from 'vue-router';
|
||||
import List from './List.vue';
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'ListView',
|
||||
props: {
|
||||
schemaName: String,
|
||||
schemaName: { type: String, required: true },
|
||||
filters: Object,
|
||||
pageTitle: { type: String, default: '' },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
shortcuts: inject(shortcutsKey),
|
||||
list: ref<InstanceType<typeof List> | null>(null),
|
||||
filterDropdown: ref<InstanceType<typeof FilterDropdown> | null>(null),
|
||||
makeNewDocButton: ref<InstanceType<typeof Button> | null>(null),
|
||||
exportButton: ref<InstanceType<typeof Button> | null>(null),
|
||||
};
|
||||
},
|
||||
components: {
|
||||
PageHeader,
|
||||
List,
|
||||
@ -78,41 +92,52 @@ export default {
|
||||
listConfig: undefined,
|
||||
openExportModal: false,
|
||||
listFilters: {},
|
||||
} as {
|
||||
listConfig: undefined | ReturnType<typeof getListConfig>;
|
||||
openExportModal: boolean;
|
||||
listFilters: QueryFilter;
|
||||
};
|
||||
},
|
||||
inject: { shortcutManager: { from: 'shortcuts' } },
|
||||
async activated() {
|
||||
if (typeof this.filters === 'object') {
|
||||
this.$refs.filterDropdown.setFilter(this.filters, true);
|
||||
this.filterDropdown?.setFilter(this.filters, true);
|
||||
}
|
||||
|
||||
this.listConfig = getListConfig(this.schemaName);
|
||||
docsPathRef.value = docsPathMap[this.schemaName] ?? docsPathMap.Entries;
|
||||
docsPathRef.value =
|
||||
docsPathMap[this.schemaName] ?? docsPathMap.Entries ?? '';
|
||||
|
||||
if (this.fyo.store.isDevelopment) {
|
||||
// @ts-ignore
|
||||
window.lv = this;
|
||||
}
|
||||
|
||||
this.shortcuts.pmod.set(['KeyN'], () =>
|
||||
this.$refs.makeNewDocButton.$el.click()
|
||||
);
|
||||
this.shortcuts.pmod.set(['KeyE'], () =>
|
||||
this.$refs.exportButton.$el.click()
|
||||
);
|
||||
this.setShortcuts();
|
||||
},
|
||||
deactivated() {
|
||||
docsPathRef.value = '';
|
||||
this.shortcuts.pmod.delete(['KeyN']);
|
||||
this.shortcuts.pmod.delete(['KeyE']);
|
||||
this.shortcuts?.delete(this);
|
||||
},
|
||||
methods: {
|
||||
updatedData(listFilters) {
|
||||
setShortcuts() {
|
||||
if (!this.shortcuts) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shortcuts.pmod.set(this, ['KeyN'], () =>
|
||||
this.makeNewDocButton?.$el.click()
|
||||
);
|
||||
this.shortcuts.pmod.set(this, ['KeyE'], () =>
|
||||
this.exportButton?.$el.click()
|
||||
);
|
||||
},
|
||||
updatedData(listFilters: QueryFilter) {
|
||||
this.listFilters = listFilters;
|
||||
},
|
||||
async openDoc(name) {
|
||||
async openDoc(name: string) {
|
||||
const doc = await this.fyo.doc.getDoc(this.schemaName, name);
|
||||
|
||||
if (this.listConfig.formRoute) {
|
||||
if (this.listConfig?.formRoute) {
|
||||
return await routeTo(this.listConfig.formRoute(name));
|
||||
}
|
||||
|
||||
@ -138,13 +163,21 @@ export default {
|
||||
this.$router.replace(path);
|
||||
});
|
||||
},
|
||||
applyFilter(filters) {
|
||||
this.$refs.list.updateData(filters);
|
||||
applyFilter(filters: QueryFilter) {
|
||||
this.list?.updateData(filters);
|
||||
},
|
||||
getFormPath(doc) {
|
||||
getFormPath(doc: Doc) {
|
||||
const { label, routeFilter } = getRouteData({ doc });
|
||||
let path = {
|
||||
path: `/list/${this.schemaName}/${label}`,
|
||||
const currentPath = this.$router.currentRoute.value.path;
|
||||
|
||||
// Maintain filters
|
||||
let path = `/list/${this.schemaName}/${label}`;
|
||||
if (currentPath.startsWith(path)) {
|
||||
path = currentPath;
|
||||
}
|
||||
|
||||
let route: RouteLocationRaw = {
|
||||
path,
|
||||
query: {
|
||||
edit: 1,
|
||||
schemaName: this.schemaName,
|
||||
@ -153,47 +186,31 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
if (this.listConfig.formRoute) {
|
||||
path = this.listConfig.formRoute(doc.name);
|
||||
if (this.listConfig?.formRoute) {
|
||||
route = this.listConfig.formRoute(doc.name!);
|
||||
}
|
||||
|
||||
if (typeof path === 'object') {
|
||||
return path;
|
||||
}
|
||||
|
||||
// Maintain filter if present
|
||||
const currentPath = this.$router.currentRoute.value.path;
|
||||
if (currentPath.slice(0, path?.path?.length ?? 0) === path.path) {
|
||||
path.path = currentPath;
|
||||
}
|
||||
|
||||
return path;
|
||||
return route;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.pageTitle || fyo.schemaMap[this.schemaName].label;
|
||||
},
|
||||
fields() {
|
||||
return fyo.schemaMap[this.schemaName].fields;
|
||||
},
|
||||
canCreate() {
|
||||
return fyo.schemaMap[this.schemaName].create !== false;
|
||||
},
|
||||
shortcuts() {
|
||||
// @ts-ignore
|
||||
const shortcutManager = this.shortcutManager;
|
||||
if (shortcutManager instanceof Shortcuts) {
|
||||
return shortcutManager;
|
||||
title(): string {
|
||||
if (this.pageTitle) {
|
||||
return this.pageTitle;
|
||||
}
|
||||
|
||||
// no-op (hopefully)
|
||||
throw Error('Shortcuts Not Found');
|
||||
return fyo.schemaMap[this.schemaName]?.label ?? this.schemaName;
|
||||
},
|
||||
fields(): Field[] {
|
||||
return fyo.schemaMap[this.schemaName]?.fields ?? [];
|
||||
},
|
||||
canCreate(): boolean {
|
||||
return fyo.schemaMap[this.schemaName]?.create !== false;
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function getListConfig(schemaName) {
|
||||
function getListConfig(schemaName: string) {
|
||||
const listConfig = fyo.models[schemaName]?.getListViewSettings?.(fyo);
|
||||
if (listConfig?.columns === undefined) {
|
||||
return {
|
||||
|
@ -219,6 +219,7 @@ import HorizontalResizer from 'src/components/HorizontalResizer.vue';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import ShortcutKeys from 'src/components/ShortcutKeys.vue';
|
||||
import { handleErrorWithDialog } from 'src/errorHandling';
|
||||
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||
import { getSavePath } from 'src/utils/ipcCalls';
|
||||
import { docsPathMap } from 'src/utils/misc';
|
||||
import {
|
||||
@ -238,12 +239,12 @@ import {
|
||||
showMessageDialog,
|
||||
showToast,
|
||||
} from 'src/utils/ui';
|
||||
import { Shortcuts } from 'src/utils/vueUtils';
|
||||
import { getMapFromList } from 'utils/index';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import PrintContainer from './PrintContainer.vue';
|
||||
import TemplateBuilderHint from './TemplateBuilderHint.vue';
|
||||
import TemplateEditor from './TemplateEditor.vue';
|
||||
import { inject } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: { name: String },
|
||||
@ -258,7 +259,11 @@ export default defineComponent({
|
||||
TemplateBuilderHint,
|
||||
ShortcutKeys,
|
||||
},
|
||||
inject: { shortcutManager: { from: 'shortcuts' } },
|
||||
setup() {
|
||||
return {
|
||||
shortcuts: inject(shortcutsKey),
|
||||
};
|
||||
},
|
||||
provide() {
|
||||
return { doc: computed(() => this.doc) };
|
||||
},
|
||||
@ -304,21 +309,28 @@ export default defineComponent({
|
||||
},
|
||||
async activated(): Promise<void> {
|
||||
docsPathRef.value = docsPathMap.PrintTemplate ?? '';
|
||||
this.shortcuts.ctrl.set(['Enter'], this.setTemplate);
|
||||
this.shortcuts.ctrl.set(['KeyE'], this.toggleEditMode);
|
||||
this.shortcuts.ctrl.set(['KeyH'], this.toggleShowHints);
|
||||
this.shortcuts.ctrl.set(['Equal'], () => this.setScale(this.scale + 0.1));
|
||||
this.shortcuts.ctrl.set(['Minus'], () => this.setScale(this.scale - 0.1));
|
||||
this.setShortcuts;
|
||||
},
|
||||
deactivated(): void {
|
||||
docsPathRef.value = '';
|
||||
this.shortcuts.ctrl.delete(['Enter']);
|
||||
this.shortcuts.ctrl.delete(['KeyE']);
|
||||
this.shortcuts.ctrl.delete(['KeyH']);
|
||||
this.shortcuts.ctrl.delete(['Equal']);
|
||||
this.shortcuts.ctrl.delete(['Minus']);
|
||||
this.shortcuts?.delete(this);
|
||||
},
|
||||
methods: {
|
||||
setShortcuts() {
|
||||
if (!this.shortcuts) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shortcuts.ctrl.set(this, ['Enter'], this.setTemplate);
|
||||
this.shortcuts.ctrl.set(this, ['KeyE'], this.toggleEditMode);
|
||||
this.shortcuts.ctrl.set(this, ['KeyH'], this.toggleShowHints);
|
||||
this.shortcuts.ctrl.set(this, ['Equal'], () =>
|
||||
this.setScale(this.scale + 0.1)
|
||||
);
|
||||
this.shortcuts.ctrl.set(this, ['Minus'], () =>
|
||||
this.setScale(this.scale - 0.1)
|
||||
);
|
||||
},
|
||||
async initialize() {
|
||||
await this.setDoc();
|
||||
if (this.doc?.type) {
|
||||
@ -580,17 +592,6 @@ export default defineComponent({
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
shortcuts(): Shortcuts {
|
||||
// @ts-ignore
|
||||
const shortcutManager = this.shortcutManager;
|
||||
if (shortcutManager instanceof Shortcuts) {
|
||||
return shortcutManager;
|
||||
}
|
||||
|
||||
// no-op (hopefully)
|
||||
throw Error('Shortcuts Not Found');
|
||||
},
|
||||
maxWidth() {
|
||||
return window.innerWidth - 12 * 16 - 100;
|
||||
},
|
||||
|
17
src/utils/injectionKeys.ts
Normal file
17
src/utils/injectionKeys.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { InjectionKey, Ref } from 'vue';
|
||||
import { Search } from './search';
|
||||
import type { Shortcuts, useKeys } from './vueUtils';
|
||||
|
||||
export const languageDirectionKey = Symbol('languageDirection') as InjectionKey<
|
||||
Ref<'ltr' | 'rtl'>
|
||||
>;
|
||||
|
||||
export const keysKey = Symbol('keys') as InjectionKey<
|
||||
ReturnType<typeof useKeys>
|
||||
>;
|
||||
|
||||
export const searcherKey = Symbol('searcher') as InjectionKey<
|
||||
Ref<null | Search>
|
||||
>;
|
||||
|
||||
export const shortcutsKey = Symbol('shortcuts') as InjectionKey<Shortcuts>;
|
@ -11,29 +11,29 @@ import { RouteLocationRaw } from 'vue-router';
|
||||
import { fuzzyMatch } from '.';
|
||||
import { getFormRoute, routeTo } from './ui';
|
||||
|
||||
export const searchGroups = ['Docs', 'List', 'Create', 'Report', 'Page'];
|
||||
enum SearchGroupEnum {
|
||||
'List' = 'List',
|
||||
'Report' = 'Report',
|
||||
'Create' = 'Create',
|
||||
'Page' = 'Page',
|
||||
'Docs' = 'Docs',
|
||||
}
|
||||
export const searchGroups = [
|
||||
'Docs',
|
||||
'List',
|
||||
'Create',
|
||||
'Report',
|
||||
'Page',
|
||||
] as const;
|
||||
|
||||
type SearchGroup = keyof typeof SearchGroupEnum;
|
||||
export type SearchGroup = typeof searchGroups[number];
|
||||
interface SearchItem {
|
||||
label: string;
|
||||
group: SearchGroup;
|
||||
group: Exclude<SearchGroup, 'Docs'>;
|
||||
route?: string;
|
||||
action?: () => void;
|
||||
}
|
||||
|
||||
interface DocSearchItem extends SearchItem {
|
||||
interface DocSearchItem extends Omit<SearchItem, 'group'> {
|
||||
group: 'Docs';
|
||||
schemaLabel: string;
|
||||
more: string[];
|
||||
}
|
||||
|
||||
type SearchItems = (DocSearchItem | SearchItem)[];
|
||||
export type SearchItems = (DocSearchItem | SearchItem)[];
|
||||
|
||||
interface Searchable {
|
||||
needsUpdate: boolean;
|
||||
@ -604,10 +604,7 @@ export class Search {
|
||||
return;
|
||||
}
|
||||
|
||||
const subArray = this._getSubSortedArray(
|
||||
keywords,
|
||||
input
|
||||
) as DocSearchItem[];
|
||||
const subArray = this._getSubSortedArray(keywords, input);
|
||||
array.push(...subArray);
|
||||
}
|
||||
|
||||
@ -615,7 +612,7 @@ export class Search {
|
||||
const filtered = this._nonDocSearchList.filter(
|
||||
(si) => this.filters.groupFilters[si.group]
|
||||
);
|
||||
const subArray = this._getSubSortedArray(filtered, input) as SearchItem[];
|
||||
const subArray = this._getSubSortedArray(filtered, input);
|
||||
array.push(...subArray);
|
||||
}
|
||||
|
||||
@ -627,38 +624,68 @@ export class Search {
|
||||
[];
|
||||
|
||||
for (const item of items) {
|
||||
const isSearchItem = !!(item as SearchItem).group;
|
||||
|
||||
if (!input && isSearchItem) {
|
||||
subArray.push({ item: item as SearchItem, distance: 0 });
|
||||
const subArrayItem = this._getSubArrayItem(item, input);
|
||||
if (!subArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const values = this._getValueList(item).filter(Boolean);
|
||||
const { isMatch, distance } = this._getMatchAndDistance(input, values);
|
||||
|
||||
if (!isMatch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSearchItem) {
|
||||
subArray.push({ item: item as SearchItem, distance });
|
||||
} else {
|
||||
subArray.push({
|
||||
item: this._getDocSearchItemFromKeyword(item as Keyword),
|
||||
distance,
|
||||
});
|
||||
}
|
||||
subArray.push(subArrayItem);
|
||||
}
|
||||
|
||||
subArray.sort((a, b) => a.distance - b.distance);
|
||||
return subArray.map(({ item }) => item);
|
||||
}
|
||||
|
||||
_getSubArrayItem(item: SearchItem | Keyword, input?: string) {
|
||||
if (isSearchItem(item)) {
|
||||
return this._getSubArrayItemFromSearchItem(item, input);
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._getSubArrayItemFromKeyword(item, input);
|
||||
}
|
||||
|
||||
_getSubArrayItemFromSearchItem(item: SearchItem, input?: string) {
|
||||
if (!input) {
|
||||
return { item, distance: 0 };
|
||||
}
|
||||
|
||||
const values = this._getValueListFromSearchItem(item).filter(Boolean);
|
||||
const { isMatch, distance } = this._getMatchAndDistance(input, values);
|
||||
|
||||
if (!isMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { item, distance };
|
||||
}
|
||||
|
||||
_getValueListFromSearchItem({ label, group }: SearchItem): string[] {
|
||||
return [label, group];
|
||||
}
|
||||
|
||||
_getSubArrayItemFromKeyword(item: Keyword, input: string) {
|
||||
const values = this._getValueListFromKeyword(item).filter(Boolean);
|
||||
const { isMatch, distance } = this._getMatchAndDistance(input, values);
|
||||
|
||||
if (!isMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
item: this._getDocSearchItemFromKeyword(item),
|
||||
distance,
|
||||
};
|
||||
}
|
||||
|
||||
_getValueListFromKeyword({ values, meta }: Keyword): string[] {
|
||||
const schemaLabel = meta.schemaName as string;
|
||||
return [values, schemaLabel].flat();
|
||||
}
|
||||
|
||||
_getMatchAndDistance(input: string, values: string[]) {
|
||||
/**
|
||||
* All the parts should match with something.
|
||||
@ -693,17 +720,6 @@ export class Search {
|
||||
return { isMatch, distance };
|
||||
}
|
||||
|
||||
_getValueList(item: SearchItem | Keyword): string[] {
|
||||
const { label, group } = item as SearchItem;
|
||||
if (group && group !== 'Docs') {
|
||||
return [label, group];
|
||||
}
|
||||
|
||||
const { values, meta } = item as Keyword;
|
||||
const schemaLabel = meta.schemaName as string;
|
||||
return [values, schemaLabel].flat();
|
||||
}
|
||||
|
||||
_getDocSearchItemFromKeyword(keyword: Keyword): DocSearchItem {
|
||||
const schemaName = keyword.meta.schemaName as string;
|
||||
const schemaLabel = this.fyo.schemaMap[schemaName]?.label!;
|
||||
@ -874,3 +890,7 @@ export class Search {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isSearchItem(item: SearchItem | Keyword): item is SearchItem {
|
||||
return !!(item as SearchItem).group;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user