home · contact · privacy
Improve tag selection interface in files index. master
authorChristian Heller <c.heller@plomlompom.de>
Sat, 15 Feb 2025 06:22:46 +0000 (07:22 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Sat, 15 Feb 2025 06:22:46 +0000 (07:22 +0100)
src/templates/_macros.tmpl
src/templates/files.tmpl
src/templates/playlist.tmpl
src/ytplom/http.py
src/ytplom/misc.py

index 9c70226f3ab0be0f3b7d579f143716e064fdb5b6..602d2d3d800619d800ee57857359e7318d4755cd 100644 (file)
@@ -3,3 +3,12 @@
 {% if display_name %}{{display_name}}{% else %}{{target}}{% endif %}
 {% if cond %}</a>{% endif %}
 {% endmacro %}
 {% if display_name %}{{display_name}}{% else %}{{target}}{% endif %}
 {% if cond %}</a>{% endif %}
 {% endmacro %}
+
+
+{% macro js_new_child_to() %}
+function new_child_to(tag, parent, textContent='') {
+    const el = document.createElement(tag);
+    parent.appendChild(el);
+    el.textContent = textContent;
+    return el; }
+{% endmacro %}
index e447c2aef021f22b3abdc04df3d1ff302adf9637..e0fc5b6103f2a245d11598e921ea23b253c348ce 100644 (file)
@@ -1,11 +1,62 @@
 {% extends '_base.tmpl' %}
 
 
 {% extends '_base.tmpl' %}
 
 
+{% block script %}
+{{ macros.js_new_child_to() }}
+
+const all_tags = {{showable_tags|tojson|safe}};
+var needed_tags = {{needed_tags|tojson|safe}};
+
+function select_tag() {
+  if (tags_select.selectedIndex < 1) {
+    return;
+  }
+  const chosen_tag = document.getElementById('tags_select').value;
+  needed_tags.push(chosen_tag);
+  reload_selector();
+}
+
+function reload_selector() {
+  const tags_select = document.getElementById('tags_select');
+  while (tags_select.options.length > 0) {
+    tags_select.remove(0);
+  }
+  new_child_to('option', tags_select, 'add tag');
+  all_tags.forEach((tag) => {
+    if (needed_tags.includes(tag)) {
+      return;
+    }
+    const option = new_child_to('option', tags_select, tag);
+  });
+  const tags_div = document.getElementById("tags");
+  tags_div.innerHTML = '';
+  needed_tags.forEach((chosen_tag) => {
+    const tag_text_node = document.createTextNode(` ${chosen_tag} `);
+    tags_div.appendChild(tag_text_node);
+    const btn_del = new_child_to('button', tags_div, 'x');
+    btn_del.onclick = function() {
+      tag_text_node.remove();
+      btn_del.remove();
+      needed_tags = needed_tags.filter(tag => tag !== chosen_tag);
+      reload_selector();
+    };
+    const input = new_child_to('input', tags_div);
+    input.type = 'hidden';
+    input.name = 'needed_tag';
+    input.value = chosen_tag;
+  });
+}
+
+window.addEventListener('load', reload_selector);
+{% endblock %}
+
+
 {% block body %}
 <form method="GET">
 {% block body %}
 <form method="GET">
-filter filename: <input name="filter_path" value="{{filter_path}}" />
-needed tags: <input name="needed_tags" value="{{needed_tags}}" />
-show absent: <input type="checkbox" name="show_absent" {% if show_absent %}checked{% endif %}/>
+<input type="checkbox" name="show_absent" {% if show_absent %}checked{% endif %}/> show absent<br />
+filename: <input name="filter_path" value="{{filter_path}}" /><br />
+tags: <span id="tags"></span><br />
+<select id="tags_select" onchange="select_tag()"></select><br />
 <input type="submit" value="filter" />
 </form>
 <p>known files (shown: {{files|length}}):</p>
 <input type="submit" value="filter" />
 </form>
 <p>known files (shown: {{files|length}}):</p>
index c4cdeb7e48f721ec916ea40bb30296038cba6829..a27d4a295f8a77b82d761bb4545bb8a5e91616dc 100644 (file)
@@ -2,17 +2,12 @@
 
 
 {% block script %}
 
 
 {% block script %}
+{{ macros.js_new_child_to() }}
 
 const CLS_PLAYLIST_ROW = 'playlist_row';
 events_params += 'playlist=1';
 var first_load = true;
 
 
 const CLS_PLAYLIST_ROW = 'playlist_row';
 events_params += 'playlist=1';
 var first_load = true;
 
-function new_child_to(tag, parent, textContent='') {
-    const el = document.createElement(tag);
-    parent.appendChild(el);
-    el.textContent = textContent;
-    return el; }
-
 event_handlers.push(function(data) {  // update playlist
     const table = document.getElementById('playlist_rows');
     var old_rows = document.getElementsByClassName(CLS_PLAYLIST_ROW);
 event_handlers.push(function(data) {  // update playlist
     const table = document.getElementById('playlist_rows');
     var old_rows = document.getElementsByClassName(CLS_PLAYLIST_ROW);
index 4f30f131b95ec65b7dc886c98d44ee88c09f285b..a17b00ed215353dec0d1e6285d824793988dff59 100644 (file)
@@ -301,20 +301,22 @@ class _TaskHandler(PlomHttpHandler):
 
     def _send_files_index(self) -> None:
         filter_path = FilterStr(self.params.first_for('filter_path'))
 
     def _send_files_index(self) -> None:
         filter_path = FilterStr(self.params.first_for('filter_path'))
-        needed_tags_str = self.params.first_for('needed_tags')
         show_absent = bool(self.params.first_for('show_absent'))
         show_absent = bool(self.params.first_for('show_absent'))
+        needed_tags_list = self.params.all_for('needed_tag')
         with DbConn() as conn:
             files = VideoFile.get_filtered(
                     conn,
                     filter_path,
         with DbConn() as conn:
             files = VideoFile.get_filtered(
                     conn,
                     filter_path,
-                    TagSet.from_joined(needed_tags_str),
+                    TagSet(set(needed_tags_list)),
                     show_absent)
                     show_absent)
+            showable_tags = sorted(list(VideoFile.all_tags_showable(conn)))
         files.sort(key=lambda t: t.rel_path)
         self._send_rendered_template(_NAME_TEMPLATE_FILES,
                                      {'files': files,
                                       'selected': 'files',
                                       'filter_path': filter_path,
         files.sort(key=lambda t: t.rel_path)
         self._send_rendered_template(_NAME_TEMPLATE_FILES,
                                      {'files': files,
                                       'selected': 'files',
                                       'filter_path': filter_path,
-                                      'needed_tags': needed_tags_str,
+                                      'showable_tags': showable_tags,
+                                      'needed_tags': needed_tags_list,
                                       'show_absent': show_absent})
 
     def _send_missing_json(self) -> None:
                                       'show_absent': show_absent})
 
     def _send_missing_json(self) -> None:
index 51a39aa0092e1b89e3c0ed91dd8b124497db47d3..010a2fb193dee7e70e8836059c20a595e527cdf0 100644 (file)
@@ -390,9 +390,17 @@ class VideoFile(DbData):
             and needed_tags_seen.whitelisted(cls.tags_display_whitelist
                                              ).are_all_in(f.tags)]
 
             and needed_tags_seen.whitelisted(cls.tags_display_whitelist
                                              ).are_all_in(f.tags)]
 
+    @classmethod
+    def all_tags_showable(cls, conn) -> TagSet:
+        """Show all used tags passing .tags_display_whitelist."""
+        tags = TagSet()
+        for file in cls.get_all(conn):
+            tags.add(file.tags.whitelisted(cls.tags_display_whitelist))
+        return tags
+
     @property
     def tags_showable(self) -> TagSet:
     @property
     def tags_showable(self) -> TagSet:
-        """Show .tags passing .tags_display_whitelist, if latter set."""
+        """Show .tags passing .tags_display_whitelist."""
         return self.tags.whitelisted(self.tags_display_whitelist)
 
     def unused_tags(self, conn: DbConn) -> TagSet:
         return self.tags.whitelisted(self.tags_display_whitelist)
 
     def unused_tags(self, conn: DbConn) -> TagSet: