Skip to content

Commit

Permalink
Chore: Convert MemoizedSetting, Setting, Section (RocketChat#25572)
Browse files Browse the repository at this point in the history
<!-- This is a pull request template, you do not need to uncomment or remove the comments, they won't show up in the PR text. -->

<!-- Your Pull Request name should start with one of the following tags
  [NEW] For new features
  [IMPROVE] For an improvement (performance or little improvements) in existing features
  [FIX] For bug fixes that affect the end-user
  [BREAK] For pull requests including breaking changes
  Chore: For small tasks
  Doc: For documentation
-->

<!-- Checklist!!! If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. 
  - I have read the Contributing Guide - https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat doc
  - I have signed the CLA - https://cla-assistant.io/RocketChat/Rocket.Chat
  - Lint and unit tests pass locally with my changes
  - I have added tests that prove my fix is effective or that my feature works (if applicable)
  - I have added necessary documentation (if applicable)
  - Any dependent changes have been merged and published in downstream modules
-->

## Proposed changes (including videos or screenshots)
<!-- CHANGELOG -->
<!--
  Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
  If it fixes a bug or resolves a feature request, be sure to link to that issue below.
  This description will appear in the release notes if we accept the contribution.
-->

<!-- END CHANGELOG -->

## Issue(s)
<!-- Link the issues being closed by or related to this PR. For example, you can use #594 if this PR closes issue number 594 -->

## Steps to test or reproduce
<!-- Mention how you would reproduce the bug if not mentioned on the issue page already. Also mention which screens are going to have the changes if applicable -->

## Further comments
<!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... -->


Co-authored-by: Tasso Evangelista <[email protected]>
  • Loading branch information
juliajforesti and tassoevan authored Jun 10, 2022
1 parent e3d184b commit e01c8ea
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 82 deletions.
4 changes: 2 additions & 2 deletions apps/meteor/client/components/Sidebar/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Box, ActionButton } from '@rocket.chat/fuselage';
import React, { FC, ReactElement } from 'react';
import React, { FC, ReactNode } from 'react';

type HeaderProps = {
title?: ReactElement | string;
title?: ReactNode;
onClose?: () => void;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TranslationContext } from '@rocket.chat/ui-contexts';
import { TranslationContext, TranslationKey } from '@rocket.chat/ui-contexts';
import i18next from 'i18next';
import React, { ContextType, ReactElement, ReactNode, useContext, useMemo } from 'react';

Expand Down Expand Up @@ -41,7 +41,7 @@ const TranslationContextMock = ({ children }: TranslationContextMockProps): Reac
});
};

translate.has = (key: string): boolean => !!key && i18next.exists(key);
translate.has = (key: string | undefined): key is TranslationKey => !!key && i18next.exists(key);

