Mercurial > libervia-web
view libervia/pages/_browser/invitation.py @ 1331:fe353fceec38
browser (invitation, photos/album): invitation manager improvments:
invitation manager has been moved to a separated module, it is generic so it can be used
with other activities.
It has been simplified, and contact to add are dynamically filtered using a text input.
Invitation are done by email using a the new modal.
New notification module is used to indicate when invitation has been done.
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 14 Aug 2020 09:31:32 +0200 |
parents | |
children | c74e5a488af6 |
line wrap: on
line source
from browser import document, window, timer from bridge import Bridge from template import Template from dialog import notification from cache import cache bridge = Bridge() class InvitationManager: def __init__(self, invitation_type, invitation_data): self.invitation_type = invitation_type self.invitation_data = invitation_data manager_panel_tpl = Template('invitation/manager.html') self.manager_panel_elt = manager_panel_tpl.get_elt() self.invite_by_email_tpl = Template('invitation/invite_by_email.html') self.affiliation_tpl = Template('invitation/affiliation_item.html') self.new_item_tpl = Template('invitation/new_item.html') # list of item passing filter when adding a new contact self._filtered_new_items = {} self._active_new_item = None self._idx = 0 def attach(self, affiliations=None): if affiliations is None: affiliations = {} self.affiliations = affiliations self.side_panel = self.manager_panel_elt.select_one( '.invitation_manager_side_panel') self.open() for close_elt in self.manager_panel_elt.select('.click_to_close'): close_elt.bind("click", self.on_manager_close) self.side_panel.bind("click", lambda evt: evt.stopPropagation()) cache.fill_identities(affiliations.keys(), callback=self._set_affiliations) contact_elt = self.manager_panel_elt.select_one('input[name="contact"]') contact_elt.bind("input", self.on_contact_input) contact_elt.bind("keydown", self.on_contact_keydown) contact_elt.bind("focus", self.on_contact_focus) contact_elt.bind("blur", self.on_contact_blur) document['invite_email'].bind('click', self.on_invite_email_click) def _set_affiliations(self): for entity_jid, affiliation in self.affiliations.items(): self.set_affiliation(entity_jid, affiliation) def open(self): """Re-attach and show a closed panel""" self._body_ori_style = document.body.style.height, document.body.style.overflow document.body.style.height = '100vh' document.body.style.overflow = 'hidden' document.body <= self.manager_panel_elt timer.set_timeout(lambda: self.side_panel.classList.add("open"), 0) def _on_close_transition_end(self, evt): self.manager_panel_elt.remove() # FIXME: not working with Brython, to report upstream # self.side_panel.unbind("transitionend", self._on_close_transition_end) self.side_panel.unbind("transitionend") def close(self): """Hide the panel""" document.body.style.height, document.body.style.overflow = self._body_ori_style self.side_panel.classList.remove('open') self.side_panel.bind("transitionend", self._on_close_transition_end) def invite_jid(self, entity_jid): if self.invitation_type == 'photos': path = self.invitation_data["path"] service = self.invitation_data["service"] album_name = path.rsplit('/')[-1] print(f"inviting {entity_jid}") bridge.FISInvite( entity_jid, service, "photos", "", path, album_name, '', callback=lambda entity_jid=entity_jid: self._on_jid_invitation_success(entity_jid), errback=lambda e: notification.show(f"invitation failed: {e}", "error") ) def on_manager_close(self, evt): self.close() def _on_jid_invitation_success(self, entity_jid): form_elt = document['invitation_form'] contact_elt = form_elt.select_one('input[name="contact"]') contact_elt.value = "" contact_elt.dispatchEvent(window.Event.new('input')) notification.show( f"{entity_jid} has been invited", level="success", ) if entity_jid not in self.affiliations: self.set_affiliation(entity_jid, "member") def on_contact_invite(self, evt, entity_jid): """User is adding a contact""" form_elt = document['invitation_form'] contact_elt = form_elt.select_one('input[name="contact"]') contact_elt.value = "" contact_elt.dispatchEvent(window.Event.new('input')) self.invite_jid(entity_jid) def on_contact_keydown(self, evt): if evt.key == "Escape": evt.target.value = "" evt.target.dispatchEvent(window.Event.new('input')) elif evt.key == "ArrowDown": evt.stopPropagation() evt.preventDefault() content_elt = document['invitation_contact_search'].select_one( ".search_dialog__content") if self._active_new_item == None: self._active_new_item = content_elt.firstElementChild self._active_new_item.classList.add('selected') else: next_item = self._active_new_item.nextElementSibling if next_item is not None: self._active_new_item.classList.remove('selected') self._active_new_item = next_item self._active_new_item.classList.add('selected') elif evt.key == "ArrowUp": evt.stopPropagation() evt.preventDefault() content_elt = document['invitation_contact_search'].select_one( ".search_dialog__content") if self._active_new_item == None: self._active_new_item = content_elt.lastElementChild self._active_new_item.classList.add('selected') else: previous_item = self._active_new_item.previousElementSibling if previous_item is not None: self._active_new_item.classList.remove('selected') self._active_new_item = previous_item self._active_new_item.classList.add('selected') elif evt.key == "Enter": evt.stopPropagation() evt.preventDefault() if self._active_new_item is not None: entity_jid = self._active_new_item.dataset.entityJid self.invite_jid(entity_jid) def on_contact_focus(self, evt): search_dialog = document['invitation_contact_search'] search_dialog.classList.add('open') self._active_new_item = None evt.target.dispatchEvent(window.Event.new('input')) def on_contact_blur(self, evt): search_dialog = document['invitation_contact_search'] search_dialog.classList.remove('open') for elt in self._filtered_new_items.values(): elt.remove() self._filtered_new_items.clear() def on_contact_input(self, evt): text = evt.target.value.strip().lower() search_dialog = document['invitation_contact_search'] content_elt = search_dialog.select_one(".search_dialog__content") for (entity_jid, identity) in cache.identities.items(): if not cache.match_identity(entity_jid, text, identity): # if the entity was present in last pass, we remove it try: filtered_item = self._filtered_new_items.pop(entity_jid) except KeyError: pass else: filtered_item.remove() continue if entity_jid not in self._filtered_new_items: # we only create a new element if the item was not already there new_item_elt = self.new_item_tpl.get_elt({ "entity_jid": entity_jid, "identities": cache.identities, }) content_elt <= new_item_elt self._filtered_new_items[entity_jid] = new_item_elt for elt in new_item_elt.select('.click_to_ok'): # we use mousedown instead of click because otherwise it would be # ignored due to "blur" event manager (see # https://stackoverflow.com/a/9335401) elt.bind( "mousedown", lambda evt, entity_jid=entity_jid: self.on_contact_invite( evt, entity_jid), ) if ((self._active_new_item is not None and not self._active_new_item.parentElement)): # active item has been filtered out self._active_new_item = None def _on_email_invitation_success(self, invitee_jid, email, name): self.set_affiliation(invitee_jid, "member") notification.show( f"{name} has been invited, he/she has received an email with a link", level="success", ) def invitationSimpleCreateCb(self, invitation_data, email, name): if self.invitation_type == 'photos': path = self.invitation_data["path"] service = self.invitation_data["service"] invitee_jid = invitation_data['jid'] album_name = path.rsplit('/')[-1] bridge.FISInvite( invitee_jid, service, "photos", "", path, album_name, '', callback=lambda: self._on_email_invitation_success(invitee_jid, email, name), errback=lambda e: window.alert(f"invitation failed for {email}: {e}") ) # we update identities to have the name instead of the invitation jid in # affiliations cache.identities[invitee_jid] = {'nicknames': [name]} cache.update() def invite_by_email(self, email, name): guest_url_tpl = f'{window.URL.new("/g", document.baseURI).href}/{{uuid}}' bridge.invitationSimpleCreate( email, name, guest_url_tpl, '', callback=lambda data: self.invitationSimpleCreateCb(data, email, name), errback=lambda e: window.alert(f"can't send email invitation: {e}") ) def on_invite_email_submit(self, evt, invite_email_elt): evt.stopPropagation() evt.preventDefault() form = document['email_invitation_form'] try: reportValidity = form.reportValidity except AttributeError: print("reportValidity is not supported by this browser!") else: if not reportValidity(): return email = form.select_one('input[name="email"]').value name = form.select_one('input[name="name"]').value self.invite_by_email(email, name) invite_email_elt.remove() self.open() def on_invite_email_close(self, evt, invite_email_elt): evt.stopPropagation() evt.preventDefault() invite_email_elt.remove() self.open() def on_invite_email_click(self, evt): evt.stopPropagation() evt.preventDefault() invite_email_elt = self.invite_by_email_tpl.get_elt() document.body <= invite_email_elt document['email_invitation_submit'].bind( 'click', lambda evt: self.on_invite_email_submit(evt, invite_email_elt) ) for close_elt in invite_email_elt.select('.click_to_close'): close_elt.bind( "click", lambda evt: self.on_invite_email_close(evt, invite_email_elt)) self.close() ## affiliations def set_affiliation(self, entity_jid, affiliation): if affiliation not in ('owner', 'member'): raise NotImplementedError( f'{affiliation} affiliation can not be set with this method for the ' 'moment') if entity_jid not in self.affiliations: self.affiliations[entity_jid] = affiliation affiliation_elt = self.affiliation_tpl.get_elt({ "entity_jid": entity_jid, "affiliation": affiliation, "identities": cache.identities, }) document['affiliations'] <= affiliation_elt for elt in affiliation_elt.select(".click_to_delete"): elt.bind( "click", lambda evt, entity_jid=entity_jid, affiliation_elt=affiliation_elt: self.on_affiliation_remove(entity_jid, affiliation_elt) ) def _on_affiliation_remove_success(self, affiliation_elt, entity_jid): affiliation_elt.remove() del self.affiliations[entity_jid] def on_affiliation_remove(self, entity_jid, affiliation_elt): if self.invitation_type == 'photos': path = self.invitation_data["path"] service = self.invitation_data["service"] bridge.FISAffiliationsSet( service, "", path, {entity_jid: "none"}, callback=lambda: self._on_affiliation_remove_success( affiliation_elt, entity_jid), errback=lambda e: notification.show( f"can't remove affiliation: {e}", "error") )