Merge branch 'develop' into sort-imports

Signed-off-by: Aaron Raimist <aaron@raim.ist>
This commit is contained in:
Aaron Raimist 2021-12-09 08:34:20 +00:00
commit 7b94e13a84
642 changed files with 30052 additions and 8035 deletions

View file

@ -0,0 +1,154 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useMemo } from "react";
import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ContextMenu, { alwaysAboveRightOf, ChevronFace, useContextMenu } from "../../structures/ContextMenu";
import AccessibleButton from "../elements/AccessibleButton";
import StyledCheckbox from "../elements/StyledCheckbox";
import { MetaSpace } from "../../../stores/spaces";
import { useSettingValue } from "../../../hooks/useSettings";
import { onMetaSpaceChangeFactory } from "../settings/tabs/user/SidebarUserSettingsTab";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { UserTab } from "../dialogs/UserSettingsDialog";
import { findNonHighContrastTheme, getOrderedThemes } from "../../../theme";
import Dropdown from "../elements/Dropdown";
import ThemeChoicePanel from "../settings/ThemeChoicePanel";
import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import dis from "../../../dispatcher/dispatcher";
import { RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload";
import classNames from "classnames";
const QuickSettingsButton = ({ isPanelCollapsed = false }) => {
const orderedThemes = useMemo(getOrderedThemes, []);
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
const {
[MetaSpace.Favourites]: favouritesEnabled,
[MetaSpace.People]: peopleEnabled,
} = useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces");
let contextMenu: JSX.Element;
if (menuDisplayed) {
const themeState = ThemeChoicePanel.calculateThemeState();
const nonHighContrast = findNonHighContrastTheme(themeState.theme);
const theme = nonHighContrast ? nonHighContrast : themeState.theme;
contextMenu = <ContextMenu
{...alwaysAboveRightOf(handle.current.getBoundingClientRect(), ChevronFace.None, 16)}
wrapperClassName="mx_QuickSettingsButton_ContextMenuWrapper"
onFinished={closeMenu}
managed={false}
focusLock={true}
>
<h2>{ _t("Quick settings") }</h2>
<AccessibleButton
onClick={() => {
closeMenu();
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Sidebar,
});
}}
kind="primary_outline"
>
{ _t("All settings") }
</AccessibleButton>
<h4 className="mx_QuickSettingsButton_pinToSidebarHeading">{ _t("Pin to sidebar") }</h4>
<StyledCheckbox
className="mx_QuickSettingsButton_favouritesCheckbox"
checked={!!favouritesEnabled}
onChange={onMetaSpaceChangeFactory(MetaSpace.Favourites)}
>
{ _t("Favourites") }
</StyledCheckbox>
<StyledCheckbox
className="mx_QuickSettingsButton_peopleCheckbox"
checked={!!peopleEnabled}
onChange={onMetaSpaceChangeFactory(MetaSpace.People)}
>
{ _t("People") }
</StyledCheckbox>
<AccessibleButton
className="mx_QuickSettingsButton_moreOptionsButton"
onClick={() => {
closeMenu();
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Sidebar,
});
}}
>
{ _t("More options") }
</AccessibleButton>
<div className="mx_QuickSettingsButton_themePicker">
<h4>{ _t("Theme") }</h4>
<Dropdown
id="mx_QuickSettingsButton_themePickerDropdown"
onOptionChange={async (newTheme: string) => {
// XXX: mostly copied from ThemeChoicePanel
// doing getValue in the .catch will still return the value we failed to set,
// so remember what the value was before we tried to set it so we can revert
// const oldTheme: string = SettingsStore.getValue("theme");
SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme).catch(() => {
dis.dispatch<RecheckThemePayload>({ action: Action.RecheckTheme });
});
// The settings watcher doesn't fire until the echo comes back from the
// server, so to make the theme change immediately we need to manually
// do the dispatch now
// XXX: The local echoed value appears to be unreliable, in particular
// when settings custom themes(!) so adding forceTheme to override
// the value from settings.
dis.dispatch<RecheckThemePayload>({ action: Action.RecheckTheme, forceTheme: newTheme });
closeMenu();
}}
value={theme}
label={_t("Space selection")}
>
{ orderedThemes.map((theme) => (
<div key={theme.id}>
{ theme.name }
</div>
)) }
</Dropdown>
</div>
</ContextMenu>;
}
return <>
<AccessibleTooltipButton
className={classNames("mx_QuickSettingsButton", { expanded: !isPanelCollapsed })}
onClick={openMenu}
title={_t("Quick settings")}
inputRef={handle}
forceHide={!isPanelCollapsed}
>
{ !isPanelCollapsed ? _t("Settings") : null }
</AccessibleTooltipButton>
{ contextMenu }
</>;
};
export default QuickSettingsButton;

