mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-04-08 05:55:35 -04:00
feat: Unit standardization / conversion (#7121)
This commit is contained in:
@@ -88,6 +88,25 @@
|
||||
validate-on="input"
|
||||
/>
|
||||
|
||||
<!-- Number Input -->
|
||||
<v-number-input
|
||||
v-else-if="inputField.type === fieldTypes.NUMBER"
|
||||
v-model="model[inputField.varName]"
|
||||
variant="underlined"
|
||||
:control-variant="inputField.numberInputConfig?.controlVariant"
|
||||
density="comfortable"
|
||||
:label="inputField.label"
|
||||
:name="inputField.varName"
|
||||
:min="inputField.numberInputConfig?.min"
|
||||
:max="inputField.numberInputConfig?.max"
|
||||
:precision="inputField.numberInputConfig?.precision"
|
||||
:hint="inputField.hint"
|
||||
:hide-details="!inputField.hint"
|
||||
:persistent-hint="!!inputField.hint"
|
||||
:rules="!(inputField.disableUpdate && updateMode) ? inputField.rules || [] : []"
|
||||
validate-on="input"
|
||||
/>
|
||||
|
||||
<!-- Option Select -->
|
||||
<v-select
|
||||
v-else-if="inputField.type === fieldTypes.SELECT"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const fieldTypes = {
|
||||
TEXT: "text",
|
||||
TEXT_AREA: "textarea",
|
||||
NUMBER: "number",
|
||||
SELECT: "select",
|
||||
BOOLEAN: "boolean",
|
||||
PASSWORD: "password",
|
||||
|
||||
@@ -1134,7 +1134,22 @@
|
||||
"example-unit-singular": "ex: Tablespoon",
|
||||
"example-unit-plural": "ex: Tablespoons",
|
||||
"example-unit-abbreviation-singular": "ex: Tbsp",
|
||||
"example-unit-abbreviation-plural": "ex: Tbsps"
|
||||
"example-unit-abbreviation-plural": "ex: Tbsps",
|
||||
"standardization": "Standardization",
|
||||
"standardization-description": "How this unit can be represented as a standard unit. This enables unit conversion features such as merging compatible units in shopping lists.",
|
||||
"standard-unit": "Standard Unit",
|
||||
"standard-quantity": "Standard Quantity",
|
||||
"unit-conversion": "Unit Conversion",
|
||||
"standard-unit-labels": {
|
||||
"fluid-ounce": "fluid ounce",
|
||||
"cup": "cup",
|
||||
"ounce": "ounce",
|
||||
"pound": "pound",
|
||||
"milliliter": "milliliter",
|
||||
"liter": "liter",
|
||||
"gram": "gram",
|
||||
"kilogram": "kilogram"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Seed the database with common labels based on your local language.",
|
||||
|
||||
@@ -329,6 +329,8 @@ export interface IngredientUnit {
|
||||
pluralAbbreviation?: string | null;
|
||||
useAbbreviation?: boolean;
|
||||
aliases?: IngredientUnitAlias[];
|
||||
standardQuantity?: number | null;
|
||||
standardUnit?: string | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
@@ -348,6 +350,8 @@ export interface CreateIngredientUnit {
|
||||
pluralAbbreviation?: string | null;
|
||||
useAbbreviation?: boolean;
|
||||
aliases?: CreateIngredientUnitAlias[];
|
||||
standardQuantity?: number | null;
|
||||
standardUnit?: string | null;
|
||||
}
|
||||
export interface CreateIngredientUnitAlias {
|
||||
name: string;
|
||||
|
||||
@@ -58,3 +58,13 @@ export interface QueryFilterJSONPart {
|
||||
relationalOperator?: RelationalKeyword | RelationalOperator | null;
|
||||
value?: string | string[] | null;
|
||||
}
|
||||
|
||||
export type StandardizedUnitType
|
||||
= | "fluid_ounce"
|
||||
| "cup"
|
||||
| "ounce"
|
||||
| "pound"
|
||||
| "milliliter"
|
||||
| "liter"
|
||||
| "gram"
|
||||
| "kilogram";
|
||||
|
||||
@@ -85,6 +85,8 @@ export interface CreateIngredientUnit {
|
||||
pluralAbbreviation?: string | null;
|
||||
useAbbreviation?: boolean;
|
||||
aliases?: CreateIngredientUnitAlias[];
|
||||
standardQuantity?: number | null;
|
||||
standardUnit?: string | null;
|
||||
}
|
||||
export interface CreateIngredientUnitAlias {
|
||||
name: string;
|
||||
@@ -174,6 +176,8 @@ export interface IngredientUnit {
|
||||
pluralAbbreviation?: string | null;
|
||||
useAbbreviation?: boolean;
|
||||
aliases?: IngredientUnitAlias[];
|
||||
standardQuantity?: number | null;
|
||||
standardUnit?: string | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
@@ -498,6 +502,8 @@ export interface SaveIngredientUnit {
|
||||
pluralAbbreviation?: string | null;
|
||||
useAbbreviation?: boolean;
|
||||
aliases?: CreateIngredientUnitAlias[];
|
||||
standardQuantity?: number | null;
|
||||
standardUnit?: string | null;
|
||||
groupId: string;
|
||||
}
|
||||
export interface ScrapeRecipe {
|
||||
|
||||
@@ -158,6 +158,7 @@ import RecipeDataAliasManagerDialog from "~/components/Domain/Recipe/RecipeDataA
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import type { CreateIngredientUnit, IngredientUnit, IngredientUnitAlias } from "~/lib/api/types/recipe";
|
||||
import type { StandardizedUnitType } from "~/lib/api/types/non-generated";
|
||||
import { useLocales } from "~/composables/use-locales";
|
||||
import { normalizeFilter } from "~/composables/use-utils";
|
||||
import { useUnitStore } from "~/composables/store";
|
||||
@@ -219,6 +220,16 @@ const tableHeaders: TableHeaders[] = [
|
||||
show: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-quantity"),
|
||||
value: "standardQuantity",
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit"),
|
||||
value: "standardUnit",
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
text: i18n.t("general.date-added"),
|
||||
value: "createdAt",
|
||||
@@ -231,7 +242,12 @@ const { store: unitStore, actions: unitActions } = useUnitStore();
|
||||
|
||||
// ============================================================
|
||||
// Form items (shared)
|
||||
const formItems: AutoFormItems = [
|
||||
type StandardizedUnitTypeOption = {
|
||||
text: string;
|
||||
value: StandardizedUnitType;
|
||||
};
|
||||
|
||||
const formItems = computed<AutoFormItems>(() => [
|
||||
{
|
||||
cols: 8,
|
||||
label: i18n.t("general.name"),
|
||||
@@ -262,6 +278,59 @@ const formItems: AutoFormItems = [
|
||||
varName: "description",
|
||||
type: fieldTypes.TEXT,
|
||||
},
|
||||
{
|
||||
section: i18n.t("data-pages.units.standardization"),
|
||||
sectionDetails: i18n.t("data-pages.units.standardization-description"),
|
||||
cols: 2,
|
||||
varName: "standardQuantity",
|
||||
type: fieldTypes.NUMBER,
|
||||
numberInputConfig: {
|
||||
min: 0,
|
||||
max: undefined,
|
||||
precision: undefined,
|
||||
controlVariant: "hidden",
|
||||
},
|
||||
},
|
||||
{
|
||||
cols: 10,
|
||||
varName: "standardUnit",
|
||||
type: fieldTypes.SELECT,
|
||||
selectReturnValue: "value",
|
||||
options: [
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.fluid-ounce"),
|
||||
value: "fluid_ounce",
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.cup"),
|
||||
value: "cup",
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.ounce"),
|
||||
value: "ounce",
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.pound"),
|
||||
value: "pound",
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.milliliter"),
|
||||
value: "milliliter",
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.liter"),
|
||||
value: "liter",
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.gram"),
|
||||
value: "gram",
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.units.standard-unit-labels.kilogram"),
|
||||
value: "kilogram",
|
||||
},
|
||||
] as StandardizedUnitTypeOption[],
|
||||
},
|
||||
{
|
||||
section: i18n.t("general.settings"),
|
||||
cols: 4,
|
||||
@@ -275,7 +344,7 @@ const formItems: AutoFormItems = [
|
||||
varName: "fraction",
|
||||
type: fieldTypes.BOOLEAN,
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
// ============================================================
|
||||
// Create
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import type { VForm as VuetifyForm } from "vuetify/components/VForm";
|
||||
|
||||
type FormFieldType = "text" | "textarea" | "list" | "select" | "object" | "boolean" | "color" | "password";
|
||||
type FormFieldType
|
||||
= | "text"
|
||||
| "textarea"
|
||||
| "number"
|
||||
| "list"
|
||||
| "select"
|
||||
| "object"
|
||||
| "boolean"
|
||||
| "color"
|
||||
| "password";
|
||||
|
||||
export type FormValidationRule = (value: any) => boolean | string;
|
||||
|
||||
@@ -9,6 +18,13 @@ export interface FormSelectOption {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface FormFieldNumberInputConfig {
|
||||
min?: number;
|
||||
max?: number;
|
||||
precision?: number;
|
||||
controlVariant?: "split" | "default" | "hidden" | "stacked";
|
||||
}
|
||||
|
||||
export interface FormField {
|
||||
section?: string;
|
||||
sectionDetails?: string;
|
||||
@@ -20,6 +36,7 @@ export interface FormField {
|
||||
rules?: FormValidationRule[];
|
||||
disableUpdate?: boolean;
|
||||
disableCreate?: boolean;
|
||||
numberInputConfig?: FormFieldNumberInputConfig;
|
||||
options?: FormSelectOption[];
|
||||
selectReturnValue?: "text" | "value";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user