From 8a9e82a87e21649bd566049dce0a19b71d82a79c Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Sat, 24 Jan 2026 19:24:26 +0100 Subject: [PATCH 1/5] Improves user QOL by enchancing riven attribute form - No strict requirement for positive/negative on riven attributes - Small UI fix for rivens multiply values --- .../CreateRivenAttribute.tsx | 86 ++++++++++++++----- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx index a3f662e8..3a3ecfb4 100644 --- a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx +++ b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx @@ -35,17 +35,61 @@ export function CreateRivenAttribute({ const useTranslateFormButtons = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`buttons.${key}`, { ...context }, i18Key); + const getExpectedSign = (isPositiveAttribute?: boolean) => { + if (!currentValue) return undefined; + if (currentValue.unit == "multiply") return undefined; + const isPositive = + typeof isPositiveAttribute == "boolean" + ? isPositiveAttribute + : positiveNumberOnly === true + ? true + : negativeNumberOnly === true + ? false + : undefined; + if (typeof isPositive != "boolean") return undefined; + const positiveIsNegative = currentValue.positiveIsNegative === true; + if (isPositive) return positiveIsNegative ? "negative" : "positive"; + return positiveIsNegative ? "positive" : "negative"; + }; + + const normalizeValue = (value: number, expectedSign: "positive" | "negative" | undefined) => { + if (!expectedSign || value == 0) return value; + if (expectedSign == "positive" && value < 0) return Math.abs(value); + if (expectedSign == "negative" && value > 0) return -Math.abs(value); + return value; + }; + + const getSignError = (value: number, expectedSign: "positive" | "negative" | undefined) => { + if (!expectedSign) return null; + if (expectedSign == "positive" && value < 0) return useTranslateFormFields("value.error.positive"); + if (expectedSign == "negative" && value > 0) return useTranslateFormFields("value.error.negative"); + return null; + }; + + const isSignMismatched = (value: number, expectedSign: "positive" | "negative" | undefined) => { + if (!expectedSign) return false; + return expectedSign == "positive" ? value < 0 : value > 0; + }; + // User form const form = useForm({ initialValues: { ...value, }, validate: { - value: (value: number) => { - if (positiveNumberOnly && value < 0) return useTranslateFormFields("value.error.positive"); - if (negativeNumberOnly && value > 0) return useTranslateFormFields("value.error.negative"); - if (currentValue?.positiveOnly && value < 0) return useTranslateFormFields("value.error.positive"); - if (currentValue?.negativeOnly && value > 0) return useTranslateFormFields("value.error.negative"); + value: (value: number, values: RivenAttribute) => { + if (currentValue?.unit == "multiply" && value < 0) return useTranslateFormFields("value.error.positive"); + const expectedSign = getExpectedSign(values.positive); + const signError = getSignError(value, expectedSign); + if (signError) return signError; + if (currentValue?.positiveOnly) { + const positiveOnlyError = getSignError(value, getExpectedSign(true)); + if (positiveOnlyError) return positiveOnlyError; + } + if (currentValue?.negativeOnly) { + const negativeOnlyError = getSignError(value, getExpectedSign(false)); + if (negativeOnlyError) return negativeOnlyError; + } return null; }, }, @@ -67,36 +111,36 @@ export function CreateRivenAttribute({ }; const GetUnitSymbol = () => { - if (currentValue?.unit == "multiply") return "+"; + if (currentValue?.unit == "multiply") return form.values.positive ? "+" : "-"; if (currentValue?.unit == "percent") return "%"; if (currentValue?.unit == "seconds") return "sec"; return undefined; }; const GetMaxValue = () => { - if ((positiveNumberOnly && form.values.value < 0) || (negativeNumberOnly && form.values.value > 0)) return undefined; - if (positiveNumberOnly && currentValue?.unit != "multiply") return 400; - if (positiveNumberOnly && currentValue?.unit == "multiply") return 4; - - if (negativeNumberOnly && currentValue?.unit == "multiply") return 1; - if (negativeNumberOnly && currentValue?.unit != "multiply") return 0; + if (!currentValue) return undefined; + if (currentValue.unit == "multiply") return form.values.positive ? 4 : 1; + const expectedSign = getExpectedSign(form.values.positive); + if (isSignMismatched(form.values.value, expectedSign)) return undefined; + if (expectedSign == "positive") return 400; + if (expectedSign == "negative") return 0; return undefined; }; const GetMinValue = () => { - if ((positiveNumberOnly && form.values.value < 0) || (negativeNumberOnly && form.values.value > 0)) return undefined; - if (positiveNumberOnly) return 0; - if (negativeNumberOnly && currentValue?.unit != "multiply") return -400; - if (negativeNumberOnly && currentValue?.unit == "multiply") return 0; + if (!currentValue) return undefined; + if (currentValue.unit == "multiply") return 0; + const expectedSign = getExpectedSign(form.values.positive); + if (isSignMismatched(form.values.value, expectedSign)) return undefined; + if (expectedSign == "positive") return 0; + if (expectedSign == "negative") return -400; return undefined; }; const ValidateValue = () => { - // TODO: Validate value based on positiveNumberOnly and negativeNumberOnly - // console.log("ValidateValue", form.values.value); - // const isNegative = negativeNumberOnly == undefined ? false : negativeNumberOnly; - // if ((positiveNumberOnly && form.values.value < 0) || ((isNegative && form.values.value > 0) != currentValue?.unit) == "multiply") - // form.setFieldValue("value", -form.values.value); + const expectedSign = getExpectedSign(form.values.positive); + const normalizedValue = normalizeValue(form.values.value, expectedSign); + if (normalizedValue != form.values.value) form.setFieldValue("value", normalizedValue); }; return ( From 07e5e9ad4f26a5d8eef81bec7b29d83946cc5324 Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Sun, 25 Jan 2026 01:58:09 +0100 Subject: [PATCH 2/5] Improves code readability, better validations - Refactors several functions for clarity - Improves form validation logic --- .../CreateRivenAttribute.tsx | 104 +++++++++--------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx index 3a3ecfb4..e31e3723 100644 --- a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx +++ b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx @@ -7,6 +7,14 @@ import { ActionWithTooltip } from "@components/Shared/ActionWithTooltip"; import { faClose } from "@fortawesome/free-solid-svg-icons"; import { TokenSearchSelect } from "@components/Forms/TokenSearchSelect"; +// warframe.market riven form limits (change if needed, or control from outside) +const RIVEN_PERCENT_ABS_MIN = 0.1; +const RIVEN_PERCENT_ABS_MAX = 699; + +const RIVEN_MULTIPLY_MIN = 0.01; +const RIVEN_MULTIPLY_MAX_POSITIVE = 2.99; +const RIVEN_MULTIPLY_MAX_NEGATIVE = 0.99; + export type CreateRivenAttributeProps = { availableAttributes: TauriTypes.CacheRivenAttribute[]; value: RivenAttribute; @@ -35,42 +43,41 @@ export function CreateRivenAttribute({ const useTranslateFormButtons = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`buttons.${key}`, { ...context }, i18Key); - const getExpectedSign = (isPositiveAttribute?: boolean) => { - if (!currentValue) return undefined; - if (currentValue.unit == "multiply") return undefined; - const isPositive = - typeof isPositiveAttribute == "boolean" - ? isPositiveAttribute - : positiveNumberOnly === true - ? true - : negativeNumberOnly === true - ? false - : undefined; - if (typeof isPositive != "boolean") return undefined; - const positiveIsNegative = currentValue.positiveIsNegative === true; - if (isPositive) return positiveIsNegative ? "negative" : "positive"; - return positiveIsNegative ? "positive" : "negative"; - }; + type ExpectedSign = "positive" | "negative" | undefined; - const normalizeValue = (value: number, expectedSign: "positive" | "negative" | undefined) => { - if (!expectedSign || value == 0) return value; - if (expectedSign == "positive" && value < 0) return Math.abs(value); - if (expectedSign == "negative" && value > 0) return -Math.abs(value); - return value; + const resolveSlotSign = (isPositiveAttribute?: boolean): ExpectedSign => { + if (typeof isPositiveAttribute == "boolean") return isPositiveAttribute ? "positive" : "negative"; + if (positiveNumberOnly === true) return "positive"; + if (negativeNumberOnly === true) return "negative"; + return undefined; }; - const getSignError = (value: number, expectedSign: "positive" | "negative" | undefined) => { - if (!expectedSign) return null; - if (expectedSign == "positive" && value < 0) return useTranslateFormFields("value.error.positive"); - if (expectedSign == "negative" && value > 0) return useTranslateFormFields("value.error.negative"); - return null; + const invertSign = (sign: ExpectedSign): ExpectedSign => + sign == "positive" ? "negative" : sign == "negative" ? "positive" : undefined; + + const getExpectedSign = (isPositiveAttribute?: boolean): ExpectedSign => { + if (!currentValue || currentValue.unit == "multiply") return undefined; + const slotSign = resolveSlotSign(isPositiveAttribute); + if (!slotSign) return undefined; + return currentValue.positiveIsNegative ? invertSign(slotSign) : slotSign; }; - const isSignMismatched = (value: number, expectedSign: "positive" | "negative" | undefined) => { + const isSignMismatched = (value: number, expectedSign: ExpectedSign) => { if (!expectedSign) return false; return expectedSign == "positive" ? value < 0 : value > 0; }; + const getSignError = (value: number, expectedSign: ExpectedSign) => { + if (!expectedSign || !isSignMismatched(value, expectedSign)) return null; + return expectedSign == "positive" ? useTranslateFormFields("value.error.positive") : useTranslateFormFields("value.error.negative"); + }; + + const normalizeValue = (value: number, expectedSign: ExpectedSign) => { + if (!expectedSign || value == 0) return value; + const absValue = Math.abs(value); + return expectedSign == "positive" ? absValue : -absValue; + }; + // User form const form = useForm({ initialValues: { @@ -110,6 +117,8 @@ export function CreateRivenAttribute({ return availableAttributes.map((item) => ({ label: item.name, value: item.url_name })); }; + const expectedSign = getExpectedSign(form.values.positive); + const GetUnitSymbol = () => { if (currentValue?.unit == "multiply") return form.values.positive ? "+" : "-"; if (currentValue?.unit == "percent") return "%"; @@ -117,30 +126,27 @@ export function CreateRivenAttribute({ return undefined; }; - const GetMaxValue = () => { - if (!currentValue) return undefined; - if (currentValue.unit == "multiply") return form.values.positive ? 4 : 1; - const expectedSign = getExpectedSign(form.values.positive); - if (isSignMismatched(form.values.value, expectedSign)) return undefined; - if (expectedSign == "positive") return 400; - if (expectedSign == "negative") return 0; - return undefined; + const getRangeForValue = (value: number) => { + if (!currentValue) return { min: undefined, max: undefined }; + if (currentValue.unit == "multiply") + return { + min: RIVEN_MULTIPLY_MIN, + max: form.values.positive ? RIVEN_MULTIPLY_MAX_POSITIVE : RIVEN_MULTIPLY_MAX_NEGATIVE, + }; + if (isSignMismatched(value, expectedSign)) return { min: undefined, max: undefined }; + if (expectedSign == "positive") return { min: RIVEN_PERCENT_ABS_MIN, max: RIVEN_PERCENT_ABS_MAX }; + if (expectedSign == "negative") return { min: -RIVEN_PERCENT_ABS_MAX, max: RIVEN_PERCENT_ABS_MIN }; + return { min: undefined, max: undefined }; }; - const GetMinValue = () => { - if (!currentValue) return undefined; - if (currentValue.unit == "multiply") return 0; - const expectedSign = getExpectedSign(form.values.positive); - if (isSignMismatched(form.values.value, expectedSign)) return undefined; - if (expectedSign == "positive") return 0; - if (expectedSign == "negative") return -400; - return undefined; - }; + const range = getRangeForValue(form.values.value); const ValidateValue = () => { - const expectedSign = getExpectedSign(form.values.positive); - const normalizedValue = normalizeValue(form.values.value, expectedSign); - if (normalizedValue != form.values.value) form.setFieldValue("value", normalizedValue); + let nextValue = normalizeValue(form.values.value, expectedSign); + const { min, max } = getRangeForValue(nextValue); + if (typeof min == "number" && nextValue < min) nextValue = min; + if (typeof max == "number" && nextValue > max) nextValue = max; + if (nextValue != form.values.value) form.setFieldValue("value", nextValue); }; return ( @@ -164,8 +170,8 @@ export function CreateRivenAttribute({ disabled={form.values.url_name == "N/A" || form.values.url_name == ""} step={currentValue?.unit == "multiply" ? 0.1 : 1} decimalScale={currentValue?.unit == "multiply" ? 2 : 1} - max={GetMaxValue()} - min={GetMinValue()} + max={range.max} + min={range.min} onBlur={() => ValidateValue()} value={form.values.value || 0} rightSection={currentValue?.unit == "multiply" ? undefined : GetUnitSymbol()} From 07c5f2e13f09dbc9ed3400478d99235d142234fd Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Sun, 25 Jan 2026 02:22:13 +0100 Subject: [PATCH 3/5] Adds better validation for Riven attributes - ensures that atribute values cannot be zero --- public/lang/en.json | 4 ++++ src/components/Forms/CreateRiven/CreateRiven.tsx | 9 ++++++++- .../Forms/CreateRivenAttribute/CreateRivenAttribute.tsx | 9 +++------ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/public/lang/en.json b/public/lang/en.json index bc734b04..8d3b794d 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -949,6 +949,10 @@ "label": "Attributes", "error": "Riven must have at least 2 positive attributes" }, + "attribute": { + "label": "Attribute", + "error": "Attribute values cannot be 0" + }, "mod_name": { "label": "Mod Name", "placeholder": "Mod Name", diff --git a/src/components/Forms/CreateRiven/CreateRiven.tsx b/src/components/Forms/CreateRiven/CreateRiven.tsx index 16712884..5ddc5de7 100644 --- a/src/components/Forms/CreateRiven/CreateRiven.tsx +++ b/src/components/Forms/CreateRiven/CreateRiven.tsx @@ -60,7 +60,14 @@ export function CreateRiven({ value, onSubmit }: CreateRivenProps) { }, polarity: value?.polarity || "madurai", }, - validate: {}, + validate: { + attributes: (value?: TauriTypes.StockRiven["attributes"]) => { + if (!value || value.length === 0) return null; + const hasZeroValue = value.some((item) => item && item.url_name && item.url_name !== "N/A" && item.value === 0); + if (hasZeroValue) return useTranslateFormFields("attribute.error"); + return null; + }, + }, }); // Effects diff --git a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx index e31e3723..96bd2c0b 100644 --- a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx +++ b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx @@ -8,10 +8,7 @@ import { faClose } from "@fortawesome/free-solid-svg-icons"; import { TokenSearchSelect } from "@components/Forms/TokenSearchSelect"; // warframe.market riven form limits (change if needed, or control from outside) -const RIVEN_PERCENT_ABS_MIN = 0.1; const RIVEN_PERCENT_ABS_MAX = 699; - -const RIVEN_MULTIPLY_MIN = 0.01; const RIVEN_MULTIPLY_MAX_POSITIVE = 2.99; const RIVEN_MULTIPLY_MAX_NEGATIVE = 0.99; @@ -130,12 +127,12 @@ export function CreateRivenAttribute({ if (!currentValue) return { min: undefined, max: undefined }; if (currentValue.unit == "multiply") return { - min: RIVEN_MULTIPLY_MIN, + min: 0, max: form.values.positive ? RIVEN_MULTIPLY_MAX_POSITIVE : RIVEN_MULTIPLY_MAX_NEGATIVE, }; if (isSignMismatched(value, expectedSign)) return { min: undefined, max: undefined }; - if (expectedSign == "positive") return { min: RIVEN_PERCENT_ABS_MIN, max: RIVEN_PERCENT_ABS_MAX }; - if (expectedSign == "negative") return { min: -RIVEN_PERCENT_ABS_MAX, max: RIVEN_PERCENT_ABS_MIN }; + if (expectedSign == "positive") return { min: 0, max: RIVEN_PERCENT_ABS_MAX }; + if (expectedSign == "negative") return { min: -RIVEN_PERCENT_ABS_MAX, max: 0 }; return { min: undefined, max: undefined }; }; From 55a9be8fa33f4eaf67dcfb59322027c5f71f65b9 Mon Sep 17 00:00:00 2001 From: Kenya-DK Date: Sun, 25 Jan 2026 22:26:19 +0100 Subject: [PATCH 4/5] Removes unused renderOption prop Removes the `renderOption` prop from the Autocomplete component in CreateRiven form. This prop was unnecessary and not being utilized, simplifying the component's configuration. --- src/components/Forms/CreateRiven/CreateRiven.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Forms/CreateRiven/CreateRiven.tsx b/src/components/Forms/CreateRiven/CreateRiven.tsx index 5ddc5de7..1f779cd8 100644 --- a/src/components/Forms/CreateRiven/CreateRiven.tsx +++ b/src/components/Forms/CreateRiven/CreateRiven.tsx @@ -165,7 +165,6 @@ export function CreateRiven({ value, onSubmit }: CreateRivenProps) { onChange={(event) => form.setFieldValue("mod_name", event || "")} error={form.errors.mod_name && useTranslateFormFields("mod_name.error")} data={modNames} - renderOption={renderSelectOption} /> From bf4c80075960d2b4a6741fbca3e3ca9e63aa17d8 Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Mon, 26 Jan 2026 22:37:47 +0100 Subject: [PATCH 5/5] Fixes app crash on weapon change in riven creation form --- src/components/Forms/CreateRiven/CreateRiven.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/Forms/CreateRiven/CreateRiven.tsx b/src/components/Forms/CreateRiven/CreateRiven.tsx index 1f779cd8..108b120f 100644 --- a/src/components/Forms/CreateRiven/CreateRiven.tsx +++ b/src/components/Forms/CreateRiven/CreateRiven.tsx @@ -70,6 +70,12 @@ export function CreateRiven({ value, onSubmit }: CreateRivenProps) { }, }); + const resetFormForWeapon = (weaponUrl: string) => { + form.reset(); + form.setFieldValue("wfm_weapon_url", weaponUrl); + setModNames([]); + }; + // Effects useEffect(() => { if (!attributes) return; @@ -140,11 +146,15 @@ export function CreateRiven({ value, onSubmit }: CreateRivenProps) { form.setFieldValue("wfm_weapon_url", item.wfm_url_name)} + onChange={(item) => { + if (item.wfm_url_name === form.values.wfm_weapon_url) return; + resetFormForWeapon(item.wfm_url_name); + }} /> {form.errors.attributes}}