mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-15 12:45:11 +08:00
Consolidate all the work thus far
This commit is contained in:
parent
31cca5e0f2
commit
98b59fb217
@ -18,6 +18,14 @@ limitations under the License.
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx_RoomHeader_buttons + .mx_HeaderButtons {
|
||||
// remove the | separator line for when next to RoomHeaderButtons
|
||||
// TODO: remove this once when we redo communities and make the right panel similar to the new rooms one
|
||||
&::before {
|
||||
content: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_HeaderButtons::before {
|
||||
content: "";
|
||||
background-color: $header-divider-color;
|
||||
|
@ -25,6 +25,7 @@ limitations under the License.
|
||||
padding: 5px;
|
||||
// margin left to not allow the handle to not encroach on the space for the scrollbar
|
||||
margin-left: 8px;
|
||||
height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel
|
||||
|
||||
&:hover .mx_RightPanel_ResizeHandle {
|
||||
// Need to use important to override element style attributes
|
||||
|
@ -70,21 +70,16 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RightPanel_membersButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
.mx_RightPanel_filesButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/files.svg');
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
.mx_RightPanel_notifsButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/notifications.svg');
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
.mx_RightPanel_roomSummaryButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
.mx_RightPanel_groupMembersButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||
mask-position: center;
|
||||
@ -144,7 +139,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_RightPanel_empty {
|
||||
margin-right: -42px;
|
||||
margin-right: -28px;
|
||||
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
|
@ -82,7 +82,6 @@ limitations under the License.
|
||||
}
|
||||
|
||||
span.mx_IconizedContextMenu_label { // labels
|
||||
padding-left: 14px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
|
||||
@ -91,6 +90,10 @@ limitations under the License.
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label {
|
||||
padding-left: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,17 +134,18 @@ limitations under the License.
|
||||
.mx_BaseCard_footer {
|
||||
padding-top: 4px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
.mx_AccessibleButton_kind_secondary {
|
||||
color: $secondary-fg-color;
|
||||
background-color: rgba(141, 151, 165, 0.2); // TODO
|
||||
font-weight: $font-semi-bold;
|
||||
font-size: $font-14px;
|
||||
min-width: 70px;
|
||||
|
||||
& + .mx_AccessibleButton_kind_secondary {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_UserInfo {
|
||||
.mx_UserInfo.mx_BaseCard {
|
||||
// UserInfo has a circular image at the top so it fits between the back & close buttons
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
@ -29,7 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver";
|
||||
import {Notifier} from "../Notifier";
|
||||
import type {Renderer} from "react-dom";
|
||||
import RightPanelStore from "../stores/RightPanelStore";
|
||||
import {WidgetStore} from "../stores/WidgetStore";
|
||||
import WidgetStore from "../stores/WidgetStore";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {CSSProperties, useRef, useState} from "react";
|
||||
import React, {CSSProperties, RefObject, useRef, useState} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
@ -416,8 +416,8 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None
|
||||
return menuOptions;
|
||||
};
|
||||
|
||||
export const useContextMenu = () => {
|
||||
const button = useRef(null);
|
||||
export const useContextMenu = (): [boolean, RefObject<HTMLElement>, () => void, () => void, (val: boolean) => void] => {
|
||||
const button = useRef<HTMLElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const open = () => {
|
||||
setIsOpen(true);
|
||||
|
@ -23,6 +23,8 @@ import * as sdk from '../../index';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||
import { _t } from '../../languageHandler';
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||
|
||||
/*
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
@ -30,6 +32,7 @@ import { _t } from '../../languageHandler';
|
||||
class FilePanel extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
// This is used to track if a decrypted event was a live event and should be
|
||||
@ -188,18 +191,26 @@ class FilePanel extends React.Component {
|
||||
|
||||
render() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||
return <BaseCard
|
||||
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<div className="mx_RoomView_empty">
|
||||
{ _t("You must <a>register</a> to use this functionality",
|
||||
{},
|
||||
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
|
||||
}
|
||||
</div>
|
||||
</div>;
|
||||
</BaseCard>;
|
||||
} else if (this.noRoom) {
|
||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||
return <BaseCard
|
||||
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<div className="mx_RoomView_empty">{ _t("You must join the room to see its files") }</div>
|
||||
</div>;
|
||||
</BaseCard>;
|
||||
}
|
||||
|
||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||
@ -215,7 +226,11 @@ class FilePanel extends React.Component {
|
||||
// console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " +
|
||||
// "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId);
|
||||
return (
|
||||
<div className="mx_FilePanel" role="tabpanel">
|
||||
<BaseCard
|
||||
className="mx_FilePanel"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<TimelinePanel
|
||||
manageReadReceipts={false}
|
||||
manageReadMarkers={false}
|
||||
@ -226,13 +241,17 @@ class FilePanel extends React.Component {
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
empty={emptyState}
|
||||
/>
|
||||
</div>
|
||||
</BaseCard>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="mx_FilePanel" role="tabpanel">
|
||||
<BaseCard
|
||||
className="mx_FilePanel"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<Loader />
|
||||
</div>
|
||||
</BaseCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,21 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { _t } from '../../languageHandler';
|
||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||
import * as sdk from "../../index";
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
|
||||
/*
|
||||
* Component which shows the global notification list using a TimelinePanel
|
||||
*/
|
||||
class NotificationPanel extends React.Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||
@ -35,10 +42,10 @@ class NotificationPanel extends React.Component {
|
||||
<p>{_t('You have no visible notifications in this room.')}</p>
|
||||
</div>);
|
||||
|
||||
let content;
|
||||
const timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
|
||||
if (timelineSet) {
|
||||
return (
|
||||
<div className="mx_NotificationPanel" role="tabpanel">
|
||||
content = (
|
||||
<TimelinePanel
|
||||
manageReadReceipts={false}
|
||||
manageReadMarkers={false}
|
||||
@ -47,16 +54,15 @@ class NotificationPanel extends React.Component {
|
||||
tileShape="notif"
|
||||
empty={emptyState}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
console.error("No notifTimelineSet available!");
|
||||
return (
|
||||
<div className="mx_NotificationPanel" role="tabpanel">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
content = <Loader />;
|
||||
}
|
||||
|
||||
return <BaseCard className="mx_NotificationPanel" onClose={this.props.onClose}>
|
||||
{ content }
|
||||
</BaseCard>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { shieldStatusForRoom } from '../../utils/ShieldUtils';
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
import {SettingLevel} from "../../settings/SettingLevel";
|
||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function() {};
|
||||
@ -1356,7 +1357,10 @@ export default class RoomView extends React.Component {
|
||||
};
|
||||
|
||||
onSettingsClick = () => {
|
||||
dis.dispatch({ action: 'open_room_settings' });
|
||||
dis.dispatch({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomSummary,
|
||||
});
|
||||
};
|
||||
|
||||
onCancelClick = () => {
|
||||
|
@ -37,7 +37,7 @@ interface IOptionListProps {
|
||||
}
|
||||
|
||||
interface IOptionProps extends React.ComponentProps<typeof MenuItem> {
|
||||
iconClassName: string;
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> {
|
||||
@ -92,7 +92,7 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
|
||||
|
||||
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({label, iconClassName, ...props}) => {
|
||||
return <MenuItem {...props} label={label}>
|
||||
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
|
||||
{ iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} /> }
|
||||
<span className="mx_IconizedContextMenu_label">{label}</span>
|
||||
</MenuItem>;
|
||||
};
|
||||
|
@ -26,6 +26,9 @@ export default class WidgetContextMenu extends React.Component {
|
||||
// Callback for when the revoke button is clicked. Required.
|
||||
onRevokeClicked: PropTypes.func.isRequired,
|
||||
|
||||
// Callback for when the unpin button is clicked. Required.
|
||||
onUnpinClicked: PropTypes.func.isRequired,
|
||||
|
||||
// Callback for when the snapshot button is clicked. Button not shown
|
||||
// without a callback.
|
||||
onSnapshotClicked: PropTypes.func,
|
||||
@ -70,6 +73,8 @@ export default class WidgetContextMenu extends React.Component {
|
||||
this.proxyClick(this.props.onRevokeClicked);
|
||||
};
|
||||
|
||||
onUnpinClicked = () => this.proxyClick(this.props.onUnpinClicked);
|
||||
|
||||
render() {
|
||||
const options = [];
|
||||
|
||||
@ -81,6 +86,12 @@ export default class WidgetContextMenu extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
options.push(
|
||||
<MenuItem className="mx_WidgetContextMenu_option" onClick={this.onUnpinClicked} key="unpin">
|
||||
{_t("Unpin")}
|
||||
</MenuItem>,
|
||||
);
|
||||
|
||||
if (this.props.onReloadClicked) {
|
||||
options.push(
|
||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked} key='reload'>
|
||||
|
@ -42,6 +42,8 @@ import {WidgetType} from "../../../widgets/WidgetType";
|
||||
import {Capability} from "../../../widgets/WidgetApi";
|
||||
import {sleep} from "../../../utils/promise";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
|
||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||
const ENABLE_REACT_PERF = false;
|
||||
@ -315,17 +317,7 @@ export default class AppTile extends React.Component {
|
||||
}
|
||||
|
||||
_onSnapshotClick() {
|
||||
console.log("Requesting widget snapshot");
|
||||
ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot()
|
||||
.catch((err) => {
|
||||
console.error("Failed to get screenshot", err);
|
||||
})
|
||||
.then((screenshot) => {
|
||||
dis.dispatch({
|
||||
action: 'picture_snapshot',
|
||||
file: screenshot,
|
||||
}, true);
|
||||
});
|
||||
WidgetUtils.snapshotWidget(this.props.app);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -406,6 +398,10 @@ export default class AppTile extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
_onUnpinClicked = () => {
|
||||
WidgetStore.instance.unpinWidget(this.props.app.id);
|
||||
}
|
||||
|
||||
_onRevokeClicked() {
|
||||
console.info("Revoke widget permissions - %s", this.props.app.id);
|
||||
this._revokeWidgetPermission();
|
||||
@ -483,6 +479,14 @@ export default class AppTile extends React.Component {
|
||||
console.warn('Ignoring sticker message. Invalid capability');
|
||||
}
|
||||
break;
|
||||
|
||||
case Action.AppTileDelete:
|
||||
this._onDeleteClick();
|
||||
break;
|
||||
|
||||
case Action.AppTileRevoke:
|
||||
this._onRevokeClicked();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -826,6 +830,7 @@ export default class AppTile extends React.Component {
|
||||
contextMenu = (
|
||||
<ContextMenu {...aboveLeftOf(elementRect, null)} onFinished={this._closeContextMenu}>
|
||||
<WidgetContextMenu
|
||||
onUnpinClicked={this._onUnpinClicked}
|
||||
onRevokeClicked={this._onRevokeClicked}
|
||||
onEditClicked={showEditButton ? this._onEditClick : undefined}
|
||||
onDeleteClicked={showDeleteButton ? this._onDeleteClick : undefined}
|
||||
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 New Vector Ltd
|
||||
Copyright 2019 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 from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||
|
||||
export default class ManageIntegsButton extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onManageIntegrations = (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
const managers = IntegrationManagers.sharedInstance();
|
||||
if (!managers.hasManager()) {
|
||||
managers.openNoManagerDialog();
|
||||
} else {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
managers.openAll(this.props.room);
|
||||
} else {
|
||||
managers.getPrimaryManager().open(this.props.room);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let integrationsButton = <div />;
|
||||
if (IntegrationManagers.sharedInstance().hasManager()) {
|
||||
integrationsButton = (
|
||||
<AccessibleTooltipButton
|
||||
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
|
||||
title={_t("Manage Integrations")}
|
||||
onClick={this.onManageIntegrations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return integrationsButton;
|
||||
}
|
||||
}
|
||||
|
||||
ManageIntegsButton.propTypes = {
|
||||
room: PropTypes.object.isRequired,
|
||||
};
|
@ -39,7 +39,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import {IApp, WidgetStore} from "../../../stores/WidgetStore";
|
||||
import WidgetStore, {IApp} from "../../../stores/WidgetStore";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -46,6 +46,7 @@ import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
|
||||
import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification';
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
|
||||
import BaseCard from "./BaseCard";
|
||||
|
||||
const _disambiguateDevices = (devices) => {
|
||||
const names = Object.create(null);
|
||||
@ -451,7 +452,7 @@ const _isMuted = (member, powerLevelContent) => {
|
||||
return member.powerLevel < levelToSend;
|
||||
};
|
||||
|
||||
const useRoomPowerLevels = (cli, room) => {
|
||||
export const useRoomPowerLevels = (cli, room) => {
|
||||
const [powerLevels, setPowerLevels] = useState({});
|
||||
|
||||
const update = useCallback(() => {
|
||||
@ -1364,16 +1365,9 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
||||
const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
||||
const UserInfoHeader = ({member, e2eStatus}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
let closeButton;
|
||||
if (onClose) {
|
||||
closeButton = <AccessibleButton className="mx_UserInfo_cancel" onClick={onClose} title={_t('Close')}>
|
||||
<div />
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const onMemberAvatarClick = useCallback(() => {
|
||||
const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl;
|
||||
if (!avatarUrl) return;
|
||||
@ -1448,7 +1442,6 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
||||
|
||||
const displayName = member.name || member.displayname;
|
||||
return <React.Fragment>
|
||||
{ closeButton }
|
||||
{ avatarElement }
|
||||
|
||||
<div className="mx_UserInfo_container mx_UserInfo_separator">
|
||||
@ -1510,15 +1503,16 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMe
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.join(" ")} role="tabpanel">
|
||||
<AutoHideScrollbar className="mx_UserInfo_scrollContainer">
|
||||
<UserInfoHeader member={member} e2eStatus={e2eStatus} onClose={onClose} />
|
||||
let previousPhase: RightPanelPhases;
|
||||
// We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time
|
||||
if (room) {
|
||||
previousPhase = RightPanelPhases.RoomMemberList;
|
||||
}
|
||||
|
||||
const header = <UserInfoHeader member={member} e2eStatus={e2eStatus} onClose={onClose} />;
|
||||
return <BaseCard className={classes.join(" ")} header={header} onClose={onClose} previousPhase={previousPhase}>
|
||||
{ content }
|
||||
</AutoHideScrollbar>
|
||||
</div>
|
||||
);
|
||||
</BaseCard>;
|
||||
};
|
||||
|
||||
UserInfo.propTypes = {
|
||||
|
@ -28,7 +28,15 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {WidgetStore} from "../../../stores/WidgetStore";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
import ActiveWidgetStore from "../../../stores/ActiveWidgetStore";
|
||||
import {ChevronFace, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload";
|
||||
import {Capability} from "../../../widgets/WidgetApi";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
@ -50,6 +58,8 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
||||
const app = apps.find(a => a.id === widgetId);
|
||||
const isPinned = app && WidgetStore.instance.isPinned(app.id);
|
||||
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
||||
|
||||
useEffect(() => {
|
||||
if (!app || isPinned) {
|
||||
// TODO maybe we should do this in the ActiveWidgetStore instead
|
||||
@ -64,6 +74,58 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
||||
<h2>{ WidgetUtils.getWidgetName(app) }</h2>
|
||||
</React.Fragment>;
|
||||
|
||||
const canModify = WidgetUtils.canUserModifyWidgets(room.roomId);
|
||||
|
||||
let contextMenu;
|
||||
if (menuDisplayed) {
|
||||
let snapshotButton;
|
||||
if (ActiveWidgetStore.widgetHasCapability(app.id, Capability.Screenshot)) {
|
||||
const onSnapshotClick = () => {
|
||||
WidgetUtils.snapshotWidget(app);
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
snapshotButton = <IconizedContextMenuOption onClick={onSnapshotClick} label={_t("Take a picture")} />;
|
||||
}
|
||||
|
||||
let deleteButton;
|
||||
if (canModify) {
|
||||
const onDeleteClick = () => {
|
||||
defaultDispatcher.dispatch<AppTileActionPayload>({
|
||||
action: Action.AppTileDelete,
|
||||
widgetId: app.id,
|
||||
});
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
deleteButton = <IconizedContextMenuOption onClick={onDeleteClick} label={_t("Remove for everyone")} />;
|
||||
}
|
||||
|
||||
const onRevokeClick = () => {
|
||||
defaultDispatcher.dispatch<AppTileActionPayload>({
|
||||
action: Action.AppTileRevoke,
|
||||
widgetId: app.id,
|
||||
});
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
const rect = handle.current.getBoundingClientRect();
|
||||
contextMenu = (
|
||||
<IconizedContextMenu
|
||||
chevronFace={ChevronFace.None}
|
||||
right={window.innerWidth - rect.right}
|
||||
bottom={window.innerHeight - rect.top}
|
||||
onFinished={closeMenu}
|
||||
>
|
||||
<IconizedContextMenuOptionList>
|
||||
{ snapshotButton }
|
||||
{ deleteButton }
|
||||
<IconizedContextMenuOption onClick={onRevokeClick} label={_t("Remove for me")} />
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
const onPinClick = () => {
|
||||
WidgetStore.instance.pinWidget(app.id);
|
||||
};
|
||||
@ -73,12 +135,24 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
||||
};
|
||||
|
||||
const footer = <React.Fragment>
|
||||
<AccessibleButton kind="secondary" onClick={onPinClick}>
|
||||
{ _t("Pin to room") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="secondary" onClick={onEditClick}>
|
||||
<AccessibleButton kind="secondary" onClick={onEditClick} disabled={!canModify}>
|
||||
{ _t("Edit") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="secondary" onClick={onPinClick} disabled={!WidgetStore.instance.canPin(app.id)}>
|
||||
{ _t("Pin to room") }
|
||||
</AccessibleButton>
|
||||
<ContextMenuButton
|
||||
kind="secondary"
|
||||
className={""}
|
||||
inputRef={handle}
|
||||
onClick={openMenu}
|
||||
isExpanded={menuDisplayed}
|
||||
label={_t("Options")}
|
||||
>
|
||||
...
|
||||
</ContextMenuButton>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
|
||||
return <BaseCard
|
||||
|
@ -17,9 +17,10 @@ limitations under the License.
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import classNames from 'classnames';
|
||||
import {Resizable} from "re-resizable";
|
||||
|
||||
import AppTile from '../elements/AppTile';
|
||||
import Modal from '../../../Modal';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import * as sdk from '../../../index';
|
||||
import * as ScalarMessaging from '../../../ScalarMessaging';
|
||||
@ -29,13 +30,9 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import classNames from 'classnames';
|
||||
import {Resizable} from "re-resizable";
|
||||
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
|
||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||
|
||||
// The maximum number of widgets that can be added in a room
|
||||
const MAX_WIDGETS = 2;
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
|
||||
export default class AppsDrawer extends React.Component {
|
||||
static propTypes = {
|
||||
@ -61,17 +58,13 @@ export default class AppsDrawer extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
ScalarMessaging.startListening();
|
||||
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
|
||||
WidgetEchoStore.on('update', this._updateApps);
|
||||
WidgetStore.instance.on(this.props.room.roomId, this._updateApps);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ScalarMessaging.stopListening();
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
|
||||
}
|
||||
WidgetEchoStore.removeListener('update', this._updateApps);
|
||||
WidgetStore.instance.off(this.props.room.roomId, this._updateApps);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
@ -100,28 +93,11 @@ export default class AppsDrawer extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
onRoomStateEvents = (ev, state) => {
|
||||
if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
|
||||
return;
|
||||
}
|
||||
this._updateApps();
|
||||
};
|
||||
|
||||
_getApps() {
|
||||
const widgets = WidgetEchoStore.getEchoedRoomWidgets(
|
||||
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
|
||||
);
|
||||
return widgets.map((ev) => {
|
||||
return WidgetUtils.makeAppConfig(
|
||||
ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
|
||||
);
|
||||
});
|
||||
}
|
||||
_getApps = () => WidgetStore.instance.getApps(this.props.room, true);
|
||||
|
||||
_updateApps = () => {
|
||||
const apps = this._getApps();
|
||||
this.setState({
|
||||
apps: apps,
|
||||
apps: this._getApps(),
|
||||
});
|
||||
};
|
||||
|
||||
@ -144,18 +120,6 @@ export default class AppsDrawer extends React.Component {
|
||||
|
||||
onClickAddWidget = (e) => {
|
||||
e.preventDefault();
|
||||
// Display a warning dialog if the max number of widgets have already been added to the room
|
||||
const apps = this._getApps();
|
||||
if (apps && apps.length >= MAX_WIDGETS) {
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`;
|
||||
console.error(errorMsg);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t('Cannot add any more widgets'),
|
||||
description: _t('The maximum permitted number of widgets have already been added to this room.'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._launchManageIntegrations();
|
||||
};
|
||||
|
||||
|
@ -20,13 +20,14 @@ import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import {isValid3pidInvite} from "../../../RoomInvite";
|
||||
import rate_limited_func from "../../../ratelimitedfunc";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../index";
|
||||
import CallHandler from "../../../CallHandler";
|
||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
||||
import BaseCard from "../right_panel/BaseCard";
|
||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||
|
||||
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||
const INITIAL_LOAD_NUM_INVITED = 5;
|
||||
@ -438,7 +439,13 @@ export default class MemberList extends React.Component {
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return <div className="mx_MemberList"><Spinner /></div>;
|
||||
return <BaseCard
|
||||
className="mx_MemberList"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<Spinner />
|
||||
</BaseCard>;
|
||||
}
|
||||
|
||||
const SearchBox = sdk.getComponent('structures.SearchBox');
|
||||
@ -485,10 +492,20 @@ export default class MemberList extends React.Component {
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_MemberList" role="tabpanel">
|
||||
{ inviteButton }
|
||||
<AutoHideScrollbar>
|
||||
const footer = (
|
||||
<SearchBox
|
||||
className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t('Filter room members') }
|
||||
onSearch={ this.onSearchQueryChanged } />
|
||||
);
|
||||
|
||||
return <BaseCard
|
||||
className="mx_MemberList"
|
||||
header={inviteButton}
|
||||
footer={footer}
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}
|
||||
createOverflowElement={this._createOverflowTileJoined}
|
||||
@ -497,13 +514,7 @@ export default class MemberList extends React.Component {
|
||||
{ invitedHeader }
|
||||
{ invitedSection }
|
||||
</div>
|
||||
</AutoHideScrollbar>
|
||||
|
||||
<SearchBox className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t('Filter room members') }
|
||||
onSearch={ this.onSearchQueryChanged } />
|
||||
</div>
|
||||
);
|
||||
</BaseCard>;
|
||||
}
|
||||
|
||||
onInviteButtonClick = () => {
|
||||
|
@ -18,14 +18,11 @@ limitations under the License.
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import Modal from "../../../Modal";
|
||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||
|
||||
import { linkifyElement } from '../../../HtmlUtils';
|
||||
import ManageIntegsButton from '../elements/ManageIntegsButton';
|
||||
import {CancelButton} from './SimpleRoomHeader';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||
@ -114,13 +111,6 @@ export default class RoomHeader extends React.Component {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onShareRoomClick = (ev) => {
|
||||
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
||||
Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
|
||||
target: this.props.room,
|
||||
});
|
||||
};
|
||||
|
||||
_hasUnreadPins() {
|
||||
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
|
||||
if (!currentPinEvent) return false;
|
||||
@ -150,7 +140,6 @@ export default class RoomHeader extends React.Component {
|
||||
render() {
|
||||
let searchStatus = null;
|
||||
let cancelButton = null;
|
||||
let settingsButton = null;
|
||||
let pinnedEventsButton = null;
|
||||
|
||||
if (this.props.onCancelClick) {
|
||||
@ -214,14 +203,6 @@ export default class RoomHeader extends React.Component {
|
||||
/>;
|
||||
}
|
||||
|
||||
if (this.props.onSettingsClick) {
|
||||
settingsButton =
|
||||
<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_settingsButton"
|
||||
onClick={this.props.onSettingsClick}
|
||||
title={_t("Settings")} />;
|
||||
}
|
||||
|
||||
if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) {
|
||||
let pinsIndicator = null;
|
||||
if (this._hasUnreadPins()) {
|
||||
@ -258,26 +239,9 @@ export default class RoomHeader extends React.Component {
|
||||
title={_t("Search")} />;
|
||||
}
|
||||
|
||||
let shareRoomButton;
|
||||
if (this.props.inRoom) {
|
||||
shareRoomButton =
|
||||
<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_shareButton"
|
||||
onClick={this.onShareRoomClick}
|
||||
title={_t('Share room')} />;
|
||||
}
|
||||
|
||||
let manageIntegsButton;
|
||||
if (this.props.room && this.props.room.roomId && this.props.inRoom) {
|
||||
manageIntegsButton = <ManageIntegsButton room={this.props.room} />;
|
||||
}
|
||||
|
||||
const rightRow =
|
||||
<div className="mx_RoomHeader_buttons">
|
||||
{ settingsButton }
|
||||
{ pinnedEventsButton }
|
||||
{ shareRoomButton }
|
||||
{ manageIntegsButton }
|
||||
{ forgetButton }
|
||||
{ searchButton }
|
||||
</div>;
|
||||
|
@ -94,4 +94,14 @@ export enum Action {
|
||||
* Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload.
|
||||
*/
|
||||
AfterRightPanelPhaseChange = "after_right_panel_phase_change",
|
||||
|
||||
/**
|
||||
* Requests that the AppTile deletes the widget. Should be used with the AppTileActionPayload.
|
||||
*/
|
||||
AppTileDelete = "appTile_delete",
|
||||
|
||||
/**
|
||||
* Requests that the AppTile revokes the widget. Should be used with the AppTileActionPayload.
|
||||
*/
|
||||
AppTileRevoke = "appTile_revoke",
|
||||
}
|
||||
|
23
src/dispatcher/payloads/AppTileActionPayload.ts
Normal file
23
src/dispatcher/payloads/AppTileActionPayload.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2020 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 { ActionPayload } from "../payloads";
|
||||
import { Action } from "../actions";
|
||||
|
||||
export interface AppTileActionPayload extends ActionPayload {
|
||||
action: Action.AppTileDelete | Action.AppTileRevoke;
|
||||
widgetId: string;
|
||||
}
|
@ -1029,8 +1029,6 @@
|
||||
"Remove %(phone)s?": "Remove %(phone)s?",
|
||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.",
|
||||
"Phone Number": "Phone Number",
|
||||
"Cannot add any more widgets": "Cannot add any more widgets",
|
||||
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
|
||||
"Add a widget": "Add a widget",
|
||||
"Drop File Here": "Drop File Here",
|
||||
"Drop file here to upload": "Drop file here to upload",
|
||||
@ -1115,10 +1113,8 @@
|
||||
"(~%(count)s results)|other": "(~%(count)s results)",
|
||||
"(~%(count)s results)|one": "(~%(count)s result)",
|
||||
"Join Room": "Join Room",
|
||||
"Settings": "Settings",
|
||||
"Forget room": "Forget room",
|
||||
"Search": "Search",
|
||||
"Share room": "Share room",
|
||||
"Invites": "Invites",
|
||||
"Favourites": "Favourites",
|
||||
"People": "People",
|
||||
@ -1135,6 +1131,7 @@
|
||||
"Can't see what you’re looking for?": "Can't see what you’re looking for?",
|
||||
"Explore all public rooms": "Explore all public rooms",
|
||||
"%(count)s results|other": "%(count)s results",
|
||||
"%(count)s results|one": "%(count)s result",
|
||||
"This room": "This room",
|
||||
"Joining room …": "Joining room …",
|
||||
"Loading …": "Loading …",
|
||||
@ -1197,6 +1194,7 @@
|
||||
"Favourited": "Favourited",
|
||||
"Favourite": "Favourite",
|
||||
"Low Priority": "Low Priority",
|
||||
"Settings": "Settings",
|
||||
"Leave Room": "Leave Room",
|
||||
"Room options": "Room options",
|
||||
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
|
||||
@ -1267,6 +1265,7 @@
|
||||
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
|
||||
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
|
||||
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
|
||||
"Back": "Back",
|
||||
"Waiting for you to accept on your other session…": "Waiting for you to accept on your other session…",
|
||||
"Waiting for %(displayName)s to accept…": "Waiting for %(displayName)s to accept…",
|
||||
"Accepting…": "Accepting…",
|
||||
@ -1284,7 +1283,18 @@
|
||||
"Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection",
|
||||
"Yours, or the other users’ session": "Yours, or the other users’ session",
|
||||
"Members": "Members",
|
||||
"Files": "Files",
|
||||
"Room Info": "Room Info",
|
||||
"Apps": "Apps",
|
||||
"Unpin app": "Unpin app",
|
||||
"Edit apps": "Edit apps",
|
||||
"Add applications": "Add applications",
|
||||
"Not encrypted": "Not encrypted",
|
||||
"About": "About",
|
||||
"%(count)s people|other": "%(count)s people",
|
||||
"%(count)s people|one": "%(count)s person",
|
||||
"Show files": "Show files",
|
||||
"Share room": "Share room",
|
||||
"Room settings": "Room settings",
|
||||
"Trusted": "Trusted",
|
||||
"Not trusted": "Not trusted",
|
||||
"%(count)s verified sessions|other": "%(count)s verified sessions",
|
||||
@ -1362,6 +1372,12 @@
|
||||
"You cancelled verification.": "You cancelled verification.",
|
||||
"Verification cancelled": "Verification cancelled",
|
||||
"Compare emoji": "Compare emoji",
|
||||
"Reload": "Reload",
|
||||
"Take a picture": "Take a picture",
|
||||
"Remove for everyone": "Remove for everyone",
|
||||
"Remove for me": "Remove for me",
|
||||
"Edit": "Edit",
|
||||
"Pin to room": "Pin to room",
|
||||
"Sunday": "Sunday",
|
||||
"Monday": "Monday",
|
||||
"Tuesday": "Tuesday",
|
||||
@ -1379,7 +1395,6 @@
|
||||
"Error decrypting audio": "Error decrypting audio",
|
||||
"React": "React",
|
||||
"Reply": "Reply",
|
||||
"Edit": "Edit",
|
||||
"Message Actions": "Message Actions",
|
||||
"Attachment": "Attachment",
|
||||
"Error decrypting attachment": "Error decrypting attachment",
|
||||
@ -1490,7 +1505,6 @@
|
||||
"Download this file": "Download this file",
|
||||
"Information": "Information",
|
||||
"Language Dropdown": "Language Dropdown",
|
||||
"Manage Integrations": "Manage Integrations",
|
||||
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
|
||||
"%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times",
|
||||
"%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined",
|
||||
@ -1670,7 +1684,6 @@
|
||||
"Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.": "Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.",
|
||||
"Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.",
|
||||
"Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)",
|
||||
"Back": "Back",
|
||||
"Send": "Send",
|
||||
"Send Custom Event": "Send Custom Event",
|
||||
"You must specify an event type!": "You must specify an event type!",
|
||||
@ -1911,10 +1924,8 @@
|
||||
"Set status": "Set status",
|
||||
"Set a new status...": "Set a new status...",
|
||||
"View Community": "View Community",
|
||||
"Reload": "Reload",
|
||||
"Unpin": "Unpin",
|
||||
"Take picture": "Take picture",
|
||||
"Remove for everyone": "Remove for everyone",
|
||||
"Remove for me": "Remove for me",
|
||||
"This room is public": "This room is public",
|
||||
"Away": "Away",
|
||||
"User Status": "User Status",
|
||||
|
@ -46,7 +46,7 @@ interface IRoomWidgets {
|
||||
|
||||
// TODO consolidate WidgetEchoStore into this
|
||||
// TODO consolidate ActiveWidgetStore into this
|
||||
export class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||
export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||
private static internalInstance = new WidgetStore();
|
||||
|
||||
private widgetMap = new Map<string, IApp>();
|
||||
@ -159,6 +159,14 @@ export class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||
return roomInfo ? roomInfo.pinned.has(widgetId) : false;
|
||||
}
|
||||
|
||||
public canPin(widgetId: string) {
|
||||
// only allow pinning up to a max of two as we do not yet have grid splits
|
||||
// the only case it will go to three is if you have two and then a Jitsi gets added
|
||||
const roomId = this.getRoomId(widgetId);
|
||||
const roomInfo = this.getRoom(roomId);
|
||||
return roomInfo && roomInfo.pinned.size < 2;
|
||||
}
|
||||
|
||||
public pinWidget(widgetId: string) {
|
||||
const roomId = this.getRoomId(widgetId);
|
||||
const roomInfo = this.getRoom(roomId);
|
||||
|
@ -479,15 +479,6 @@ export default class WidgetUtils {
|
||||
return url.href;
|
||||
}
|
||||
|
||||
static editWidget(room, app) {
|
||||
// TODO: Open the right manager for the widget
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id);
|
||||
}
|
||||
}
|
||||
|
||||
static getWidgetName(app) {
|
||||
if (!app || !app.name) return "";
|
||||
return app.name.trim() || _t("Unknown App");
|
||||
@ -497,4 +488,25 @@ export default class WidgetUtils {
|
||||
if (!app || !app.data || !app.data.title) return "";
|
||||
return app.data.title.trim();
|
||||
}
|
||||
|
||||
static editWidget(room, app) {
|
||||
// TODO: Open the right manager for the widget
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id);
|
||||
}
|
||||
}
|
||||
|
||||
static snapshotWidget(app) {
|
||||
console.log("Requesting widget snapshot");
|
||||
ActiveWidgetStore.getWidgetMessaging(app.id).getScreenshot().catch((err) => {
|
||||
console.error("Failed to get screenshot", err);
|
||||
}).then((screenshot) => {
|
||||
dis.dispatch({
|
||||
action: 'picture_snapshot',
|
||||
file: screenshot,
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user