Compare commits
4 Commits
d39aef0aac
...
lotus
| Author | SHA1 | Date | |
|---|---|---|---|
| fc8eb70617 | |||
| 1a5896ef84 | |||
| 7b94eeaa60 | |||
| 50076962f6 |
+24
-24
@@ -175,26 +175,26 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
|
||||
| Category | Issue Description | File Path | Status |
|
||||
| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Localization | Hardcoded UI string: "Chat Room" | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Messages, photos, and videos." | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Voice Room" | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Live audio and video conversations." | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Download" | `src/app/components/image-viewer/ImageViewer.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Open Location" | `src/app/components/message/MsgTypeRenderers.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Thread" | `src/app/components/message/Reply.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "View" | `src/app/components/message/content/ImageContent.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Spoiler" | `src/app/components/message/content/ImageContent.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Retry" | `src/app/components/message/content/ImageContent.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Close" | `src/app/components/DeviceVerification.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Accept" | `src/app/components/DeviceVerification.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "They Match" | `src/app/components/DeviceVerification.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Okay" | `src/app/components/DeviceVerification.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Join Server" | `src/app/components/url-preview/UrlPreviewCard.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Invite" | `src/app/components/invite-user-prompt/InviteUserPrompt.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Files" | `src/app/components/upload-board/UploadBoard.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Send" | `src/app/components/upload-board/UploadBoard.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Upload Failed" | `src/app/components/upload-board/UploadBoard.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Password" | `src/app/components/uia-stages/PasswordStage.tsx` | OPEN |
|
||||
| Localization | Hardcoded UI string: "Chat Room" | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Messages, photos, and videos." | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Voice Room" | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Live audio and video conversations." | `src/app/components/create-room/CreateRoomTypeSelector.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Download" | `src/app/components/image-viewer/ImageViewer.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Open Location" | `src/app/components/message/MsgTypeRenderers.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Thread" | `src/app/components/message/Reply.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "View" | `src/app/components/message/content/ImageContent.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Spoiler" | `src/app/components/message/content/ImageContent.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Retry" | `src/app/components/message/content/ImageContent.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Close" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Accept" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "They Match" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Okay" | `src/app/components/DeviceVerification.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Join Server" | `src/app/components/url-preview/UrlPreviewCard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Invite" | `src/app/components/invite-user-prompt/InviteUserPrompt.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Files" | `src/app/components/upload-board/UploadBoard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Send" | `src/app/components/upload-board/UploadBoard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Upload Failed" | `src/app/components/upload-board/UploadBoard.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Localization | Hardcoded UI string: "Password" | `src/app/components/uia-stages/PasswordStage.tsx` | FIXED (`1a5896ef`) — wrapped in `t()` + key added to `en.json` |
|
||||
| Bundle Size | Large unoptimized media asset (213KB) | `public/res/Lotus.png` | OPEN |
|
||||
| Matrix Logic | Inefficient repeated `mx.getRoom()` calls in component render loops | `src/app/features/lobby/Lobby.tsx` | FALSE POSITIVE — `Lobby` already routes its render loop through the memoized `useGetRoom(allJoinedRooms)`. The two remaining `mx.getRoom()` calls are inside drag/drop event handlers (not render loops) and are O(1) SDK map lookups. No change warranted. |
|
||||
| Matrix Logic | Inefficient repeated `mx.getRoom()` calls in component render loops | `src/app/components/emoji-board/EmojiBoard.tsx` | FIXED (`b7e1f89c`) — pack-label `mx.getRoom()` lookups in `EmojiSidebar`/`StickerSidebar` hoisted into a `useMemo`'d `Map` built once per pack list. |
|
||||
@@ -376,8 +376,8 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
|
||||
**N53 — PTT Badge (Lotus Terminal path): Raw `<div>` tree with `--lt-*` CSS vars instead of folds `<Chip>`**
|
||||
|
||||
- **File:** `src/app/features/call/CallControls.tsx`, lines 242–282
|
||||
- **Status:** **OPEN**
|
||||
- **File:** `src/app/features/call/CallControls.tsx`
|
||||
- **Status:** **FIXED** (`50076962`) — removed the `lotusTerminal` branch entirely; the PTT badge is now the single folds `<Chip variant={pttActive ? 'Success' : 'Warning'} fill="Soft" radii="400" outlined>` path for all themes (TDS styling still flows through the CSS-variable layer over the Chip). Dropped the now-unused `lotusTerminal` read. Build-verified; visual parity to confirm only if you specifically used the terminal-mode PTT look.
|
||||
- **Issue:** When `lotusTerminal` is true the PTT badge renders as a bare `<Box>` with inline styles referencing `--lt-accent-green-dim`, `--lt-accent-green-border`, `--lt-accent-green` — variables absent outside TDS mode — hardcoded rem padding, `borderRadius: '99px'` (non-token), a raw monospace `fontFamily` string, non-token `letterSpacing`, and a raw `animation:` CSS string for the live-pulse dot. The live `●` dot is a raw `<span>` with inline style.
|
||||
- **Root Cause:** Two entirely separate component trees for the same badge depending on a theme boolean. The non-terminal path (lines 284–301) uses the correct `<Chip variant="Success"|"Warning" fill="Soft" radii="400" outlined>`.
|
||||
- **Fix:** Remove the terminal branch. The standard `<Chip>` path already exists and TDS theming can be applied via the CSS variable layer without a separate component tree.
|
||||
@@ -440,8 +440,8 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
|
||||
| N78 | HasLink Chip Active Color | `SearchFilters.tsx` | 755 | `HasLink` active state uses `variant="Primary"` (blue); all boolean scope-toggle chips in the same bar use `variant="Success"` (green) with `outlined` — **FIXED**: changed to `variant={containsUrl ? 'Success' : 'SurfaceVariant'} outlined={!!containsUrl}` | `variant="Success" outlined` is the established active-state pattern for boolean toggles in the filter bar |
|
||||
| N79 | Server Notice Chip Radii | `RoomViewHeader.tsx` | 570 | `<Chip size="400" radii="Pill">` — `Pill` radii on a room-type label — **FIXED**: changed to `radii="300"` | Room/space type labels in lobby (`RoomItem.tsx:83`, `SpaceItem.tsx:63`) use `radii="300"`; `radii="Pill"` is for filter/tag chips only |
|
||||
| N80 | Server Support Contact Layout | `About.tsx` | 172–239 | Homeserver support contacts rendered as raw `<Box direction="Column">` with `<Text as="a">` pairs — custom label/link layout — **WON'T FIX (deliberate)**: a contact is `role → {matrix_id?, email?, …}` (one-to-many links per role), which doesn't map onto `SettingTile`'s single `title`/`description`/`after` slots without contortion. The current layout already uses folds `Box`/`Text`/`SequenceCard` + tokens and `Text as="a"` (a valid folds pattern); no undefined vars or raw HTML chrome. | All other `<SequenceCard>` content in `About.tsx` and `General.tsx` uses `<SettingTile title="..." description="..." after={...}>` as the content unit |
|
||||
| N81 | Background Picker Grid — No Responsive Layout | `General.tsx` | 1707–1742 | Fixed `width: toRem(76)` flex-wrap cells with no `minWidth` floor or CSS grid `auto-fill` — SeasonalBgGrid's 13 items produce a visually lopsided orphan last row at any viewport width | Cinny's native grids use `grid-template-columns: repeat(auto-fill, minmax(N, 1fr))` or equivalent for responsive fill |
|
||||
| N82 | Join/Leave Sounds Auto-Preview | `General.tsx` | 1592–1609 | Selecting a sound in the dropdown immediately plays a preview, but no UI affordance (button label, description text, or "▶ Preview" button) communicates this to the user | Settings tiles with side effects on selection (theme picker, chat background) show a live visual preview or a dedicated control explaining the side effect |
|
||||
| N81 | Background Picker Grid — No Responsive Layout | `General.tsx` | 1707–1742 | Fixed `width: toRem(76)` flex-wrap cells with no `minWidth` floor or CSS grid `auto-fill` — SeasonalBgGrid's 13 items produce a visually lopsided orphan last row at any viewport width — **FIXED** (`50076962`): both `ChatBgGrid` and `SeasonalBgGrid` containers switched to `display: grid; grid-template-columns: repeat(auto-fill, minmax(toRem(76), 1fr))`, so swatches fill each row evenly | Cinny's native grids use `grid-template-columns: repeat(auto-fill, minmax(N, 1fr))` or equivalent for responsive fill |
|
||||
| N82 | Join/Leave Sounds Auto-Preview | `General.tsx` | 1592–1609 | Selecting a sound in the dropdown immediately plays a preview, but no UI affordance communicates this to the user — **FIXED**: the tile description now reads "…Selecting an option plays a preview." (the same affordance was applied to the new Ringtone selector) | Settings tiles with side effects on selection (theme picker, chat background) show a live visual preview or a dedicated control explaining the side effect |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -254,6 +254,11 @@ In Settings → Appearance:
|
||||
2. Pick a **seasonal theme** → confirm the **chat background** auto-clears to none.
|
||||
3. (Edge) If you have old data with both set, after reload only one should visibly apply (no double-overlay clutter).
|
||||
|
||||
### F3. Background / seasonal picker grid layout (N81)
|
||||
|
||||
In Settings → Appearance, look at the **Chat Background** and **Seasonal Theme** swatch grids; resize the window narrow→wide.
|
||||
**Expected:** swatches reflow to fill each row evenly (responsive grid), with no lopsided/orphaned last row at any width.
|
||||
|
||||
---
|
||||
|
||||
## G. Calls — additional unverified (👥 2 people)
|
||||
@@ -271,6 +276,11 @@ In a call with at least one other person, pop out the **Picture-in-Picture** min
|
||||
1. In a **camera-only** call (no screenshare), confirm the **Fullscreen** button is available (previously only showed during screenshare).
|
||||
2. Use **MemberGlance → Focus camera** to full-screen/spotlight a specific person's camera. (Overlaps **A5**; if you've done A5 you can skip.)
|
||||
|
||||
### G3. PTT badge renders on all themes (N53)
|
||||
|
||||
Enable **Push-to-talk** (Settings → Calls) and join a call. Hold the PTT key.
|
||||
**Expected:** the floating PTT badge above the controls shows "PTT — Hold KEY" when idle and "● Live" (green) while held — on **both** a default theme and Lotus Terminal (it's now a single folds Chip; the old terminal-only variant was removed).
|
||||
|
||||
---
|
||||
|
||||
## H. Media / performance (needs a room with many images)
|
||||
|
||||
@@ -2,6 +2,46 @@
|
||||
"Organisms": {
|
||||
"RoomCommon": {
|
||||
"changed_room_name": " changed room name"
|
||||
},
|
||||
"CreateRoom": {
|
||||
"chat_room": "Chat Room",
|
||||
"chat_room_desc": "Messages, photos, and videos.",
|
||||
"voice_room": "Voice Room",
|
||||
"voice_room_desc": "Live audio and video conversations."
|
||||
},
|
||||
"ImageViewer": {
|
||||
"download": "Download"
|
||||
},
|
||||
"Message": {
|
||||
"open_location": "Open Location",
|
||||
"thread": "Thread"
|
||||
},
|
||||
"ImageContent": {
|
||||
"view": "View",
|
||||
"spoiler": "Spoiler",
|
||||
"retry": "Retry"
|
||||
},
|
||||
"DeviceVerification": {
|
||||
"close": "Close",
|
||||
"accept": "Accept",
|
||||
"they_match": "They Match",
|
||||
"okay": "Okay"
|
||||
},
|
||||
"UrlPreview": {
|
||||
"join_server": "Join Server"
|
||||
},
|
||||
"InviteUser": {
|
||||
"invite": "Invite"
|
||||
},
|
||||
"UploadBoard": {
|
||||
"files": "Files",
|
||||
"send": "Send",
|
||||
"upload_failed": "Upload Failed"
|
||||
},
|
||||
"PasswordStage": {
|
||||
"account_password": "Account Password",
|
||||
"password": "Password",
|
||||
"invalid_password": "Invalid Password!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Verifier,
|
||||
} from 'matrix-js-sdk/lib/crypto-api';
|
||||
import React, { CSSProperties, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { VerificationMethod } from 'matrix-js-sdk/lib/types';
|
||||
import {
|
||||
Box,
|
||||
@@ -51,11 +52,12 @@ function WaitingMessage({ message }: WaitingMessageProps) {
|
||||
|
||||
type VerificationUnexpectedProps = { message: string; onClose: () => void };
|
||||
function VerificationUnexpected({ message, onClose }: VerificationUnexpectedProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box direction="Column" gap="400">
|
||||
<Text>{message}</Text>
|
||||
<Button variant="Secondary" fill="Soft" onClick={onClose}>
|
||||
<Text size="B400">Close</Text>
|
||||
<Text size="B400">{t('Organisms.DeviceVerification.close')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
@@ -74,6 +76,7 @@ type VerificationAcceptProps = {
|
||||
onAccept: () => Promise<void>;
|
||||
};
|
||||
function VerificationAccept({ onAccept }: VerificationAcceptProps) {
|
||||
const { t } = useTranslation();
|
||||
const [acceptState, accept] = useAsyncCallback(onAccept);
|
||||
|
||||
const accepting = acceptState.status === AsyncStatus.Loading;
|
||||
@@ -87,7 +90,7 @@ function VerificationAccept({ onAccept }: VerificationAcceptProps) {
|
||||
before={accepting && <Spinner size="100" variant="Primary" fill="Solid" />}
|
||||
disabled={accepting}
|
||||
>
|
||||
<Text size="B400">Accept</Text>
|
||||
<Text size="B400">{t('Organisms.DeviceVerification.accept')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
@@ -118,6 +121,7 @@ function AutoVerificationStart({ onStart }: VerificationStartProps) {
|
||||
}
|
||||
|
||||
function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) {
|
||||
const { t } = useTranslation();
|
||||
const [confirmState, confirm] = useAsyncCallback(useCallback(() => sasData.confirm(), [sasData]));
|
||||
|
||||
const confirming =
|
||||
@@ -157,7 +161,7 @@ function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) {
|
||||
disabled={confirming}
|
||||
before={confirming && <Spinner size="100" variant="Primary" />}
|
||||
>
|
||||
<Text size="B400">They Match</Text>
|
||||
<Text size="B400">{t('Organisms.DeviceVerification.they_match')}</Text>
|
||||
</Button>
|
||||
<Button
|
||||
variant="Primary"
|
||||
@@ -201,13 +205,14 @@ type VerificationDoneProps = {
|
||||
onExit: () => void;
|
||||
};
|
||||
function VerificationDone({ onExit }: VerificationDoneProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box direction="Column" gap="400">
|
||||
<div>
|
||||
<Text>Your device is verified.</Text>
|
||||
</div>
|
||||
<Button variant="Primary" fill="Solid" onClick={onExit}>
|
||||
<Text size="B400">Okay</Text>
|
||||
<Text size="B400">{t('Organisms.DeviceVerification.okay')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
@@ -217,11 +222,12 @@ type VerificationCanceledProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
function VerificationCanceled({ onClose }: VerificationCanceledProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box direction="Column" gap="400">
|
||||
<Text>Verification has been canceled.</Text>
|
||||
<Button variant="Secondary" fill="Soft" onClick={onClose}>
|
||||
<Text size="B400">Close</Text>
|
||||
<Text size="B400">{t('Organisms.DeviceVerification.close')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, Icon, Icons, config, IconSrc } from 'folds';
|
||||
import { SequenceCard } from '../sequence-card';
|
||||
import { SettingTile } from '../setting-tile';
|
||||
@@ -17,6 +18,7 @@ export function CreateRoomTypeSelector({
|
||||
disabled,
|
||||
getIcon,
|
||||
}: CreateRoomTypeSelectorProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box shrink="No" direction="Column" gap="100">
|
||||
<SequenceCard
|
||||
@@ -36,10 +38,10 @@ export function CreateRoomTypeSelector({
|
||||
>
|
||||
<Box gap="200" alignItems="Baseline">
|
||||
<Text size="H6" style={{ flexShrink: 0 }}>
|
||||
Chat Room
|
||||
{t('Organisms.CreateRoom.chat_room')}
|
||||
</Text>
|
||||
<Text size="T300" priority="300" truncate>
|
||||
- Messages, photos, and videos.
|
||||
- {t('Organisms.CreateRoom.chat_room_desc')}
|
||||
</Text>
|
||||
</Box>
|
||||
</SettingTile>
|
||||
@@ -61,10 +63,10 @@ export function CreateRoomTypeSelector({
|
||||
>
|
||||
<Box gap="200" alignItems="Baseline">
|
||||
<Text size="H6" style={{ flexShrink: 0 }}>
|
||||
Voice Room
|
||||
{t('Organisms.CreateRoom.voice_room')}
|
||||
</Text>
|
||||
<Text size="T300" priority="300" truncate>
|
||||
- Live audio and video conversations.
|
||||
- {t('Organisms.CreateRoom.voice_room_desc')}
|
||||
</Text>
|
||||
<BetaNoticeBadge />
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FileSaver from 'file-saver';
|
||||
import classNames from 'classnames';
|
||||
import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
|
||||
@@ -15,6 +16,7 @@ export type ImageViewerProps = {
|
||||
|
||||
export const ImageViewer = as<'div', ImageViewerProps>(
|
||||
({ className, alt, src, requestClose, ...props }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
|
||||
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
|
||||
|
||||
@@ -69,7 +71,7 @@ export const ImageViewer = as<'div', ImageViewerProps>(
|
||||
radii="300"
|
||||
before={<Icon size="50" src={Icons.Download} />}
|
||||
>
|
||||
<Text size="B300">Download</Text>
|
||||
<Text size="B300">{t('Organisms.ImageViewer.download')}</Text>
|
||||
</Chip>
|
||||
</Box>
|
||||
</Header>
|
||||
|
||||
@@ -7,6 +7,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Overlay,
|
||||
OverlayBackdrop,
|
||||
@@ -66,6 +67,7 @@ type InviteUserProps = {
|
||||
requestClose: () => void;
|
||||
};
|
||||
export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const modalStyle = useModalStyle(560);
|
||||
const alive = useAlive();
|
||||
@@ -194,7 +196,7 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="H4" truncate>
|
||||
Invite
|
||||
{t('Organisms.InviteUser.invite')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No" gap="100" alignItems="Center">
|
||||
@@ -351,7 +353,7 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
||||
disabled={!validUserId || inviting}
|
||||
before={inviting && <Spinner size="200" variant="Primary" fill="Solid" />}
|
||||
>
|
||||
<Text size="B400">Invite</Text>
|
||||
<Text size="B400">{t('Organisms.InviteUser.invite')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Button, config, Icon, Icons, Text, color, toRem } from 'folds';
|
||||
import { IContent } from 'matrix-js-sdk';
|
||||
import { JUMBO_EMOJI_REG, URL_REG } from '../../utils/regex';
|
||||
@@ -507,6 +508,7 @@ type MLocationProps = {
|
||||
content: IContent;
|
||||
};
|
||||
export function MLocation({ content }: MLocationProps) {
|
||||
const { t } = useTranslation();
|
||||
const geoUri = content.geo_uri;
|
||||
if (typeof geoUri !== 'string') return <BrokenContent />;
|
||||
const location = parseGeoUri(geoUri);
|
||||
@@ -549,7 +551,7 @@ export function MLocation({ content }: MLocationProps) {
|
||||
radii="300"
|
||||
before={<Icon src={Icons.External} size="50" />}
|
||||
>
|
||||
<Text size="B300">Open Location</Text>
|
||||
<Text size="B300">{t('Organisms.Message.open_location')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Box, Icon, Icons, Text, as, color, toRem } from 'folds';
|
||||
import { EventTimelineSet, Room } from 'matrix-js-sdk';
|
||||
import React, { MouseEventHandler, ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import classNames from 'classnames';
|
||||
import { getMemberDisplayName, trimReplyFromBody } from '../../utils/room';
|
||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
||||
@@ -37,19 +38,22 @@ export const ReplyLayout = as<'div', ReplyLayoutProps>(
|
||||
),
|
||||
);
|
||||
|
||||
export const ThreadIndicator = as<'div'>(({ ...props }, ref) => (
|
||||
<Box
|
||||
shrink="No"
|
||||
className={css.ThreadIndicator}
|
||||
alignItems="Center"
|
||||
gap="100"
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<Icon size="50" src={Icons.Thread} />
|
||||
<Text size="L400">Thread</Text>
|
||||
</Box>
|
||||
));
|
||||
export const ThreadIndicator = as<'div'>(({ ...props }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box
|
||||
shrink="No"
|
||||
className={css.ThreadIndicator}
|
||||
alignItems="Center"
|
||||
gap="100"
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<Icon size="50" src={Icons.Thread} />
|
||||
<Text size="L400">{t('Organisms.Message.thread')}</Text>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
type ReplyProps = {
|
||||
room: Room;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
@@ -81,6 +82,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const blurHash = validBlurHash(info?.[MATRIX_BLUR_HASH_PROPERTY_NAME]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [load, setLoad] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [viewer, setViewer] = useState(false);
|
||||
@@ -168,7 +170,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||
onClick={loadSrc}
|
||||
before={<Icon size="Inherit" src={Icons.Photo} filled />}
|
||||
>
|
||||
<Text size="B300">View</Text>
|
||||
<Text size="B300">{t('Organisms.ImageContent.view')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
@@ -212,7 +214,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text size="B300">Spoiler</Text>
|
||||
<Text size="B300">{t('Organisms.ImageContent.spoiler')}</Text>
|
||||
</Chip>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
@@ -247,7 +249,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||
onClick={handleRetry}
|
||||
before={<Icon size="Inherit" src={Icons.Warning} filled />}
|
||||
>
|
||||
<Text size="B300">Retry</Text>
|
||||
<Text size="B300">{t('Organisms.ImageContent.retry')}</Text>
|
||||
</Button>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Button, color, config, Dialog, Header, Icon, IconButton, Icons, Text } from 'folds';
|
||||
import React, { FormEventHandler } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AuthType } from 'matrix-js-sdk';
|
||||
import { StageComponentProps } from './types';
|
||||
import { ErrorCode } from '../../cs-errorcode';
|
||||
@@ -13,6 +14,7 @@ export function PasswordStage({
|
||||
}: StageComponentProps & {
|
||||
userId: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { errorCode, error, session } = stageData;
|
||||
|
||||
const handleFormSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
||||
@@ -44,7 +46,7 @@ export function PasswordStage({
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text as="h2" size="H4">
|
||||
Account Password
|
||||
{t('Organisms.PasswordStage.account_password')}
|
||||
</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
|
||||
@@ -64,7 +66,7 @@ export function PasswordStage({
|
||||
password.
|
||||
</Text>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Password</Text>
|
||||
<Text size="L400">{t('Organisms.PasswordStage.password')}</Text>
|
||||
<PasswordInput size="400" name="passwordInput" outlined autoFocus required />
|
||||
{errorCode && (
|
||||
<Box alignItems="Center" gap="100" style={{ color: color.Critical.Main }}>
|
||||
@@ -72,7 +74,7 @@ export function PasswordStage({
|
||||
<Text size="T200">
|
||||
<b>
|
||||
{errorCode === ErrorCode.M_FORBIDDEN
|
||||
? 'Invalid Password!'
|
||||
? t('Organisms.PasswordStage.invalid_password')
|
||||
: `${errorCode}: ${error}`}
|
||||
</b>
|
||||
</Text>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { MutableRefObject, ReactNode, useImperativeHandle, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Badge, Box, Chip, Header, Icon, Icons, Spinner, Text, as, percent } from 'folds';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
@@ -43,6 +44,7 @@ export function UploadBoardHeader({
|
||||
onSend,
|
||||
imperativeHandlerRef,
|
||||
}: UploadBoardHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
const sendingRef = useRef(false);
|
||||
const uploads = useAtomValue(uploadFamilyObserverAtom);
|
||||
|
||||
@@ -88,7 +90,7 @@ export function UploadBoardHeader({
|
||||
gap="100"
|
||||
>
|
||||
<Icon src={open ? Icons.ChevronTop : Icons.ChevronRight} size="50" />
|
||||
<Text size="H6">Files</Text>
|
||||
<Text size="H6">{t('Organisms.UploadBoard.files')}</Text>
|
||||
</Box>
|
||||
<Box className={css.UploadBoardHeaderContent} alignItems="Center" gap="100">
|
||||
{isSuccess && (
|
||||
@@ -100,12 +102,12 @@ export function UploadBoardHeader({
|
||||
outlined
|
||||
after={<Icon src={Icons.Send} size="50" filled />}
|
||||
>
|
||||
<Text size="B300">Send</Text>
|
||||
<Text size="B300">{t('Organisms.UploadBoard.send')}</Text>
|
||||
</Chip>
|
||||
)}
|
||||
{isError && !open && (
|
||||
<Badge variant="Critical" fill="Solid" radii="300">
|
||||
<Text size="L400">Upload Failed</Text>
|
||||
<Text size="L400">{t('Organisms.UploadBoard.upload_failed')}</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{!isSuccess && !isError && !open && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IPreviewUrlResponse } from 'matrix-js-sdk';
|
||||
import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds';
|
||||
import { ImageOverlay } from '../ImageOverlay';
|
||||
@@ -1343,6 +1344,7 @@ function WikipediaCard({ url, prev }: { url: string; prev: IPreviewUrlResponse }
|
||||
}
|
||||
|
||||
function DiscordCard({ url, prev }: { url: string; prev: IPreviewUrlResponse }) {
|
||||
const { t } = useTranslation();
|
||||
const title = prev['og:title'] ?? '';
|
||||
const description = prev['og:description'] ?? '';
|
||||
const iconUrl = (prev['og:image'] as string | undefined) ?? '';
|
||||
@@ -1383,7 +1385,9 @@ function DiscordCard({ url, prev }: { url: string; prev: IPreviewUrlResponse })
|
||||
priority="300"
|
||||
>
|
||||
<SiteBadge label="Discord" colorClass={previewCss.BadgeDiscord} />
|
||||
<span style={{ marginLeft: '6px', opacity: 0.7, fontSize: '0.85em' }}>Join Server</span>
|
||||
<span style={{ marginLeft: '6px', opacity: 0.7, fontSize: '0.85em' }}>
|
||||
{t('Organisms.UrlPreview.join_server')}
|
||||
</span>
|
||||
</Text>
|
||||
{title && (
|
||||
<Text truncate priority="400">
|
||||
|
||||
@@ -87,7 +87,6 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
const [pttMode] = useSetting(settingsAtom, 'pttMode');
|
||||
const [pttKey] = useSetting(settingsAtom, 'pttKey');
|
||||
const [deafenKey] = useSetting(settingsAtom, 'deafenKey');
|
||||
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
||||
const [pttActive, setPttActive] = useState(false);
|
||||
|
||||
// Track microphone via ref so the PTT effect doesn't need it as a dep (avoids listener churn)
|
||||
@@ -244,68 +243,26 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
justifyContent="Center"
|
||||
alignItems="Center"
|
||||
>
|
||||
{pttMode &&
|
||||
(lotusTerminal ? (
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-2.5rem',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
background: pttActive ? 'var(--lt-accent-green-dim)' : 'var(--lt-accent-orange-dim)',
|
||||
border: `1px solid ${pttActive ? 'var(--lt-accent-green-border)' : 'var(--lt-accent-orange-border)'}`,
|
||||
borderRadius: '99px',
|
||||
padding: '0.2rem 0.9rem',
|
||||
pointerEvents: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
size="T200"
|
||||
style={{
|
||||
color: pttActive ? 'var(--lt-accent-green)' : 'var(--lt-accent-orange)',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.08em',
|
||||
fontFamily: 'JetBrains Mono, monospace',
|
||||
}}
|
||||
>
|
||||
{pttActive ? (
|
||||
<>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
animation: 'pttLivePulse 900ms ease-in-out infinite',
|
||||
}}
|
||||
>
|
||||
●
|
||||
</span>
|
||||
{' LIVE'}
|
||||
</>
|
||||
) : (
|
||||
`PTT — Hold ${pttKeyLabel}`
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<Chip
|
||||
variant={pttActive ? 'Success' : 'Warning'}
|
||||
fill="Soft"
|
||||
radii="400"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-2.2rem',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
pointerEvents: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
outlined
|
||||
>
|
||||
<Text size="T200" style={{ fontWeight: 700 }}>
|
||||
{pttActive ? '● Live' : `PTT — Hold ${pttKeyLabel}`}
|
||||
</Text>
|
||||
</Chip>
|
||||
))}
|
||||
{pttMode && (
|
||||
<Chip
|
||||
variant={pttActive ? 'Success' : 'Warning'}
|
||||
fill="Soft"
|
||||
radii="400"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-2.2rem',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
pointerEvents: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
outlined
|
||||
>
|
||||
<Text size="T200" style={{ fontWeight: 700 }}>
|
||||
{pttActive ? '● Live' : `PTT — Hold ${pttKeyLabel}`}
|
||||
</Text>
|
||||
</Chip>
|
||||
)}
|
||||
{shareConfirm && (
|
||||
<>
|
||||
<div
|
||||
|
||||
@@ -1667,7 +1667,13 @@ function SeasonalBgGrid({
|
||||
onChange: (v: Settings['seasonalThemeOverride']) => void;
|
||||
}) {
|
||||
return (
|
||||
<Box wrap="Wrap" gap="200">
|
||||
<Box
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(auto-fill, minmax(${toRem(76)}, 1fr))`,
|
||||
gap: config.space.S200,
|
||||
}}
|
||||
>
|
||||
{SEASONAL_OPTIONS.map((opt) => {
|
||||
const selected = value === opt.value;
|
||||
const isSpecial = opt.value === 'auto' || opt.value === 'off';
|
||||
@@ -1727,7 +1733,13 @@ function ChatBgGrid() {
|
||||
const isDark = theme.kind === ThemeKind.Dark;
|
||||
|
||||
return (
|
||||
<Box wrap="Wrap" gap="200">
|
||||
<Box
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(auto-fill, minmax(${toRem(76)}, 1fr))`,
|
||||
gap: config.space.S200,
|
||||
}}
|
||||
>
|
||||
{BG_OPTIONS.map((opt) => (
|
||||
<Box key={opt.value} direction="Column" gap="100" style={{ alignItems: 'center' }}>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user