diff --git a/src/app/molecules/space-add-existing/SpaceAddExisting.jsx b/src/app/molecules/space-add-existing/SpaceAddExisting.jsx index 5bbb1da..01de3b1 100644 --- a/src/app/molecules/space-add-existing/SpaceAddExisting.jsx +++ b/src/app/molecules/space-add-existing/SpaceAddExisting.jsx @@ -7,7 +7,7 @@ import { twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; -import { joinRuleToIconSrc } from '../../../util/matrixUtil'; +import { joinRuleToIconSrc, getIdServer, genRoomVia } from '../../../util/matrixUtil'; import { Debounce } from '../../../util/common'; import Text from '../../atoms/text/Text'; @@ -23,7 +23,10 @@ import Dialog from '../dialog/Dialog'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import SearchIC from '../../../../public/res/ic/outlined/search.svg'; +import { useStore } from '../../hooks/useStore'; + function SpaceAddExistingContent({ roomId }) { + const mountStore = useStore(roomId); const [debounce] = useState(new Debounce()); const [process, setProcess] = useState(null); const [selected, setSelected] = useState([]); @@ -75,6 +78,26 @@ function SpaceAddExistingContent({ roomId }) { const handleAdd = async () => { setProcess(`Adding ${selected.length} items...`); + + const promises = selected.map((rId) => { + const room = mx.getRoom(rId); + const via = genRoomVia(room); + if (via.length === 0) { + via.push(getIdServer(rId)); + } + + return mx.sendStateEvent(roomId, 'm.space.child', { + auto_join: false, + suggested: false, + via, + }, rId); + }); + + mountStore.setItem(true); + await Promise.allSettled(promises); + if (mountStore.getItem() !== true) return; + setSelected([]); + setProcess(null); }; return ( diff --git a/src/util/matrixUtil.js b/src/util/matrixUtil.js index f52117b..78d5bb8 100644 --- a/src/util/matrixUtil.js +++ b/src/util/matrixUtil.js @@ -9,7 +9,7 @@ import SpaceLockIC from '../../public/res/ic/outlined/space-lock.svg'; const WELL_KNOWN_URI = '/.well-known/matrix/client'; -async function getBaseUrl(servername) { +export async function getBaseUrl(servername) { let protocol = 'https://'; if (servername.match(/^https?:\/\//) !== null) protocol = ''; const serverDiscoveryUrl = `${protocol}${servername}${WELL_KNOWN_URI}`; @@ -24,7 +24,7 @@ async function getBaseUrl(servername) { } } -function getUsername(userId) { +export function getUsername(userId) { const mx = initMatrix.matrixClient; const user = mx.getUser(userId); if (user === null) return userId; @@ -35,11 +35,11 @@ function getUsername(userId) { return username; } -function getUsernameOfRoomMember(roomMember) { +export function getUsernameOfRoomMember(roomMember) { return roomMember.name || roomMember.userId; } -async function isRoomAliasAvailable(alias) { +export async function isRoomAliasAvailable(alias) { try { const result = await initMatrix.matrixClient.resolveRoomAlias(alias); if (result.room_id) return false; @@ -50,7 +50,7 @@ async function isRoomAliasAvailable(alias) { } } -function getPowerLabel(powerLevel) { +export function getPowerLabel(powerLevel) { if (powerLevel > 9000) return 'Goku'; if (powerLevel > 100) return 'Founder'; if (powerLevel === 100) return 'Admin'; @@ -58,7 +58,7 @@ function getPowerLabel(powerLevel) { return null; } -function parseReply(rawBody) { +export function parseReply(rawBody) { if (rawBody?.indexOf('>') !== 0) return null; let body = rawBody.slice(rawBody.indexOf('<') + 1); const user = body.slice(0, body.indexOf('>')); @@ -79,7 +79,7 @@ function parseReply(rawBody) { }; } -function hasDMWith(userId) { +export function hasDMWith(userId) { const mx = initMatrix.matrixClient; const directIds = [...initMatrix.roomList.directs]; @@ -93,7 +93,7 @@ function hasDMWith(userId) { }); } -function joinRuleToIconSrc(joinRule, isSpace) { +export function joinRuleToIconSrc(joinRule, isSpace) { return ({ restricted: () => (isSpace ? SpaceIC : HashIC), invite: () => (isSpace ? SpaceLockIC : HashLockIC), @@ -101,8 +101,63 @@ function joinRuleToIconSrc(joinRule, isSpace) { }[joinRule]?.() || null); } -export { - getBaseUrl, getUsername, getUsernameOfRoomMember, - isRoomAliasAvailable, getPowerLabel, parseReply, - hasDMWith, joinRuleToIconSrc, -}; +// NOTE: it gives userId with minimum power level 50; +function getHighestPowerUserId(room) { + const userIdToPower = room.currentState.getStateEvents('m.room.power_levels', '')?.getContent().users; + let powerUserId = null; + if (!userIdToPower) return powerUserId; + + Object.keys(userIdToPower).forEach((userId) => { + if (userIdToPower[userId] < 50) return; + if (powerUserId === null) { + powerUserId = userId; + return; + } + if (userIdToPower[userId] > userIdToPower[powerUserId]) { + powerUserId = userId; + } + }); + return powerUserId; +} + +export function getIdServer(userId) { + const idParts = userId.split(':'); + return idParts[1]; +} + +export function getServerToPopulation(room) { + const members = room.getMembers(); + const serverToPop = {}; + + members?.forEach((member) => { + const { userId } = member; + const server = getIdServer(userId); + const serverPop = serverToPop[server]; + if (serverPop === undefined) { + serverToPop[server] = 1; + return; + } + serverToPop[server] = serverPop + 1; + }); + + return serverToPop; +} + +export function genRoomVia(room) { + const via = []; + const userId = getHighestPowerUserId(room); + if (userId) { + const server = getIdServer(userId); + if (server) via.push(server); + } + const serverToPop = getServerToPopulation(room); + const sortedServers = Object.keys(serverToPop).sort( + (svrA, svrB) => serverToPop[svrB] - serverToPop[svrA], + ); + const mostPop3 = sortedServers.slice(0, 3); + if (via.length === 0) return mostPop3; + if (mostPop3.includes(via[0])) { + mostPop3.splice(mostPop3.indexOf(via[0]), 1); + } + return via.concat(mostPop3.slice(0, 2)); +}