From ea0a1c190b55487cd636c45ee58d293a6dc9383d Mon Sep 17 00:00:00 2001 From: Shea Date: Tue, 23 Jun 2026 05:19:40 +0300 Subject: [PATCH] allow arbitrary animal identities Signed-off-by: Shea --- .changeset/feat_add_allow_arbitrary_animal.md | 5 + .../user-profile/UserRoomProfile.tsx | 11 +- .../settings/account/AnimalCosmetics.tsx | 172 +++++++++++++++--- src/app/features/settings/settingsLink.ts | 5 +- src/app/hooks/useUserProfile.ts | 21 ++- src/unstable/prefixes/sable/profile.ts | 4 + 6 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 .changeset/feat_add_allow_arbitrary_animal.md diff --git a/.changeset/feat_add_allow_arbitrary_animal.md b/.changeset/feat_add_allow_arbitrary_animal.md new file mode 100644 index 000000000..911a9d276 --- /dev/null +++ b/.changeset/feat_add_allow_arbitrary_animal.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Change animal identity to allow arbitrary animals diff --git a/src/app/components/user-profile/UserRoomProfile.tsx b/src/app/components/user-profile/UserRoomProfile.tsx index 951e4f167..31903dc52 100644 --- a/src/app/components/user-profile/UserRoomProfile.tsx +++ b/src/app/components/user-profile/UserRoomProfile.tsx @@ -107,14 +107,17 @@ function UserExtendedSection({ const [renderAnimals] = useSetting(settingsAtom, 'renderAnimals'); const isCat = profile.isCat === true; const hasCats = profile.hasCats === true; + const isAnimal = profile.isAnimal ?? (isCat && 'cat'); + const hasAnimal = profile.hasAnimal ?? (hasCats && 'cats'); + const animalNeed = profile.animalNeed ?? 'headpats'; const catStatusText = useMemo(() => { if (!renderAnimals) return null; - if (isCat && hasCats) return 'Cat with cats—needs pets & love!'; - if (isCat) return 'Is a cat—give pets & love!'; - if (hasCats) return 'Has cats—send love!'; + if (isAnimal && hasAnimal) return `${isAnimal} with ${hasAnimal}, give ${animalNeed}!`; + if (isAnimal) return `Is ${isAnimal}, give ${animalNeed}!`; + if (hasAnimal) return `Has ${hasAnimal}, give ${animalNeed}!`; return null; - }, [renderAnimals, isCat, hasCats]); + }, [renderAnimals, isAnimal, hasAnimal, animalNeed]); const languageFilterEnabled = getSettings().filterPronounsBasedOnLanguage ?? false; const languagesToFilterFor = getSettings().filterPronounsLanguages ?? ['en']; diff --git a/src/app/features/settings/account/AnimalCosmetics.tsx b/src/app/features/settings/account/AnimalCosmetics.tsx index 6768de55a..93a0c58ee 100644 --- a/src/app/features/settings/account/AnimalCosmetics.tsx +++ b/src/app/features/settings/account/AnimalCosmetics.tsx @@ -5,11 +5,67 @@ import type { UserProfile } from '$hooks/useUserProfile'; import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { profilesCacheAtom } from '$state/userRoomProfile'; -import { Box, Switch, Text } from 'folds'; +import { Box, IconButton, Input, Switch, Text } from 'folds'; import { useSetAtom } from 'jotai'; -import { useCallback } from 'react'; +import { useCallback, useEffect, useState, type ChangeEvent } from 'react'; import { SequenceCardStyle } from '../styles.css'; import * as prefix from '$unstable/prefixes'; +import { menuIcon, X } from '$components/icons/phosphor'; + +type inputProps = { + onSave: (newValue: unknown) => void; + onReset?: () => void; + initialValue?: string; + disabled?: boolean; + placeholder?: string; +}; + +function FreeInput({ initialValue: current, onSave, onReset, disabled, placeholder }: inputProps) { + const [val, setVal] = useState(current); + + useEffect(() => setVal(current), [current]); + + const handleSave = () => { + if (val === current) return; + onSave(val); + }; + + const handleChange = (e: ChangeEvent) => { + setVal(e.currentTarget.value); + }; + + return ( + <> + e.key === 'Enter' && handleSave()} + style={{ width: '232px' }} + /> + + {onReset && ( + + {menuIcon(X)} + + )} + + + ); +} type AnimalCosmeticsProps = { profile: UserProfile; @@ -20,15 +76,12 @@ export function AnimalCosmetics({ profile, userId }: Readonly { + async (key: string, value: unknown) => { await mx.setExtendedProfileProperty?.(key, value); setGlobalProfiles((prev) => { const newCache = { ...prev }; @@ -38,6 +91,34 @@ export function AnimalCosmetics({ profile, userId }: Readonly { + const asyncClean = async () => { + const isCat = profile.isCat; + if (typeof isCat === 'boolean') { + await handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_CAT_PROPERTY_NAME, + null + ); + await handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_ANIMAL_PROPERTY_NAME, + 'cat' + ); + } + const hasCats = profile.hasCats; + if (typeof hasCats === 'boolean') { + await handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_CAT_PROPERTY_NAME, + null + ); + await handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_ANIMAL_PROPERTY_NAME, + 'cats' + ); + } + }; + asyncClean(); + }, [handleSaveField, profile.hasCats, profile.isCat]); return ( @@ -46,44 +127,81 @@ export function AnimalCosmetics({ profile, userId }: Readonly} /> + handleSaveField( - prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_CAT_PROPERTY_NAME, - !isCat + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_ANIMAL_PROPERTY_NAME, + newValue ) } + onReset={() => + handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_ANIMAL_PROPERTY_NAME, + null + ) + } + placeholder="bunny..." /> } /> + + handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_ANIMAL_PROPERTY_NAME, + newValue + ) + } + onReset={() => + handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_ANIMAL_PROPERTY_NAME, + null + ) + } + placeholder="sables..." + /> + } + /> + + + + handleSaveField( + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_ANIMAL_NEED_PROPERTY_NAME, + newValue + ) + } + onReset={() => handleSaveField( - prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_CAT_PROPERTY_NAME, - !hasCats + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_ANIMAL_NEED_PROPERTY_NAME, + null ) } + placeholder="hugs..." /> } /> diff --git a/src/app/features/settings/settingsLink.ts b/src/app/features/settings/settingsLink.ts index c45e8ff1e..962c522ec 100644 --- a/src/app/features/settings/settingsLink.ts +++ b/src/app/features/settings/settingsLink.ts @@ -71,8 +71,9 @@ const settingsLinkFocusIdsBySection: Record; isCat?: boolean; hasCats?: boolean; + isAnimal?: string; + hasAnimal?: string; + animalNeed?: string; extended?: Record; _fetched?: boolean; }; @@ -61,6 +64,9 @@ const normalizeInfo = (info: Record): UserProfile => { prefix.MATRIX_COMMET_UNSTABLE_PROFILE_COLOR_SCHEME_PROPERTY_NAME, prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_CAT_PROPERTY_NAME, prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_CAT_PROPERTY_NAME, + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_ANIMAL_PROPERTY_NAME, + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_ANIMAL_PROPERTY_NAME, + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_ANIMAL_NEED_PROPERTY_NAME, ]); const extended: Record = {}; @@ -94,8 +100,19 @@ const normalizeInfo = (info: Record): UserProfile => { heroColorScheme: info[prefix.MATRIX_COMMET_UNSTABLE_PROFILE_COLOR_SCHEME_PROPERTY_NAME] as | Record | undefined, - isCat: info[prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_CAT_PROPERTY_NAME] === true, - hasCats: info[prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_CAT_PROPERTY_NAME] === true, + isCat: info[prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_CAT_PROPERTY_NAME] as + | boolean + | undefined, + hasCats: info[prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_CAT_PROPERTY_NAME] as + | boolean + | undefined, + isAnimal: info[prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_ANIMAL_PROPERTY_NAME] as string, + hasAnimal: info[ + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_ANIMAL_PROPERTY_NAME + ] as string, + animalNeed: info[ + prefix.MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_ANIMAL_NEED_PROPERTY_NAME + ] as string, extended, _fetched: true, }; diff --git a/src/unstable/prefixes/sable/profile.ts b/src/unstable/prefixes/sable/profile.ts index 2b6b20afd..103261bcc 100644 --- a/src/unstable/prefixes/sable/profile.ts +++ b/src/unstable/prefixes/sable/profile.ts @@ -12,3 +12,7 @@ export const MATRIX_SABLE_UNSTABLE_NAME_COLOR_LIGHT_PROPERTY_NAME = 'moe.sable.app.name_color_light_theme'; export const MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_CAT_PROPERTY_NAME = 'kitty.meow.is_cat'; export const MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_CAT_PROPERTY_NAME = 'kitty.meow.has_cats'; + +export const MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_IS_ANIMAL_PROPERTY_NAME = 'pet.plz.me'; +export const MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_HAS_ANIMAL_PROPERTY_NAME = 'pet.plz.my'; +export const MATRIX_SABLE_UNSTABLE_ANIMAL_IDENTITY_ANIMAL_NEED_PROPERTY_NAME = 'pet.plz.gib';