Skip to content

Commit 3519226

Browse files
author
Mark Graham
committed
Address review comments
1 parent e8616e7 commit 3519226

File tree

2 files changed

+117
-122
lines changed

2 files changed

+117
-122
lines changed

frontend/src/components/chat/chat_message.scss

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,12 @@ $bubble-width: 400px;
177177
}
178178

179179
.image-modal {
180+
@include common.flex-row-align-center;
180181
position: fixed;
181-
top: 0;
182-
left: 0;
183-
width: 100vw;
184-
height: 100vh;
182+
inset: 0;
185183
background: rgba(0, 0, 0, 0.92);
186-
display: flex;
187-
align-items: center;
188184
justify-content: center;
189-
z-index: 9999;
185+
z-index: 1;
190186
cursor: pointer;
191187
animation: fadeIn 0.2s ease;
192188
// Ensure modal is isolated from parent stacking context
@@ -211,9 +207,10 @@ $bubble-width: 400px;
211207
}
212208

213209
.close-button {
214-
position: absolute;
215-
top: -40px;
216-
right: 0;
210+
@include common.flex-row-align-center;
211+
position: fixed;
212+
top: 20px;
213+
right: 20px;
217214
background: var(--md-sys-color-surface);
218215
color: var(--md-sys-color-on-surface);
219216
border: none;
@@ -222,11 +219,10 @@ $bubble-width: 400px;
222219
height: 36px;
223220
font-size: 20px;
224221
cursor: pointer;
225-
display: flex;
226-
align-items: center;
227222
justify-content: center;
228223
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
229224
transition: background 0.2s ease;
225+
z-index: 2;
230226

231227
&:hover {
232228
background: var(--md-sys-color-surface-variant);

frontend/src/components/chat/chat_message.ts

Lines changed: 109 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import '../participant_profile/avatar_icon';
33
import {MobxLitElement} from '@adobe/lit-mobx';
44

55
import {CSSResultGroup, html, nothing} from 'lit';
6-
import {customElement, property} from 'lit/decorators.js';
6+
import {customElement, property, state} from 'lit/decorators.js';
77
import {classMap} from 'lit/directives/class-map.js';
88
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
99

@@ -32,147 +32,146 @@ export class ChatMessageComponent extends MobxLitElement {
3232
private readonly participantService = core.getService(ParticipantService);
3333

3434
@property() chat: ChatMessage | undefined = undefined;
35-
private maximizedImageUrl: string | null = null;
35+
@state() private maximizedImageUrl: string | null = null;
3636
private modalElement: HTMLDivElement | null = null;
37-
private keyboardHandler: ((e: KeyboardEvent) => void) | null = null;
3837

3938
private openImageModal(imageUrl: string) {
4039
this.maximizedImageUrl = imageUrl;
41-
this.renderModalToBody();
4240
}
4341

4442
private closeImageModal() {
4543
this.maximizedImageUrl = null;
44+
}
45+
46+
private handleEscapeKey = (e: KeyboardEvent) => {
47+
if (e.key === 'Escape' && this.maximizedImageUrl) {
48+
this.closeImageModal();
49+
}
50+
};
51+
52+
override connectedCallback() {
53+
super.connectedCallback();
54+
document.addEventListener('keydown', this.handleEscapeKey);
55+
}
56+
57+
override disconnectedCallback() {
58+
super.disconnectedCallback();
59+
document.removeEventListener('keydown', this.handleEscapeKey);
4660
this.removeModalFromBody();
4761
}
4862

63+
override updated(changedProperties: Map<string, unknown>) {
64+
super.updated(changedProperties);
65+
66+
if (changedProperties.has('maximizedImageUrl')) {
67+
if (this.maximizedImageUrl) {
68+
this.renderModalToBody();
69+
} else {
70+
this.removeModalFromBody();
71+
}
72+
}
73+
}
74+
4975
private renderModalToBody() {
5076
// Remove existing modal if present
5177
this.removeModalFromBody();
5278

5379
// Create modal element
5480
this.modalElement = document.createElement('div');
5581
this.modalElement.className = 'chat-image-modal';
56-
this.modalElement.innerHTML = `
57-
<div class="modal-backdrop">
58-
<div class="modal-content">
59-
<button class="close-button">✕</button>
60-
<img src="${this.maximizedImageUrl}" alt="Maximized Image" />
61-
</div>
62-
</div>
82+
this.modalElement.style.cssText = `
83+
position: fixed;
84+
inset: 0;
85+
background: rgba(0, 0, 0, 0.92);
86+
display: flex;
87+
align-items: center;
88+
justify-content: center;
89+
z-index: 1;
90+
cursor: pointer;
91+
animation: fadeIn 0.2s ease;
6392
`;
6493

65-
// Add event listeners
66-
const backdrop = this.modalElement.querySelector('.modal-backdrop');
67-
const closeButton = this.modalElement.querySelector('.close-button');
68-
const img = this.modalElement.querySelector('img');
94+
const content = document.createElement('div');
95+
content.style.cssText = `
96+
position: relative;
97+
max-width: 95vw;
98+
max-height: 95vh;
99+
display: flex;
100+
align-items: center;
101+
justify-content: center;
102+
`;
69103

70-
backdrop?.addEventListener('click', () => this.closeImageModal());
71-
closeButton?.addEventListener('click', () => this.closeImageModal());
72-
img?.addEventListener('click', (e) => e.stopPropagation());
104+
const closeButton = document.createElement('button');
105+
closeButton.textContent = '✕';
106+
closeButton.style.cssText = `
107+
position: fixed;
108+
top: 20px;
109+
right: 20px;
110+
background: var(--md-sys-color-surface);
111+
color: var(--md-sys-color-on-surface);
112+
border: 2px solid var(--md-sys-color-outline);
113+
border-radius: 50%;
114+
width: 40px;
115+
height: 40px;
116+
font-size: 24px;
117+
font-weight: bold;
118+
cursor: pointer;
119+
display: flex;
120+
align-items: center;
121+
justify-content: center;
122+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
123+
transition: all 0.2s ease;
124+
z-index: 2;
125+
`;
126+
closeButton.addEventListener('click', () => this.closeImageModal());
127+
closeButton.addEventListener('mouseenter', () => {
128+
closeButton.style.background = 'var(--md-sys-color-surface-variant)';
129+
closeButton.style.transform = 'scale(1.1)';
130+
});
131+
closeButton.addEventListener('mouseleave', () => {
132+
closeButton.style.background = 'var(--md-sys-color-surface)';
133+
closeButton.style.transform = 'scale(1)';
134+
});
73135

74-
// Add keyboard listener for Escape key
75-
this.keyboardHandler = (e: KeyboardEvent) => {
76-
if (e.key === 'Escape') {
77-
this.closeImageModal();
78-
}
79-
};
80-
document.addEventListener('keydown', this.keyboardHandler);
136+
const img = document.createElement('img');
137+
img.src = this.maximizedImageUrl!;
138+
img.alt = 'Maximized Image';
139+
img.style.cssText = `
140+
max-width: 100%;
141+
max-height: 95vh;
142+
object-fit: contain;
143+
border-radius: 8px;
144+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
145+
`;
146+
img.addEventListener('click', (e) => e.stopPropagation());
81147

82-
// Add styles
83-
this.injectModalStyles();
148+
content.appendChild(img);
149+
this.modalElement.appendChild(content);
150+
this.modalElement.appendChild(closeButton);
151+
this.modalElement.addEventListener('click', () => this.closeImageModal());
84152

85-
// Append to body
153+
// Append modal to body
86154
document.body.appendChild(this.modalElement);
155+
156+
// Add fade-in keyframes if not already present
157+
if (!document.getElementById('chat-modal-keyframes')) {
158+
const style = document.createElement('style');
159+
style.id = 'chat-modal-keyframes';
160+
style.textContent = `
161+
@keyframes fadeIn {
162+
from { opacity: 0; }
163+
to { opacity: 1; }
164+
}
165+
`;
166+
document.head.appendChild(style);
167+
}
87168
}
88169

89170
private removeModalFromBody() {
90171
if (this.modalElement) {
91172
this.modalElement.remove();
92173
this.modalElement = null;
93174
}
94-
// Remove keyboard listener
95-
if (this.keyboardHandler) {
96-
document.removeEventListener('keydown', this.keyboardHandler);
97-
this.keyboardHandler = null;
98-
}
99-
}
100-
101-
private injectModalStyles() {
102-
// Check if styles already exist
103-
if (document.getElementById('chat-message-modal-styles')) {
104-
return;
105-
}
106-
107-
const styleElement = document.createElement('style');
108-
styleElement.id = 'chat-message-modal-styles';
109-
styleElement.textContent = `
110-
.chat-image-modal .modal-backdrop {
111-
position: fixed;
112-
top: 0;
113-
left: 0;
114-
width: 100vw;
115-
height: 100vh;
116-
background: rgba(0, 0, 0, 0.92);
117-
display: flex;
118-
align-items: center;
119-
justify-content: center;
120-
z-index: 999999;
121-
cursor: pointer;
122-
animation: fadeIn 0.2s ease;
123-
}
124-
125-
.chat-image-modal .modal-content {
126-
position: relative;
127-
max-width: 95vw;
128-
max-height: 95vh;
129-
display: flex;
130-
align-items: center;
131-
justify-content: center;
132-
}
133-
134-
.chat-image-modal .modal-content img {
135-
max-width: 100%;
136-
max-height: 95vh;
137-
object-fit: contain;
138-
border-radius: 8px;
139-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
140-
}
141-
142-
.chat-image-modal .close-button {
143-
position: absolute;
144-
top: -40px;
145-
right: 0;
146-
background: #fff;
147-
color: #000;
148-
border: none;
149-
border-radius: 50%;
150-
width: 36px;
151-
height: 36px;
152-
font-size: 20px;
153-
cursor: pointer;
154-
display: flex;
155-
align-items: center;
156-
justify-content: center;
157-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
158-
transition: background 0.2s ease;
159-
}
160-
161-
.chat-image-modal .close-button:hover {
162-
background: #f0f0f0;
163-
}
164-
165-
@keyframes fadeIn {
166-
from { opacity: 0; }
167-
to { opacity: 1; }
168-
}
169-
`;
170-
document.head.appendChild(styleElement);
171-
}
172-
173-
override disconnectedCallback() {
174-
super.disconnectedCallback();
175-
this.removeModalFromBody();
176175
}
177176

178177
override render() {

0 commit comments

Comments
 (0)