View file

@ -23,7 +23,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
import ContextMenu, { ChevronFace } from "../../structures/ContextMenu";
import createRoom, { IOpts as ICreateOpts } from "../../../createRoom";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings";
@ -118,6 +118,7 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
rageshakeLabel: "spaces-feedback",
rageshakeData: Object.fromEntries([
"Spaces.allRoomsInHome",
"Spaces.enabledMetaSpaces",
].map(k => [k, SettingsStore.getValue(k)])),
});
}}

View file

@ -34,14 +34,16 @@ import SpaceCreateMenu from "./SpaceCreateMenu";
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import SpaceStore, {
HOME_SPACE,
import SpaceStore from "../../../stores/spaces/SpaceStore";
import {
getMetaSpaceName,
MetaSpace,
SpaceKey,
UPDATE_HOME_BEHAVIOUR,
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
UPDATE_TOP_LEVEL_SPACES,
} from "../../../stores/SpaceStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
} from "../../../stores/spaces";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
@ -52,18 +54,31 @@ import IconizedContextMenu, {
import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import UIStore from "../../../stores/UIStore";
import QuickSettingsButton from "./QuickSettingsButton";
import { useSettingValue } from "../../../hooks/useSettings";
import UserMenu from "../../structures/UserMenu";
import IndicatorScrollbar from "../../structures/IndicatorScrollbar";
import { isMac } from "../../../Keyboard";
import { useDispatcher } from "../../../hooks/useDispatcher";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { ActionPayload } from "../../../dispatcher/payloads";
import { Action } from "../../../dispatcher/actions";
const useSpaces = (): [Room[], Room[], Room | null] => {
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
return SpaceStore.instance.invitedSpaces;
});
const spaces = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, () => {
return SpaceStore.instance.spacePanelSpaces;
});
const activeSpace = useEventEmitterState<Room>(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => {
const [metaSpaces, actualSpaces] = useEventEmitterState<[MetaSpace[], Room[]]>(
SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES,
() => [
SpaceStore.instance.enabledMetaSpaces,
SpaceStore.instance.spacePanelSpaces,
],
);
const activeSpace = useEventEmitterState<SpaceKey>(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => {
return SpaceStore.instance.activeSpace;
});
return [invites, spaces, activeSpace];
return [invites, metaSpaces, actualSpaces, activeSpace];
};
interface IInnerSpacePanelProps {
@ -72,7 +87,11 @@ interface IInnerSpacePanelProps {
setPanelCollapsed: Dispatch<SetStateAction<boolean>>;
}
const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps<typeof SpaceContextMenu>) => {
export const HomeButtonContextMenu = ({
onFinished,
hideHeader,
...props
}: ComponentProps<typeof SpaceContextMenu>) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
@ -83,9 +102,9 @@ const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps<typeof S
className="mx_SpacePanel_contextMenu"
compact
>
<div className="mx_SpacePanel_contextMenu_header">
{ !hideHeader && <div className="mx_SpacePanel_contextMenu_header">
{ _t("Home") }
</div>
</div> }
<IconizedContextMenuOptionList first>
<IconizedContextMenuCheckbox
iconClassName="mx_SpacePanel_noIcon"
@ -99,37 +118,76 @@ const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps<typeof S
</IconizedContextMenu>;
};
interface IHomeButtonProps {
interface IMetaSpaceButtonProps extends ComponentProps<typeof SpaceButton> {
selected: boolean;
isPanelCollapsed: boolean;
}
const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
type MetaSpaceButtonProps = Pick<IMetaSpaceButtonProps, "selected" | "isPanelCollapsed">;
const MetaSpaceButton = ({ selected, isPanelCollapsed, ...props }: IMetaSpaceButtonProps) => {
return <li
className={classNames("mx_SpaceItem", {
"collapsed": isPanelCollapsed,
})}
role="treeitem"
>
<SpaceButton
className="mx_SpaceButton_home"
onClick={() => SpaceStore.instance.setActiveSpace(null)}
selected={selected}
label={allRoomsInHome ? _t("All rooms") : _t("Home")}
notificationState={allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(HOME_SPACE)}
isNarrow={isPanelCollapsed}
ContextMenuComponent={HomeButtonContextMenu}
contextMenuTooltip={_t("Options")}
/>
<SpaceButton {...props} selected={selected} isNarrow={isPanelCollapsed} />
</li>;
};
const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
return <MetaSpaceButton
spaceKey={MetaSpace.Home}
className="mx_SpaceButton_home"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={getMetaSpaceName(MetaSpace.Home, allRoomsInHome)}
notificationState={allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(MetaSpace.Home)}
ContextMenuComponent={HomeButtonContextMenu}
contextMenuTooltip={_t("Options")}
/>;
};
const FavouritesButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
return <MetaSpaceButton
spaceKey={MetaSpace.Favourites}
className="mx_SpaceButton_favourites"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={getMetaSpaceName(MetaSpace.Favourites)}
notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Favourites)}
/>;
};
const PeopleButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
return <MetaSpaceButton
spaceKey={MetaSpace.People}
className="mx_SpaceButton_people"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={getMetaSpaceName(MetaSpace.People)}
notificationState={SpaceStore.instance.getNotificationState(MetaSpace.People)}
/>;
};
const OrphansButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
return <MetaSpaceButton
spaceKey={MetaSpace.Orphans}
className="mx_SpaceButton_orphans"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={getMetaSpaceName(MetaSpace.Orphans)}
notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Orphans)}
/>;
};
const CreateSpaceButton = ({
isPanelCollapsed,
setPanelCollapsed,
@ -181,13 +239,25 @@ const CreateSpaceButton = ({
</li>;
};
const metaSpaceComponentMap: Record<MetaSpace, typeof HomeButton> = {
[MetaSpace.Home]: HomeButton,
[MetaSpace.Favourites]: FavouritesButton,
[MetaSpace.People]: PeopleButton,
[MetaSpace.Orphans]: OrphansButton,
};
// Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation
const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCollapsed, setPanelCollapsed }) => {
const [invites, spaces, activeSpace] = useSpaces();
const [invites, metaSpaces, actualSpaces, activeSpace] = useSpaces();
const activeSpaces = activeSpace ? [activeSpace] : [];
const metaSpacesSection = metaSpaces.map(key => {
const Component = metaSpaceComponentMap[key];
return <Component key={key} selected={activeSpace === key} isPanelCollapsed={isPanelCollapsed} />;
});
return <div className="mx_SpaceTreeLevel">
<HomeButton selected={!activeSpace} isPanelCollapsed={isPanelCollapsed} />
{ metaSpacesSection }
{ invites.map(s => (
<SpaceItem
key={s.roomId}
@ -197,7 +267,7 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
onExpand={() => setPanelCollapsed(false)}
/>
)) }
{ spaces.map((s, i) => (
{ actualSpaces.map((s, i) => (
<Draggable key={s.roomId} draggableId={s.roomId} index={i}>
{ (provided, snapshot) => (
<SpaceItem
@ -220,6 +290,7 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
});
const SpacePanel = () => {
const metaSpacesEnabled = useSettingValue("feature_spaces_metaspaces");
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
const ref = useRef<HTMLUListElement>();
useLayoutEffect(() => {
@ -227,6 +298,12 @@ const SpacePanel = () => {
return () => UIStore.instance.stopTrackingElementDimensions("SpacePanel");
}, []);
useDispatcher(defaultDispatcher, (payload: ActionPayload) => {
if (payload.action === Action.ToggleSpacePanel) {
setPanelCollapsed(!isPanelCollapsed);
}
});
return (
<DragDropContext onDragEnd={result => {
if (!result.destination) return; // dropped outside the list
@ -241,9 +318,24 @@ const SpacePanel = () => {
aria-label={_t("Spaces")}
ref={ref}
>
<UserMenu isPanelCollapsed={isPanelCollapsed}>
<AccessibleTooltipButton
className={classNames("mx_SpacePanel_toggleCollapse", { expanded: !isPanelCollapsed })}
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
title={isPanelCollapsed ? _t("Expand") : _t("Collapse")}
tooltip={<div>
<div className="mx_Tooltip_title">
{ isPanelCollapsed ? _t("Expand") : _t("Collapse") }
</div>
<div className="mx_Tooltip_sub">
{ isMac ? "⌘ + ⇧ + D" : "Ctrl + Shift + D" }
</div>
</div>}
/>
</UserMenu>
<Droppable droppableId="top-level-spaces">
{ (provided, snapshot) => (
<AutoHideScrollbar
<IndicatorScrollbar
{...provided.droppableProps}
wrappedRef={provided.innerRef}
className="mx_SpacePanel_spaceTreeWrapper"
@ -257,14 +349,11 @@ const SpacePanel = () => {
>
{ provided.placeholder }
</InnerSpacePanel>
</AutoHideScrollbar>
</IndicatorScrollbar>
) }
</Droppable>
<AccessibleTooltipButton
className={classNames("mx_SpacePanel_toggleCollapse", { expanded: !isPanelCollapsed })}
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
/>
{ metaSpacesEnabled && <QuickSettingsButton isPanelCollapsed={isPanelCollapsed} /> }
</ul>
) }
</RovingTabIndexProvider>

View file

@ -16,7 +16,6 @@ limitations under the License.
import React, {
createRef,
MouseEvent,
InputHTMLAttributes,
LegacyRef,
ComponentProps,
@ -27,14 +26,15 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import SpaceStore from "../../../stores/spaces/SpaceStore";
import { SpaceKey } from "../../../stores/spaces";
import SpaceTreeLevelLayoutStore from "../../../stores/spaces/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge";
import { _t } from "../../../languageHandler";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
@ -43,8 +43,9 @@ import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title"> {
interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick"> {
space?: Room;
spaceKey?: SpaceKey;
className?: string;
selected?: boolean;
label: string;
@ -53,14 +54,14 @@ interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButto
isNarrow?: boolean;
avatarSize?: number;
ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
onClick(ev: MouseEvent): void;
onClick?(ev?: ButtonEvent): void;
}
export const SpaceButton: React.FC<IButtonProps> = ({
space,
spaceKey,
className,
selected,
onClick,
label,
contextMenuTooltip,
notificationState,
@ -88,7 +89,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
notifBadge = <div className="mx_SpacePanel_badgeContainer">
<NotificationBadge
onClick={() => SpaceStore.instance.setActiveRoomInSpace(space || null)}
onClick={() => SpaceStore.instance.setActiveRoomInSpace(spaceKey ?? space.roomId)}
forceCount={false}
notification={notificationState}
aria-label={ariaLabel}
@ -116,7 +117,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
mx_SpaceButton_narrow: isNarrow,
})}
title={label}
onClick={onClick}
onClick={spaceKey ? () => SpaceStore.instance.setActiveSpace(spaceKey) : props.onClick}
onContextMenu={openMenu}
forceHide={!isNarrow || menuDisplayed}
inputRef={handle}
@ -146,7 +147,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
space?: Room;
activeSpaces: Room[];
activeSpaces: SpaceKey[];
isNested?: boolean;
isPanelCollapsed?: boolean;
onExpand?: Function;
@ -258,7 +259,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
private onClick = (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
SpaceStore.instance.setActiveSpace(this.props.space);
SpaceStore.instance.setActiveSpace(this.props.space.roomId);
};
render() {
@ -316,7 +317,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
{...restDragHandleProps}
space={space}
className={isInvite ? "mx_SpaceButton_invite" : undefined}
selected={activeSpaces.includes(space)}
selected={activeSpaces.includes(space.roomId)}
label={space.name}
contextMenuTooltip={_t("Space options")}
notificationState={notificationState}
@ -337,7 +338,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
interface ITreeLevelProps {
spaces: Room[];
activeSpaces: Room[];
activeSpaces: SpaceKey[];
isNested?: boolean;
parents: Set<string>;
}