Mercurial > libervia-web
comparison libervia/pages/_browser/editor.py @ 1415:8415d882b686
browser: new `editor` module:
This module provide tools for dynamtic item edition.
For now it implements forms autosave, and a tags editor.
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 29 Apr 2021 16:45:39 +0200 |
parents | |
children | 925a7c498cda |
comparison
equal
deleted
inserted
replaced
1414:97b8ce9ce54b | 1415:8415d882b686 |
---|---|
1 """text edition management""" | |
2 | |
3 from browser import document, window, bind, timer | |
4 from browser.local_storage import storage | |
5 from browser.object_storage import ObjectStorage | |
6 from template import Template | |
7 | |
8 object_storage = ObjectStorage(storage) | |
9 profile = window.profile | |
10 | |
11 # how often we save forms, in seconds | |
12 AUTOSAVE_FREQUENCY = 20 | |
13 | |
14 | |
15 def serialise_form(form_elt): | |
16 ret = {} | |
17 for elt in form_elt.elements: | |
18 if elt.tagName == "INPUT": | |
19 if elt.type in ("hidden", "submit"): | |
20 continue | |
21 elif elt.type == "text": | |
22 ret[elt.name] = elt.value | |
23 else: | |
24 print(f"elt.type not managet yet: {elt.type}") | |
25 continue | |
26 elif elt.tagName == "TEXTAREA": | |
27 ret[elt.name] = elt.value | |
28 elif elt.tagName in ("BUTTON",): | |
29 continue | |
30 else: | |
31 print(f"tag not managet yet: {elt.tagName}") | |
32 continue | |
33 return ret | |
34 | |
35 | |
36 def restore_form(form_elt, data): | |
37 for elt in form_elt.elements: | |
38 if elt.tagName not in ("INPUT", "TEXTAREA"): | |
39 continue | |
40 try: | |
41 value = data[elt.name] | |
42 except KeyError: | |
43 continue | |
44 else: | |
45 elt.value = value | |
46 | |
47 | |
48 def set_form_autosave(form_id): | |
49 """Save locally form data regularly and restore it until it's submitted | |
50 | |
51 form is saved every AUTOSAVE_FREQUENCY seconds and when visibility is lost. | |
52 Saved data is restored when the method is called. | |
53 Saved data is cleared when the form is submitted. | |
54 """ | |
55 if profile is None: | |
56 print(f"No session started, won't save and restore form {form_id}") | |
57 return | |
58 | |
59 form_elt = document[form_id] | |
60 submitted = False | |
61 | |
62 key = {"profile": profile, "type": "form_autosave", "form": form_id} | |
63 try: | |
64 form_saved_data = object_storage[key] | |
65 except KeyError: | |
66 last_serialised = None | |
67 else: | |
68 print(f"restoring content of form {form_id!r}") | |
69 last_serialised = form_saved_data | |
70 restore_form(form_elt, form_saved_data) | |
71 | |
72 def save_form(): | |
73 if not submitted: | |
74 nonlocal last_serialised | |
75 serialised = serialise_form(form_elt) | |
76 if serialised != last_serialised: | |
77 last_serialised = serialised | |
78 print(f"saving content of form {form_id!r}") | |
79 object_storage[key] = serialised | |
80 | |
81 @bind(form_elt, "submit") | |
82 def on_submit(evt): | |
83 nonlocal submitted | |
84 submitted = True | |
85 print(f"clearing stored content of form {form_id!r}") | |
86 try: | |
87 del object_storage[key] | |
88 except KeyError: | |
89 print("key error") | |
90 pass | |
91 | |
92 @bind(document, "visibilitychange") | |
93 def on_visibiliy_change(evt): | |
94 print("visibility change") | |
95 if document.visibilityState != "visible": | |
96 save_form() | |
97 | |
98 timer.set_interval(save_form, AUTOSAVE_FREQUENCY * 1000) | |
99 | |
100 | |
101 class TagsEditor: | |
102 | |
103 def __init__(self, input_selector): | |
104 print("installing Tags Editor") | |
105 self.input_elt = document.select_one(input_selector) | |
106 self.input_elt.style.display = "none" | |
107 tags_editor_tpl = Template('editor/tags_editor.html') | |
108 self.tag_tpl = Template('editor/tag.html') | |
109 | |
110 editor_elt = tags_editor_tpl.get_elt() | |
111 self.input_elt.parent <= editor_elt | |
112 self.tag_input_elt = editor_elt.select_one(".tag_input") | |
113 self.tag_input_elt.bind("keydown", self.on_key_down) | |
114 self._current_tags = None | |
115 self.tags_map = {} | |
116 for tag in self.current_tags: | |
117 self.add_tag(tag, force=True) | |
118 | |
119 @property | |
120 def current_tags(self): | |
121 if self._current_tags is None: | |
122 self._current_tags = { | |
123 t.strip() for t in self.input_elt.value.split(',') if t.strip() | |
124 } | |
125 return self._current_tags | |
126 | |
127 @current_tags.setter | |
128 def current_tags(self, tags): | |
129 self._current_tags = tags | |
130 | |
131 def add_tag(self, tag, force=False): | |
132 tag = tag.strip() | |
133 if not force and (not tag or tag in self.current_tags): | |
134 return | |
135 self.current_tags = self.current_tags | {tag} | |
136 self.input_elt.value = ','.join(self.current_tags) | |
137 tag_elt = self.tag_tpl.get_elt({"label": tag}) | |
138 self.tags_map[tag] = tag_elt | |
139 self.tag_input_elt.parent.insertBefore(tag_elt, self.tag_input_elt) | |
140 tag_elt.select_one(".click_to_delete").bind( | |
141 "click", lambda evt: self.on_tag_click(evt, tag) | |
142 ) | |
143 | |
144 def remove_tag(self, tag): | |
145 try: | |
146 tag_elt = self.tags_map[tag] | |
147 except KeyError: | |
148 print(f"trying to remove an inexistant tag: {tag}") | |
149 else: | |
150 self.current_tags = self.current_tags - {tag} | |
151 self.input_elt.value = ','.join(self.current_tags) | |
152 tag_elt.remove() | |
153 | |
154 def on_tag_click(self, evt, tag): | |
155 evt.stopPropagation() | |
156 self.remove_tag(tag) | |
157 | |
158 def on_key_down(self, evt): | |
159 if evt.key in (",", "Enter"): | |
160 evt.stopPropagation() | |
161 evt.preventDefault() | |
162 self.add_tag(self.tag_input_elt.value) | |
163 self.tag_input_elt.value = "" |