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..108b120f 100644 --- a/src/components/Forms/CreateRiven/CreateRiven.tsx +++ b/src/components/Forms/CreateRiven/CreateRiven.tsx @@ -60,9 +60,22 @@ 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; + }, + }, }); + const resetFormForWeapon = (weaponUrl: string) => { + form.reset(); + form.setFieldValue("wfm_weapon_url", weaponUrl); + setModNames([]); + }; + // Effects useEffect(() => { if (!attributes) return; @@ -133,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}} form.setFieldValue("mod_name", event || "")} error={form.errors.mod_name && useTranslateFormFields("mod_name.error")} data={modNames} - renderOption={renderSelectOption} /> diff --git a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx index a3f662e8..96bd2c0b 100644 --- a/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx +++ b/src/components/Forms/CreateRivenAttribute/CreateRivenAttribute.tsx @@ -7,6 +7,11 @@ 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_MAX = 699; +const RIVEN_MULTIPLY_MAX_POSITIVE = 2.99; +const RIVEN_MULTIPLY_MAX_NEGATIVE = 0.99; + export type CreateRivenAttributeProps = { availableAttributes: TauriTypes.CacheRivenAttribute[]; value: RivenAttribute; @@ -35,17 +40,60 @@ export function CreateRivenAttribute({ const useTranslateFormButtons = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`buttons.${key}`, { ...context }, i18Key); + type ExpectedSign = "positive" | "negative" | undefined; + + 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 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: 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: { ...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; }, }, @@ -66,37 +114,36 @@ 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 "+"; + 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; - return undefined; + const getRangeForValue = (value: number) => { + if (!currentValue) return { min: undefined, max: undefined }; + if (currentValue.unit == "multiply") + return { + 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: 0, max: RIVEN_PERCENT_ABS_MAX }; + if (expectedSign == "negative") return { min: -RIVEN_PERCENT_ABS_MAX, max: 0 }; + return { min: undefined, max: 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; - return undefined; - }; + const range = getRangeForValue(form.values.value); 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); + 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 ( @@ -120,8 +167,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()}