2018-04-12 01:58:04 +03:00
|
|
|
/*
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
2019-07-31 14:19:29 +03:00
|
|
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
2020-03-13 03:02:50 +03:00
|
|
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
2018-04-12 01:58:04 +03:00
|
|
|
|
|
|
|
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';
|
2019-07-31 14:19:29 +03:00
|
|
|
import PropTypes from 'prop-types';
|
2020-03-13 03:02:50 +03:00
|
|
|
|
2019-12-21 00:13:46 +03:00
|
|
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
2018-04-12 01:58:04 +03:00
|
|
|
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
|
2020-03-13 17:38:36 +03:00
|
|
|
import {
|
|
|
|
ContextMenu,
|
|
|
|
useContextMenu,
|
|
|
|
ContextMenuButton,
|
|
|
|
MenuItemRadio,
|
|
|
|
MenuItem,
|
2020-03-13 17:42:19 +03:00
|
|
|
MenuGroup,
|
2020-03-13 17:38:36 +03:00
|
|
|
} from "../../structures/ContextMenu";
|
2020-03-13 03:02:50 +03:00
|
|
|
import {_t} from "../../../languageHandler";
|
|
|
|
import SdkConfig from "../../../SdkConfig";
|
|
|
|
import {useSettingValue} from "../../../hooks/useSettings";
|
|
|
|
import * as sdk from "../../../index";
|
|
|
|
import Modal from "../../../Modal";
|
|
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
2020-03-13 20:32:02 +03:00
|
|
|
import withValidation from "../elements/Validation";
|
2020-03-13 03:02:50 +03:00
|
|
|
|
|
|
|
export const ALL_ROOMS = Symbol("ALL_ROOMS");
|
|
|
|
|
|
|
|
const SETTING_NAME = "room_directory_servers";
|
|
|
|
|
|
|
|
const inPlaceOf = (elementRect) => ({
|
|
|
|
right: window.innerWidth - elementRect.right,
|
|
|
|
top: elementRect.top,
|
|
|
|
chevronOffset: 0,
|
|
|
|
chevronFace: "none",
|
|
|
|
});
|
|
|
|
|
2020-03-13 20:32:02 +03:00
|
|
|
const validServer = withValidation({
|
|
|
|
rules: [
|
|
|
|
{
|
|
|
|
key: "required",
|
|
|
|
test: async ({ value }) => !!value,
|
2020-03-16 14:45:51 +03:00
|
|
|
invalid: () => _t("Enter a server name"),
|
2020-03-13 20:32:02 +03:00
|
|
|
}, {
|
|
|
|
key: "available",
|
|
|
|
final: true,
|
|
|
|
test: async ({ value }) => {
|
|
|
|
try {
|
|
|
|
const opts = {
|
|
|
|
limit: 1,
|
|
|
|
server: value,
|
|
|
|
};
|
|
|
|
// check if we can successfully load this server's room directory
|
|
|
|
await MatrixClientPeg.get().publicRooms(opts);
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
valid: () => _t("Looks good"),
|
|
|
|
invalid: () => _t("Can't find this server or its room list"),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
// This dropdown sources homeservers from three places:
|
|
|
|
// + your currently connected homeserver
|
|
|
|
// + homeservers in config.json["roomDirectory"]
|
|
|
|
// + homeservers in SettingsStore["room_directory_servers"]
|
|
|
|
// if a server exists in multiple, only keep the top-most entry.
|
|
|
|
|
|
|
|
const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, selectedInstanceId}) => {
|
|
|
|
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
|
|
|
const userDefinedServers = useSettingValue(SETTING_NAME);
|
|
|
|
|
|
|
|
const handlerFactory = (server, instanceId) => {
|
|
|
|
return () => {
|
|
|
|
onOptionChange(server, instanceId);
|
|
|
|
closeMenu();
|
2018-04-12 01:58:04 +03:00
|
|
|
};
|
2020-03-13 03:02:50 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// we either show the button or the dropdown in its place.
|
|
|
|
let content;
|
|
|
|
if (menuDisplayed) {
|
|
|
|
const config = SdkConfig.get();
|
|
|
|
const roomDirectory = config.roomDirectory || {};
|
|
|
|
|
|
|
|
const hsName = MatrixClientPeg.getHomeserverName();
|
|
|
|
const configServers = new Set(roomDirectory.servers);
|
|
|
|
|
|
|
|
// configured servers take preference over user-defined ones, if one occurs in both ignore the latter one.
|
|
|
|
const removableServers = new Set(userDefinedServers.filter(s => !configServers.has(s) && s !== hsName));
|
|
|
|
const servers = [
|
|
|
|
// we always show our connected HS, this takes precedence over it being configured or user-defined
|
|
|
|
hsName,
|
|
|
|
...Array.from(configServers).filter(s => s !== hsName).sort(),
|
|
|
|
...Array.from(removableServers).sort(),
|
|
|
|
];
|
2018-04-12 01:58:04 +03:00
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
// For our own HS, we can use the instance_ids given in the third party protocols
|
|
|
|
// response to get the server to filter the room list by network for us.
|
|
|
|
// We can't get thirdparty protocols for remote server yet though, so for those
|
|
|
|
// we can only show the default room list.
|
|
|
|
const options = servers.map(server => {
|
|
|
|
const serverSelected = server === selectedServerName;
|
|
|
|
const entries = [];
|
|
|
|
|
|
|
|
const protocolsList = server === hsName ? Object.values(protocols) : [];
|
|
|
|
if (protocolsList.length > 0) {
|
|
|
|
// add a fake protocol with the ALL_ROOMS symbol
|
|
|
|
protocolsList.push({
|
|
|
|
instances: [{
|
|
|
|
instance_id: ALL_ROOMS,
|
|
|
|
desc: _t("All rooms"),
|
|
|
|
}],
|
|
|
|
});
|
|
|
|
}
|
2018-04-12 01:58:04 +03:00
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
protocolsList.forEach(({instances=[]}) => {
|
|
|
|
[...instances].sort((b, a) => {
|
|
|
|
return a.desc.localeCompare(b.desc);
|
|
|
|
}).forEach(({desc, instance_id: instanceId}) => {
|
|
|
|
entries.push(
|
|
|
|
<MenuItemRadio
|
|
|
|
key={String(instanceId)}
|
|
|
|
active={serverSelected && instanceId === selectedInstanceId}
|
|
|
|
onClick={handlerFactory(server, instanceId)}
|
|
|
|
label={desc}
|
|
|
|
className="mx_NetworkDropdown_server_network"
|
|
|
|
>
|
|
|
|
{ desc }
|
|
|
|
</MenuItemRadio>);
|
|
|
|
});
|
2018-04-12 01:58:04 +03:00
|
|
|
});
|
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
let subtitle;
|
|
|
|
if (server === hsName) {
|
|
|
|
subtitle = (
|
|
|
|
<div className="mx_NetworkDropdown_server_subtitle">
|
|
|
|
{_t("Your server")}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2018-04-12 01:58:04 +03:00
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
let removeButton;
|
|
|
|
if (removableServers.has(server)) {
|
|
|
|
const onClick = async () => {
|
|
|
|
closeMenu();
|
|
|
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
|
|
const {finished} = Modal.createTrackedDialog("Network Dropdown", "Remove server", QuestionDialog, {
|
|
|
|
title: _t("Are you sure?"),
|
|
|
|
description: _t("Are you sure you want to remove <b>%(serverName)s</b>", {
|
|
|
|
serverName: server,
|
|
|
|
}, {
|
|
|
|
b: serverName => <b>{ serverName }</b>,
|
|
|
|
}),
|
|
|
|
button: _t("Remove"),
|
2020-03-16 15:00:56 +03:00
|
|
|
fixedWidth: false,
|
|
|
|
}, "mx_NetworkDropdown_dialog");
|
2020-03-13 03:02:50 +03:00
|
|
|
|
|
|
|
const [ok] = await finished;
|
|
|
|
if (!ok) return;
|
|
|
|
|
|
|
|
// delete from setting
|
2020-03-13 17:50:41 +03:00
|
|
|
SettingsStore.setValue(SETTING_NAME, null, "account", servers.filter(s => s !== server));
|
2020-03-13 03:02:50 +03:00
|
|
|
|
|
|
|
// the selected server is being removed, reset to our HS
|
|
|
|
if (serverSelected === server) {
|
|
|
|
onOptionChange(hsName, undefined);
|
|
|
|
}
|
|
|
|
};
|
2020-03-13 17:38:36 +03:00
|
|
|
removeButton = <MenuItem onClick={onClick} label={_t("Remove server")} />;
|
2020-03-13 03:02:50 +03:00
|
|
|
}
|
2018-04-12 01:58:04 +03:00
|
|
|
|
2020-03-13 17:38:36 +03:00
|
|
|
// ARIA: in actual fact the entire menu is one large radio group but for better screen reader support
|
|
|
|
// we use group to notate server wrongly.
|
2020-03-13 03:02:50 +03:00
|
|
|
return (
|
2020-03-13 17:38:36 +03:00
|
|
|
<MenuGroup label={server} className="mx_NetworkDropdown_server" key={server}>
|
2020-03-13 03:02:50 +03:00
|
|
|
<div className="mx_NetworkDropdown_server_title">
|
|
|
|
{ server }
|
|
|
|
{ removeButton }
|
|
|
|
</div>
|
|
|
|
{ subtitle }
|
|
|
|
|
|
|
|
<MenuItemRadio
|
|
|
|
active={serverSelected && !selectedInstanceId}
|
|
|
|
onClick={handlerFactory(server, undefined)}
|
|
|
|
label={_t("Matrix")}
|
|
|
|
className="mx_NetworkDropdown_server_network"
|
|
|
|
>
|
|
|
|
{_t("Matrix")}
|
|
|
|
</MenuItemRadio>
|
|
|
|
{ entries }
|
2020-03-13 17:38:36 +03:00
|
|
|
</MenuGroup>
|
2020-03-13 03:02:50 +03:00
|
|
|
);
|
2018-04-12 01:58:04 +03:00
|
|
|
});
|
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
const onClick = async () => {
|
|
|
|
closeMenu();
|
|
|
|
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
|
|
|
const { finished } = Modal.createTrackedDialog("Network Dropdown", "Add a new server", TextInputDialog, {
|
|
|
|
title: _t("Add a new server"),
|
|
|
|
description: _t("Enter the address of a new server you want to explore."),
|
|
|
|
button: _t("Add"),
|
|
|
|
hasCancel: false,
|
2020-03-16 14:45:51 +03:00
|
|
|
placeholder: _t("Server name"),
|
2020-03-13 20:32:02 +03:00
|
|
|
validator: validServer,
|
2020-03-16 15:00:56 +03:00
|
|
|
fixedWidth: false,
|
|
|
|
}, "mx_NetworkDropdown_dialog");
|
2018-04-12 01:58:04 +03:00
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
const [ok, newServer] = await finished;
|
|
|
|
if (!ok) return;
|
2018-04-12 01:58:04 +03:00
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
if (!userDefinedServers.includes(newServer)) {
|
|
|
|
const servers = [...userDefinedServers, newServer];
|
2020-03-13 17:50:41 +03:00
|
|
|
SettingsStore.setValue(SETTING_NAME, null, "account", servers);
|
2018-04-12 01:58:04 +03:00
|
|
|
}
|
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
onOptionChange(newServer); // change filter to the new server
|
|
|
|
};
|
2018-04-12 01:58:04 +03:00
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
const buttonRect = handle.current.getBoundingClientRect();
|
2020-03-13 17:38:36 +03:00
|
|
|
content = <ContextMenu {...inPlaceOf(buttonRect)} onFinished={closeMenu}>
|
2020-03-13 03:02:50 +03:00
|
|
|
<div className="mx_NetworkDropdown_menu">
|
|
|
|
{options}
|
|
|
|
<MenuItem className="mx_NetworkDropdown_server_add" label={undefined} onClick={onClick}>
|
|
|
|
{_t("Add a new server...")}
|
|
|
|
</MenuItem>
|
|
|
|
</div>
|
|
|
|
</ContextMenu>;
|
|
|
|
} else {
|
2019-03-01 12:39:39 +03:00
|
|
|
let currentValue;
|
2020-03-13 03:02:50 +03:00
|
|
|
if (selectedInstanceId === ALL_ROOMS) {
|
|
|
|
currentValue = _t("All rooms");
|
|
|
|
} else if (selectedInstanceId) {
|
|
|
|
const instance = instanceForInstanceId(protocols, selectedInstanceId);
|
|
|
|
currentValue = _t("%(networkName)s rooms", {
|
|
|
|
networkName: instance.desc,
|
|
|
|
});
|
2018-04-12 01:58:04 +03:00
|
|
|
} else {
|
2020-03-13 03:02:50 +03:00
|
|
|
currentValue = _t("Matrix rooms");
|
2018-04-12 01:58:04 +03:00
|
|
|
}
|
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
content = <ContextMenuButton
|
|
|
|
className="mx_NetworkDropdown_handle"
|
|
|
|
onClick={openMenu}
|
|
|
|
isExpanded={menuDisplayed}
|
|
|
|
>
|
|
|
|
<span>
|
2019-03-01 12:39:39 +03:00
|
|
|
{currentValue}
|
2020-03-13 03:18:53 +03:00
|
|
|
</span> <span className="mx_NetworkDropdown_handle_server">
|
2020-03-13 03:02:50 +03:00
|
|
|
({selectedServerName})
|
|
|
|
</span>
|
|
|
|
</ContextMenuButton>;
|
2018-04-12 01:58:04 +03:00
|
|
|
}
|
2020-03-13 03:02:50 +03:00
|
|
|
|
|
|
|
return <div className="mx_NetworkDropdown" ref={handle}>
|
|
|
|
{content}
|
|
|
|
</div>;
|
|
|
|
};
|
2018-04-12 01:58:04 +03:00
|
|
|
|
|
|
|
NetworkDropdown.propTypes = {
|
2019-07-31 14:19:29 +03:00
|
|
|
onOptionChange: PropTypes.func.isRequired,
|
|
|
|
protocols: PropTypes.object,
|
2018-04-12 01:58:04 +03:00
|
|
|
};
|
|
|
|
|
2020-03-13 03:02:50 +03:00
|
|
|
export default NetworkDropdown;
|