diff --git a/CHANGELOG.md b/CHANGELOG.md index bf252ece2a..524bc9b90e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Add support for new format of invitation link: `c=&t=&s=&i=` ([#2310](https://github.com/TryQuiet/quiet/issues/2310)) * Use server for downloading initial community metadata if v2 invitation link is detected ([#2295](https://github.com/TryQuiet/quiet/issues/2295)) +* Adds connection status information to messages panel on desktop when no peers are connected ([#1706](https://github.com/TryQuiet/quiet/ # Refactorings: @@ -25,6 +26,7 @@ # Refactorings: * Use ack for CREATE_NETWORK and simplify +* Logging from all sources can be written to node console * Move Community model to the backend # Fixes: diff --git a/bs.log b/bs.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json index c9e44e9828..1f87134098 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,11 @@ "postpublish": "node copy-changelog.js && git add . && git commit -m 'Update packages CHANGELOG.md' && git push", "start:desktop": "lerna run --scope @quiet/desktop start", "lint:all": "lerna run lint", - "distAndRunE2ETests:mac:local": "lerna run --scope @quiet/desktop distMac:local && lerna run --scope e2e-tests test:localBinary --", + "clean": "lerna clean", + "bootstrap": "lerna bootstrap", + "bootstrap:clean": "npm run clean && npm run bootstrap", + "lerna:run:desktop": "lerna run --scope @quiet/desktop", + "distAndRunE2ETests:mac:local": "npm run lerna:run:desktop distMac:local && lerna run --scope e2e-tests test:localBinary --", "e2e:linux:build": "lerna run --scope @quiet/backend webpack:prod && lerna run --scope @quiet/desktop distUbuntu && lerna run --scope e2e-tests linux:copy", "e2e:linux:run": "lerna run --scope e2e-tests test --", "prepare": "husky", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 13228c2b77..73dd1dc142 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -12,6 +12,10 @@ * Refactor: Consolidate profile photo validation and match magic byte check to type check +* Refactor: Updates peer connecting events to include updating peers DB and refactors some payload types ([#1706](https://github.com/TryQuiet/quiet/issues/1706)) + +* Refactor: Update Tor initialized status when Tor is connected ([#1706](https://github.com/TryQuiet/quiet/issues/1706)) + [2.0.3-alpha.6] * Fix: filter out invalid peer addresses in peer list. Update peer list in localdb. diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.ts index 0489557f08..bc34eeb2d1 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -45,6 +45,7 @@ import { type SavedOwnerCertificatePayload, type UserProfile, type UserProfilesStoredEvent, + PeersNetworkDataPayload, } from '@quiet/types' import Logger from '../common/logger' import { CONFIG_OPTIONS, QUIET_DIR, SERVER_IO_PROVIDER, SOCKS_PROXY_AGENT } from '../const' @@ -565,7 +566,21 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI await this.libp2pService.createInstance(params) // Libp2p event listeners - this.libp2pService.on(Libp2pEvents.PEER_CONNECTED, (payload: { peers: string[] }) => { + this.libp2pService.on(Libp2pEvents.PEER_CONNECTED, async (payload: PeersNetworkDataPayload) => { + const peerStats: { [peerId: string]: NetworkStats } = await payload.peers.reduce( + async (updateObj, peer) => { + return { + ...(await updateObj), + [peer.peer]: { + peerId: peer.peer, + lastSeen: peer.lastSeen, + connectionTime: peer.connectionDuration, + } as NetworkStats, + } + }, + Promise.resolve({} as { [peerId: string]: NetworkStats }) + ) + await this.localDbService.update(LocalDBKeys.PEERS, peerStats) this.serverIoProvider.io.emit(SocketActionTypes.PEER_CONNECTED, payload) }) diff --git a/packages/backend/src/nest/libp2p/libp2p.service.ts b/packages/backend/src/nest/libp2p/libp2p.service.ts index c4129e9888..c185218d38 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.ts @@ -5,7 +5,13 @@ import { mplex } from '@libp2p/mplex' import { multiaddr } from '@multiformats/multiaddr' import { Inject, Injectable } from '@nestjs/common' import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' -import { ConnectionProcessInfo, type NetworkDataPayload, PeerId, SocketActionTypes } from '@quiet/types' +import { + ConnectionProcessInfo, + type NetworkDataPayload, + PeerId, + SocketActionTypes, + PeersNetworkDataPayload, +} from '@quiet/types' import crypto from 'crypto' import { EventEmitter } from 'events' import { Agent } from 'https' @@ -153,20 +159,32 @@ export class Libp2pService extends EventEmitter { }) this.libp2pInstance.addEventListener('peer:connect', async peer => { + this.logger(`Connecting peer: ${JSON.stringify(peer)}`) const remotePeerId = peer.detail.remotePeer.toString() const localPeerId = peerId.toString() this.logger(`${localPeerId} connected to ${remotePeerId}`) - this.connectedPeers.set(remotePeerId, DateTime.utc().valueOf()) - this.logger(`${localPeerId} is connected to ${this.connectedPeers.size} peers`) + const now = DateTime.utc() + this.connectedPeers.set(remotePeerId, now.valueOf()) + this.logger(`${localPeerId} is now connected to ${this.connectedPeers.size} peers`) this.logger(`${localPeerId} has ${this.libp2pInstance?.getConnections().length} open connections`) - this.emit(Libp2pEvents.PEER_CONNECTED, { - peers: [remotePeerId], - }) + const payload: PeersNetworkDataPayload = { + peers: [ + { + peer: remotePeerId, + lastSeen: now.toSeconds(), + connectionDuration: 0, + }, + ], + } + + this.logger(`Emitting ${Libp2pEvents.PEER_CONNECTED} event with payload ${JSON.stringify(payload)}`) + this.emit(Libp2pEvents.PEER_CONNECTED, payload) }) this.libp2pInstance.addEventListener('peer:disconnect', async peer => { + this.logger(`Disconnecting peer: ${JSON.stringify(peer)}`) const remotePeerId = peer.detail.remotePeer.toString() const localPeerId = peerId.toString() this.logger(`${localPeerId} disconnected from ${remotePeerId}`) @@ -187,12 +205,14 @@ export class Libp2pService extends EventEmitter { const connectionDuration: number = connectionEndTime - connectionStartTime this.connectedPeers.delete(remotePeerId) - this.logger(`${localPeerId} is connected to ${this.connectedPeers.size} peers`) + this.logger(`${localPeerId} is now connected to ${this.connectedPeers.size} peers`) const peerStat: NetworkDataPayload = { peer: remotePeerId, connectionDuration, lastSeen: connectionEndTime, } + + this.logger(`Emitting ${Libp2pEvents.PEER_DISCONNECTED} event with payload ${JSON.stringify(peerStat)}`) this.emit(Libp2pEvents.PEER_DISCONNECTED, peerStat) }) diff --git a/packages/backend/src/nest/tor/tor-control.service.ts b/packages/backend/src/nest/tor/tor-control.service.ts index b83040c7a4..4cea2a2075 100644 --- a/packages/backend/src/nest/tor/tor-control.service.ts +++ b/packages/backend/src/nest/tor/tor-control.service.ts @@ -1,9 +1,10 @@ import { Inject, Injectable } from '@nestjs/common' import net from 'net' -import { CONFIG_OPTIONS, TOR_CONTROL_PARAMS } from '../const' -import { ConfigOptions } from '../types' +import { CONFIG_OPTIONS, SERVER_IO_PROVIDER, TOR_CONTROL_PARAMS } from '../const' +import { ConfigOptions, ServerIoProviderTypes } from '../types' import { TorControlAuthType, TorControlParams } from './tor.types' import Logger from '../common/logger' +import { SocketActionTypes } from '@quiet/types' @Injectable() export class TorControl { @@ -14,7 +15,8 @@ export class TorControl { constructor( @Inject(TOR_CONTROL_PARAMS) public torControlParams: TorControlParams, - @Inject(CONFIG_OPTIONS) public configOptions: ConfigOptions + @Inject(CONFIG_OPTIONS) public configOptions: ConfigOptions, + @Inject(SERVER_IO_PROVIDER) public readonly serverIoProvider: ServerIoProviderTypes ) { this.isSending = false } @@ -63,6 +65,7 @@ export class TorControl { this.logger(`Connecting to Tor, host: ${this.torControlParams.host} port: ${this.torControlParams.port}`) await this._connect() this.logger('Tor connected') + this.serverIoProvider.io.emit(SocketActionTypes.TOR_INITIALIZED) return } catch (e) { this.logger(e) diff --git a/packages/backend/tsconfig.build.json b/packages/backend/tsconfig.build.json index e8c62532d1..975dfc9768 100644 --- a/packages/backend/tsconfig.build.json +++ b/packages/backend/tsconfig.build.json @@ -4,7 +4,7 @@ "target": "ES2020", "module": "ES2022", "strict": true, - "declaration": true, + "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md index e5ad80e5c2..3f4b361c9f 100644 --- a/packages/desktop/CHANGELOG.md +++ b/packages/desktop/CHANGELOG.md @@ -2,8 +2,13 @@ # New features: +* Adds connection status information to messages panel when no peers are connected ([#1706](https://github.com/TryQuiet/quiet/issues/1706)) + # Refactorings: +* Updates all logging from renderer to write to the node console +* Updates usages of `act` in tests to use `@testing-library/react` to avoid errors/warnings + # Fixes: [2.2.0] diff --git a/packages/desktop/package-lock.json b/packages/desktop/package-lock.json index 8c5ca79ed2..e4cd07c031 100644 --- a/packages/desktop/package-lock.json +++ b/packages/desktop/package-lock.json @@ -28,6 +28,7 @@ "@babel/core": "^7.22.5", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-transform-block-scoping": "7.5.5", "@babel/preset-env": "^7.22.5", "@babel/preset-react": "^7.22.5", "@cypress/react18": "2.0.0", @@ -1490,15 +1491,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz", + "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -9515,6 +9514,30 @@ "@babel/core": "^7.4.0-0" } }, + "node_modules/@storybook/builder-webpack5/node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/@storybook/addons": { "version": "6.5.15", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.15.tgz", @@ -11415,6 +11438,30 @@ "@babel/core": "^7.4.0-0" } }, + "node_modules/@storybook/core-common/node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@storybook/core-common/node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@storybook/core-common/node_modules/@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -13709,6 +13756,30 @@ "@babel/core": "^7.4.0-0" } }, + "node_modules/@storybook/manager-webpack5/node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@storybook/manager-webpack5/node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@storybook/manager-webpack5/node_modules/@storybook/addons": { "version": "6.5.15", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.15.tgz", @@ -47880,12 +47951,13 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz", + "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.13" } }, "@babel/plugin-transform-classes": { @@ -53767,6 +53839,21 @@ "semver": "^6.1.2" } }, + "@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.0" + } + }, "@storybook/addons": { "version": "6.5.15", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.15.tgz", @@ -55152,6 +55239,21 @@ "semver": "^6.1.2" } }, + "@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.0" + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -56928,6 +57030,21 @@ "semver": "^6.1.2" } }, + "@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.0" + } + }, "@storybook/addons": { "version": "6.5.15", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.15.tgz", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 303b5cbb7b..d7d1c3dedd 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -89,7 +89,7 @@ "copyBinariesDarwin": "cp -R ../../3rd-party/tor/$SOURCE_PATH/ ./tor/ && chmod 775 ./tor/arm64/tor ./tor/x64/tor", "copyBinariesWin": "xcopy ..\\..\\3rd-party\\tor\\win32 .\\tor\\", "pullLibs": "wget -N https://zbay-binaries.s3.us-east-2.amazonaws.com/$SOURCE_PATH/libssl.so -P ./ && chmod 775 ./libssl.so", - "distMac:local": "export DEBUG=* SOURCE_PATH=darwin TEST_MODE=true IS_LOCAL=true && npm run copyBinariesDarwin && npm run build:dev:dist && electron-builder --mac -p never -c.mac.type=development -c.mac.identity=null", + "distMac:local": "export DEBUG=* SOURCE_PATH=darwin TEST_MODE=true IS_LOCAL=true && npm run copyBinariesDarwin && ./scripts/shell/clear_electron_builds.sh && npm run build:dev:dist && electron-builder --mac -p never -c.mac.type=development -c.mac.identity=null", "dist": "npm run distMac", "distMac": "export SOURCE_PATH=darwin TEST_MODE=true && npm run copyBinariesDarwin && npm run build:prod && electron-builder --mac", "distUbuntu": "export SOURCE_PATH=linux TEST_MODE=true && npm run setMainEnvs && npm run copyBinaries && npm run pullLibs && npm run build:prod && electron-builder --linux", @@ -151,6 +151,7 @@ "@babel/core": "^7.22.5", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-transform-block-scoping": "7.5.5", "@babel/preset-env": "^7.22.5", "@babel/preset-react": "^7.22.5", "@cypress/react18": "2.0.0", diff --git a/packages/desktop/scripts/shell/clear_electron_builds.sh b/packages/desktop/scripts/shell/clear_electron_builds.sh new file mode 100755 index 0000000000..8ee4b66916 --- /dev/null +++ b/packages/desktop/scripts/shell/clear_electron_builds.sh @@ -0,0 +1,3 @@ +#! /bin/zsh +set -o kshglob +rm -rf dist/*.(zip|blockmap|dmg) || true \ No newline at end of file diff --git a/packages/desktop/src/renderer/components/ChangeUsername/ChangeUsername.component.tsx b/packages/desktop/src/renderer/components/ChangeUsername/ChangeUsername.component.tsx index 669eaa1391..f59af39bbb 100644 --- a/packages/desktop/src/renderer/components/ChangeUsername/ChangeUsername.component.tsx +++ b/packages/desktop/src/renderer/components/ChangeUsername/ChangeUsername.component.tsx @@ -16,6 +16,7 @@ import { TextInput } from '../../forms/components/textInput' import { userNameField } from '../../forms/fields/createUserFields' import { parseName } from '@quiet/common' +import { defaultLogger } from '../../logger' const PREFIX = 'ChangeUsername-' @@ -174,7 +175,7 @@ export const ChangeUsername: React.FC = ({ const onSubmit = useCallback( (values: ChangeUserNameValues) => { if (errors.userName) { - console.error('Cannot submit form with errors') + defaultLogger.error('Cannot submit form with errors') return } diff --git a/packages/desktop/src/renderer/components/Channel/Channel.stories.cy.tsx b/packages/desktop/src/renderer/components/Channel/Channel.stories.cy.tsx index b9c82b83be..87b1fad56b 100644 --- a/packages/desktop/src/renderer/components/Channel/Channel.stories.cy.tsx +++ b/packages/desktop/src/renderer/components/Channel/Channel.stories.cy.tsx @@ -12,6 +12,7 @@ import { DisplayableMessage } from '@quiet/types' import ChannelComponent from './ChannelComponent' import { payloadDuplicated, payloadUnregistered } from '../widgets/userLabel/UserLabel.types' +import { DateTime } from 'luxon' const Template: ComponentStory = () => { const [messages, setMessages] = useState<{ @@ -21,6 +22,8 @@ const Template: ComponentStory = () => { } }>(mock_messages()) + const [connectedPeers, setConnectedPeers] = useState(["peer"]) + const onInputEnter = (message: string) => { const _message: DisplayableMessage = { id: '32', @@ -62,7 +65,7 @@ const Template: ComponentStory = () => { privateKey: 'privateKey', }, peerId: { - id: 'id', + id: 'peer', privKey: 'privKey', pubKey: 'pubKey', }, @@ -79,6 +82,10 @@ const Template: ComponentStory = () => { joinTimestamp: null, }} isCommunityInitialized={true} + connectedPeers={connectedPeers} + communityPeerList={connectedPeers} + lastConnectedTime={DateTime.utc().toSeconds()} + allPeersDisconnectedTime={undefined} uploadedFileModal={{ open: false, handleOpen: function (_args?: any): any {}, diff --git a/packages/desktop/src/renderer/components/Channel/Channel.stories.tsx b/packages/desktop/src/renderer/components/Channel/Channel.stories.tsx index 948bfc678a..21df63e2e8 100644 --- a/packages/desktop/src/renderer/components/Channel/Channel.stories.tsx +++ b/packages/desktop/src/renderer/components/Channel/Channel.stories.tsx @@ -11,6 +11,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend' import ChannelComponent, { ChannelComponentProps } from './ChannelComponent' import { UploadFilesPreviewsProps } from './File/UploadingPreview' import { DownloadState } from '@quiet/types' +import { DateTime } from 'luxon' const args: Partial = { user: { @@ -54,6 +55,7 @@ const args: Partial = { pubKey: 'pubKey', }, pendingMessages: {}, + lastConnectedTime: DateTime.utc().toMillis(), channelId: 'general', channelName: 'general', lazyLoading: function (_load: boolean): void {}, diff --git a/packages/desktop/src/renderer/components/Channel/Channel.tsx b/packages/desktop/src/renderer/components/Channel/Channel.tsx index 0b56f29188..f93f3048be 100644 --- a/packages/desktop/src/renderer/components/Channel/Channel.tsx +++ b/packages/desktop/src/renderer/components/Channel/Channel.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useEffect } from 'react' import { shell, ipcRenderer } from 'electron' import { useDispatch, useSelector } from 'react-redux' -import { identity, messages, publicChannels, communities, files, network } from '@quiet/state-manager' +import { identity, messages, publicChannels, communities, files, network, connection } from '@quiet/state-manager' import { FileMetadata, CancelDownload, FileContent, FilePreviewData } from '@quiet/types' import ChannelComponent, { ChannelComponentProps } from './ChannelComponent' @@ -38,6 +38,10 @@ const Channel = () => { const initializedCommunities = useSelector(network.selectors.initializedCommunities) const isCommunityInitialized = Boolean(community && initializedCommunities[community.id]) + const communityPeerList = useSelector(communities.selectors.peerList) + const connectedPeers = useSelector(network.selectors.connectedPeers) + const lastConnectedTime = useSelector(network.selectors.communityLastConnectedAt) + const allPeersDisconnectedTime = useSelector(network.selectors.allPeersDisconnectedAt) const pendingGeneralChannelRecreationSelector = useSelector(publicChannels.selectors.pendingGeneralChannelRecreation) @@ -194,7 +198,7 @@ const Channel = () => { if (!user || !currentChannelId) return null const channelComponentProps: ChannelComponentProps = { - user: user, + user, channelId: currentChannelId, channelName: currentChannelName, messages: { @@ -202,7 +206,7 @@ const Channel = () => { groups: currentChannelDisplayableMessages, }, newestMessage: newestCurrentChannelMessage, - pendingMessages: pendingMessages, + pendingMessages, downloadStatuses: downloadStatusesMapping, lazyLoading: lazyLoading, onInputChange: onInputChange, @@ -210,11 +214,15 @@ const Channel = () => { openUrl: openUrl, handleFileDrop: handleFileDrop, openFilesDialog: openFilesDialog, - isCommunityInitialized: isCommunityInitialized, + isCommunityInitialized, + communityPeerList, + connectedPeers, + lastConnectedTime, + allPeersDisconnectedTime, handleClipboardFiles: handleClipboardFiles, - uploadedFileModal: uploadedFileModal, + uploadedFileModal, openContextMenu: openContextMenu, - pendingGeneralChannelRecreation: pendingGeneralChannelRecreation, + pendingGeneralChannelRecreation, unregisteredUsernameModalHandleOpen, duplicatedUsernameModalHandleOpen, } diff --git a/packages/desktop/src/renderer/components/Channel/ChannelComponent.tsx b/packages/desktop/src/renderer/components/Channel/ChannelComponent.tsx index 752a06f0e1..82543b59cf 100644 --- a/packages/desktop/src/renderer/components/Channel/ChannelComponent.tsx +++ b/packages/desktop/src/renderer/components/Channel/ChannelComponent.tsx @@ -26,6 +26,8 @@ import { NewMessagesInfoComponent } from './NewMessagesInfo/NewMessagesInfoCompo import { FileActionsProps } from './File/FileComponent/FileComponent' import { UseModalType } from '../../containers/hooks' import { HandleOpenModalType } from '../widgets/userLabel/UserLabel.types' +import { defaultLogger } from '../../logger' +import ChannelNetworkStatus from '../widgets/channels/ChannelNetworkStatus' const ChannelMessagesWrapperStyled = styled(Grid)(({ theme }) => ({ position: 'relative', @@ -51,6 +53,10 @@ export interface ChannelComponentProps { openFilesDialog: () => void handleFileDrop: (arg: any) => void isCommunityInitialized: boolean + connectedPeers: string[] | undefined + communityPeerList: string[] | undefined + lastConnectedTime: number + allPeersDisconnectedTime: number | undefined handleClipboardFiles: (arg: ArrayBuffer, ext: string, name: string) => void uploadedFileModal?: UseModalType<{ src: string @@ -84,6 +90,10 @@ export const ChannelComponent: React.FC(0) + const checkForConnectedPeers = (connectedPeers: string[] | undefined) => { + if (connectedPeers && connectedPeers.length > 0) { + return true + } + return false + } + + const checkForCommunityPeers = (peerList: string[] | undefined) => { + defaultLogger.info(peerList, peerList?.length) + if (peerList && peerList.length > 1) { + return true + } + return false + } + + const [isConnectedToOtherPeers, onConnectedPeersChange] = React.useState( + checkForConnectedPeers(connectedPeers) + ) + + const [communityHasPeers, onCommunityPeerListChanged] = React.useState( + checkForCommunityPeers(communityPeerList) + ) + + useEffect(() => { + onConnectedPeersChange(checkForConnectedPeers(connectedPeers)) + }, [connectedPeers]) + + useEffect(() => { + onCommunityPeerListChanged(checkForCommunityPeers(communityPeerList)) + }, [communityPeerList]) + const updateMathMessagesRendered = () => { // To rerender Channel on each call onMathMessageRendered(mathMessagesRendered + 1) @@ -227,6 +268,10 @@ export const ChannelComponent: React.FC + = args => { return @@ -13,7 +14,7 @@ export const Component = Template.bind({}) const args: CreateChannelProps = { open: true, createChannel: function (name: string): void { - console.log('creating channel: ', name) + defaultLogger.info('creating channel: ', name) }, handleClose: function (): void {}, clearErrorsDispatch: function (): void {}, diff --git a/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.test.tsx b/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.test.tsx index 59a36aa431..b54d71f586 100644 --- a/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.test.tsx +++ b/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.test.tsx @@ -2,7 +2,7 @@ import React from 'react' import '@testing-library/jest-dom/extend-expect' import userEvent from '@testing-library/user-event' import { screen, waitFor } from '@testing-library/dom' -import { act } from 'react-dom/test-utils' +import { act } from '@testing-library/react' import { take } from 'typed-redux-saga' import MockedSocket from 'socket.io-mock' import { ioMock } from '../../../../shared/setupTests' @@ -16,6 +16,7 @@ import { ModalName } from '../../../sagas/modals/modals.types' import { modalsActions } from '../../../sagas/modals/modals.slice' import { getFactory, identity, publicChannels } from '@quiet/state-manager' +import { defaultLogger } from '../../../logger' describe('Add new channel', () => { let socket: MockedSocket @@ -50,7 +51,7 @@ describe('Add new channel', () => { async () => await waitFor(() => { user.click(screen.getByText('Create Channel')).catch(e => { - console.error(e) + defaultLogger.error(e) }) }) ) diff --git a/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.tsx b/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.tsx index 3d72eb91d1..9f45e5ff3b 100644 --- a/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.tsx +++ b/packages/desktop/src/renderer/components/Channel/CreateChannel/CreateChannel.tsx @@ -8,6 +8,7 @@ import { useModal } from '../../../containers/hooks' import { ModalName } from '../../../sagas/modals/modals.types' import { flushSync } from 'react-dom' import { generateChannelId } from '@quiet/common' +import { defaultLogger } from '../../../logger' export const CreateChannel = () => { const dispatch = useDispatch() @@ -46,7 +47,7 @@ export const CreateChannel = () => { // Clear errors clearErrors() if (!user) { - console.error('No identity found') + defaultLogger.error('No identity found') dispatch( errors.actions.addError({ type: SocketActionTypes.CREATE_CHANNEL, diff --git a/packages/desktop/src/renderer/components/Channel/DeleteChannel/DeleteChannel.stories.tsx b/packages/desktop/src/renderer/components/Channel/DeleteChannel/DeleteChannel.stories.tsx index bd498ae0d9..6fec7b8839 100644 --- a/packages/desktop/src/renderer/components/Channel/DeleteChannel/DeleteChannel.stories.tsx +++ b/packages/desktop/src/renderer/components/Channel/DeleteChannel/DeleteChannel.stories.tsx @@ -5,6 +5,7 @@ import { withTheme } from '../../../storybook/decorators' import { useModal } from '../../../containers/hooks' import DeleteChannelComponent, { DeleteChannelProps } from './DeleteChannelComponent' +import { defaultLogger } from '../../../logger' const Template: ComponentStory = args => { return @@ -15,7 +16,7 @@ export const Component = Template.bind({}) const args: ReturnType & DeleteChannelProps = { channelName: 'general', deleteChannel: () => { - console.log('deleting channel') + defaultLogger.info('deleting channel') }, open: true, // @ts-expect-error diff --git a/packages/desktop/src/renderer/components/Channel/DropZone/DropZoneComponent.tsx b/packages/desktop/src/renderer/components/Channel/DropZone/DropZoneComponent.tsx index 0773af35d9..ba69e0e1ea 100644 --- a/packages/desktop/src/renderer/components/Channel/DropZone/DropZoneComponent.tsx +++ b/packages/desktop/src/renderer/components/Channel/DropZone/DropZoneComponent.tsx @@ -7,6 +7,7 @@ import Icon from '../../ui/Icon/Icon' import dropFiles from '../../../static/images/dropFiles.svg' import { DropTargetMonitor, useDrop } from 'react-dnd' import { NativeTypes } from 'react-dnd-html5-backend' +import { defaultLogger } from '../../../logger' const StyledDropZoneComponent = styled(Grid)(() => ({ position: 'relative', @@ -55,7 +56,7 @@ export const DropZoneComponent: React.FC = ({ children, if (fs.statSync(item.files[0].path).isDirectory()) return } catch (e) { // See: https://github.com/react-dnd/react-dnd/issues/3458 - console.error('drop error: ', e.message) + defaultLogger.error('drop error: ', e.message) return } handleFileDrop(item) @@ -67,7 +68,7 @@ export const DropZoneComponent: React.FC = ({ children, collect: (monitor: DropTargetMonitor) => { const item: any = monitor.getItem() if (item) { - console.log('collect', item.files, item.items) + defaultLogger.info('collect', item.files, item.items) } return { diff --git a/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.stories.tsx b/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.stories.tsx index 0c33e712ab..f134f34831 100644 --- a/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.stories.tsx +++ b/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.stories.tsx @@ -3,6 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react' import FileComponent, { FileComponentProps } from './FileComponent' import { withTheme } from '../../../../storybook/decorators' import { DownloadState } from '@quiet/types' +import { defaultLogger } from '../../../../logger' const Template: ComponentStory = args => { return ( @@ -56,6 +57,7 @@ const args: FileComponentProps = { downloadState: DownloadState.Ready, downloadProgress: undefined, }, + isUnsent: false, } Uploading.args = { @@ -97,13 +99,13 @@ Queued.args = { }, }, cancelDownload: () => { - console.log('cancel download') + defaultLogger.info('cancel download') }, } Ready.args = { ...args, downloadFile: () => { - console.log('download file') + defaultLogger.info('download file') }, } Downloading.args = { @@ -119,7 +121,7 @@ Downloading.args = { }, }, cancelDownload: () => { - console.log('cancel download') + defaultLogger.info('cancel download') }, } Canceling.args = { @@ -151,7 +153,7 @@ Completed.args = { }, }, openContainingFolder: () => { - console.log('show in folder') + defaultLogger.info('show in folder') }, } Malicious.args = { diff --git a/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.test.tsx b/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.test.tsx index 3fe8c311fc..1edcc86790 100644 --- a/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.test.tsx +++ b/packages/desktop/src/renderer/components/Channel/File/FileComponent/FileComponent.test.tsx @@ -38,13 +38,14 @@ describe('FileComponent', () => { downloadState: DownloadState.Ready, downloadProgress: undefined, }} + isUnsent={false} /> ) expect(result.baseElement).toMatchInlineSnapshot(`
@@ -82,6 +83,113 @@ describe('FileComponent', () => {
+
+
+ +

+ Download file +

+
+
+
+
+ + + `) + }) + + it('renders component as unsent', () => { + const result = renderComponent( + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
+
+ +
+
+ +
+
+
+ my-file-name-goes-here-an-isnt-truncated + .zip +
+

+ 2 KB +

+
+
+
+
({ @@ -52,6 +54,10 @@ const FileComponentStyled = styled('div')(({ theme }) => ({ [`& .${classes.filename}`]: { marginLeft: '16px', }, + + [`& .${classes.unsent}`]: { + opacity: 0.5, + }, })) const ActionIndicatorStyled = styled('div')(() => ({ @@ -126,6 +132,7 @@ const ActionIndicator: React.FC<{ export interface FileComponentProps { message: DisplayableMessage downloadStatus?: DownloadStatus + isUnsent: boolean } export interface FileActionsProps { @@ -137,6 +144,7 @@ export interface FileActionsProps { export const FileComponent: React.FC = ({ message, downloadStatus, + isUnsent, openContainingFolder, downloadFile, cancelDownload, @@ -343,7 +351,7 @@ export const FileComponent: React.FC = ({ } placement='top' > -
+
{renderIcon()}
@@ -362,6 +370,7 @@ export const FileComponent: React.FC = ({ width: 'fit-content', display: downloadState ? 'block' : 'none', }} + className={classNames({ [classes.unsent]: isUnsent })} > {renderActionIndicator()}
diff --git a/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.test.tsx b/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.test.tsx index be24ec1abf..ddca4bd69d 100644 --- a/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.test.tsx +++ b/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.test.tsx @@ -52,7 +52,7 @@ describe('UploadedFile', () => {
{
{ `) }) + + it('renders an unsent placeholder if image is not finished downloading yet', () => { + const result = renderComponent( + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
+
+
+

+ test.png +

+
+ +
+ +
+ + + + + +
+
+
+
+
+
+
+ + `) + }) + it('renders unsent image if image is downloaded', () => { + // @ts-expect-error + message.media.path = 'path/to/file/test.png' + // @ts-expect-error + message.media.message = { + id: 'string', + channelId: 'general', + } + const result = renderComponent( + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
+
+
+
+

+ test.png +

+ +
+
+
+
+ + `) + }) }) diff --git a/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.tsx b/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.tsx index e93c3beb23..5afadfb30c 100644 --- a/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.tsx +++ b/packages/desktop/src/renderer/components/Channel/File/UploadedImage/UploadedImage.tsx @@ -1,6 +1,9 @@ import React, { useEffect, useState } from 'react' +import classNames from 'classnames' import { styled } from '@mui/material/styles' + import { DownloadStatus, FileMetadata } from '@quiet/types' + import { UseModalType } from '../../../../containers/hooks' import UploadedFileModal from './UploadedImagePreview' import { UploadedFilename, UploadedImagePlaceholder } from '../UploadedImagePlaceholder/UploadedImagePlaceholder' @@ -10,6 +13,8 @@ const PREFIX = 'UploadedImage' const classes = { image: `${PREFIX}image`, container: `${PREFIX}container`, + pending: `${PREFIX}pending`, + unsent: `${PREFIX}unsent`, } const Root = styled('div')(() => ({ @@ -22,6 +27,14 @@ const Root = styled('div')(() => ({ maxWidth: '400px', cursor: 'pointer', }, + + [`& .${classes.pending}`]: { + opacity: 0.5, + }, + + [`& .${classes.unsent}`]: { + opacity: 0.5, + }, })) export interface UploadedImageProps { @@ -31,9 +44,10 @@ export interface UploadedImageProps { }> downloadStatus?: DownloadStatus + isUnsent?: boolean } -export const UploadedImage: React.FC = ({ media, uploadedFileModal, downloadStatus }) => { +export const UploadedImage: React.FC = ({ media, uploadedFileModal, downloadStatus, isUnsent }) => { const [showImage, setShowImage] = useState(false) const { cid, path, name, ext } = media @@ -62,7 +76,11 @@ export const UploadedImage: React.FC = ({ media, uploadedFil {path ? ( <>
{ setShowImage(true) }} diff --git a/packages/desktop/src/renderer/components/ContextMenu/ContextMenu.stories.tsx b/packages/desktop/src/renderer/components/ContextMenu/ContextMenu.stories.tsx index cb75dfd915..0fa54d6c62 100644 --- a/packages/desktop/src/renderer/components/ContextMenu/ContextMenu.stories.tsx +++ b/packages/desktop/src/renderer/components/ContextMenu/ContextMenu.stories.tsx @@ -3,6 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react' import { withTheme } from '../../storybook/decorators' import { ContextMenu, ContextMenuItemList } from './ContextMenu.component' import { ContextMenuItemProps, ContextMenuProps } from './ContextMenu.types' +import { defaultLogger } from '../../logger' const Template: ComponentStory = args => { return @@ -14,7 +15,7 @@ const channel_items: ContextMenuItemProps[] = [ { title: 'Delete', action: () => { - console.log('clicked on delete channel') + defaultLogger.info('clicked on delete channel') }, }, ] @@ -24,7 +25,7 @@ const args: ContextMenuProps = { children: , visible: true, handleClose: () => { - console.log('closing menu') + defaultLogger.info('closing menu') }, } diff --git a/packages/desktop/src/renderer/components/ContextMenu/menus/UserProfileContextMenu.container.tsx b/packages/desktop/src/renderer/components/ContextMenu/menus/UserProfileContextMenu.container.tsx index 31ab730701..3ea516b294 100644 --- a/packages/desktop/src/renderer/components/ContextMenu/menus/UserProfileContextMenu.container.tsx +++ b/packages/desktop/src/renderer/components/ContextMenu/menus/UserProfileContextMenu.container.tsx @@ -13,6 +13,7 @@ import { ContextMenuItemProps, ContextMenuProps } from '../ContextMenu.types' import { MenuName } from '../../../../const/MenuNames.enum' import { ModalName } from '../../../sagas/modals/modals.types' import Jdenticon from '../../Jdenticon/Jdenticon' +import { defaultLogger } from '../../../logger' const PREFIX = 'UserProfileContextMenu' @@ -345,21 +346,21 @@ export const UserProfileMenuEditView: FC = ({ ;({ width, height } = await getImageSize(photo)) } catch (err) { const msg = 'Failed to get image size' - console.error(msg) + defaultLogger.error(msg) setError(msg) return } if (width === 0 || height === 0) { const msg = `Image has invalid dimensions: width: ${width}, height: ${height}` - console.error(msg) + defaultLogger.error(msg) setError(msg) return } if (width > 200 || height > 200) { const msg = 'Image dimensions must be less than or equal to 200px by 200px' - console.error(msg) + defaultLogger.error(msg) setError(msg) return } @@ -367,7 +368,7 @@ export const UserProfileMenuEditView: FC = ({ // 200 KB = 204800 B limit if (photo.size > 204800) { const msg = 'Image size must be less than or equal to 200KB' - console.error(msg) + defaultLogger.error(msg) setError(msg) return } diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.stories.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.stories.tsx index 01188395c6..239206803f 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.stories.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.stories.tsx @@ -5,6 +5,7 @@ import { withTheme } from '../../../storybook/decorators' import PerformCommunityActionComponent, { PerformCommunityActionProps } from '../PerformCommunityActionComponent' import { CommunityOwnership } from '@quiet/types' +import { defaultLogger } from '../../../logger' const Template: ComponentStory = args => { return @@ -16,10 +17,10 @@ const args: PerformCommunityActionProps = { open: true, communityOwnership: CommunityOwnership.Owner, handleCommunityAction: function (value: string): void { - console.log('Creating community: ', value) + defaultLogger.info('Creating community: ', value) }, handleRedirection: function (): void { - console.log('Redirected to join community') + defaultLogger.info('Redirected to join community') }, handleClose: function (): void {}, isCloseDisabled: false, diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx index aef565bf9a..970a329aac 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx @@ -1,7 +1,7 @@ import React from 'react' import '@testing-library/jest-dom/extend-expect' import { screen, waitFor } from '@testing-library/dom' -import { act } from 'react-dom/test-utils' +import { act } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { renderComponent } from '../../../testUtils/renderComponent' import { prepareStore } from '../../../testUtils/prepareStore' diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.stories.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.stories.tsx index 070d6e674f..2e862cde99 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.stories.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.stories.tsx @@ -5,6 +5,7 @@ import { withTheme } from '../../../storybook/decorators' import PerformCommunityActionComponent, { PerformCommunityActionProps } from '../PerformCommunityActionComponent' import { CommunityOwnership } from '@quiet/types' +import { defaultLogger } from '../../../logger' const Template: ComponentStory = args => { return @@ -17,10 +18,10 @@ const args: PerformCommunityActionProps = { open: true, communityOwnership: CommunityOwnership.User, handleCommunityAction: function (value: string): void { - console.log('Joining community: ', value) + defaultLogger.info('Joining community: ', value) }, handleRedirection: function (): void { - console.log('Redirected to create community') + defaultLogger.info('Redirected to create community') }, handleClose: function (): void {}, isCloseDisabled: false, diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx index 6d3d270fc8..a3fadcab17 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.test.tsx @@ -1,7 +1,7 @@ import React from 'react' import '@testing-library/jest-dom/extend-expect' import { screen, waitFor } from '@testing-library/dom' -import { act } from 'react-dom/test-utils' +import { act } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { renderComponent } from '../../../testUtils/renderComponent' import { prepareStore } from '../../../testUtils/prepareStore' diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx index b1e3546e60..fd243697b9 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx @@ -22,6 +22,7 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff' import Visibility from '@mui/icons-material/Visibility' import { parseName } from '@quiet/common' import { getInvitationCodes } from '@quiet/state-manager' +import { defaultLogger } from '../../logger' const PREFIX = 'PerformCommunityActionComponent' @@ -192,7 +193,7 @@ export const PerformCommunityActionComponent: React.FC = args => { return @@ -15,7 +16,7 @@ const args: CreateUsernameComponentProps = { open: true, handleClose: function (): void {}, registerUsername: function (nickname: string): void { - console.log('Registering username: ', nickname) + defaultLogger.info('Registering username: ', nickname) }, } diff --git a/packages/desktop/src/renderer/components/CreateUsername/CreateUsernameComponent.tsx b/packages/desktop/src/renderer/components/CreateUsername/CreateUsernameComponent.tsx index a445d4c858..4cffd1748a 100644 --- a/packages/desktop/src/renderer/components/CreateUsername/CreateUsernameComponent.tsx +++ b/packages/desktop/src/renderer/components/CreateUsername/CreateUsernameComponent.tsx @@ -18,6 +18,7 @@ import { TextInput } from '../../forms/components/textInput' import { userNameField } from '../../forms/fields/createUserFields' import { parseName } from '@quiet/common' +import { defaultLogger } from '../../logger' const PREFIX = 'CreateUsernameComponent-' @@ -174,7 +175,7 @@ export const CreateUsernameComponent: React.FC = ( const onSubmit = useCallback( (values: CreateUserValues) => { if (errors.userName) { - console.error('Cannot submit form with errors') + defaultLogger.error('Cannot submit form with errors') return } diff --git a/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.stories.tsx b/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.stories.tsx index 1107935fab..b5bed75048 100644 --- a/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.stories.tsx +++ b/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.stories.tsx @@ -4,6 +4,7 @@ import { withTheme } from '../../storybook/decorators' import JoiningPanelComponent, { JoiningPanelComponentProps } from './JoiningPanelComponent' import StartingPanelComponent, { StartingPanelComponentProps } from './StartingPanelComponent' import { ConnectionProcessInfo } from '@quiet/types' +import { defaultLogger } from '../../logger' const JoiningPanelTemplate: ComponentStory = args => { return @@ -18,7 +19,7 @@ export const StartingPanel = StartingPanelTemplate.bind({}) const JoiningPanelArgs: JoiningPanelComponentProps = { open: true, handleClose: function (): void {}, - openUrl: () => console.log('OpenURL'), + openUrl: () => defaultLogger.info('OpenURL'), connectionInfo: { number: 10, text: ConnectionProcessInfo.BACKEND_MODULES }, isOwner: false, } diff --git a/packages/desktop/src/renderer/components/MathMessage/MathMessageComponent.test.tsx b/packages/desktop/src/renderer/components/MathMessage/MathMessageComponent.test.tsx index 3a4a5afeb4..0d2ab23c52 100644 --- a/packages/desktop/src/renderer/components/MathMessage/MathMessageComponent.test.tsx +++ b/packages/desktop/src/renderer/components/MathMessage/MathMessageComponent.test.tsx @@ -1,19 +1,25 @@ import React from 'react' -import { act } from 'react-dom/test-utils' +import { act } from '@testing-library/react' import { renderComponent } from '../../testUtils/renderComponent' import { MathMessageComponent } from './MathMessageComponent' describe('MathMessageComponent', () => { it('renders tex', async () => { const result = renderComponent( - {}} /> + {}} + /> ) await act(async () => {}) expect(result.baseElement).toMatchInlineSnapshot(`
{ message={'It is $$a + b = c$$ and $$a - b = d$$'} messageId={'1'} pending={false} + isUnsent={false} openUrl={() => {}} onMathMessageRendered={onMathMessageRendered} /> @@ -113,7 +120,7 @@ describe('MathMessageComponent', () => { It is { and { message={String.raw`$$sum_{i=0}^n i = \frac{n(n+1)}{2}$$ - look`} messageId={'1'} pending={false} + isUnsent={false} openUrl={() => {}} onMathMessageRendered={onMathMessageRendered} /> @@ -287,7 +295,7 @@ describe('MathMessageComponent', () => {
({ color: theme.palette.colors.lightGray, }, + [`&.${classes.unsent}`]: { + opacity: 0.5, + }, + [`&.${classes.middle}`]: { margin: '0 5px 0 5px', }, @@ -46,6 +52,7 @@ const MathComponent: React.FC = ({ onMathMessageRendered, messageId, pending, + isUnsent, openUrl, index, }) => { @@ -71,7 +78,7 @@ const MathComponent: React.FC = ({ } }, [node, message, display]) - if (error) console.error(`Error converting tex '${message}'`, error) + if (error) defaultLogger.error(`Error converting tex '${message}'`, error) if (isMath && !error) { const props = { @@ -82,6 +89,7 @@ const MathComponent: React.FC = ({ const className = { [classes.message]: true, [classes.pending]: pending, + [classes.unsent]: isUnsent, [classes.beginning]: index === 0, [classes.middle]: index !== 0, } @@ -92,6 +100,7 @@ const MathComponent: React.FC = ({ message={message} messageId={`${messageId}-${index}`} pending={pending} + isUnsent={isUnsent} openUrl={openUrl} key={`${messageId}-${index}`} /> @@ -107,6 +116,7 @@ export const MathMessageComponent: React.FC + defaultLogger.error('Error extracting tex from message', e.message) + return ( + + ) } return ( @@ -129,6 +147,7 @@ export const MathMessageComponent: React.FC { + it('renders text followed by animated ellipsis', () => { + const result = renderComponent( + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
+
+
+

+ Sending +

+

+ . +

+

+ . +

+

+ . +

+
+
+
+ + `) + }) +}) diff --git a/packages/desktop/src/renderer/components/ui/AnimatedEllipsis/AnimatedEllipsis.tsx b/packages/desktop/src/renderer/components/ui/AnimatedEllipsis/AnimatedEllipsis.tsx new file mode 100644 index 0000000000..d4398aa3dd --- /dev/null +++ b/packages/desktop/src/renderer/components/ui/AnimatedEllipsis/AnimatedEllipsis.tsx @@ -0,0 +1,187 @@ +import React from 'react' +import Typography from '@mui/material/Typography' +import { Grid } from '@mui/material' +import { styled } from '@mui/material/styles' +import classNames from 'classnames' + +const PREFIX = 'AnimatedEllipsis' + +const classes = { + wrapper: `${PREFIX}-wrapper`, + content: `${PREFIX}-content`, + dot1: `${PREFIX}-dot1`, + dot2: `${PREFIX}-dot2`, + dot3: `${PREFIX}-dot3`, +} + +const getAnimationName = (className: string) => `${className}-visibility-anim` +const getAnimationProperties = (className: string) => { + return { + animationName: getAnimationName(className), + animationDuration: '1800ms', + animationTimingFunction: 'ease-in-out', + animationIterationCount: 'infinite', + } +} + +const StyledGrid = styled(Grid)(({ theme }) => ({ + [`& .${classes.wrapper}`]: { + display: 'flex', + flexDirection: 'row', + }, + + // dot 1 + + [`& .${classes.dot1}`]: getAnimationProperties(classes.dot1), + + [`@keyframes ${getAnimationName(classes.dot1)}`]: { + '0%': { + opacity: 1, + }, + '65%': { + opacity: 1, + }, + '66%': { + opacity: 0.5, + }, + '75%': { + opacity: 0.3, + }, + '90%': { + opacity: 0.1, + }, + '100%': { + opacity: 0, + }, + }, + + // dot2 + + [`& .${classes.dot2}`]: getAnimationProperties(classes.dot2), + + [`@keyframes ${getAnimationName(classes.dot2)}`]: { + '0%': { + opacity: 0, + }, + '5%': { + opacity: 0.1, + }, + '15%': { + opacity: 0.25, + }, + '18%': { + opacity: 0.5, + }, + '20%': { + opacity: 0.75, + }, + '22%': { + opacity: 1, + }, + '65%': { + opacity: 1, + }, + '66%': { + opacity: 0.5, + }, + '75%': { + opacity: 0.3, + }, + '90%': { + opacity: 0.1, + }, + '100%': { + opacity: 0, + }, + }, + + // dot 3 + + [`& .${classes.dot3}`]: getAnimationProperties(classes.dot3), + + [`@keyframes ${getAnimationName(classes.dot3)}`]: { + '0%': { + opacity: 0, + }, + '25%': { + opacity: 0.1, + }, + '35%': { + opacity: 0.25, + }, + '39%': { + opacity: 0.5, + }, + '43%': { + opacity: 0.75, + }, + '44%': { + opacity: 1, + }, + '65%': { + opacity: 1, + }, + '66%': { + opacity: 0.5, + }, + '75%': { + opacity: 0.3, + }, + '90%': { + opacity: 0.1, + }, + '100%': { + opacity: 0, + }, + }, +})) + +interface AnimatedEllipsis { + content: string + color: string + fontSize: number + fontWeight: string +} + +export const AnimatedEllipsis: React.FC = ({ content, color, fontSize, fontWeight }) => { + return ( + + + + {content} + + + . + + + . + + + . + + + + ) +} + +export default AnimatedEllipsis diff --git a/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.stories.tsx b/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.stories.tsx index 1654d78466..bc85042b95 100644 --- a/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.stories.tsx +++ b/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.stories.tsx @@ -4,6 +4,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react' import { withTheme } from '../../../storybook/decorators' import TextWithLink, { TextWithLinkProps } from './TextWithLink' +import { defaultLogger } from '../../../logger' const Template: ComponentStory = args => { return @@ -18,7 +19,7 @@ const args: TextWithLinkProps = { tag: 'a', label: 'linked', action: () => { - console.log('link clicked') + defaultLogger.info('link clicked') }, }, ], diff --git a/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.test.tsx b/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.test.tsx index 6ac48c706c..1dc3d98f7b 100644 --- a/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.test.tsx +++ b/packages/desktop/src/renderer/components/ui/TextWithLink/TextWithLink.test.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { defaultLogger } from '../../../logger' import { renderComponent } from '../../../testUtils/renderComponent' import { TextWithLink } from './TextWithLink' @@ -13,7 +14,7 @@ describe('TextWithLink', () => { tag: 'simple', label: 'simple', action: () => { - console.log('linked clicked') + defaultLogger.info('linked clicked') }, }, ]} diff --git a/packages/desktop/src/renderer/components/widgets/WarningModal/WarningModal.stories.tsx b/packages/desktop/src/renderer/components/widgets/WarningModal/WarningModal.stories.tsx index ec618f4f40..f2f83a0287 100644 --- a/packages/desktop/src/renderer/components/widgets/WarningModal/WarningModal.stories.tsx +++ b/packages/desktop/src/renderer/components/widgets/WarningModal/WarningModal.stories.tsx @@ -3,6 +3,7 @@ import { ComponentMeta, ComponentStory } from '@storybook/react' import { withTheme } from '../../../storybook/decorators' import WarningModalComponent, { WarningModalComponentProps } from './WarningModal' +import { defaultLogger } from '../../../logger' const Template: ComponentStory = args => { return @@ -13,7 +14,7 @@ export const Component = Template.bind({}) const args: WarningModalComponentProps = { open: true, handleClose: function (): void { - console.log('Closed modal') + defaultLogger.info('Closed modal') }, title: 'Warning title', subtitle: 'Warning description', diff --git a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx index 2b9beeff3d..67c43af4e5 100644 --- a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx +++ b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx @@ -14,394 +14,1079 @@ describe('BasicMessage', () => { jest.spyOn(DateTime, 'utc').mockImplementationOnce(() => DateTime.utc(2019, 3, 7, 13, 3, 48)) }) - it('renders component', async () => { - const messages = generateMessages() - const result = renderComponent( - - - - - - ) - expect(result.baseElement).toMatchInlineSnapshot(` - -
-
  • -
    { + it('renders component', async () => { + const messages = generateMessages() + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • - Jdenticon +
    + Jdenticon +
    +
    +
    +
    + +
    +

    + gringo +

    +
    +
    +

    + string +

    +
    +
    +
    +
    +
    + + message0 + +
    +
    +
  • + +
    + + `) + }) + + it('renders component with multiple messages', async () => { + const messages = generateMessages({ amount: 2 }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • +
    +
    + Jdenticon +
    +
    +
    -

    - gringo -

    +

    + gringo +

    +
    +
    +

    + string +

    +
    +
    +
    -

    - string -

    + message0 + +
    +
    + + message1 +
    +
    +
  • + +
    + + `) + }) + + it('renders with separate info messages', async () => { + const messages = generateMessages({ amount: 2, type: 3 }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • +
    +
    - +
    +
    +
    +
    +
    - message0 - +
    +

    + Quiet +

    +
    +
    +

    + string +

    +
    +
    +
    +
    +
    + + message0 + +
    +
    + + message1 + +
    -
  • - -
    - - `) - }) - it('renders component with multiple messages', async () => { - const messages = generateMessages({ amount: 2 }) - const result = renderComponent( - - - - - - ) - expect(result.baseElement).toMatchInlineSnapshot(` - -
    -
  • -
    +
    + + `) + }) + + it('renders with basic message and info message', async () => { + const message1 = generateMessages() + const message2 = generateMessages({ type: 3 }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • - Jdenticon +
    + Jdenticon +
    -
    -
    +
    +
    +

    + gringo +

    +
    +
    +

    + string +

    +
    +
    +
    +
    -

    - gringo -

    + message0 +
    -

    - string -

    + message0 +
    +
    +
    +
  • +
    + + `) + }) + + it('renders info messages as sent even when other messages would be unsent', async () => { + const nowSeconds = DateTime.utc().toSeconds() + const messages = generateMessages({ amount: 2, type: 3, createdAtSeconds: nowSeconds }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • +
    +
    - +
    +
    +
    +
    +
    - message0 - +
    +

    + Quiet +

    +
    +
    +

    + string +

    +
    +
    - - message1 - + + message0 + +
    +
    + + message1 + +
  • -
    - -
    - - `) - }) - it('renders with separate info messages', async () => { - const messages = generateMessages({ amount: 2, type: 3 }) - const result = renderComponent( - - - - - - ) - expect(result.baseElement).toMatchInlineSnapshot(` - -
    -
  • -
    +
    + + `) + }) + + it('renders messages as sent when no peers are connected but community is fresh', async () => { + const nowSeconds = DateTime.utc().toSeconds() + const messages = generateMessages({ amount: 2, createdAtSeconds: nowSeconds }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • - +
    + Jdenticon +
    -
    -
    +
    +
    +

    + gringo +

    +
    +
    +

    + string +

    +
    +
    +
    +
    -

    - Quiet -

    + message0 +
    -

    - string -

    + message1 +
    +
    +
    +
  • +
    + + `) + }) + }) + + describe('unsent messages', () => { + it('renders component with unsent messages when peers disconnected this session', async () => { + const nowSeconds = DateTime.utc().toSeconds() + const messages = generateMessages({ createdAtSeconds: nowSeconds }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • +
    +
    + Jdenticon +
    +
    +
    +
    - message0 - +
    +

    + gringo +

    +
    +
    +

    + string +

    +
    +
    +
    +
    +

    + Sending +

    +

    + . +

    +

    + . +

    +

    + . +

    +
    +
    +
    +
    - message1 - + + message0 + +
  • -
    - -
    - - `) - }) - it('renders with basic message and info message', async () => { - const message1 = generateMessages() - const message2 = generateMessages({ type: 3 }) - const result = renderComponent( - - - - - - ) - expect(result.baseElement).toMatchInlineSnapshot(` - -
    -
  • -
    +
    + + `) + }) + + it('renders component with unsent messages when no peers seen this session', async () => { + const nowSeconds = DateTime.utc().toSeconds() + const messages = generateMessages({ createdAtSeconds: nowSeconds }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • - Jdenticon +
    + Jdenticon +
    -
    -
    -
    -

    +

    + gringo +

    +
    +
    - gringo -

    +

    + string +

    +
    +
    +
    +
    +

    + Sending +

    +

    + . +

    +

    + . +

    +

    + . +

    +
    +
    +
    -
    +
    + -

    - string -

    + message0 +
    +
    +
    +
  • +
    + + `) + }) + + it('renders component with multiple unsent messages', async () => { + const nowSeconds = DateTime.utc().toSeconds() + const messages = generateMessages({ createdAtSeconds: nowSeconds, amount: 5 }) + const result = renderComponent( + + + + + + ) + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
  • +
    +
    + Jdenticon +
    +
    +
    +
    - message0 - +
    +

    + gringo +

    +
    +
    +

    + string +

    +
    +
    +
    +
    +

    + Sending +

    +

    + . +

    +

    + . +

    +

    + . +

    +
    +
    +
    +
    - message0 - + + message0 + +
    +
    + + message1 + +
    +
    + + message2 + +
    +
    + + message3 + +
    +
    + + message4 + +
  • -
    - -
    - - `) + +
    + + `) + }) }) }) diff --git a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx index 1a43ad6625..3b8f9ff205 100644 --- a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx +++ b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { DateTime } from 'luxon' import { styled } from '@mui/material/styles' import { Dictionary } from '@reduxjs/toolkit' import classNames from 'classnames' @@ -23,6 +24,8 @@ import Icon from '../../ui/Icon/Icon' import { UseModalType } from '../../../containers/hooks' import { HandleOpenModalType, UserLabelType } from '../userLabel/UserLabel.types' import UserLabel from '../userLabel/UserLabel.component' +import AnimatedEllipsis from '../../ui/AnimatedEllipsis/AnimatedEllipsis' +import { isMessageUnsent } from '@quiet/state-manager' const PREFIX = 'BasicMessageComponent' @@ -37,6 +40,7 @@ const classes = { broadcasted: `${PREFIX}broadcasted`, failed: `${PREFIX}failed`, avatar: `${PREFIX}avatar`, + avatarUnsent: `${PREFIX}avatar-unsent`, alignAvatar: `${PREFIX}alignAvatar`, moderation: `${PREFIX}moderation`, time: `${PREFIX}time`, @@ -44,6 +48,8 @@ const classes = { pending: `${PREFIX}pending`, info: `${PREFIX}info`, infoIcon: `${PREFIX}infoIcon`, + unsent: `${PREFIX}unsent`, + sending: `${PREFIX}sending`, } const StyledListItem = styled(ListItem)(({ theme }) => ({ @@ -116,6 +122,7 @@ const StyledListItem = styled(ListItem)(({ theme }) => ({ color: theme.palette.colors.lightGray, fontSize: 14, marginTop: -2, + marginRight: 5, }, [`& .${classes.iconBox}`]: { @@ -126,6 +133,15 @@ const StyledListItem = styled(ListItem)(({ theme }) => ({ color: theme.palette.colors.lightGray, }, + [`& .${classes.unsent}`]: { + opacity: 0.5, + }, + + [`& .${classes.sending}`]: { + color: theme.palette.colors.darkGray, + marginTop: -2, + }, + [`& .${classes.info}`]: { color: theme.palette.colors.white, }, @@ -161,6 +177,10 @@ const MessageProfilePhoto: React.FC<{ message: DisplayableMessage }> = ({ messag export interface BasicMessageProps { messages: DisplayableMessage[] pendingMessages?: Dictionary + connectedPeers: string[] | undefined + communityPeerList: string[] | undefined + lastConnectedTime: number + allPeersDisconnectedTime: number | undefined openUrl: (url: string) => void downloadStatuses?: Dictionary uploadedFileModal?: UseModalType<{ @@ -174,6 +194,10 @@ export interface BasicMessageProps { export const BasicMessageComponent: React.FC = ({ messages, pendingMessages = {}, + connectedPeers = [], + communityPeerList = [], + lastConnectedTime, + allPeersDisconnectedTime, downloadStatuses = {}, uploadedFileModal, onMathMessageRendered, @@ -196,6 +220,13 @@ export const BasicMessageComponent: React.FC - +
    {infoMessage ? ( @@ -225,10 +260,11 @@ export const BasicMessageComponent: React.FC {infoMessage ? 'Quiet' : messageDisplayData.nickname} @@ -249,12 +285,23 @@ export const BasicMessageComponent: React.FC {messageDisplayData.date} )} + {isUnsent && ( + + + + )} = args => { return ( @@ -23,7 +24,7 @@ const args: ChannelInputProps = { inputPlaceholder: '#general as @alice', onChange: function (_arg: string): void {}, onKeyPress: function (input: string): void { - console.log('send message', input) + defaultLogger.info('send message', input) }, infoClass: '', setInfoClass: function (_arg: string): void {}, @@ -37,7 +38,7 @@ const argsDisabledInput: ChannelInputProps = { inputPlaceholder: '#general as @alice', onChange: function (_arg: string): void {}, onKeyPress: function (input: string): void { - console.log('send message', input) + defaultLogger.info('send message', input) }, infoClass: '', setInfoClass: function (_arg: string): void {}, @@ -248,7 +249,7 @@ const argsLongMessage: ChannelInputProps = { initialMessage: initialMessage, onChange: function (_arg: string): void {}, onKeyPress: function (input: string): void { - console.log('send message', input) + defaultLogger.info('send message', input) }, infoClass: '', setInfoClass: function (_arg: string): void {}, diff --git a/packages/desktop/src/renderer/components/widgets/channels/ChannelMessages.test.tsx b/packages/desktop/src/renderer/components/widgets/channels/ChannelMessages.test.tsx index 24b937b94d..61345bef64 100644 --- a/packages/desktop/src/renderer/components/widgets/channels/ChannelMessages.test.tsx +++ b/packages/desktop/src/renderer/components/widgets/channels/ChannelMessages.test.tsx @@ -36,6 +36,10 @@ describe('ChannelMessages', () => { duplicatedUsernameModalHandleOpen={jest.fn()} unregisteredUsernameModalHandleOpen={jest.fn()} messages={messages} + lastConnectedTime={1636995489} + allPeersDisconnectedTime={undefined} + connectedPeers={['foobar']} + communityPeerList={['foobar', 'barbaz']} scrollbarRef={React.createRef()} onScroll={jest.fn()} openUrl={jest.fn()} @@ -83,7 +87,7 @@ describe('ChannelMessages', () => {
  • { >
    { >

    string

    @@ -136,10 +142,196 @@ describe('ChannelMessages', () => { style="margin-top: -3px;" >
    + string + +
    +
    +
    +
    +
  • + +
    + + + + + `) + }) + + it('renders component with unsent messages', async () => { + const now = DateTime.utc().toSeconds() + const message = { + id: 'string', + type: 1, + message: 'string', + createdAt: now, + date: 'string', + nickname: 'string', + isDuplicated: false, + isRegistered: true, + pubKey: 'string', + } + + jest.spyOn(DateTime, 'utc').mockImplementationOnce(() => DateTime.utc(2019, 3, 7, 13, 3, 48)) + + const messages = { + Today: [[message]], + } + + const result = renderComponent( + + ) + + expect(result.baseElement).toMatchInlineSnapshot(` + +
    +
    +