emilepharand/Babilonia

View on GitHub
src/views/SearchExpressions.vue

Summary

Maintainability
Test Coverage
<template>
  <div>
    <h1>Search Ideas</h1>
    <div class="form-check">
      <input
        id="regexSearch"
        v-model="regexSearch"
        class="form-check-input"
        type="checkbox"
        value=""
      >
      <label
        class="form-check-label"
        for="regexSearch"
      >
        Regex search
      </label>
    </div>
    <table
      id="table"
      class="table table-hover"
    >
      <thead />
    </table>
  </div>
</template>

<script lang="ts" setup>
import DataTable, {type Api as DataTablesApi} from 'datatables.net-bs5';
import {ref, watch} from 'vue';
import {KnownUnknown} from '../../server/model/ideas/expression';
import {removeContext} from '../../server/utils/expressionStringUtils';
import * as Api from '../ts/api';

let dt: DataTablesApi;

const regexSearch = ref(false);

const columns = [
    {data: 'ideaId', title: 'Idea&nbsp;ID'},
    {data: 'languageName', title: 'Language'},
    {data: 'text', title: 'Text'},
    {data: 'textWithoutContext', title: 'Text&nbsp;(without&nbsp;context)'},
    {data: 'known', title: 'Known'},
];

const searchTds: HTMLTableCellElement[] = [];
let searchTr: HTMLTableRowElement;
let searchResults: Array<{ideaId: number; languageName: string; text: string; known: string}>;
let languageNames: string[];

(async () => {
    searchResults = await Api.getExpressions();
    languageNames = (await Api.getLanguages()).map(language => language.name);
    await initialize();
})();

function getSearchResults() {
    return searchResults.map(({ideaId, languageName, text, known}) => ({
        ideaId,
        languageName,
        text,
        textWithoutContext: removeContext(text).trim(),
        known,
    }));
}

async function initialize() {
    dt = new DataTable('#table', {
        data: getSearchResults(),
        columns,
        pageLength: 100,
        async initComplete() {
            const thead = document.querySelector('#table thead')!;
            searchTr = document.createElement('tr');
            thead.appendChild(searchTr);

            for (let i = 0; i < columns.length; i++) {
                searchTds.push(document.createElement('td'));
                searchTr.appendChild(searchTds[i]);
            }

            const languageSelect = createSearchSelect('searchLanguages', 1, languageNames);
            const textSearch = createSearchInput('searchText', 2);
            const textWithoutContextSearch = createSearchInput('searchTextWithoutContext', 3);
            const searchKnown = createSearchSelect('searchKnown', 4, Object.values(KnownUnknown));
            searchTds[1].appendChild(languageSelect);
            searchTds[2].appendChild(textSearch);
            searchTds[3].appendChild(textWithoutContextSearch);
            searchTds[4].appendChild(searchKnown);

            document.querySelector('.dt-search')!.remove();
        },
    });

    initOpenIdeaOnClick();
}

function initOpenIdeaOnClick() {
    dt.on('click', (e: Event) => {
        const mouseEvent = e as MouseEvent;
        const newTab = mouseEvent.ctrlKey || mouseEvent.button === 1;
        const target = e.target as HTMLTableCellElement;
        if (target instanceof HTMLTableCellElement) {
            const rowData = dt.row(target).data() as {ideaId: number};
            window.open(`/ideas/${rowData.ideaId}`, newTab ? '_blank' : '_self');
        }
    });
}

function createSearchInput(id: string, columnIndex: number) {
    const input = document.createElement('input');
    input.classList.add('form-control', 'form-control-sm');
    input.id = id;
    input.type = 'text';
    input.placeholder = 'Search';
    input.oninput = () => {
        dt.column(columnIndex).search(input.value, {regex: regexSearch.value}).draw();
    };
    return input;
}

watch(regexSearch, () => {
    searchTr.querySelectorAll('input').forEach(input => input.dispatchEvent(new Event('input')));
});

function createSearchSelect(id: string, columnIndex: number, options: string[]) {
    const select = document.createElement('select');
    select.classList.add('form-select', 'form-select-sm');
    select.id = id;
    select.innerHTML = '<option value=""></option>';
    options.forEach(option => select.appendChild(createOption(option, option)));
    select.onchange = () => dt.column(columnIndex).search(select.value, {exact: true}).draw();
    adjustSelectWidth(select, searchTds[columnIndex]);
    return select;
}

function createOption(value: string, text: string) {
    const optionElement = document.createElement('option');
    optionElement.value = value;
    optionElement.textContent = text;
    return optionElement;
}

function adjustSelectWidth(select: HTMLSelectElement, parentElement = document.body) {
    const tempOption = document.createElement('option');
    parentElement.appendChild(tempOption);
    let maxWidth = 0;

    select.querySelectorAll('option').forEach(option => {
        tempOption.textContent = option.textContent;
        const width = tempOption.offsetWidth;
        maxWidth = Math.max(maxWidth, width);
    });

    select.style.width = `${maxWidth + 40}px`;
    parentElement.removeChild(tempOption);
}
</script>

<script type="module" lang="ts">
import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css';
</script>

<style>
table.dataTable td.dt-type-numeric, table.dataTable th.dt-type-numeric {
    text-align: left;
}
table {
    width: 100%!important;
    cursor: pointer;
}
</style>