# HG changeset patch # User Goffi # Date 1619707539 -7200 # Node ID 8415d882b68670781dd5e8a7f662ddae9cd215ac # Parent 97b8ce9ce54bfa0aee858dbaaf67f8b06c630c66 browser: new `editor` module: This module provide tools for dynamtic item edition. For now it implements forms autosave, and a tags editor. diff -r 97b8ce9ce54b -r 8415d882b686 libervia/pages/_browser/editor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/pages/_browser/editor.py Thu Apr 29 16:45:39 2021 +0200 @@ -0,0 +1,163 @@ +"""text edition management""" + +from browser import document, window, bind, timer +from browser.local_storage import storage +from browser.object_storage import ObjectStorage +from template import Template + +object_storage = ObjectStorage(storage) +profile = window.profile + +# how often we save forms, in seconds +AUTOSAVE_FREQUENCY = 20 + + +def serialise_form(form_elt): + ret = {} + for elt in form_elt.elements: + if elt.tagName == "INPUT": + if elt.type in ("hidden", "submit"): + continue + elif elt.type == "text": + ret[elt.name] = elt.value + else: + print(f"elt.type not managet yet: {elt.type}") + continue + elif elt.tagName == "TEXTAREA": + ret[elt.name] = elt.value + elif elt.tagName in ("BUTTON",): + continue + else: + print(f"tag not managet yet: {elt.tagName}") + continue + return ret + + +def restore_form(form_elt, data): + for elt in form_elt.elements: + if elt.tagName not in ("INPUT", "TEXTAREA"): + continue + try: + value = data[elt.name] + except KeyError: + continue + else: + elt.value = value + + +def set_form_autosave(form_id): + """Save locally form data regularly and restore it until it's submitted + + form is saved every AUTOSAVE_FREQUENCY seconds and when visibility is lost. + Saved data is restored when the method is called. + Saved data is cleared when the form is submitted. + """ + if profile is None: + print(f"No session started, won't save and restore form {form_id}") + return + + form_elt = document[form_id] + submitted = False + + key = {"profile": profile, "type": "form_autosave", "form": form_id} + try: + form_saved_data = object_storage[key] + except KeyError: + last_serialised = None + else: + print(f"restoring content of form {form_id!r}") + last_serialised = form_saved_data + restore_form(form_elt, form_saved_data) + + def save_form(): + if not submitted: + nonlocal last_serialised + serialised = serialise_form(form_elt) + if serialised != last_serialised: + last_serialised = serialised + print(f"saving content of form {form_id!r}") + object_storage[key] = serialised + + @bind(form_elt, "submit") + def on_submit(evt): + nonlocal submitted + submitted = True + print(f"clearing stored content of form {form_id!r}") + try: + del object_storage[key] + except KeyError: + print("key error") + pass + + @bind(document, "visibilitychange") + def on_visibiliy_change(evt): + print("visibility change") + if document.visibilityState != "visible": + save_form() + + timer.set_interval(save_form, AUTOSAVE_FREQUENCY * 1000) + + +class TagsEditor: + + def __init__(self, input_selector): + print("installing Tags Editor") + self.input_elt = document.select_one(input_selector) + self.input_elt.style.display = "none" + tags_editor_tpl = Template('editor/tags_editor.html') + self.tag_tpl = Template('editor/tag.html') + + editor_elt = tags_editor_tpl.get_elt() + self.input_elt.parent <= editor_elt + self.tag_input_elt = editor_elt.select_one(".tag_input") + self.tag_input_elt.bind("keydown", self.on_key_down) + self._current_tags = None + self.tags_map = {} + for tag in self.current_tags: + self.add_tag(tag, force=True) + + @property + def current_tags(self): + if self._current_tags is None: + self._current_tags = { + t.strip() for t in self.input_elt.value.split(',') if t.strip() + } + return self._current_tags + + @current_tags.setter + def current_tags(self, tags): + self._current_tags = tags + + def add_tag(self, tag, force=False): + tag = tag.strip() + if not force and (not tag or tag in self.current_tags): + return + self.current_tags = self.current_tags | {tag} + self.input_elt.value = ','.join(self.current_tags) + tag_elt = self.tag_tpl.get_elt({"label": tag}) + self.tags_map[tag] = tag_elt + self.tag_input_elt.parent.insertBefore(tag_elt, self.tag_input_elt) + tag_elt.select_one(".click_to_delete").bind( + "click", lambda evt: self.on_tag_click(evt, tag) + ) + + def remove_tag(self, tag): + try: + tag_elt = self.tags_map[tag] + except KeyError: + print(f"trying to remove an inexistant tag: {tag}") + else: + self.current_tags = self.current_tags - {tag} + self.input_elt.value = ','.join(self.current_tags) + tag_elt.remove() + + def on_tag_click(self, evt, tag): + evt.stopPropagation() + self.remove_tag(tag) + + def on_key_down(self, evt): + if evt.key in (",", "Enter"): + evt.stopPropagation() + evt.preventDefault() + self.add_tag(self.tag_input_elt.value) + self.tag_input_elt.value = ""