mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 14:30:29 +03:00
Add some explanatory comments
This commit is contained in:
parent
458b1c0172
commit
f9b0d8c86f
3 changed files with 56 additions and 5 deletions
|
|
@ -27,8 +27,6 @@ import { ProfileTextField } from './fields/ProfileTextField';
|
|||
import { ProfilePronouns } from './fields/ProfilePronouns';
|
||||
import { ProfileTimezone } from './fields/ProfileTimezone';
|
||||
|
||||
export type FieldContext = { busy: boolean };
|
||||
|
||||
function IdentityProviderSettings({ authMetadata }: { authMetadata: ValidatedAuthMetadata }) {
|
||||
const accountManagementActions = useAccountManagementActions();
|
||||
|
||||
|
|
@ -66,6 +64,12 @@ function IdentityProviderSettings({ authMetadata }: { authMetadata: ValidatedAut
|
|||
);
|
||||
}
|
||||
|
||||
/// Context props which are passed to every field element.
|
||||
/// Right now this is only a flag for if the profile is being saved.
|
||||
export type FieldContext = { busy: boolean };
|
||||
|
||||
/// Field editor elements for the pre-MSC4133 profile fields. This should only
|
||||
/// ever contain keys for `displayname` and `avatar_url`.
|
||||
const LEGACY_FIELD_ELEMENTS = {
|
||||
avatar_url: ProfileAvatar,
|
||||
displayname: (props: ProfileFieldElementProps<'displayname', FieldContext>) => (
|
||||
|
|
@ -73,6 +77,8 @@ const LEGACY_FIELD_ELEMENTS = {
|
|||
),
|
||||
};
|
||||
|
||||
/// Field editor elements for MSC4133 extended profile fields.
|
||||
/// These will appear in the UI in the order they are defined in this map.
|
||||
const EXTENDED_FIELD_ELEMENTS = {
|
||||
'io.fsky.nyx.pronouns': ProfilePronouns,
|
||||
'us.cloke.msc4175.tz': ProfileTimezone,
|
||||
|
|
@ -91,6 +97,7 @@ export function Profile() {
|
|||
const extendedProfileSupported = extendedProfile !== null;
|
||||
const legacyProfile = useUserProfile(userId);
|
||||
|
||||
// next-gen auth identity providers may provide profile settings if they want
|
||||
const profileEditableThroughIDP =
|
||||
authMetadata !== undefined &&
|
||||
authMetadata.account_management_actions_supported?.includes(accountManagementActions.profile);
|
||||
|
|
@ -98,8 +105,12 @@ export function Profile() {
|
|||
const [fieldElementConstructors, profileEditableThroughClient] = useMemo(() => {
|
||||
const entries = Object.entries({
|
||||
...LEGACY_FIELD_ELEMENTS,
|
||||
// don't show the MSC4133 elements if the HS doesn't support them
|
||||
...(extendedProfileSupported ? EXTENDED_FIELD_ELEMENTS : {}),
|
||||
}).filter(([key]) => profileEditsAllowed(key, capabilities, extendedProfileSupported));
|
||||
}).filter(([key]) =>
|
||||
// don't show fields if the HS blocks them with capabilities
|
||||
profileEditsAllowed(key, capabilities, extendedProfileSupported)
|
||||
);
|
||||
return [Object.fromEntries(entries), entries.length > 0];
|
||||
}, [capabilities, extendedProfileSupported]);
|
||||
|
||||
|
|
@ -107,7 +118,13 @@ export function Profile() {
|
|||
displayname: legacyProfile.displayName,
|
||||
avatar_url: legacyProfile.avatarUrl,
|
||||
});
|
||||
|
||||
// this updates the field defaults when the extended profile data is (re)loaded.
|
||||
// it has to be a layout effect to prevent flickering on saves.
|
||||
// if MSC4133 isn't supported by the HS this does nothing
|
||||
useLayoutEffect(() => {
|
||||
// `extendedProfile` includes the old dn/av fields, so
|
||||
// we don't have to add those here
|
||||
if (extendedProfile) {
|
||||
setFieldDefaults(extendedProfile);
|
||||
}
|
||||
|
|
@ -126,8 +143,13 @@ export function Profile() {
|
|||
}
|
||||
})
|
||||
);
|
||||
|
||||
// calling this will trigger the layout effect to update the defaults
|
||||
// once the profile request completes
|
||||
await refreshExtendedProfile();
|
||||
// XXX: synthesise a profile update for ourselves because Synapse is broken and won't
|
||||
|
||||
// synthesise a profile update for ourselves to update our name and avatr in the rest
|
||||
// of the UI. code copied from matrix-js-sdk
|
||||
const user = mx.getUser(userId);
|
||||
if (user) {
|
||||
user.displayName = fields.displayname;
|
||||
|
|
@ -138,6 +160,8 @@ export function Profile() {
|
|||
} else {
|
||||
await mx.setDisplayName(fields.displayname ?? '');
|
||||
await mx.setAvatarUrl(fields.avatar_url ?? '');
|
||||
// layout effect does nothing because `extendedProfile` is undefined
|
||||
// so we have to update the defaults explicitly here
|
||||
setFieldDefaults(fields);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,12 +9,23 @@ import React, {
|
|||
import { deepCompare } from 'matrix-js-sdk/lib/utils';
|
||||
import { ExtendedProfile } from '../../../../hooks/useExtendedProfile';
|
||||
|
||||
/// These types ensure the element functions are actually able to manipulate
|
||||
/// the profile fields they're mapped to. The <C> generic parameter represents
|
||||
/// extra "context" props which are passed to every element.
|
||||
|
||||
// strip the index signature from ExtendedProfile using mapped type magic.
|
||||
// keeping the index signature causes weird typechecking issues further down the line
|
||||
// plus there should never be field elements passed with keys which don't exist in ExtendedProfile.
|
||||
type ExtendedProfileKeys = keyof {
|
||||
[Property in keyof ExtendedProfile as string extends Property
|
||||
? never
|
||||
: Property]: ExtendedProfile[Property];
|
||||
};
|
||||
|
||||
// these are the props which all field elements must accept.
|
||||
// this is split into `RawProps` and `Props` so we can type `V` instead of
|
||||
// spraying `ExtendedProfile[K]` all over the place.
|
||||
// don't use this directly, use the `ProfileFieldElementProps` type instead
|
||||
type ProfileFieldElementRawProps<V, C> = {
|
||||
defaultValue: V;
|
||||
value: V;
|
||||
|
|
@ -26,6 +37,7 @@ export type ProfileFieldElementProps<
|
|||
C
|
||||
> = ProfileFieldElementRawProps<ExtendedProfile[K], C>;
|
||||
|
||||
// the map of extended profile keys to field element functions
|
||||
type ProfileFieldElements<C> = {
|
||||
[Property in ExtendedProfileKeys]?: FunctionComponent<ProfileFieldElementProps<Property, C>>;
|
||||
};
|
||||
|
|
@ -42,6 +54,12 @@ type ProfileFieldContextProps<C> = {
|
|||
context: C;
|
||||
};
|
||||
|
||||
/// This element manages the pending state of the profile field widgets.
|
||||
/// It takes the default values of each field, as well as a map associating a profile field key
|
||||
/// with an element _function_ (not a rendered element!) that will be used to edit that field.
|
||||
/// It renders the editor elements internally using React.createElement and passes the rendered
|
||||
/// elements into the child UI. This allows it to handle the pending state entirely by itself,
|
||||
/// and provides strong typechecking.
|
||||
export function ProfileFieldContext<C>({
|
||||
fieldDefaults,
|
||||
fieldElements: fieldElementConstructors,
|
||||
|
|
@ -49,11 +67,14 @@ export function ProfileFieldContext<C>({
|
|||
context,
|
||||
}: ProfileFieldContextProps<C>): ReactNode {
|
||||
const [fields, setFields] = useState<ExtendedProfile>(fieldDefaults);
|
||||
|
||||
|
||||
// this callback also runs when fieldDefaults changes,
|
||||
// which happens when the profile is saved and the pending fields become the new defaults
|
||||
const reset = useCallback(() => {
|
||||
setFields(fieldDefaults);
|
||||
}, [fieldDefaults]);
|
||||
|
||||
// set the pending values to the defaults on the first render
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [reset]);
|
||||
|
|
@ -72,6 +93,7 @@ export function ProfileFieldContext<C>({
|
|||
() =>
|
||||
Object.entries(fields).find(
|
||||
([key, value]) =>
|
||||
// deep comparison is necessary here because field values can be any JSON type
|
||||
deepCompare(fieldDefaults[key as keyof ExtendedProfile], value)
|
||||
) !== undefined,
|
||||
[fields, fieldDefaults]
|
||||
|
|
@ -86,6 +108,8 @@ export function ProfileFieldContext<C>({
|
|||
setValue: (value) => setField(key, value),
|
||||
key,
|
||||
};
|
||||
// element can be undefined if the field defaults didn't include its key,
|
||||
// which means the HS doesn't support setting that field
|
||||
if (element !== undefined) {
|
||||
return React.createElement(element, props);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export function useExtendedProfileSupported(): boolean {
|
|||
return unstableFeatures?.['uk.tcpip.msc4133'] || versions.includes('v1.15');
|
||||
}
|
||||
|
||||
/// Returns the user's MSC4133 extended profile, if our homeserver supports it.
|
||||
/// This will return `undefined` while the request is in flight and `null` if the HS lacks support.
|
||||
export function useExtendedProfile(
|
||||
userId: string
|
||||
): [ExtendedProfile | undefined | null, () => Promise<void>] {
|
||||
|
|
@ -54,6 +56,7 @@ export function useExtendedProfile(
|
|||
|
||||
const LEGACY_FIELDS = ['displayname', 'avatar_url'];
|
||||
|
||||
/// Returns whether the given profile field may be edited by the user.
|
||||
export function profileEditsAllowed(
|
||||
field: string,
|
||||
capabilities: Capabilities,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue