Skip to content

Commit

Permalink
Fix #167 Multiple speakers, Autocomplete on backend + layout (#181)
Browse files Browse the repository at this point in the history
* Add diacritics-insensitive simple word search to Corpus query form
* Allow for more granular sort (ie. by speaker number) of lists
* Fix layout issues on Profile
* Move word search autocomplete logic to back-end to improve performance
* Add number of results
* Handle array of persons as simpleteimetadata.person field

Needs acdh-oeaw/vicav-app-api#28 to funciton
properly
  • Loading branch information
ctot-nondef authored Oct 24, 2024
2 parents 3d06291 + e1adfb4 commit 943afcf
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 124 deletions.
61 changes: 59 additions & 2 deletions components/corpus-query-window-content.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { Info } from "lucide-vue-next";
import type { CorpusSearchHits } from "@/lib/api-client";
import type { CorpusQuerySchema } from "@/types/global";
Expand All @@ -9,6 +11,10 @@ const queryString = ref(props.params.queryString);
const hits = ref<Array<CorpusSearchHits & { label?: string }> | undefined>([]);
async function searchCorpus() {
if (words.value.length > 0)
queryString.value =
'[word="' + words.value.map((w) => w.replace("*", ".*").replace("?", ".")).join("|") + '"]';
const result = await api.vicav.searchCorpus(
{ query: queryString.value },
{ headers: { Accept: "application/json" } },
Expand All @@ -29,16 +35,67 @@ const openNewWindowFromAnchor = useAnchorClickHandler();
const { data: config } = useProjectInfo();
const specialCharacters = config.value?.projectConfig?.specialCharacters;
const wordSearch = ref("");
const dataWordsQuery = useDataWords(
{ dataType: "CorpusText", query: wordSearch },
{ enabled: false },
);
watch(wordSearch, async (value) => {
if (!value || value.length < 2) return;
await dataWordsQuery.refetch();
});
const wordOptions = computed(() => {
return ((dataWordsQuery.data.value as unknown as Array<string>) ?? []).map((item: string) => {
return { label: item, value: item };
});
});
const words: Ref<Array<string>> = ref([]);
</script>

<template>
<!-- eslint-disable vue/no-v-html -->
<div class="p-2">
<form
class="block w-full rounded border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
class="block w-full rounded border border-gray-300 bg-gray-50 p-2.5 px-4 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
>
<label class="mb-2 flex !w-40 p-0 font-bold" for="word_tags">
<span class="grow">Search for words</span>
</label>
<div class="flex items-center gap-2">
<Info class="size-4" /><span class="text-gray-500"
>Enter non-accented Latin characters and select word options from the list</span
>
</div>
<TagsSelect
v-if="wordOptions"
id="word_tags"
v-model="words"
v-model:search-term="wordSearch"
:filter-function="(i) => i"
:options="wordOptions"
:placeholder="`Search for words...`"
/>

<label class="mb-2 flex !w-40 p-0 font-bold" for="word_tags">
<span class="grow">Advanced search</span>
</label>
<div class="mb-2 flex items-center gap-2">
<Info class="size-4" /><span class="text-gray-500"
>Enter exact words within a CQL query. (<a
class="content-center"
href="https://howto.acdh.oeaw.ac.at/de/resources/corpus-query-language-im-austrian-media-corpus"
target="_blank"
title="More information about CQL syntax"
><span>More info</span></a
>)
</span>
</div>
<InputExtended
v-if="specialCharacters"
id="query"
v-model="queryString"
aria-label="Search"
placeholder="Search in corpus ..."
Expand All @@ -47,7 +104,7 @@ const specialCharacters = config.value?.projectConfig?.specialCharacters;
/>
<button
class="inline-block h-10 w-full whitespace-nowrap rounded border-2 border-solid border-primary bg-on-primary text-center align-middle font-bold text-primary hover:bg-primary hover:text-on-primary disabled:border-gray-400 disabled:text-gray-400 hover:disabled:bg-on-primary hover:disabled:text-gray-400"
:disabled="queryString === ''"
:disabled="queryString === '' && words.length == 0"
@click.prevent.stop="searchCorpus"
>
Query
Expand Down
8 changes: 5 additions & 3 deletions components/corpus-text-window-content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,12 @@ onMounted(async () => {
<td>{{ teiHeader?.resp }}</td>
</tr>
<tr>
<th>Speaker:</th>
<th>Speakers:</th>
<td>
{{ teiHeader?.person.name }} (age: {{ teiHeader?.person.age }}, sex:
{{ teiHeader?.person.sex }})
<span v-for="(person, index) in teiHeader?.person" :key="index">
{{ person.name }} (age: {{ person.age }}, sex: {{ person.sex }})
<span v-if="index < (teiHeader?.person.length || 1) - 1">, </span>
</span>
</td>
</tr>
</tbody>
Expand Down
20 changes: 18 additions & 2 deletions components/data-list-window-content.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<!-- eslint-disable @typescript-eslint/sort-type-constituents -->
<script lang="ts" setup>
import type { JsonObject } from "type-fest";
import type { DataListWindowItem, DataTypesEnum } from "@/types/global.d";
import type { simpleTEIMetadata } from "@/types/teiCorpus.d";
Expand All @@ -25,7 +27,16 @@ const groupedItems = getGroupedItems(
["place.country", "place.region", "place.settlement", "dataType"],
"dataType",
props.params.dataTypes,
"label",
(a: JsonObject, b: JsonObject) => {
const amatch = Number((a as simpleTEIMetadata).label?.match(/[a-z]+(\d+)/i)?.at(1));
const bmatch = Number((b as simpleTEIMetadata).label?.match(/[a-z]+(\d+)/i)?.at(1));
if (!amatch || !bmatch)
return !a.label || !b.label ? 0 : a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
else {
return amatch < bmatch ? -1 : amatch > bmatch ? 1 : 0;
}
},
props.params.filterListBy,
) as groupedByCountry;
const openNewWindowFromAnchor = useAnchorClickHandler();
Expand All @@ -52,7 +63,12 @@ const debugString = debug ? JSON.stringify(groupedItems, null, 2) : "";
</h2>
<h2 v-else-if="Object.keys(groupedItems).length > 1" class="text-lg">Unspecified country</h2>
<div v-for="(itemsByPlace, region) in itemsByRegion" :key="region" class="p-2 text-base">
<h4 v-if="region !== ''" class="text-lg italic">{{ region }}</h4>
<h4 v-if="region !== ''" class="text-lg italic">
{{ region }}
<span v-if="Object.values(itemsByPlace).flat(2).length > 1"
>({{ Object.values(itemsByPlace).flat(2).length }})</span
>
</h4>
<h4 v-else-if="Object.keys(itemsByRegion).length > 1" class="text-lg italic">
Unspecified region
</h4>
Expand Down
20 changes: 12 additions & 8 deletions components/data-table-window-content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ const items = computed(() => {
});
const columns = ref([
columnHelper.accessor((row) => row.person.name, {
columnHelper.accessor((row) => row.person.at(0)?.name, {
id: "label",
cell: (info) => {
const identifier =
info.getValue() +
(info.row.original.person.sex ? `/${info.row.original.person.sex}` : "") +
(info.row.original.person.age ? `/${info.row.original.person.age}` : "");
(info.row.original.person.at(0)?.sex ? `/${info.row.original.person.at(0)?.sex}` : "") +
(info.row.original.person.at(0)?.age ? `/${info.row.original.person.at(0)?.age}` : "");
let linked_id: string | undefined = undefined;
let linked_type: string | undefined = undefined;
if (info.row.original.secondaryDataType === "Sample Text") {
Expand All @@ -43,7 +43,10 @@ const columns = ref([
if (linked_type) {
linked_id = simpleItems.value.find((i) => {
return i.dataType === linked_type && i.person.name === info.row.original.person.name;
return (
i.dataType === linked_type &&
i.person.at(0)?.name === info.row.original.person.at(0)?.name
);
})?.id;
}
return linked_id
Expand All @@ -61,20 +64,20 @@ const columns = ref([
header: "Name",
footer: (props) => props.column.id,
}),
columnHelper.accessor((row) => row.person.name, {
columnHelper.accessor((row) => row.person.at(0)?.name, {
id: "name",
cell: (info) => info.getValue(),
header: "Name",
footer: (props) => props.column.id,
}),
columnHelper.accessor((row) => row.person.age, {
columnHelper.accessor((row) => row.person.at(0)?.age, {
id: "age",
cell: (info) => info.getValue(),
header: "Age",
footer: (props) => props.column.id,
filterFn: "inNumberRange",
}),
columnHelper.accessor((row) => row.person.sex, {
columnHelper.accessor((row) => row.person.at(0)?.sex, {
id: "sex",
cell: (info) => info.getValue(),
header: "Sex",
Expand Down Expand Up @@ -139,8 +142,9 @@ const setFilters = function (value: ColumnFiltersState) {
<template>
<div v-if="simpleItems">
<div class="flex flex-wrap justify-between py-2">
<DataTableFilterTeiHeaders v-if="tables" :filters="columnFilters" :table="tables" />
<DataTableFilterTeiHeaders v-if="tables" :filters="columnFilters" rows="" :table="tables" />
<DataTablePagination v-if="tables" :table="tables as unknown as Table<never>" />
<div>{{ tables?.getFilteredRowModel().rows.length }} results</div>
</div>
<DataTable
:columns="columns as Array<ColumnDef<never>>"
Expand Down
105 changes: 68 additions & 37 deletions components/explore-samples-form-window-content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,10 @@ import {
TagsInputItemText,
TagsInputRoot,
} from "radix-vue";
import { transliterate as tr } from "transliteration";
import dataTypes from "@/config/dataTypes";
import type { ExploreSamplesFormWindowItem, GeoMapWindowItem, WindowItem } from "@/types/global.d";
const trOptions = {
replace: {
ᵃ: "a",
ᵉ: "e",
ⁱ: "i",
ᵒ: "o",
ᵘ: "u",
ᵊ: "e",
ʷ: "w",
ʸ: "y",
ˢ: "s",
ᶴ: "s",
q: "q",
},
};
const { findWindowByTypeAndParam } = useWindowsStore();
interface Props {
Expand Down Expand Up @@ -95,35 +78,41 @@ const uniqueFilter = function (value: unknown, index: number, array: Array<unkno
return array.indexOf(value) === index;
};
const personOptions = dataset
.map((item) => item.person.name)
.map((item) => item.person.map((i) => i.name))
.flat()
.filter(uniqueFilter)
.map((item: string) => {
return { label: item, value: item };
});
const wordSearch = ref("");
const featureLabelsQuery = useFeatureLabels();
const dataWordsQuery = useDataWords({ dataType: props.params.dataTypes[0]! });
const wordOptions = computed(() => {
return (dataWordsQuery.data.value ?? []).map((item) => {
return { label: item, value: item };
});
watch(wordSearch, async (value) => {
if (!value || value.length < 2) return;
await dataWordsQuery.refetch();
});
const wordFilter = function (list: Array<string>, searchTerm: string) {
const translitTerm = tr(searchTerm, trOptions);
const dataWordsQuery = useDataWords(
{ dataType: props.params.dataTypes[0]!, query: wordSearch },
{ enabled: false },
);
return list.filter((item) => {
return tr(item, trOptions).indexOf(translitTerm) !== -1;
const wordOptions = computed(() => {
return ((dataWordsQuery.data.value as unknown as Array<string>) ?? []).map((item) => {
return { label: item, value: item };
});
};
});
const sex = ref(["m", "f"]);
const personsFilter = computed(() =>
simpleItems.value
.filter((item) => {
if (!params.value.dataTypes.includes(item.dataType)) return false;
if (persons.value.length > 0) return persons.value.includes(item.person.name);
if (persons.value.length > 0)
return item.person.forEach((p) => persons.value.includes(p.name));
else if (places.value.length > 0) {
const found = places.value.map((place) => {
const p = place.split(":");
Expand All @@ -134,10 +123,16 @@ const personsFilter = computed(() =>
});
if (!found.includes(true)) return false;
}
if (sex.value.length > 0 && !sex.value.includes(item.person.sex)) return false;
return !(
age.value[0]! > parseInt(item.person.age) || age.value[1]! < parseInt(item.person.age)
);
if (sex.value.length > 0) {
if (
// If none of the participants are of the given sex
!item.person.map((p) => sex.value.includes(p.sex)).includes(true)
)
return false;
}
return item.person
.map((p) => age.value[0]! > parseInt(p.age) && age.value[1]! < parseInt(p.age))
.includes(true);
})
.map((item) => item.id),
);
Expand Down Expand Up @@ -227,6 +222,27 @@ const openSearchResultsWindow = function () {
}, 1000);
}
};
/**
* Open search results in new window.
*/
const openSearchResultsNewWindow = function () {
addWindow({
targetType: "ExploreSamples",
params: resultWindowParams.value,
title: `Search results for ${[words.value.join(","), places.value.join(",")].join(", ")}`,
} as WindowState)!;
addWindow({
targetType: "WMap",
params: {
title: "Search results",
queryString: "",
endpoint: "compare_markers",
queryParams: queryParams.value,
},
title: `${params.value.dataTypes[0]}s for ${[words.value.join(","), places.value.join(",")].join(", ")}`,
} as WindowState)!;
};
</script>

<template>
Expand Down Expand Up @@ -267,9 +283,9 @@ const openSearchResultsWindow = function () {
<label for="word">Word</label>

<TagsSelect
v-if="wordOptions"
v-model="words"
:filter-function="wordFilter"
v-model:search-term="wordSearch"
:filter-function="(i) => i"
:options="wordOptions"
:placeholder="`Search for words...`"
/>
Expand Down Expand Up @@ -312,7 +328,7 @@ const openSearchResultsWindow = function () {
</TagsInputRoot>
</div>

<div class="flex flex-row gap-2.5">
<div v-if="params.dataTypes.includes('Feature')" class="flex flex-row gap-2.5">
<label for="translation">Translation</label>
<input
id="translation"
Expand Down Expand Up @@ -349,7 +365,22 @@ const openSearchResultsWindow = function () {
>
Query
</button>
<br />

<button
class="inline-block h-10 w-full whitespace-nowrap rounded border-2 border-solid border-primary bg-on-primary text-center align-middle font-bold text-primary hover:bg-primary hover:text-on-primary disabled:border-gray-400 disabled:text-gray-400 hover:disabled:bg-on-primary hover:disabled:text-gray-400"
:disabled="
words.length === 0 &&
translation == '' &&
comment == '' &&
places.length === 0 &&
persons.length === 0 &&
features.length === 0 &&
sentences.length === 0
"
@click.prevent.stop="openSearchResultsNewWindow"
>
Search in new window
</button>
</form>
</div>
</template>
Expand Down
Loading

0 comments on commit 943afcf

Please sign in to comment.