fix/polish: wave 1+2 improvements across six features
CI / Build & Quality Checks (push) Successful in 10m25s
CI / Build & Quality Checks (push) Successful in 10m25s
B1 - GIF upload progress: spinner on GIF button + disabled state while
fetch+upload is in flight; clears on success or error
B2 - PiP position persistence: drag end saves left/top to localStorage;
entering PiP restores saved position (clamped to current viewport)
B3 - PiP snap-to-corner: double-click the PiP overlay snaps to nearest
corner with a 180ms CSS transition; saves new position
B4 - Device sessions loading state: useOtherUserDevices now returns
{status:'loading'|'error'|'success', devices} instead of bare
array; UserDeviceSessions shows spinner while loading
B5 - Device sessions error state: catch in hook sets status:'error';
panel shows warning icon + 'Could not load sessions' message
B6 - Screenshare fullscreen Safari guard: hide button when
document.fullscreenEnabled is false (iOS Safari, some mobile)
B7 - Status save error: show critical-coloured error text below Save
button when saveState.status === AsyncStatus.Error
B9 - Encrypted search coverage counter: 'X / Y cached' badge next to
'Encrypted Rooms' heading using existing localResult fields
D2 - PiP screenshare spotlight: track auto-spotlight in a ref; release
spotlight when screenshare ends while in PiP mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -299,6 +299,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
const gifBtnRef = useRef<HTMLButtonElement>(null);
|
||||
const [hideStickerBtn, setHideStickerBtn] = useState(document.body.clientWidth < 500);
|
||||
const [gifError, setGifError] = React.useState<string | null>(null);
|
||||
const [gifUploading, setGifUploading] = React.useState(false);
|
||||
|
||||
const isComposing = useComposingCheck();
|
||||
|
||||
@@ -519,6 +520,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
|
||||
const handleGifSelect = useCallback(
|
||||
async (gifUrl: string, w: number, h: number) => {
|
||||
setGifUploading(true);
|
||||
try {
|
||||
// Only fetch from trusted Giphy CDN domains
|
||||
const allowed = [
|
||||
@@ -562,6 +564,8 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
if (!alive()) return;
|
||||
setGifError('Failed to send GIF. Please try again.');
|
||||
setTimeout(() => setGifError(null), 4000);
|
||||
} finally {
|
||||
if (alive()) setGifUploading(false);
|
||||
}
|
||||
},
|
||||
[mx, roomId, alive],
|
||||
@@ -840,22 +844,27 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
ref={gifBtnRef}
|
||||
aria-label="Insert GIF"
|
||||
aria-pressed={gifOpen}
|
||||
onClick={() => setGifOpen(!gifOpen)}
|
||||
onClick={() => !gifUploading && setGifOpen(!gifOpen)}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
disabled={gifUploading}
|
||||
>
|
||||
<Text
|
||||
size="T200"
|
||||
style={{
|
||||
fontWeight: 800,
|
||||
fontSize: '11px',
|
||||
letterSpacing: '0.04em',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
GIF
|
||||
</Text>
|
||||
{gifUploading ? (
|
||||
<Spinner variant="Secondary" size="100" />
|
||||
) : (
|
||||
<Text
|
||||
size="T200"
|
||||
style={{
|
||||
fontWeight: 800,
|
||||
fontSize: '11px',
|
||||
letterSpacing: '0.04em',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
GIF
|
||||
</Text>
|
||||
)}
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user