feat(lite/components): rework of CollectionFilterRow to be i18n-able (#6619)

This commit is contained in:
Thierry Goettelmann
2023-03-17 09:53:05 +01:00
committed by GitHub
parent 1f6e29084f
commit 4beb49041d
6 changed files with 170 additions and 89 deletions

View File

@@ -19,74 +19,59 @@
</option>
</select>
</FormWidget>
<template v-if="hasComparisonSelect">
<FormWidget v-if="currentFilter?.type === 'string'">
<select v-model="newFilter.builder.negate">
<option :value="false">does</option>
<option :value="true">does not</option>
</select>
</FormWidget>
<FormWidget v-if="hasComparisonSelect">
<select v-model="newFilter.builder.comparison">
<option
v-for="(label, type) in comparisons"
:key="type"
:value="type"
>
{{ label }}
</option>
</select>
</FormWidget>
</template>
<FormWidget v-if="hasComparisonSelect">
<select v-model="newFilter.builder.comparison">
<option
v-for="(label, type) in comparisons"
:key="type"
:value="type"
>
{{ label }}
</option>
</select>
</FormWidget>
<FormWidget v-if="currentFilter?.type === 'enum'">
<select v-model="newFilter.builder.value">
<option v-if="!newFilter.builder.value" value="" />
<option v-for="choice in enumChoices" :key="choice" :value="choice">
{{ choice }}
</option>
</select>
</FormWidget>
<FormWidget
v-if="hasValueInput"
v-else-if="hasValueInput"
:after="valueInputAfter"
:before="valueInputBefore"
>
<input v-model="newFilter.builder.value" />
</FormWidget>
<template v-else-if="currentFilter?.type === 'enum'">
<FormWidget>
<select v-model="newFilter.builder.negate">
<option :value="false">is</option>
<option :value="true">is not</option>
</select>
</FormWidget>
<FormWidget>
<select v-model="newFilter.builder.value">
<option v-if="!newFilter.builder.value" value="" />
<option v-for="choice in enumChoices" :key="choice" :value="choice">
{{ choice }}
</option>
</select>
</FormWidget>
</template>
</template>
<UiActionButton
v-if="!newFilter.isAdvanced"
@click="enableAdvancedMode"
:icon="faPencil"
@click="enableAdvancedMode"
/>
<UiActionButton @click="emit('remove', newFilter.id)" :icon="faRemove" />
<UiActionButton :icon="faRemove" @click="emit('remove', newFilter.id)" />
</div>
</template>
<script lang="ts" setup>
import { computed, watch } from "vue";
import type {
Filter,
FilterComparisonType,
FilterComparisons,
FilterType,
Filters,
NewFilter,
} from "@/types/filter";
import { faPencil, faRemove } from "@fortawesome/free-solid-svg-icons";
import { useVModel } from "@vueuse/core";
import FormWidget from "@/components/FormWidget.vue";
import UiActionButton from "@/components/ui/UiActionButton.vue";
import { buildComplexMatcherNode } from "@/libs/complex-matcher.utils";
import { getFilterIcon } from "@/libs/utils";
import type {
Filter,
FilterComparisons,
FilterComparisonType,
Filters,
FilterType,
NewFilter,
} from "@/types/filter";
import { faPencil, faRemove } from "@fortawesome/free-solid-svg-icons";
import { useVModel } from "@vueuse/core";
import { computed, type Ref, watch } from "vue";
import { useI18n } from "vue-i18n";
const props = defineProps<{
availableFilters: Filters;
@@ -98,14 +83,16 @@ const emit = defineEmits<{
(event: "remove", filterId: number): void;
}>();
const newFilter = useVModel(props, "modelValue", emit);
const { t } = useI18n();
const newFilter: Ref<NewFilter> = useVModel(props, "modelValue", emit);
const getDefaultComparisonType = () => {
const defaultTypes: { [key in FilterType]: FilterComparisonType } = {
string: "stringContains",
boolean: "booleanTrue",
number: "numberEquals",
enum: "stringEquals",
enum: "enumIs",
};
return defaultTypes[
@@ -118,7 +105,6 @@ watch(
() => {
newFilter.value.builder.comparison = getDefaultComparisonType();
newFilter.value.builder.value = "";
newFilter.value.builder.negate = false;
}
);
@@ -133,7 +119,7 @@ const hasValueInput = computed(() =>
);
const hasComparisonSelect = computed(
() => newFilter.value.builder.property && currentFilter.value?.type !== "enum"
() => newFilter.value.builder.property !== ""
);
const enumChoices = computed(() => {
@@ -164,8 +150,7 @@ const generatedFilter = computed(() => {
const node = buildComplexMatcherNode(
newFilter.value.builder.comparison,
newFilter.value.builder.property,
newFilter.value.builder.value,
newFilter.value.builder.negate
newFilter.value.builder.value
);
if (node) {
@@ -190,15 +175,20 @@ watch(generatedFilter, (value) => {
const comparisons = computed<FilterComparisons>(() => {
const comparisonsByType = {
string: {
stringContains: "contain",
stringEquals: "equal",
stringStartsWith: "start with",
stringEndsWith: "end with",
stringMatchesRegex: "match regex",
stringContains: t("filter.comparison.contains"),
stringEquals: t("filter.comparison.equals"),
stringStartsWith: t("filter.comparison.starts-with"),
stringEndsWith: t("filter.comparison.ends-with"),
stringMatchesRegex: t("filter.comparison.matches-regex"),
stringDoesNotContain: t("filter.comparison.not-contain"),
stringDoesNotEqual: t("filter.comparison.not-equal"),
stringDoesNotStartWith: t("filter.comparison.not-start-with"),
stringDoesNotEndWith: t("filter.comparison.not-end-with"),
stringDoesNotMatchRegex: t("filter.comparison.not-match-regex"),
},
boolean: {
booleanTrue: "is true",
booleanFalse: "is false",
booleanTrue: t("filter.comparison.is-true"),
booleanFalse: t("filter.comparison.is-false"),
},
number: {
numberLessThan: "<",
@@ -207,23 +197,22 @@ const comparisons = computed<FilterComparisons>(() => {
numberGreaterThanOrEquals: ">=",
numberGreaterThan: ">",
},
enum: {},
enum: {
enumIs: t("filter.comparison.is"),
enumIsNot: t("filter.comparison.is-not"),
},
};
return comparisonsByType[currentFilter.value.type];
});
const valueInputBefore = computed(() => {
return newFilter.value.builder.comparison === "stringMatchesRegex"
? "/"
: undefined;
});
const valueInputBefore = computed(() =>
newFilter.value.builder.comparison === "stringMatchesRegex" ? "/" : undefined
);
const valueInputAfter = computed(() => {
return newFilter.value.builder.comparison === "stringMatchesRegex"
? "/i"
: undefined;
});
const valueInputAfter = computed(() =>
newFilter.value.builder.comparison === "stringMatchesRegex" ? "/i" : undefined
);
</script>
<style lang="postcss" scoped>

View File

@@ -1,7 +1,7 @@
import { escapeRegExp } from "@/libs/utils";
import type { FilterComparisonType } from "@/types/filter";
import type { ComparisonOperator, ComplexMatcherNode } from "complex-matcher";
import * as CM from "complex-matcher";
import type { FilterComparisonType } from "@/types/filter";
import { escapeRegExp } from "@/libs/utils";
function buildStringNode(property: string, value: string, negate = false) {
if (!value) {
@@ -81,20 +81,63 @@ function buildBooleanNode(property: string, value: boolean) {
export function buildComplexMatcherNode(
comparisonType: FilterComparisonType,
property: string,
value: string,
negate: boolean
value: string
): ComplexMatcherNode | undefined {
switch (comparisonType) {
case "stringContains":
return buildStringNode(property, value, negate);
case "stringDoesNotContains":
return buildStringNode(
property,
value,
comparisonType === "stringDoesNotContains"
);
case "stringStartsWith":
return buildRegexNode(property, value, "^", "", true, negate);
case "stringDoesNotStartWith":
return buildRegexNode(
property,
value,
"^",
"",
true,
comparisonType === "stringDoesNotStartWith"
);
case "stringEndsWith":
return buildRegexNode(property, value, "", "$", true, negate);
case "stringDoesNotEndWith":
return buildRegexNode(
property,
value,
"",
"$",
true,
comparisonType === "stringDoesNotEndWith"
);
case "stringEquals":
return buildRegexNode(property, value, "^", "$", true, negate);
case "stringDoesNotEqual":
case "enumIs":
case "enumIsNot":
return buildRegexNode(
property,
value,
"^",
"$",
true,
["stringDoesNotEqual", "enumIsNot"].includes(comparisonType)
);
case "stringMatchesRegex":
return buildRegexNode(property, value, "", "", false, negate);
case "stringDoesNotMatchRegex":
return buildRegexNode(
property,
value,
"",
"",
false,
comparisonType === "stringDoesNotMatchRegex"
);
case "numberLessThan":
return buildNumberComparisonNode(property, value, "<");
case "numberLessThanOrEquals":

View File

@@ -22,11 +22,30 @@
"dashboard": "Dashboard",
"delete": "Delete",
"descending": "descending",
"description": "Description",
"edit-config": "Edit config",
"error-occured": "An error has occurred",
"export": "Export",
"export-table-to": "Export table to {type}",
"export-vms": "Export VMs",
"filter": {
"comparison": {
"contains": "Contains",
"ends-with": "Ends with",
"equals": "Equals",
"is": "Is",
"is-false": "Is false",
"is-not": "Is not",
"is-true": "Is true",
"matches-regex": "Matches regex",
"not-contain": "Doesn't contain",
"not-end-with": "Doesn't end with",
"not-equal": "Doesn't equal",
"not-match-regex": "Doesn't match regex",
"not-start-with": "Doesn't start with",
"starts-with": "Starts with"
}
},
"following-hosts-unreachable": "The following hosts are unreachable",
"force-reboot": "Force reboot",
"force-shutdown": "Force shutdown",
@@ -53,6 +72,7 @@
"pause": "Pause",
"pool-cpu-usage": "Pool CPU Usage",
"pool-ram-usage": "Pool RAM Usage",
"power-state": "Power state",
"property": "Property",
"ram-usage": "RAM usage",
"reboot": "Reboot",

View File

@@ -22,11 +22,30 @@
"dashboard": "Tableau de bord",
"delete": "Supprimer",
"descending": "descendant",
"description": "Description",
"edit-config": "Modifier config",
"error-occured": "Une erreur est survenue",
"export": "Exporter",
"export-table-to": "Exporter le tableau en {type}",
"export-vms": "Exporter les VMs",
"filter": {
"comparison": {
"contains": "Contient",
"ends-with": "Termine par",
"equals": "Est égal à",
"is": "Est",
"is-false": "Est faux",
"is-not": "N'est pas",
"is-true": "Est vrai",
"matches-regex": "Correspond à la regex",
"not-contain": "Ne contient pas",
"not-end-with": "Ne termine pas par",
"not-equal": "N'est pas égal à",
"not-match-regex": "Ne correspond pas à la regex",
"not-start-with": "Ne commence pas par",
"starts-with": "Commence par"
}
},
"following-hosts-unreachable": "Les hôtes suivants sont inaccessibles",
"force-reboot": "Forcer le redémarrage",
"force-shutdown": "Forcer l'arrêt",
@@ -53,6 +72,7 @@
"pause": "Pause",
"pool-cpu-usage": "Utilisation CPU du Pool",
"pool-ram-usage": "Utilisation RAM du Pool",
"power-state": "État d'alimentation",
"property": "Propriété",
"ram-usage": "Utilisation de la RAM",
"reboot": "Redémarrer",

View File

@@ -4,17 +4,24 @@ export type FilterType = "string" | "boolean" | "number" | "enum";
export type FilterComparisonType =
| "stringContains"
| "stringDoesNotContains"
| "stringEquals"
| "stringDoesNotEqual"
| "stringStartsWith"
| "stringDoesNotStartWith"
| "stringEndsWith"
| "stringDoesNotEndWith"
| "stringMatchesRegex"
| "stringDoesNotMatchRegex"
| "numberLessThan"
| "numberLessThanOrEquals"
| "numberEquals"
| "numberGreaterThanOrEquals"
| "numberGreaterThan"
| "booleanTrue"
| "booleanFalse";
| "booleanFalse"
| "enumIs"
| "enumIsNot";
export type FilterComparisons = {
[key in FilterComparisonType]?: string;

View File

@@ -16,8 +16,8 @@
>
<template #header>
<ColumnHeader :icon="faPowerOff" />
<ColumnHeader>Name</ColumnHeader>
<ColumnHeader>Description</ColumnHeader>
<ColumnHeader>{{ $t("name") }}</ColumnHeader>
<ColumnHeader>{{ $t("description") }}</ColumnHeader>
</template>
<template #row="{ item: vm }">
<td>
@@ -43,18 +43,20 @@ import type { Filters } from "@/types/filter";
import { faPowerOff } from "@fortawesome/free-solid-svg-icons";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
const { allRecords: vms } = storeToRefs(useVmStore());
const { isMobile, isDesktop } = storeToRefs(useUiStore());
const { t } = useI18n();
const filters: Filters = {
name_label: { label: "VM Name", type: "string" },
name_description: { label: "VM Description", type: "string" },
name_label: { label: t("name"), type: "string" },
name_description: { label: t("description"), type: "string" },
power_state: {
label: "VM State",
label: t("power-state"),
icon: faPowerOff,
type: "enum",
choices: ["Running", "Halted", "Paused"],
choices: ["Running", "Halted", "Paused", "Suspended"],
},
};