mirror of https://github.com/vector-im/riot-web
Spaces quick settings (#7196)
parent
138f6685d4
commit
9fefeefc8c
|
@ -22,6 +22,7 @@
|
||||||
@import "./structures/_MyGroups.scss";
|
@import "./structures/_MyGroups.scss";
|
||||||
@import "./structures/_NonUrgentToastContainer.scss";
|
@import "./structures/_NonUrgentToastContainer.scss";
|
||||||
@import "./structures/_NotificationPanel.scss";
|
@import "./structures/_NotificationPanel.scss";
|
||||||
|
@import "./structures/_QuickSettingsButton.scss";
|
||||||
@import "./structures/_RightPanel.scss";
|
@import "./structures/_RightPanel.scss";
|
||||||
@import "./structures/_RoomDirectory.scss";
|
@import "./structures/_RoomDirectory.scss";
|
||||||
@import "./structures/_RoomSearch.scss";
|
@import "./structures/_RoomSearch.scss";
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
margin: 12px auto;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: 16px;
|
||||||
|
background: $secondary-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $quaternary-content;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $primary-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_ContextMenuWrapper .mx_ContextualMenu {
|
||||||
|
padding: 16px;
|
||||||
|
width: max-content;
|
||||||
|
min-width: 200px;
|
||||||
|
contain: unset; // let the dropdown paint beyond the context menu
|
||||||
|
|
||||||
|
> div > h2 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $primary-content;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_primary_outline {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > h4 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: $tertiary-content;
|
||||||
|
margin: 20px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_pinToSidebarHeading {
|
||||||
|
padding-left: 24px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $secondary-content;
|
||||||
|
content: "";
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/pin-upright.svg');
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_favouritesCheckbox,
|
||||||
|
.mx_QuickSettingsButton_peopleCheckbox {
|
||||||
|
.mx_Checkbox_background + div {
|
||||||
|
padding-left: 22px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-content;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $secondary-content;
|
||||||
|
content: "";
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_favouritesCheckbox .mx_Checkbox_background + div::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_peopleCheckbox .mx_Checkbox_background + div::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_moreOptionsButton {
|
||||||
|
padding-left: 22px;
|
||||||
|
margin-left: 22px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-content;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $secondary-content;
|
||||||
|
content: "";
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/ellipsis.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_themePicker {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> h4 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-content;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dropdown {
|
||||||
|
min-width: 100px;
|
||||||
|
margin-left: auto;
|
||||||
|
height: min-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,6 @@ $activeBorderColor: $secondary-content;
|
||||||
mask-size: 32px;
|
mask-size: 32px;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
margin-left: $gutterSize;
|
margin-left: $gutterSize;
|
||||||
margin-bottom: 12px;
|
|
||||||
background-color: $tertiary-content;
|
background-color: $tertiary-content;
|
||||||
mask-image: url('$(res)/img/element-icons/expand-space-panel.svg');
|
mask-image: url('$(res)/img/element-icons/expand-space-panel.svg');
|
||||||
|
|
||||||
|
|
|
@ -178,26 +178,20 @@ export default class Dropdown extends React.Component<IProps, IState> {
|
||||||
this.ignoreEvent = ev;
|
this.ignoreEvent = ev;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onChevronClick = (ev: React.MouseEvent) => {
|
|
||||||
if (this.state.expanded) {
|
|
||||||
this.setState({ expanded: false });
|
|
||||||
ev.stopPropagation();
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAccessibleButtonClick = (ev: ButtonEvent) => {
|
private onAccessibleButtonClick = (ev: ButtonEvent) => {
|
||||||
if (this.props.disabled) return;
|
if (this.props.disabled) return;
|
||||||
|
|
||||||
if (!this.state.expanded) {
|
if (!this.state.expanded) {
|
||||||
this.setState({
|
this.setState({ expanded: true });
|
||||||
expanded: true,
|
|
||||||
});
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
} else if ((ev as React.KeyboardEvent).key === Key.ENTER) {
|
} else if ((ev as React.KeyboardEvent).key === Key.ENTER) {
|
||||||
// the accessible button consumes enter onKeyDown for firing onClick, so handle it here
|
// the accessible button consumes enter onKeyDown for firing onClick, so handle it here
|
||||||
this.props.onOptionChange(this.state.highlightedOption);
|
this.props.onOptionChange(this.state.highlightedOption);
|
||||||
this.close();
|
this.close();
|
||||||
|
} else if (!(ev as React.KeyboardEvent).key) {
|
||||||
|
// collapse on other non-keyboard event activations
|
||||||
|
this.setState({ expanded: false });
|
||||||
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -383,7 +377,7 @@ export default class Dropdown extends React.Component<IProps, IState> {
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
>
|
>
|
||||||
{ currentValue }
|
{ currentValue }
|
||||||
<span onClick={this.onChevronClick} className="mx_Dropdown_arrow" />
|
<span className="mx_Dropdown_arrow" />
|
||||||
{ menu }
|
{ menu }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { enumerateThemes, findHighContrastTheme, findNonHighContrastTheme, isHighContrastTheme } from "../../../theme";
|
import { findHighContrastTheme, findNonHighContrastTheme, getOrderedThemes, isHighContrastTheme } from "../../../theme";
|
||||||
import ThemeWatcher from "../../../settings/watchers/ThemeWatcher";
|
import ThemeWatcher from "../../../settings/watchers/ThemeWatcher";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
@ -28,7 +28,6 @@ import Field from '../elements/Field';
|
||||||
import StyledRadioGroup from "../elements/StyledRadioGroup";
|
import StyledRadioGroup from "../elements/StyledRadioGroup";
|
||||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { compare } from "../../../utils/strings";
|
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
@ -58,13 +57,13 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.calculateThemeState(),
|
...ThemeChoicePanel.calculateThemeState(),
|
||||||
customThemeUrl: "",
|
customThemeUrl: "",
|
||||||
customThemeMessage: { isError: false, text: "" },
|
customThemeMessage: { isError: false, text: "" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateThemeState(): IThemeState {
|
public static calculateThemeState(): IThemeState {
|
||||||
// We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
|
// We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
|
||||||
// show the right values for things.
|
// show the right values for things.
|
||||||
|
|
||||||
|
@ -238,14 +237,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: replace any type here
|
const orderedThemes = getOrderedThemes();
|
||||||
const themes = Object.entries<any>(enumerateThemes())
|
|
||||||
.map(p => ({ id: p[0], name: p[1] })) // convert pairs to objects for code readability
|
|
||||||
.filter(p => !isHighContrastTheme(p.id));
|
|
||||||
const builtInThemes = themes.filter(p => !p.id.startsWith("custom-"));
|
|
||||||
const customThemes = themes.filter(p => !builtInThemes.includes(p))
|
|
||||||
.sort((a, b) => compare(a.name, b.name));
|
|
||||||
const orderedThemes = [...builtInThemes, ...customThemes];
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SettingsTab_section mx_ThemeChoicePanel">
|
<div className="mx_SettingsTab_section mx_ThemeChoicePanel">
|
||||||
<span className="mx_SettingsTab_subheading">{ _t("Theme") }</span>
|
<span className="mx_SettingsTab_subheading">{ _t("Theme") }</span>
|
||||||
|
|
|
@ -23,7 +23,7 @@ import StyledCheckbox from "../../../elements/StyledCheckbox";
|
||||||
import { useSettingValue } from "../../../../../hooks/useSettings";
|
import { useSettingValue } from "../../../../../hooks/useSettings";
|
||||||
import { MetaSpace } from "../../../../../stores/spaces";
|
import { MetaSpace } from "../../../../../stores/spaces";
|
||||||
|
|
||||||
const onMetaSpaceChangeFactory = (metaSpace: MetaSpace) => (e: ChangeEvent<HTMLInputElement>) => {
|
export const onMetaSpaceChangeFactory = (metaSpace: MetaSpace) => (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const currentValue = SettingsStore.getValue("Spaces.enabledMetaSpaces");
|
const currentValue = SettingsStore.getValue("Spaces.enabledMetaSpaces");
|
||||||
SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.ACCOUNT, {
|
SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.ACCOUNT, {
|
||||||
...currentValue,
|
...currentValue,
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
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 { alwaysAboveRightOf, ChevronFace, ContextMenu, 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";
|
||||||
|
|
||||||
|
const QuickSettingsButton = () => {
|
||||||
|
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="mx_QuickSettingsButton"
|
||||||
|
onClick={openMenu}
|
||||||
|
title={_t("Quick settings")}
|
||||||
|
inputRef={handle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ contextMenu }
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QuickSettingsButton;
|
|
@ -54,6 +54,8 @@ import IconizedContextMenu, {
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
import UIStore from "../../../stores/UIStore";
|
import UIStore from "../../../stores/UIStore";
|
||||||
|
import QuickSettingsButton from "./QuickSettingsButton";
|
||||||
|
import { useSettingValue } from "../../../hooks/useSettings";
|
||||||
|
|
||||||
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
|
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
|
||||||
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
|
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
|
||||||
|
@ -277,6 +279,7 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
|
||||||
});
|
});
|
||||||
|
|
||||||
const SpacePanel = () => {
|
const SpacePanel = () => {
|
||||||
|
const metaSpacesEnabled = useSettingValue("feature_spaces_metaspaces");
|
||||||
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
|
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
|
||||||
const ref = useRef<HTMLUListElement>();
|
const ref = useRef<HTMLUListElement>();
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
@ -322,6 +325,7 @@ const SpacePanel = () => {
|
||||||
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
|
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
|
||||||
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
|
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
|
||||||
/>
|
/>
|
||||||
|
{ metaSpacesEnabled && <QuickSettingsButton /> }
|
||||||
</ul>
|
</ul>
|
||||||
) }
|
) }
|
||||||
</RovingTabIndexProvider>
|
</RovingTabIndexProvider>
|
||||||
|
|
|
@ -1045,6 +1045,14 @@
|
||||||
"Your server isn't responding to some <a>requests</a>.": "Your server isn't responding to some <a>requests</a>.",
|
"Your server isn't responding to some <a>requests</a>.": "Your server isn't responding to some <a>requests</a>.",
|
||||||
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
||||||
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
||||||
|
"Quick settings": "Quick settings",
|
||||||
|
"All settings": "All settings",
|
||||||
|
"Pin to sidebar": "Pin to sidebar",
|
||||||
|
"Favourites": "Favourites",
|
||||||
|
"People": "People",
|
||||||
|
"More options": "More options",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Space selection": "Space selection",
|
||||||
"Delete avatar": "Delete avatar",
|
"Delete avatar": "Delete avatar",
|
||||||
"Delete": "Delete",
|
"Delete": "Delete",
|
||||||
"Upload avatar": "Upload avatar",
|
"Upload avatar": "Upload avatar",
|
||||||
|
@ -1081,8 +1089,6 @@
|
||||||
"Show all rooms": "Show all rooms",
|
"Show all rooms": "Show all rooms",
|
||||||
"All rooms": "All rooms",
|
"All rooms": "All rooms",
|
||||||
"Options": "Options",
|
"Options": "Options",
|
||||||
"Favourites": "Favourites",
|
|
||||||
"People": "People",
|
|
||||||
"Other rooms": "Other rooms",
|
"Other rooms": "Other rooms",
|
||||||
"Expand space panel": "Expand space panel",
|
"Expand space panel": "Expand space panel",
|
||||||
"Collapse space panel": "Collapse space panel",
|
"Collapse space panel": "Collapse space panel",
|
||||||
|
@ -1325,7 +1331,6 @@
|
||||||
"Use high contrast": "Use high contrast",
|
"Use high contrast": "Use high contrast",
|
||||||
"Custom theme URL": "Custom theme URL",
|
"Custom theme URL": "Custom theme URL",
|
||||||
"Add theme": "Add theme",
|
"Add theme": "Add theme",
|
||||||
"Theme": "Theme",
|
|
||||||
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
|
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
|
||||||
"Checking for an update...": "Checking for an update...",
|
"Checking for an update...": "Checking for an update...",
|
||||||
"No update available.": "No update available.",
|
"No update available.": "No update available.",
|
||||||
|
@ -1647,7 +1652,6 @@
|
||||||
"Show Stickers": "Show Stickers",
|
"Show Stickers": "Show Stickers",
|
||||||
"Send a sticker": "Send a sticker",
|
"Send a sticker": "Send a sticker",
|
||||||
"Send voice message": "Send voice message",
|
"Send voice message": "Send voice message",
|
||||||
"More options": "More options",
|
|
||||||
"The conversation continues here.": "The conversation continues here.",
|
"The conversation continues here.": "The conversation continues here.",
|
||||||
"This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
|
"This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
|
||||||
"You do not have permission to post to this room": "You do not have permission to post to this room",
|
"You do not have permission to post to this room": "You do not have permission to post to this room",
|
||||||
|
@ -2259,7 +2263,6 @@
|
||||||
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
|
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
|
||||||
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
|
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
|
||||||
"Direct Messages": "Direct Messages",
|
"Direct Messages": "Direct Messages",
|
||||||
"Space selection": "Space selection",
|
|
||||||
"Add existing rooms": "Add existing rooms",
|
"Add existing rooms": "Add existing rooms",
|
||||||
"Want to add a new room instead?": "Want to add a new room instead?",
|
"Want to add a new room instead?": "Want to add a new room instead?",
|
||||||
"Create a new room": "Create a new room",
|
"Create a new room": "Create a new room",
|
||||||
|
@ -3066,7 +3069,6 @@
|
||||||
"New here? <a>Create an account</a>": "New here? <a>Create an account</a>",
|
"New here? <a>Create an account</a>": "New here? <a>Create an account</a>",
|
||||||
"Notification settings": "Notification settings",
|
"Notification settings": "Notification settings",
|
||||||
"Security & privacy": "Security & privacy",
|
"Security & privacy": "Security & privacy",
|
||||||
"All settings": "All settings",
|
|
||||||
"Community settings": "Community settings",
|
"Community settings": "Community settings",
|
||||||
"User settings": "User settings",
|
"User settings": "User settings",
|
||||||
"Switch to light mode": "Switch to light mode",
|
"Switch to light mode": "Switch to light mode",
|
||||||
|
|
16
src/theme.ts
16
src/theme.ts
|
@ -19,6 +19,7 @@ import { _t } from "./languageHandler";
|
||||||
|
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import ThemeWatcher from "./settings/watchers/ThemeWatcher";
|
import ThemeWatcher from "./settings/watchers/ThemeWatcher";
|
||||||
|
import { compare } from "./utils/strings";
|
||||||
|
|
||||||
export const DEFAULT_THEME = "light";
|
export const DEFAULT_THEME = "light";
|
||||||
const HIGH_CONTRAST_THEMES = {
|
const HIGH_CONTRAST_THEMES = {
|
||||||
|
@ -86,6 +87,21 @@ export function enumerateThemes(): {[key: string]: string} {
|
||||||
return Object.assign({}, customThemeNames, BUILTIN_THEMES);
|
return Object.assign({}, customThemeNames, BUILTIN_THEMES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ITheme {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrderedThemes(): ITheme[] {
|
||||||
|
const themes = Object.entries(enumerateThemes())
|
||||||
|
.map(p => ({ id: p[0], name: p[1] })) // convert pairs to objects for code readability
|
||||||
|
.filter(p => !isHighContrastTheme(p.id));
|
||||||
|
const builtInThemes = themes.filter(p => !p.id.startsWith("custom-"));
|
||||||
|
const customThemes = themes.filter(p => !builtInThemes.includes(p))
|
||||||
|
.sort((a, b) => compare(a.name, b.name));
|
||||||
|
return [...builtInThemes, ...customThemes];
|
||||||
|
}
|
||||||
|
|
||||||
function clearCustomTheme(): void {
|
function clearCustomTheme(): void {
|
||||||
// remove all css variables, we assume these are there because of the custom theme
|
// remove all css variables, we assume these are there because of the custom theme
|
||||||
const inlineStyleProps = Object.values(document.body.style);
|
const inlineStyleProps = Object.values(document.body.style);
|
||||||
|
|
Loading…
Reference in New Issue