From 8bd1f384b94d37ad150874ca64e2f3f962a2ae23 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 8 Sep 2021 10:16:20 +0100 Subject: [PATCH 1/8] Improve tooltips on space quick actions and explore button --- src/components/structures/LeftPanel.tsx | 4 +++- src/components/views/rooms/RoomList.tsx | 12 ++++++++---- src/i18n/strings/en_EN.json | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index d955271249..9a2ebd45e2 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -399,7 +399,9 @@ export default class LeftPanel extends React.Component { mx_LeftPanel_exploreButton_space: !!this.state.activeSpace, })} onClick={this.onExplore} - title={_t("Explore rooms")} + title={this.state.activeSpace + ? _t("Explore %(spaceName)s", { spaceName: this.state.activeSpace.name }) + : _t("Explore rooms")} /> ); diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 4988ea6691..541d0e1d9d 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -48,6 +48,7 @@ import SpaceStore, { ISuggestedRoom, SUGGESTED_ROOMS } from "../../../stores/Spa import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; @@ -522,20 +523,23 @@ export default class RoomList extends React.PureComponent { } else if ( this.props.activeSpace?.canInvite(userId) || this.props.activeSpace?.getMyMembership() === "join" ) { + const spaceName = this.props.activeSpace.name; explorePrompt =
{ _t("Quick actions") }
- { this.props.activeSpace.canInvite(userId) && { _t("Invite people") } - } - { this.props.activeSpace.getMyMembership() === "join" && } + { this.props.activeSpace.getMyMembership() === "join" && { _t("Explore rooms") } - } + }
; } else if (Object.values(this.state.sublists).some(list => list.length > 0)) { const unfilteredLists = RoomListStore.instance.unfilteredLists; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7d754a618a..b2f09686bb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1643,6 +1643,7 @@ "Start a new chat": "Start a new chat", "Explore all public rooms": "Explore all public rooms", "Quick actions": "Quick actions", + "Explore %(spaceName)s": "Explore %(spaceName)s", "Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below", "%(count)s results in all spaces|other": "%(count)s results in all spaces", "%(count)s results in all spaces|one": "%(count)s result in all spaces", From 47fd11050fe0634e3cc2872a527622dcceec4826 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Sep 2021 11:43:46 +0100 Subject: [PATCH 2/8] Switch type check to GitHub Actions and add (working) type check for release mode js-sdk types --- .github/workflows/lint.yaml | 25 +++++++++++++++++++++++++ scripts/ci/js-sdk-to-release.js | 17 +++++++++++++++++ scripts/ci/js-sdk-to-release.sh | 21 --------------------- 3 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/lint.yaml create mode 100755 scripts/ci/js-sdk-to-release.js delete mode 100755 scripts/ci/js-sdk-to-release.sh diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000000..e7f12ab65d --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,25 @@ +name: Lint +on: + pull_request: + branches: [develop] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: c-hive/gha-yarn-cache@v2 + - name: Install Deps + run: "./scripts/ci/install-deps.sh --ignore-scripts" + - name: Typecheck + run: "yarn run lint:types" + - name: Switch js-sdk to release mode + run: | + scripts/ci/js-sdk-to-release.js + pushd node_modules/matrix-js-sdk + yarn install + yarn run build:compile + yarn run build:types + popd + - name: Typecheck (release mode) + run: "yarn run lint:types" + diff --git a/scripts/ci/js-sdk-to-release.js b/scripts/ci/js-sdk-to-release.js new file mode 100755 index 0000000000..e1fecfde03 --- /dev/null +++ b/scripts/ci/js-sdk-to-release.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +const fsProm = require('fs/promises'); + +const PKGJSON = 'node_modules/matrix-js-sdk/package.json'; + +async function main() { + const pkgJson = JSON.parse(await fsProm.readFile(PKGJSON, 'utf8')); + for (const field of ['main', 'typings']) { + if (pkgJson["matrix_lib_"+field] !== undefined) { + pkgJson[field] = pkgJson["matrix_lib_"+field]; + } + } + await fsProm.writeFile(PKGJSON, JSON.stringify(pkgJson, null, 2)); +} + +main(); diff --git a/scripts/ci/js-sdk-to-release.sh b/scripts/ci/js-sdk-to-release.sh deleted file mode 100755 index a03165bd82..0000000000 --- a/scripts/ci/js-sdk-to-release.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -# This changes the js-sdk into 'release mode', that is: -# * The entry point for the library is the babel-compiled lib/index.js rather than src/index.ts -# * There's a 'typings' entry referencing the types output by tsc -# We do this so we can test that each PR still builds / type checks correctly when built -# against the released js-sdk, because if you do things like `import { User } from 'matrix-js-sdk';` -# rather than `import { User } from 'matrix-js-sdk/src/models/user';` it will work fine with the -# js-sdk in development mode but then break at release time. -# We can't use the last release of the js-sdk though: it might not be up to date enough. - -cd node_modules/matrix-js-sdk -for i in main typings -do - lib_value=$(jq -r ".matrix_lib_$i" package.json) - if [ "$lib_value" != "null" ]; then - jq ".$i = .matrix_lib_$i" package.json > package.json.new && mv package.json.new package.json - fi -done -yarn run build:compile -yarn run build:types From f3abb13dc9619a32eee8308cc7eb9e4c0a51bd13 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 8 Sep 2021 12:34:44 +0100 Subject: [PATCH 3/8] Convert crypto/verification/* to Typescript --- .../views/right_panel/EncryptionPanel.tsx | 2 +- .../views/right_panel/VerificationPanel.tsx | 38 ++++++------------- .../verification/VerificationShowSas.tsx | 4 +- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/components/views/right_panel/EncryptionPanel.tsx b/src/components/views/right_panel/EncryptionPanel.tsx index b1c8d427bf..8beb089b38 100644 --- a/src/components/views/right_panel/EncryptionPanel.tsx +++ b/src/components/views/right_panel/EncryptionPanel.tsx @@ -57,7 +57,7 @@ const EncryptionPanel: React.FC = (props: IProps) => { // state to show a spinner immediately after clicking "start verification", // before we have a request const [isRequesting, setRequesting] = useState(false); - const [phase, setPhase] = useState(request && request.phase); + const [phase, setPhase] = useState(request?.phase); useEffect(() => { setRequest(verificationRequest); if (verificationRequest) { diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index 395bdc21e0..a29bdea90b 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -29,43 +29,27 @@ import VerificationQRCode from "../elements/crypto/VerificationQRCode"; import { _t } from "../../../languageHandler"; import SdkConfig from "../../../SdkConfig"; import E2EIcon from "../rooms/E2EIcon"; -import { - PHASE_READY, - PHASE_DONE, - PHASE_STARTED, - PHASE_CANCELLED, -} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import { Phase } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import Spinner from "../elements/Spinner"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import AccessibleButton from "../elements/AccessibleButton"; import VerificationShowSas from "../verification/VerificationShowSas"; -// XXX: Should be defined in matrix-js-sdk -enum VerificationPhase { - PHASE_UNSENT, - PHASE_REQUESTED, - PHASE_READY, - PHASE_DONE, - PHASE_STARTED, - PHASE_CANCELLED, -} - interface IProps { layout: string; request: VerificationRequest; member: RoomMember | User; - phase: VerificationPhase; + phase: Phase; onClose: () => void; isRoomEncrypted: boolean; inDialog: boolean; - key: number; } interface IState { - sasEvent?: SAS; + sasEvent?: SAS["sasEvent"]; emojiButtonClicked?: boolean; reciprocateButtonClicked?: boolean; - reciprocateQREvent?: ReciprocateQRCode; + reciprocateQREvent?: ReciprocateQRCode["reciprocateQREvent"]; } @replaceableComponent("views.right_panel.VerificationPanel") @@ -321,9 +305,9 @@ export default class VerificationPanel extends React.PureComponent { const { request } = this.props; - const { sasEvent, reciprocateQREvent } = request.verifier; + const sasEvent = (request.verifier as SAS).sasEvent; + const reciprocateQREvent = (request.verifier as ReciprocateQRCode).reciprocateQREvent; request.verifier.off('show_sas', this.updateVerifierState); request.verifier.off('show_reciprocate_qr', this.updateVerifierState); this.setState({ sasEvent, reciprocateQREvent }); @@ -402,7 +387,8 @@ export default class VerificationPanel extends React.PureComponent void; onCancel: () => void; - sas: SAS.sas; + sas: IGeneratedSas; isSelf?: boolean; inDialog?: boolean; // whether this component is being shown in a dialog and to use DialogButtons } From 83912daced7bafdea313436ac1270229c1585065 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 8 Sep 2021 13:16:31 +0100 Subject: [PATCH 4/8] Improve the upgrade for restricted user experience --- .../views/dialogs/RoomSettingsDialog.tsx | 5 +- .../tabs/room/SecurityRoomSettingsTab.tsx | 18 ++++++- src/utils/RoomUpgrade.ts | 51 ++++++++++++------- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx index a426dce5c7..a73f0a595b 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.tsx +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -79,7 +79,10 @@ export default class RoomSettingsDialog extends React.Component { ROOM_SECURITY_TAB, _td("Security & Privacy"), "mx_RoomSettingsDialog_securityIcon", - , + this.props.onFinished(true)} + />, )); tabs.push(new Tab( ROOM_ROLES_TAB, diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 081b1a8698..5cb76ebc25 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -39,9 +39,12 @@ import { arrayHasDiff } from "../../../../../utils/arrays"; import SettingsFlag from '../../../elements/SettingsFlag'; import createRoom, { IOpts } from '../../../../../createRoom'; import CreateRoomDialog from '../../../dialogs/CreateRoomDialog'; +import dis from "../../../../../dispatcher/dispatcher"; +import { ROOM_SECURITY_TAB } from "../../../dialogs/RoomSettingsDialog"; interface IProps { roomId: string; + closeSettingsFn: () => void; } interface IState { @@ -220,9 +223,20 @@ export default class SecurityRoomSettingsTab extends React.Component { + onFinished: async (resp) => { if (!resp?.continue) return; - upgradeRoom(room, targetVersion, resp.invite); + const roomId = await upgradeRoom(room, targetVersion, resp.invite, true, true, true); + this.props.closeSettingsFn(); + // switch to the new room in the background + dis.dispatch({ + action: "view_room", + room_id: roomId, + }); + // open new settings on this tab + dis.dispatch({ + action: "open_room_settings", + initial_tab_id: ROOM_SECURITY_TAB, + }); }, }); return; diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index e632ec6345..4dd2a880a0 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -22,6 +22,7 @@ import Modal from "../Modal"; import { _t } from "../languageHandler"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import SpaceStore from "../stores/SpaceStore"; +import Spinner from "../components/views/elements/Spinner"; export async function upgradeRoom( room: Room, @@ -29,8 +30,10 @@ export async function upgradeRoom( inviteUsers = false, handleError = true, updateSpaces = true, + awaitRoom = false, ): Promise { const cli = room.client; + const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); let newRoomId: string; try { @@ -46,27 +49,36 @@ export async function upgradeRoom( throw e; } - // We have to wait for the js-sdk to give us the room back so - // we can more effectively abuse the MultiInviter behaviour - // which heavily relies on the Room object being available. - if (inviteUsers) { - const checkForUpgradeFn = async (newRoom: Room): Promise => { - // The upgradePromise should be done by the time we await it here. - if (newRoom.roomId !== newRoomId) return; - - const toInvite = [ - ...room.getMembersWithMembership("join"), - ...room.getMembersWithMembership("invite"), - ].map(m => m.userId).filter(m => m !== cli.getUserId()); - - if (toInvite.length > 0) { - // Errors are handled internally to this function - await inviteUsersToRoom(newRoomId, toInvite); + if (awaitRoom || inviteUsers) { + await new Promise(resolve => { + // already have the room + if (room.client.getRoom(newRoomId)) { + resolve(); + return; } - cli.removeListener('Room', checkForUpgradeFn); - }; - cli.on('Room', checkForUpgradeFn); + // We have to wait for the js-sdk to give us the room back so + // we can more effectively abuse the MultiInviter behaviour + // which heavily relies on the Room object being available. + const checkForRoomFn = (newRoom: Room) => { + if (newRoom.roomId !== newRoomId) return; + resolve(); + cli.off("Room", checkForRoomFn); + }; + cli.on("Room", checkForRoomFn); + }); + } + + if (inviteUsers) { + const toInvite = [ + ...room.getMembersWithMembership("join"), + ...room.getMembersWithMembership("invite"), + ].map(m => m.userId).filter(m => m !== cli.getUserId()); + + if (toInvite.length > 0) { + // Errors are handled internally to this function + await inviteUsersToRoom(newRoomId, toInvite); + } } if (updateSpaces) { @@ -89,5 +101,6 @@ export async function upgradeRoom( } } + modal.close(); return newRoomId; } From d4bac4752d0eabf8304f6bb310505ad06406233d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 8 Sep 2021 13:34:26 +0100 Subject: [PATCH 5/8] Make space members and user info behave more expectedly --- src/components/structures/RightPanel.tsx | 2 +- .../views/context_menus/SpaceContextMenu.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 13 ++++++++----- src/i18n/strings/en_EN.json | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 67634c63d2..5b12e542bd 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -269,7 +269,7 @@ export default class RightPanel extends React.Component { case RightPanelPhases.EncryptionPanel: panel = { defaultDispatcher.dispatch({ action: Action.SetRightPanelPhase, phase: RightPanelPhases.SpaceMemberList, - refireParams: { space: space }, + refireParams: { space }, }); onFinished(); }; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index d15f349d62..f90643f1df 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -1278,7 +1278,9 @@ const BasicUserInfo: React.FC<{ // hide the Roles section for DMs as it doesn't make sense there if (!DMRoomMap.shared().getUserIdForRoomId((member as RoomMember).roomId)) { memberDetails =
-

{ _t("Role") }

+

{ _t("Role in ", {}, { + RoomName: () => { room.name }, + }) }

= ({ // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time if (room && phase === RightPanelPhases.EncryptionPanel) { previousPhase = RightPanelPhases.RoomMemberInfo; - refireParams = { member: member }; + refireParams = { member }; + } else if (room?.isSpaceRoom() && SpaceStore.spacesEnabled) { + previousPhase = previousPhase = RightPanelPhases.SpaceMemberList; + refireParams = { space: room }; } else if (room) { - previousPhase = previousPhase = SpaceStore.spacesEnabled && room.isSpaceRoom() - ? RightPanelPhases.SpaceMemberList - : RightPanelPhases.RoomMemberList; + previousPhase = RightPanelPhases.RoomMemberList; } const onEncryptionPanelClose = () => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7d754a618a..10152193a3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1866,7 +1866,7 @@ "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?", "Deactivate user": "Deactivate user", "Failed to deactivate user": "Failed to deactivate user", - "Role": "Role", + "Role in ": "Role in ", "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", "Edit devices": "Edit devices", "Security": "Security", From 17e0a4b3d772cdf96043fac3029d13c19bc9d1d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 8 Sep 2021 17:14:51 +0100 Subject: [PATCH 6/8] iterate PR based on feedback --- src/utils/RoomUpgrade.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index 4dd2a880a0..366f49d892 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -33,7 +33,7 @@ export async function upgradeRoom( awaitRoom = false, ): Promise { const cli = room.client; - const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); + const spinnerModal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); let newRoomId: string; try { @@ -101,6 +101,6 @@ export async function upgradeRoom( } } - modal.close(); + spinnerModal.close(); return newRoomId; } From 8f221a484940b610d3f8ace353d20925872fb948 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Sep 2021 18:37:13 +0100 Subject: [PATCH 7/8] Rename type checking script 'cos it's type chekcing, not linting --- .github/workflows/{lint.yaml => typecheck.yaml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{lint.yaml => typecheck.yaml} (97%) diff --git a/.github/workflows/lint.yaml b/.github/workflows/typecheck.yaml similarity index 97% rename from .github/workflows/lint.yaml rename to .github/workflows/typecheck.yaml index e7f12ab65d..bbbf7185d7 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/typecheck.yaml @@ -1,4 +1,4 @@ -name: Lint +name: Type Check on: pull_request: branches: [develop] From b67883b4f19abe2aa2c59d3c8baf36443c826670 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 9 Sep 2021 09:41:11 +0100 Subject: [PATCH 8/8] Remove unnecessary pushd type: task --- .github/workflows/typecheck.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/typecheck.yaml b/.github/workflows/typecheck.yaml index bbbf7185d7..2e08418cf6 100644 --- a/.github/workflows/typecheck.yaml +++ b/.github/workflows/typecheck.yaml @@ -15,11 +15,10 @@ jobs: - name: Switch js-sdk to release mode run: | scripts/ci/js-sdk-to-release.js - pushd node_modules/matrix-js-sdk + cd node_modules/matrix-js-sdk yarn install yarn run build:compile yarn run build:types - popd - name: Typecheck (release mode) run: "yarn run lint:types"