return {
...parent,
Expand Down
20 changes: 10 additions & 10 deletions apps/meteor/client/views/admin/EditableSettingsContext.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { ISettingBase, SectionName, SettingId, GroupId, TabId } from '@rocket.chat/core-typings';
import { ISettingBase, SectionName, SettingId, GroupId, TabId, ISettingColor } from '@rocket.chat/core-typings';
import { SettingsContextQuery } from '@rocket.chat/ui-contexts';
import { createContext, useContext, useMemo } from 'react';
import { useSubscription, Subscription, Unsubscribe } from 'use-subscription';

export interface IEditableSetting extends ISettingBase {
export type EditableSetting = (ISettingBase | ISettingColor) & {
disabled: boolean;
changed: boolean;
invisible: boolean;
}
};

export type EditableSettingsContextQuery = SettingsContextQuery & {
changed?: boolean;
};

export type EditableSettingsContextValue = {
readonly queryEditableSetting: (_id: SettingId) => Subscription<IEditableSetting | undefined>;
readonly queryEditableSettings: (query: EditableSettingsContextQuery) => Subscription<IEditableSetting[]>;
readonly queryEditableSetting: (_id: SettingId) => Subscription<EditableSetting | undefined>;
readonly queryEditableSettings: (query: EditableSettingsContextQuery) => Subscription<EditableSetting[]>;
readonly queryGroupSections: (_id: GroupId, tab?: TabId) => Subscription<SectionName[]>;
readonly queryGroupTabs: (_id: GroupId) => Subscription<TabId[]>;
readonly dispatch: (changes: Partial<IEditableSetting>[]) => void;
readonly dispatch: (changes: Partial<EditableSetting>[]) => void;
};

export const EditableSettingsContext = createContext<EditableSettingsContextValue>({
Expand All @@ -27,7 +27,7 @@ export const EditableSettingsContext = createContext<EditableSettingsContextValu
subscribe: (): Unsubscribe => (): void => undefined,
}),
queryEditableSettings: () => ({
getCurrentValue: (): IEditableSetting[] => [],
getCurrentValue: (): EditableSetting[] => [],
subscribe: (): Unsubscribe => (): void => undefined,
}),
queryGroupSections: () => ({
Expand All @@ -41,14 +41,14 @@ export const EditableSettingsContext = createContext<EditableSettingsContextValu
dispatch: () => undefined,
});

export const useEditableSetting = (_id: SettingId): IEditableSetting | undefined => {
export const useEditableSetting = (_id: SettingId): EditableSetting | undefined => {
const { queryEditableSetting } = useContext(EditableSettingsContext);

const subscription = useMemo(() => queryEditableSetting(_id), [queryEditableSetting, _id]);
return useSubscription(subscription);
};

export const useEditableSettings = (query?: EditableSettingsContextQuery): IEditableSetting[] => {
export const useEditableSettings = (query?: EditableSettingsContextQuery): EditableSetting[] => {
const { queryEditableSettings } = useContext(EditableSettingsContext);
const subscription = useMemo(() => queryEditableSettings(query ?? {}), [queryEditableSettings, query]);
return useSubscription(subscription);
Expand All @@ -68,5 +68,5 @@ export const useEditableSettingsGroupTabs = (_id: SettingId): TabId[] => {
return useSubscription(subscription);
};

export const useEditableSettingsDispatch = (): ((changes: Partial<IEditableSetting>[]) => void) =>
export const useEditableSettingsDispatch = (): ((changes: Partial<EditableSetting>[]) => void) =>
useContext(EditableSettingsContext).dispatch;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FilterQuery } from 'mongodb';
import React, { useEffect, useMemo, FunctionComponent, useRef, MutableRefObject } from 'react';

import { createReactiveSubscriptionFactory } from '../../../providers/createReactiveSubscriptionFactory';
import { EditableSettingsContext, IEditableSetting, EditableSettingsContextValue } from '../EditableSettingsContext';
import { EditableSettingsContext, EditableSetting, EditableSettingsContextValue } from '../EditableSettingsContext';

const defaultQuery: SettingsContextQuery = {};

Expand All @@ -16,7 +16,7 @@ type EditableSettingsProviderProps = {
};

const EditableSettingsProvider: FunctionComponent<EditableSettingsProviderProps> = ({ children, query = defaultQuery }) => {
const settingsCollectionRef = useRef<Mongo.Collection<IEditableSetting>>(null) as MutableRefObject<Mongo.Collection<IEditableSetting>>;
const settingsCollectionRef = useRef<Mongo.Collection<EditableSetting>>(null) as MutableRefObject<Mongo.Collection<EditableSetting>>;
const persistedSettings = useSettings(query);

const getSettingsCollection = useMutableCallback(() => {
Expand All @@ -25,7 +25,7 @@ const EditableSettingsProvider: FunctionComponent<EditableSettingsProviderProps>
}

return settingsCollectionRef.current;
}) as () => Mongo.Collection<IEditableSetting>;
}) as () => Mongo.Collection<EditableSetting>;

useEffect(() => {
const settingsCollection = getSettingsCollection();
Expand All @@ -39,7 +39,7 @@ const EditableSettingsProvider: FunctionComponent<EditableSettingsProviderProps>
const queryEditableSetting = useMemo(() => {
const validateSettingQueries = (
query: undefined | string | FilterQuery<ISetting> | FilterQuery<ISetting>[],
settingsCollection: Mongo.Collection<IEditableSetting>,
settingsCollection: Mongo.Collection<EditableSetting>,
): boolean => {
if (!query) {
return true;
Expand All @@ -49,7 +49,7 @@ const EditableSettingsProvider: FunctionComponent<EditableSettingsProviderProps>
return queries.every((query) => settingsCollection.find(query).count() > 0);
};

return createReactiveSubscriptionFactory((_id: SettingId): IEditableSetting | undefined => {
return createReactiveSubscriptionFactory((_id: SettingId): EditableSetting | undefined => {
const settingsCollection = getSettingsCollection();
const editableSetting = settingsCollection.findOne(_id);

Expand Down Expand Up @@ -169,7 +169,7 @@ const EditableSettingsProvider: FunctionComponent<EditableSettingsProviderProps>
[getSettingsCollection],
);

const dispatch = useMutableCallback((changes: Partial<IEditableSetting>[]): void => {
const dispatch = useMutableCallback((changes: Partial<EditableSetting>[]): void => {
for (const { _id, ...data } of changes) {
if (!_id) {
continue;
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/views/admin/settings/GroupPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import React, { useMemo, memo, FC, ReactNode, FormEvent, MouseEvent } from 'react';

import Page from '../../../components/Page';
import { useEditableSettingsDispatch, useEditableSettings, IEditableSetting } from '../EditableSettingsContext';
import { useEditableSettingsDispatch, useEditableSettings, EditableSetting } from '../EditableSettingsContext';
import GroupPageSkeleton from './GroupPageSkeleton';

type GroupPageProps = {
Expand Down Expand Up @@ -129,7 +129,7 @@ const GroupPage: FC<GroupPageProps> = ({
};
})
.filter(Boolean);
dispatchToEditing(settingsToDispatch as Partial<IEditableSetting>[]);
dispatchToEditing(settingsToDispatch as Partial<EditableSetting>[]);
});

const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ISettingBase, SettingEditor, SettingValue } from '@rocket.chat/core-typings';
import { Callout, Field, Margins } from '@rocket.chat/fuselage';
import React, { memo } from 'react';
import React, { ElementType, memo, ReactElement, ReactNode } from 'react';

import ActionSettingInput from './inputs/ActionSettingInput';
import AssetSettingInput from './inputs/AssetSettingInput';
Expand All @@ -18,40 +19,62 @@ import SelectSettingInput from './inputs/SelectSettingInput';
import SelectTimezoneSettingInput from './inputs/SelectTimezoneSettingInput';
import StringSettingInput from './inputs/StringSettingInput';

// @todo: the props are loosely typed because `Setting` needs to typecheck them.
const inputsByType: Record<ISettingBase['type'], ElementType<any>> = {
boolean: BooleanSettingInput,
string: StringSettingInput,
relativeUrl: RelativeUrlSettingInput,
password: PasswordSettingInput,
int: IntSettingInput,
select: SelectSettingInput,
multiSelect: MultiSelectSettingInput,
language: LanguageSettingInput,
color: ColorSettingInput,
font: FontSettingInput,
code: CodeSettingInput,
action: ActionSettingInput,
asset: AssetSettingInput,
roomPick: RoomPickSettingInput,
timezone: SelectTimezoneSettingInput,
date: GenericSettingInput, // @todo: implement
group: GenericSettingInput, // @todo: implement
};

type MemoizedSettingProps = {
_id?: string;
type: ISettingBase['type'];
hint?: ReactNode;
callout?: ReactNode;
value?: SettingValue;
editor?: SettingEditor;
onChangeValue?: (value: unknown) => void;
onChangeEditor?: (value: unknown) => void;
onResetButtonClick?: () => void;
className?: string;
invisible?: boolean;
label?: string;
sectionChanged?: boolean;
hasResetButton?: boolean;
actionText?: string;
};

const MemoizedSetting = ({
type,
hint = undefined,
callout = undefined,
value = undefined,
editor = undefined,
onChangeValue = () => {},
onChangeEditor = () => {},
onChangeValue,
onChangeEditor,
className = undefined,
invisible = undefined,
...inputProps
}) => {
}: MemoizedSettingProps): ReactElement | null => {
if (invisible) {
return null;
}

const InputComponent =
{
boolean: BooleanSettingInput,
string: StringSettingInput,
relativeUrl: RelativeUrlSettingInput,
password: PasswordSettingInput,
int: IntSettingInput,
select: SelectSettingInput,
multiSelect: MultiSelectSettingInput,
language: LanguageSettingInput,
color: ColorSettingInput,
font: FontSettingInput,
code: CodeSettingInput,
action: ActionSettingInput,
asset: AssetSettingInput,
roomPick: RoomPickSettingInput,
timezone: SelectTimezoneSettingInput,
}[type] || GenericSettingInput;
const InputComponent = inputsByType[type];

return (
<Field className={className}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { isSettingColor } from '@rocket.chat/core-typings';
import { Accordion, Box, Button, FieldGroup } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useMemo } from 'react';
import { TranslationKey, useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, ReactNode, useMemo } from 'react';

import { useEditableSettings, useEditableSettingsDispatch } from '../EditableSettingsContext';
import SectionSkeleton from './SectionSkeleton';
import Setting from './Setting';

function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, ...props }) {
type SectionProps = {
groupId: string;
hasReset?: boolean;
sectionName: string;
tabName?: string;
solo: boolean;
help?: ReactNode;
children?: ReactNode;
};

function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, help, children }: SectionProps): ReactElement {
const editableSettings = useEditableSettings(
useMemo(
() => ({
Expand All @@ -32,26 +43,41 @@ function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, ..
dispatch(
editableSettings
.filter(({ disabled }) => !disabled)
.map(({ _id, value, packageValue, editor, packageEditor }) => ({
_id,
value: packageValue,
editor: packageEditor,
changed: JSON.stringify(value) !== JSON.stringify(packageValue) || JSON.stringify(editor) !== JSON.stringify(packageEditor),
})),
.map((setting) => {
if (isSettingColor(setting)) {
return {
_id: setting._id,
value: setting.packageValue,
editor: setting.packageEditor,
changed:
JSON.stringify(setting.value) !== JSON.stringify(setting.packageValue) ||
JSON.stringify(setting.editor) !== JSON.stringify(setting.packageEditor),
};
}
return {
_id: setting._id,
value: setting.packageValue,
changed: JSON.stringify(setting.value) !== JSON.stringify(setting.packageValue),
};
}),
);
});

const t = useTranslation();

const handleResetSectionClick = () => {
const handleResetSectionClick = (): void => {
reset();
};

return (
<Accordion.Item data-qa-section={sectionName} noncollapsible={solo || !sectionName} title={sectionName && t(sectionName)}>
{props.help && (
<Accordion.Item
data-qa-section={sectionName}
noncollapsible={solo || !sectionName}
title={sectionName && t(sectionName as TranslationKey)}
>
{help && (
<Box is='p' color='hint' fontScale='p2'>
{props.help}
{help}
</Box>
)}

Expand All @@ -60,7 +86,7 @@ function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, ..
<Setting key={setting._id} settingId={setting._id} sectionChanged={changed} />
))}

{props.children}
{children}
</FieldGroup>
{hasReset && canReset && (
<Button
Expand Down
Loading

0 comments on commit e01c8ea

Please sign in to comment.