comparison libervia/web/pages/_browser/dialog.py @ 1625:698eaabfca0e

browser (chat): side/panel and keyword handling: - `dialog.Modal` can now be used with a `position` argument. The default `center` keeps the old behaviour of modal in the middle of the screen, while using one of the direction makes the modal appear from top/bottom/right/left with an animation. The closing cross has been removed in favor of clicking/touching any part outside of the modal. - A method to create mockup clones of elements is now used to make placeholders. It is used for the input panel in the main area when it is moved to a sub-panel modal, this way there is not element disappearing. - Clicking on a keyword now shows a sub-messages panel with all messages with this keyword, in a similar way as for threads. Writing a messages there will add the keyword automatically. - Sub-messages panel will auto-scroll when messages are added. rel 458
author Goffi <goffi@goffi.org>
date Fri, 06 Jun 2025 11:08:05 +0200
parents 3a60bf3762ef
children
comparison
equal deleted inserted replaced
1624:fd421f1be8f5 1625:698eaabfca0e
1 """manage common dialogs""" 1 """manage common dialogs"""
2 2
3 from typing import Callable, Literal
3 from browser import document, window, timer, console as log 4 from browser import document, window, timer, console as log
4 from template import Template 5 from template import Template
5 6
6 log.warning = log.warn 7 log.warning = log.warn
7 8
181 elt.bind('click', lambda __: self.retry(notif_elt)) 182 elt.bind('click', lambda __: self.retry(notif_elt))
182 183
183 184
184 class Modal: 185 class Modal:
185 186
186 def __init__(self, content_elt, is_card=False, closable=False, close_cb=None): 187 def __init__(
187 """Init a Modal instance. 188 self,
188 189 content_elt,
189 @param content_elt: Content of the modal. 190 is_card: bool = False,
190 @param is_card: If True, a Modal card will be used. The ``content_elt`` must be a 191 closable: bool = False,
191 <div> with the "modal-card" class. 192 close_cb: Callable|None = None,
192 @param closable: if True, add a close cross at the top right of the modal. 193 position: Literal["center", "left", "right", "top", "bottom"] = "center"
193 """ 194 ) -> None:
195 self.position = position
194 self.is_card = is_card 196 self.is_card = is_card
195 if is_card: 197 if is_card and position == 'center':
196 if not content_elt.classList.contains("modal-card"): 198 if not content_elt.classList.contains("modal-card"):
197 raise ValueError( 199 raise ValueError(
198 'Element must have a "modal-card" class when `is_card` is used' 200 'Element must have a "modal-card" class when `is_card` is used for center modal'
199 ) 201 )
200 self.closable = closable 202 self.closable = closable
201 self._close_cb = close_cb 203 self._close_cb = close_cb
202 self._tpl = Template("dialogs/modal.html") 204 self._tpl = Template("dialogs/modal.html")
203 self.content_elt = content_elt 205 self.content_elt = content_elt
204 self._modal_elt = None 206 self._modal_elt = None
205 self.reset() 207 self._closing = False
206 208 self._is_panel = position != 'center'
207 def reset(self): 209
208 """Reset values of callbacks and notif element""" 210 def _cleanup(self):
209 if self._modal_elt is not None: 211 if self._modal_elt is not None:
210 self._modal_elt.remove() 212 self._modal_elt.remove()
211 self._modal_elt = None 213 self._modal_elt = None
212 214 self._closing = False
213 def _default_cancel_cb(self, evt, notif_elt):
214 notif_elt.remove()
215 215
216 def close(self): 216 def close(self):
217 """Close the dialog.""" 217 if self._modal_elt is None or self._closing:
218 if self._modal_elt is None: 218 return
219 log.warning("Calling close on an unshown dialog.") 219
220 self.reset() 220 self._closing = True
221 221
222 def on_close_click(self, evt) -> None: 222 self._modal_elt.classList.remove('is-active')
223 evt.preventDefault() 223
224 evt.stopPropagation() 224 if self._is_panel:
225 if self._close_cb is not None: 225 self._modal_elt.classList.add('closing')
226 self._close_cb() 226
227 self.close() 227 def on_close_finished():
228 if self._close_cb:
229 self._close_cb()
230 self._cleanup()
231
232 timer.set_timeout(on_close_finished, 300)
233
234 def on_background_click(self, evt):
235 if self.closable and not self._closing:
236 evt.preventDefault()
237 evt.stopPropagation()
238 self.close()
228 239
229 def show(self) -> None: 240 def show(self) -> None:
230 modal_elt = self._tpl.get_elt({ 241 if self._modal_elt:
242 self._cleanup()
243
244 self._modal_elt = self._tpl.get_elt({
231 "closable": self.closable, 245 "closable": self.closable,
232 }) 246 "position": self.position,
233 self._modal_elt = modal_elt 247 "is_panel": self._is_panel
234 if self.is_card: 248 })
235 container_elt = modal_elt 249
250 if self.position == 'center':
251 container_elt = self._modal_elt.select_one(".modal-content")
252 container_elt <= self.content_elt
236 else: 253 else:
237 container_elt = modal_elt.select_one(".modal-content") 254 container_elt = self._modal_elt.select_one(".modal-panel-container")
238 container_elt <= self.content_elt 255 container_elt <= self.content_elt
239 256 container_elt.classList.add(f"is-{self.position}")
240 document['notifs_area'] <= modal_elt 257
241 for ok_elt in modal_elt.select(".modal-close"): 258 document['notifs_area'] <= self._modal_elt
242 ok_elt.bind("click", self.on_close_click) 259
260 if self.closable:
261 bg = self._modal_elt.select_one(".modal-background, .modal-panel-background")
262 if bg:
263 bg.bind("click", self.on_background_click)
264
265 # Add active class after a small delay to trigger animation
266 timer.set_timeout(lambda: self._modal_elt.classList.add('is-active'), 10)
243 267
244 268
245 notification = Notification() 269 notification = Notification()