From a39be1881dd1480d1c7645427b48a6123a716639 Mon Sep 17 00:00:00 2001
From: dcz <dcz@ipipan.waw.pl>
Date: Fri, 10 Mar 2023 08:14:56 +0200
Subject: [PATCH] Hierarchy window - step 1

---
 entries/form_fields/generic_fields.py         |    1 -
 entries/static/entries/css/entries.css        |   12 +
 entries/templates/entries_base.html           |    1 +
 entries/templates/hierarchy.html              |   24 +
 entries/urls.py                               |    1 +
 entries/views.py                              |   14 +
 .../Unification/LexicalUnitEdit.vue           |    5 +-
 .../unification/hierarchy/Hierarchy.vue       |   80 ++
 .../unification/hierarchy/HierarchyEdit.vue   | 1095 +++++++++++++++++
 .../hierarchy/HierarchyPreview.vue            |   64 +
 .../hierarchy/HierarchyRightPane.vue          |   68 +
 frontend/src/main.js                          |    2 +
 unifier/views.py                              |    5 +-
 13 files changed, 1368 insertions(+), 4 deletions(-)
 create mode 100644 entries/templates/hierarchy.html
 create mode 100644 frontend/src/components/unification/hierarchy/Hierarchy.vue
 create mode 100644 frontend/src/components/unification/hierarchy/HierarchyEdit.vue
 create mode 100644 frontend/src/components/unification/hierarchy/HierarchyPreview.vue
 create mode 100644 frontend/src/components/unification/hierarchy/HierarchyRightPane.vue

diff --git a/entries/form_fields/generic_fields.py b/entries/form_fields/generic_fields.py
index 6525566..b982ebe 100644
--- a/entries/form_fields/generic_fields.py
+++ b/entries/form_fields/generic_fields.py
@@ -30,7 +30,6 @@ class CheckboxesLayoutField(object):
         else:
             return layout.Field(x, **kwargs)
 
-
 class RadiosLayoutField(object):
     
     def layout(self, x, **kwargs):
diff --git a/entries/static/entries/css/entries.css b/entries/static/entries/css/entries.css
index 3142fbf..4094eed 100644
--- a/entries/static/entries/css/entries.css
+++ b/entries/static/entries/css/entries.css
@@ -184,6 +184,18 @@ legend {
     border-color: #564c4c;
 }
 
+.active-lu {
+    font-weight: bold;
+}
+
+.lu-underline {
+    text-decoration: underline;
+}
+
+.lu-cursor-pointer {
+    cursor: pointer;
+}
+
 .sticky-bottom {
     position: sticky;
     bottom: 0;
diff --git a/entries/templates/entries_base.html b/entries/templates/entries_base.html
index 0d1501e..9745960 100644
--- a/entries/templates/entries_base.html
+++ b/entries/templates/entries_base.html
@@ -30,6 +30,7 @@
 {% block additional-nav-items %}
 {% if request.user.is_authenticated %}
     <li class="nav-item"><a href="{% url 'entries:unification' %}" class="nav-link">{% trans "Unifikacja" %}</a></li>
+    <li class="nav-item"><a href="{% url 'entries:hierarchy' %}" class="nav-link">{% trans "Hierarchia" %}</a></li>
 {% endif %}
 <li class="nav-item dropdown">
     <a class="nav-link dropdown-toggle" href="#" id="nav-filters" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
diff --git a/entries/templates/hierarchy.html b/entries/templates/hierarchy.html
new file mode 100644
index 0000000..6c75714
--- /dev/null
+++ b/entries/templates/hierarchy.html
@@ -0,0 +1,24 @@
+{% extends "entries_base.html" %}
+
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Hasła" %}{% endblock %}
+
+{% block scripts %}
+    {{ block.super }}
+    <link rel="stylesheet" type="text/css" href="{% static 'entries/css/unification_frames.css' %}">
+    <link rel="stylesheet" type="text/css" href="{% static 'common/css/role_colours.css' %}">
+    <script src="{% static 'entries/js/unification_entries_list.js' %}"></script>
+    <script src="{% static 'entries/js/unification_frames_list.js' %}"></script>
+    <script src="{% static 'entries/js/unification_entries_for_frames_list.js' %}"></script>
+    <script src="{% static 'entries/js/jquery-impromptu.min.js' %}"></script>
+    <script>
+        window.initialUnifiedFrameId = {{ unified_frame_id|default:'null' }};
+    </script>
+{% endblock %}
+
+{% block modals %}
+    {{ block.super }}
+    <div id="lexical-unit-notes-template" class="d-none">{% include 'notes.html' %}</div>
+{% endblock %}
diff --git a/entries/urls.py b/entries/urls.py
index 3b4bd7c..7e244f5 100644
--- a/entries/urls.py
+++ b/entries/urls.py
@@ -18,6 +18,7 @@ urlpatterns = [
     path('change_show_reals_desc/', views.change_show_reals_desc, name='change_show_reals_desc'),
     path('change_show_linked_entries/', views.change_show_linked_entries, name='change_show_linked_entries'),
     path('unification/', views.unification, name='unification'),
+    path('hierarchy/', views.hierarchy, name='hierarchy'),
 
     path('autocomplete/', autocompletes.autocomplete, name='autocomplete'),
     path('plWN_context_lookup/', ajax_plWN_context_lookup, name='plWN_context_lookup'),
diff --git a/entries/views.py b/entries/views.py
index a0c9f1c..1bd5081 100644
--- a/entries/views.py
+++ b/entries/views.py
@@ -86,6 +86,20 @@ def unification(request):
         },
     )
 
+@login_required
+def hierarchy(request):
+    return render(
+        request,
+        'hierarchy.html',
+        {
+            'is_vue_app': True,
+            'unified_frame_id': request.GET.get("unified_frame_id"),
+            'entries_form': EntryForm(),
+            'frames_form': FrameFormFactory.get_form(as_subform=False),
+            'schemata_form': SchemaFormFactory.get_form(as_subform=False),
+            'unified_frames_form': UnifiedFrameFormFactory.get_form(as_subform=False),
+        },
+    )
 
 FORM_TYPES = {
     'entry'      : EntryForm,
diff --git a/frontend/src/components/unification/Unification/LexicalUnitEdit.vue b/frontend/src/components/unification/Unification/LexicalUnitEdit.vue
index 32f5969..2afeec2 100644
--- a/frontend/src/components/unification/Unification/LexicalUnitEdit.vue
+++ b/frontend/src/components/unification/Unification/LexicalUnitEdit.vue
@@ -959,7 +959,7 @@ export default LexicalUnitEdit;
           <tbody>
             <tr>
               <th scope="row" class="py-2 px-1 text-secondary role-header">Role</th>
-              
+
               <template v-for="argument in unified_frame_arguments">
               <td
                 class="argument py-2 px-1 border-top border-left border-secondary role-column"
@@ -1023,7 +1023,8 @@ export default LexicalUnitEdit;
               <tbody>
                 <tr>
                   <th scope="row" class="py-2 px-1 text-secondary">Jednostka leksykalna</th>
-                  <th scope="row" class="py-2 px-1 text-secondary">Opinion</th>
+                  <th scope="row" class="py-2 px-1 text-secondary">Opinia</th>
+                  <th scope="row" class="py-2 px-1 text-secondary">Status</th>
                 </tr>
                 <tr class="preferences py-0 px-0 border-top border-left border-secondary"
                   v-for='lexical_unit in lexical_units'
diff --git a/frontend/src/components/unification/hierarchy/Hierarchy.vue b/frontend/src/components/unification/hierarchy/Hierarchy.vue
new file mode 100644
index 0000000..82876fe
--- /dev/null
+++ b/frontend/src/components/unification/hierarchy/Hierarchy.vue
@@ -0,0 +1,80 @@
+<script>
+import UnificationFramesList from "../Unification/UnificationFramesList.vue";
+import HierarchyRightPane from "./HierarchyRightPane.vue";
+
+export default {
+  data () {
+    return {
+      entryId: null,
+      lexicalUnitId: null,
+      unifiedFrameId: null,
+      gettext: window.gettext,
+      unificationEntriesListRefreshKey: 1,
+    };
+  },
+  components: {HierarchyRightPane, UnificationFramesList},
+  methods: {
+    lexicalUnitSelected (entryId, lexicalUnitId) {
+      this.entryId = entryId;
+      this.lexicalUnitId = lexicalUnitId;
+    },
+    unifiedFrameSelected (unifiedFrameId, entryId, lexicalUnitId) {
+      this.unifiedFrameId = unifiedFrameId;
+      this.entryId = entryId;
+      this.lexicalUnitId = lexicalUnitId;
+    },
+    refreshEntriesList() {
+      this.unificationEntriesListRefreshKey++;
+    }
+  },
+  setup() {
+    const lexicalUnit = new URL(location.href).searchParams.get('lexical_unit_id');
+    const entryId = new URL(location.href).searchParams.get('entry_id');
+    return {
+      initialLexicalUnitId: parseInt(lexicalUnit),
+      initialEntryId: parseInt(entryId),
+    };
+  },
+  mounted () {
+    this.unifiedFrameSelected(window.initialUnifiedFrameId);
+    $('#entries-list').length && Split(['#entries-list', '#entry-display'], {
+      sizes: [20, 80],
+      gutterSize: 4,
+      minSize: 10,
+      elementStyle: (dimension, size, gutterSize) => {
+        return {
+          'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)'
+        }
+      },
+    });
+  },
+};
+</script>
+
+<template>
+  <div id="entries-list" class="col h-100 w-100 pr-0 overflow-hidden">
+    <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-hidden">
+      <unification-frames-list
+              :unificationEntriesListRefreshKey="unificationEntriesListRefreshKey"
+              @unified-frame-selected="unifiedFrameSelected"
+      />
+<!--      <unification-switchable-list-->
+<!--          :unificationEntriesListRefreshKey="unificationEntriesListRefreshKey"-->
+<!--          :initialLexicalUnitId="lexicalUnitId ? lexicalUnitId : initialLexicalUnitId"-->
+<!--          :initialEntryId="entryId ? entryId : initialEntryId"-->
+<!--          @lexical-unit-selected="lexicalUnitSelected"-->
+<!--          @unified-frame-selected="unifiedFrameSelected"-->
+<!--      />-->
+    </div>
+  </div>
+  <div id="entry-display" class="col h-100 p-0 overflow-hidden">
+    <hierarchy-right-pane
+        ref="hierarchyRightPane"
+        :entryId="entryId"
+        :lexicalUnitId="lexicalUnitId"
+        :initialUnifiedFrameId="unifiedFrameId"
+        @refresh-entries-list="refreshEntriesList"
+    />
+  </div>
+</template>
+
diff --git a/frontend/src/components/unification/hierarchy/HierarchyEdit.vue b/frontend/src/components/unification/hierarchy/HierarchyEdit.vue
new file mode 100644
index 0000000..79f174b
--- /dev/null
+++ b/frontend/src/components/unification/hierarchy/HierarchyEdit.vue
@@ -0,0 +1,1095 @@
+<script>
+import InfoTooltip from "../../shared/InfoTooltip.vue";
+import Spinner from "../../shared/Spinner.vue";
+import ExamplesComponent from "../shared/frame-components/ExamplesComponent.vue";
+import SlowalFrameComponent from "../shared/frame-components/SlowalFrameComponent.vue";
+import SemanticsSchemataComponent from "../shared/frame-components/SemanticsSchemataComponent.vue";
+import MeaningComponent from "../shared/frame-components/MeaningComponent.vue";
+import SelectionalPreference from "../Unification/SelectionalPreference.js";
+import UnificationFramePreview from "../Unification/UnificationFramePreview.vue";
+import { slowal_frames2selecional_preferencies } from "../shared/utils.js";
+import HierarchyPreview from "./HierarchyPreview.vue";
+
+let HierarchyEdit = {
+  components: {HierarchyPreview}
+};
+
+Object.assign(HierarchyEdit, {
+  props: {
+    unifiedFrameId: Number,
+    previewedUnifiedFrameId: Number,
+    readOnly: Boolean,
+    initialRightPaneTab: String,
+    forceRefresh: Number,
+  },
+  data() {
+    return {
+      gettext: window.gettext,
+      HierarchyEdit: HierarchyEdit,
+      unified_frame: {},
+      unified_frame_title: '',
+      unified_frame_arguments: [],
+      active_unified_frame_argument: null,
+      slowal_frames2selecional_preferencies_mapping: {},
+      lexical_units: [],
+      img_prefix: window.STATIC_URL,
+      frames: [],
+      right_pane_tabs: [
+        {id: 'schemata', label: gettext('Schematy')},
+        {id: 'frame_preview', label: gettext('PodglÄ…d ram')},
+        {id: 'hierarchy', label: gettext('Hierarchia')},
+        {id: 'notes', label: gettext('Notatki')},
+      ],
+      right_pane_tab: this.initialRightPaneTab || 'schemata',
+      currentPreviewedUnifiedFrameId: this.previewedUnifiedFrameId,
+      internalForceRefresh: this.forceRefresh,
+      statusButtonTitle: '',
+      active_slowal_frame: null,
+      showVerifiedFrames: false,
+      subentries: null,
+      alternations: null,
+      realisation_phrases: null,
+      realisation_descriptions: null,
+      examples: null,
+      selectedFrameArguments: null,
+      frame_arguments_or_type: false,
+      selectedLus: null,
+      selectedSchemas: null,
+      selectedExamples: null,
+      hidden_frames: [],
+    }
+  },
+  components: {InfoTooltip, Spinner, HierarchyPreview, SlowalFrameComponent, ExamplesComponent, SemanticsSchemataComponent, MeaningComponent},
+  emits: ['goToDisplay', 'refresh', 'swapFrames', 'refreshEntriesList', 'clearUnifiedFrameView'],
+  watch: {
+    forceRefresh(newVal, oldVal) {
+      this.loadFrame();
+    }
+  },
+  computed: {
+    selectionalPreference() {
+      return new SelectionalPreference();
+    }
+  },
+  methods: {
+    hasWhiteSpace(s) {
+      return /\s/g.test(s);
+    },
+    async loadFrame() {
+      try {
+        const data = {'unified_frame_id': this.unifiedFrameId, 'no_filters' : false};
+        $.ajax({
+          type: 'post',
+          url: '/' + lang + '/unifier/get_unified_frame/',
+          dataType: 'json',
+          data: data,
+          timeout: 60000,
+          success: function (response) {
+
+            this.img_prefix = window.STATIC_URL;
+            this.lexical_units = this.frames2lexical_units(response.frames);
+            this.unified_frame = response.unified_frame;
+            this.unified_frame_title = this.unified_frame.title;
+            this.unified_frame_arguments = this.unified_frame.arguments;
+            this.frames = response.frames;
+            this.slowal_frames2selecional_preferencies_mapping = slowal_frames2selecional_preferencies(this.unified_frame, response.frames);
+
+            this.subentries = response.subentries;
+            this.alternations = response.alternations;
+            this.realisation_phrases = response.realisation_phrases;
+            this.realisation_descriptions = response.realisation_descriptions;
+            this.examples = response.examples;
+
+            this.fulfill_slowal_frames_arguments_with_empty_elems(response.unified_frame, response.frames)
+            window.update_last_visited(response.last_visited);
+            window.clear_info();
+
+            this.changeStatusButtonTitleToDefault();
+            if (!this.active_slowal_frame) {
+              this.setup_notes_unified_frame();
+            }
+          }.bind(this),
+          error: function (request, errorType, errorMessage) {
+            show_error(errorType + ' (' + errorMessage + ')');
+          }
+        });
+
+      } catch (error) {
+        console.log(error);
+      }
+    },
+    setup_notes_unified_frame() {
+      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.unified_frame.id, 'unifier.UnifiedFrame', this.setup_notes_unified_frame);
+    },
+    setup_notes_slowal_frame() {
+      if (this.active_slowal_frame) {
+        setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame);
+      }
+    },
+    setup_notes_slowal_frame_with_title_and_body(title, body) {
+      this.right_pane_tab = 'notes';
+      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame, title, body);
+    },
+    unifiedFrameArgumentSelected(argument) {
+      if (this.active_unified_frame_argument === argument) {
+        this.active_unified_frame_argument = null;
+        this.frame_arguments_or_type = false;
+        this.deselectSlowalFrameSelectedElements();
+      } else {
+        this.active_slowal_frame = null;
+        this.active_unified_frame_argument = argument;
+        const slowalFrameArguments = this.getSlowalFrameArgumentsBy(argument);
+        this.frame_arguments_or_type = true;
+        this.deselectSlowalFrameSelectedElements();
+        this.selectedFrameArguments = slowalFrameArguments;
+      }
+      this.unifiedFrameArgumentHovered(argument);
+    },
+    unifiedFrameArgumentHovered(argument) {
+      clear_info();
+      if (argument && this.active_unified_frame_argument === argument) {
+        show_info(gettext('Kliknij, aby cofnąć wybór kolumny do edycji.'));
+      }
+      if (argument && this.active_unified_frame_argument !== argument) {
+        show_info(gettext('Kliknij, aby wybrać kolumnę do edycji.'));
+      }
+    },
+    addSelectivePreference() {
+      if (!this.active_unified_frame_argument) {
+        alert(gettext("Zaznacz argument, do którego chcesz dodać preferencję."));
+      } else {
+        this.selectionalPreference.addSelectivePreference(this.unified_frame, this.active_unified_frame_argument.id, this.frames, function () {
+          this.loadFrame();
+        }.bind(this));
+        //   window.addSelectivePreference(this.unified_frame, this.active_unified_frame_argument.id, this.frames, function () {
+        //   this.loadFrame();
+        // }.bind(this));
+      }
+    },
+    removeSelectionalPreference() {
+      if (!this.active_unified_frame_argument) {
+        alert(gettext("Zaznacz argument, do którego chcesz dodać preferencję."));
+      } else {
+        const existingPreferencies = function () {
+          return this.active_unified_frame_argument.preferences.map(preference => {
+            return `<label><input type="checkbox" name="preference" value="${preference.id}" /> ${preference.str}</label><br />`;
+          }).join("");
+        }.bind(this);
+
+        const remove_preference_popup = {
+          state0: {
+            title: 'Wybierz preferencje do usunięcia',
+            html: existingPreferencies,
+            buttons: {Anuluj: 0, Usuń: 1},
+            focus: -1,
+            submit: function (e, v, m, f) {
+              if (v == 0) {
+                e.preventDefault();
+                $.prompt.close();
+              }
+              if (v === 1) {
+                e.preventDefault();
+                let preference_ids = normalizeFormData(f.preference);
+                const data = {
+                  'unified_frame_id': this.unified_frame.id,
+                  'argument_id': this.active_unified_frame_argument.id,
+                  'preference_ids': JSON.stringify(preference_ids)
+                };
+                $.ajax({
+                  type: 'post',
+                  url: '/' + lang + '/unifier/remove_selectional_preference/',
+                  dataType: 'json',
+                  data: data,
+                  timeout: 60000,
+                  success: function (response) {
+                    show_info('Wybrane preferencje zostału usunięte.');
+                    this.loadFrame();
+                    $.prompt.close();
+                  }.bind(this),
+                  error: function (request, errorType, errorMessage) {
+                    show_error(errorType + ' (' + errorMessage + ')');
+                    $.prompt.close();
+                  }
+                });
+              }
+            }.bind(this)
+          }
+        }
+        $.prompt(remove_preference_popup);
+      }
+    },
+    changeTitle() {
+      let title = this.unified_frame.title != null ? this.unified_frame.title : '';
+      const change_title_popup = {
+        state0: {
+          title: 'Zmiana nazwy ramy',
+          html: '<input type="text" size="32" value="' + title + '" name="title" />',
+          buttons: {Anuluj: 0, Zapisz: 1},
+          focus: -1,
+          submit: function (e, v, m, f) {
+            if (v == 0) {
+              $.prompt.close();
+            }
+            if (v === 1) {
+              e.preventDefault();
+              const title = f.title;
+
+              if (this.hasWhiteSpace(title)) {
+                alert(gettext("Nazwa zunifikowanej ramy nie może zawierać białych znaków."));
+              } else {
+                const data = {'unified_frame_id': this.unified_frame.id, 'unified_frame_title': title};
+                $.ajax({
+                  type: 'post',
+                  url: '/' + lang + '/unifier/save_unified_frame_title/',
+                  dataType: 'json',
+                  data: data,
+                  timeout: 60000,
+                  success: function (response) {
+                    show_info('Tytuł ramy zosał zapisany');
+                    $.prompt.close();
+                    this.loadFrame();
+                  }.bind(this),
+                  error: function (request, errorType, errorMessage) {
+                    show_error(errorType + ' (' + errorMessage + ')');
+                    $.prompt.close();
+                  }
+                });
+              }
+            }
+          }.bind(this)
+        }
+      }
+      $.prompt(change_title_popup);
+    },
+    changeRole() {
+      if (!this.active_unified_frame_argument) {
+        alert(gettext("Zaznacz argument, dla którego chcesz wybrać rolę."));
+      } else {
+        const existingSelect = function () {
+          let selected_unified_frame_argument = this.unified_frame_arguments.find(o => o.id === this.active_unified_frame_argument.id);
+          if (!selected_unified_frame_argument.proposed_roles) {
+            return gettext('Brak ról do wyboru.')
+          }
+          return selected_unified_frame_argument.proposed_roles.map(role => {
+            return `<label><input type="radio" name="roles" value="${role.id}" /> ${role.str}</label><br />`;
+          }).join("");
+        }.bind(this);
+
+        const newSelect = function () {
+          let rolesHTML = roles.map(role => {
+            return `<label><input type="radio" name="role" value="${role.id}" /> ${role.role}</label><br />`;
+          }).join("");
+          let attributesHTML = role_attributes.map(attribute => {
+            return `<label><input type="radio" name="attribute" value="${attribute.id}" /> ${attribute.attribute}</label><br />`;
+          }).join("");
+          const roleTypeHTML = ['role', 'modifier'].map(type => {
+            return `<label><input type="radio" name="role_type" value="${type}" /> ${type}</label><br />`;
+          }).join("");
+          return '<div class="row">' +
+              '<div class="column"><div class="role_select_header">Type</div>' + roleTypeHTML + '</div>' +
+              '<div class="column"><div class="role_select_header">Role</div>' + rolesHTML + '</div>' +
+              '<div class="column"><div class="role_select_header">Atrybuty</div>' + attributesHTML + '</div>' +
+              '</div>';
+        }.bind(this);
+
+        let change_role_popup = {
+          state0: {
+            title: 'Wybór roli',
+            html: 'Wybierz lub dodaj rolÄ™',
+            buttons: {Wybierz: 0, Dodaj: 1, Koniec: -1},
+            focus: -1,
+            submit: function (e, v, m, f) {
+              if (v == -1) {
+                e.preventDefault();
+
+                this.loadFrame();
+
+                $.prompt.close();
+              }
+              if (v === 0) {
+                e.preventDefault();
+                $.prompt.goToState('state1');
+              }
+              if (v == 1) {
+                e.preventDefault();
+                $.prompt.goToState('state2');
+              }
+            }.bind(this)
+          },
+          state1: {
+            title: 'Wybierz rolÄ™',
+            html: existingSelect(),
+            buttons: {Anuluj: -1, Zatwierdź: 1},
+            focus: -1,
+            submit: function (e, v, m, f) {
+              if (v == -1) {
+                e.preventDefault();
+                $.prompt.goToState('state0');
+              }
+              if (v == 1) {
+                e.preventDefault();
+                normalizeFormData(f.roles).map(role_id => {
+                  const data = {
+                    'unified_frame_id': this.unified_frame.id,
+                    'complement_id': this.active_unified_frame_argument.id,
+                    'role_id': role_id
+                  };
+                  $.ajax({
+                    type: 'post',
+                    url: '/' + lang + '/unifier/save_selected_role/',
+                    dataType: 'json',
+                    data: data,
+                    timeout: 60000,
+                    success: function (response) {
+                      show_info('Wybrana rola zosała zapisana');
+                      $.prompt.goToState('state0');
+                    }.bind(this),
+                    error: function (request, errorType, errorMessage) {
+                      show_error(errorType + ' (' + errorMessage + ')');
+                      $.prompt.close();
+                    }
+                  });
+                });
+              }
+            }.bind(this)
+          },
+          state2: {
+            title: 'Dodaj rolÄ™',
+            html: newSelect(),
+            buttons: {Anuluj: -1, Zatwierdź: 1},
+            focus: -1,
+            submit: function (e, v, m, f) {
+              if (v == -1) {
+                e.preventDefault();
+                $.prompt.goToState('state0');
+              }
+              if (v == 1) {
+                e.preventDefault();
+
+                const role_id = normalizeFormData(f.role)[0];
+                const role_type = normalizeFormData(f.role_type)[0];
+
+                if (role_id != null && role_type != null) {
+                  const attribute_id = normalizeFormData(f.attribute)[0];
+
+                  const data = {
+                    'unified_frame_id': this.unified_frame.id,
+                    'complement_id': this.active_unified_frame_argument.id,
+                    'role_type': role_type,
+                    'role_id': role_id,
+                    'attribute_id': attribute_id
+                  };
+                  $.ajax({
+                    type: 'post',
+                    url: '/' + lang + '/unifier/save_new_role/',
+                    dataType: 'json',
+                    data: data,
+                    timeout: 60000,
+                    success: function (response) {
+                      show_info('Nowa rola zosała zapisana');
+                      $.prompt.goToState('state0');
+                    }.bind(this),
+                    error: function (request, errorType, errorMessage) {
+                      show_error(errorType + ' (' + errorMessage + ')');
+                      $.prompt.close();
+                    }
+                  });
+                } else {
+                  alert(gettext("Musisz wybrać typ oraz rolę."));
+                }
+              }
+            }.bind(this)
+          }
+        };
+        $.prompt(change_role_popup);
+      }
+    },
+    addArgument() {
+      const data = {'unified_frame_id': this.unified_frame.id};
+      $.ajax({
+        type: 'post',
+        url: '/' + lang + '/unifier/add_argument/',
+        dataType: 'json',
+        data: data,
+        timeout: 60000,
+        success: function (response) {
+          show_info('Nowy argument zosał dodany');
+          this.loadFrame();
+        }.bind(this),
+        error: function (request, errorType, errorMessage) {
+          show_error(errorType + ' (' + errorMessage + ')');
+          $.prompt.close();
+        }
+      });
+    },
+    getSlowalFrameArgumentsBy(unified_frame_argument) {
+      const slowalFrameArgumentIds = [];
+      for (let i in this.unified_frame.slowal_frame_mapping) {
+        const slowal_frame_mapping = this.unified_frame.slowal_frame_mapping[i];
+        const slowalFrame = this.frames.find(frame => frame.id === slowal_frame_mapping.slowal_frame_id);
+        if(slowalFrame != null) {
+          for (let j in slowal_frame_mapping.slowal_frame_argument_mapping) {
+            const slowal_frame_argument_mapping = slowal_frame_mapping.slowal_frame_argument_mapping[j];
+            if (slowal_frame_argument_mapping.unified_frame_agrument_id == unified_frame_argument.id) {
+              const slowalFrameArgument = slowalFrame.arguments.find(arg => arg.argument_id === slowal_frame_argument_mapping.slowal_frame_agrument_id);
+              slowalFrameArgumentIds.push(slowalFrameArgument);
+            }
+          }
+        }
+      }
+      return slowalFrameArgumentIds;
+    },
+    removeArgument() {
+      if (!this.active_unified_frame_argument) {
+        alert(gettext("Zaznacz argument, który chcesz usunąć."));
+      } else {
+        let hasSlowalFrameArgumentMapping = false;
+        for (let i in this.unified_frame.slowal_frame_mapping) {
+          const slowal_frame_mapping = this.unified_frame.slowal_frame_mapping[i];
+          for (let j in slowal_frame_mapping.slowal_frame_argument_mapping) {
+            const slowal_frame_argument_mapping = slowal_frame_mapping.slowal_frame_argument_mapping[j];
+            if (slowal_frame_argument_mapping.unified_frame_agrument_id == this.active_unified_frame_argument.id) {
+              hasSlowalFrameArgumentMapping = true;
+              break;
+            }
+          }
+        }
+        if (hasSlowalFrameArgumentMapping) {
+          alert(gettext("Zaznaczony argument nie może zostać usunięty - podpięte ramy posiadają do niego dowiązania."));
+        } else {
+          const data = {
+            'unified_frame_id': this.unified_frame.id,
+            'complement_id': this.active_unified_frame_argument.id
+          };
+          $.ajax({
+            type: 'post',
+            url: '/' + lang + '/unifier/remove_argument/',
+            dataType: 'json',
+            data: data,
+            timeout: 60000,
+            success: function (response) {
+              show_info('Wybrany argument zosał usunięty');
+              this.loadFrame();
+            }.bind(this),
+            error: function (request, errorType, errorMessage) {
+              show_error(errorType + ' (' + errorMessage + ')');
+              $.prompt.close();
+            }
+          });
+        }
+      }
+    },
+    duplicate() {
+      let title = this.unified_frame.title != null ? this.unified_frame.title : '';
+      const duplicate_popup = {
+        state0: {
+          title: 'Podaj nazwÄ™ zduplikowanej ramy',
+          html: '<input type="text" size="32" value="KOPIA_' + title + '" name="title" />',
+          buttons: {Anuluj: 0, Zapisz: 1},
+          focus: -1,
+          submit: function (e, v, m, f) {
+            if (v == 0) {
+              e.preventDefault();
+              $.prompt.close();
+            }
+            if (v === 1) {
+              e.preventDefault();
+              const title = f.title;
+
+              if (this.hasWhiteSpace(title)) {
+                alert(gettext("Nazwa zunifikowanej ramy nie może zawierać białych znaków."));
+              } else {
+                const data = {
+                  'unified_frame_id': this.unified_frame.id,
+                  'target_unified_frame_title': title
+                };
+                $.ajax({
+                  type: 'post',
+                  url: '/' + lang + '/unifier/duplicate_unified_frame/',
+                  dataType: 'json',
+                  data: data,
+                  timeout: 60000,
+                  success: function (response) {
+                    show_info('Zunifikowana rama została zduplikowana.');
+                    this.currentPreviewedUnifiedFrameId = response.unified_frame_id;
+                    $.prompt.close();
+                  }.bind(this),
+                  error: function (request, errorType, errorMessage) {
+                    show_error(errorType + ' (' + errorMessage + ')');
+                    $.prompt.close();
+                  }
+                });
+              }
+            }
+          }.bind(this)
+        }
+      }
+      $.prompt(duplicate_popup);
+    },
+    changeUnifiedFrameStatusToReadyOrVerified() {
+      if (this.isSuperLeksykograf())
+        this.changeUnifiedFrameStatusToReadyOrVerifiedByPath('change_unified_frame_status_to_verified_by_superleksykograf');
+      else
+        this.changeUnifiedFrameStatusToReadyOrVerifiedByPath('change_unified_frame_status_to_ready')
+    },
+    changeUnifiedFrameStatusToReadyOrVerifiedByPath(url_path) {
+      let foundNotVerifiedFrame = this.frames.find(frame => frame.status !== 'G' && frame.status !== 'S');
+      if (foundNotVerifiedFrame) {
+        alert(gettext("Wszystkie podpięte ramy powinny być zweryfikowane."));
+      } else {
+        let roleDict = {};
+        let hasPreferenceSelected = true;
+        for (let i in this.unified_frame_arguments) {
+          const argument = this.unified_frame_arguments[i];
+          if (argument.role) {
+            roleDict[argument.role.str] = argument.role;
+          }
+          if (!argument.preferences || argument.preferences.length == 0) {
+            hasPreferenceSelected = false;
+          }
+        }
+        if (Object.keys(roleDict).length === this.unified_frame_arguments.length && hasPreferenceSelected) {
+          //all roles are set, and are uniq
+          //TODO: aktywne preferencje w argumencie nie znajdujÄ… siÄ™ w relacji hipo-/hiperonimii.
+          const data = {'unified_frame_id': this.unified_frame.id};
+          $.ajax({
+            type: 'post',
+            url: '/' + lang + '/unifier/' + url_path + '/',
+            dataType: 'json',
+            data: data,
+            timeout: 60000,
+            success: function (response) {
+              show_info('Status ramy został zmieniony');
+              this.loadFrame();
+            }.bind(this),
+            error: function (request, errorType, errorMessage) {
+              show_error(errorType + ' (' + errorMessage + ')');
+            }
+          });
+        } else {
+          alert(gettext("Role dla wszystkich argumentów powinny być ustawione oraz unikalne. Preferencje selekcyjne dla wszystkich argumentów powinny być ustawione."));
+        }
+      }
+    },
+    change_slowal2unified_frame_argument_mapping(slowal_frame) {
+      if (slowal_frame === this.active_slowal_frame && this.selectedFrameArguments && this.selectedFrameArguments.length == 2) {
+        const data = {
+          'unified_frame_id': this.unified_frame.id,
+          'slowal_frame_id': slowal_frame.id,
+          'slowal_frame_selected_arguments': JSON.stringify(this.selectedFrameArguments.map(arg => arg.argument_id))
+        };
+        $.ajax({
+          type: 'post',
+          url: '/' + lang + '/unifier/change_slowal2unified_fram_argument_mapping/',
+          dataType: 'json',
+          data: data,
+          timeout: 60000,
+          success: function (response) {
+            show_info('Argumenty w ramie zostały zmienione');
+            this.loadFrame();
+          }.bind(this),
+          error: function (request, errorType, errorMessage) {
+            show_error(errorType + ' (' + errorMessage + ')');
+          }
+        });
+      } else {
+        alert(gettext("Zamiany pozycji argumentu w ramie wymaga zaznaczenia dokładnie 2 argumentów."));
+      }
+    },
+    isSuperLeksykograf() {
+      return has_permission("users.view_assignment");
+    },
+    isFrameVerified(frame) {
+      const isSuperLeksykograf = this.isSuperLeksykograf();
+      return (!isSuperLeksykograf && frame.status === 'G') || (isSuperLeksykograf && frame.status === 'S')
+    },
+    select_slowal_frame_req(to_invoke) {
+      if (this.active_slowal_frame) {
+        to_invoke();
+      } else {
+        alert(gettext("Wybierz ramę, dla której chcesz zmienić status."));
+      }
+    },
+    change_slowal_frame_status(status) {
+      this.select_slowal_frame_req(() => {
+        let frame = this.active_slowal_frame;
+
+        if (status === 'B') {
+          this.setup_notes_slowal_frame_with_title_and_body("Niedopasowana jednostka (identyfikator jednostki)", "Do zatwierdzenia uznanie ramy Walentego jednostek (lista jednostek)" +
+              "za niedopasowana do zunifikowanej ramy (identyfikator) przez (Leksykograf).");
+        } else if (status === 'Z') {
+          this.setup_notes_slowal_frame_with_title_and_body("Błędna jednostka (identyfikator jednostki)", "Do zatwierdzenia uznanie ramy Walentego jednostek (lista jednostek)" +
+              "za niedopasowana do zunifikowanej ramy (identyfikator) przez (Leksykograf).");
+        }
+
+        const data = {
+          'unified_frame_id': this.unified_frame.id,
+          'slowal_frame_id': frame.id,
+          'status': status
+        };
+        $.ajax({
+          type: 'post',
+          url: '/' + lang + '/unifier/change_slowal_frame_status/',
+          dataType: 'json',
+          data: data,
+          timeout: 60000,
+          success: function (response) {
+            show_info('Status ramy został zmieniony');
+            this.$emit('refreshEntriesList');
+            this.loadFrame();
+          }.bind(this),
+          error: function (request, errorType, errorMessage) {
+            show_error(errorType + ' (' + errorMessage + ')');
+          }
+        });
+      });
+    },
+    slowal_frame_ready_rollback(status) {
+      this.select_slowal_frame_req(() => {
+        let frame = this.active_slowal_frame;
+        const isSuperLeksykograf = this.isSuperLeksykograf();
+        let status = null;
+        if (!isSuperLeksykograf && frame.status === 'G') {
+          //przywracamy O
+          status = 'O';
+        } else if (!isSuperLeksykograf && frame.status === 'O') {
+          //ustawiany na Gotowe
+          status = 'G';
+        } else if (isSuperLeksykograf && frame.status === 'S') {
+          //ustawiany Sprawdzone
+          status = 'G';
+        } else {
+          status = 'S';
+        }
+
+        this.change_slowal_frame_status(status);
+      });
+    },
+    isFrameVisible(status) {
+      return (status != 'B' && status != 'C') || this.isSuperLeksykograf();
+    },
+    changeStatusButtonTitleToDefault() {
+      const isSuperLeksykograf = this.isSuperLeksykograf();
+      this.statusButtonTitle = isSuperLeksykograf ? 'Sprawdź' : 'Gotowe';
+    },
+    deselectSlowalFrameSelectedElements() {
+      this.subentries.forEach(subentry => {
+        subentry.schemata.forEach(s => {
+          s.selected = false;
+        });
+      });
+      this.frames.forEach(frame => {
+        frame.lexical_units.forEach(lu => {
+          lu.selected = false;
+        });
+        frame.arguments.forEach(argument => {
+          argument.selected = false;
+        });
+      });
+      this.examples.forEach(example => {
+        example.selected = false;
+      });
+      this.selectedLus = [];
+      this.selectedFrameArguments = [];
+      this.selectedSchemas = [];
+      this.selectedExamples = [];
+    },
+    slowalFrameSelected(frame) {
+      this.deselectSlowalFrameSelectedElements();
+      if (this.active_slowal_frame === frame) {
+        this.active_slowal_frame = null;
+        if (!this.readOnly) {
+          this.setup_notes_unified_frame();
+          this.changeStatusButtonTitleToDefault();
+        }
+      } else {
+        this.active_slowal_frame = frame;
+        if (!this.readOnly) {
+          this.setup_notes_slowal_frame()
+          if (this.isFrameVerified(frame)) {
+            this.statusButtonTitle = 'Przywróć';
+          }
+        }
+      }
+    },
+    isSelectedFrame(frame) {
+      if (this.active_slowal_frame) {
+        return frame.id === this.active_slowal_frame.id;
+      } else {
+        return false;
+      }
+    },
+    extract_frames_to_new_frame() {
+
+      const existingSelect = function () {
+        return this.frames.map(frame => {
+          return `<label><input type="checkbox" name="frames" value="${frame.id}" /> ${lexical_units2dom(frame.lexical_units)}</label><br />`;
+        }).join("");
+      }.bind(this);
+
+      const extract_frames_to_new_frame_popup = {
+        state0: {
+          title: 'Podaj nazwÄ™ zduplikowanej ramy',
+          html: existingSelect,
+          buttons: {Anuluj: 0, Wydziel: 1},
+          focus: -1,
+          submit: function (e, v, m, f) {
+            if (v == 0) {
+              e.preventDefault();
+              $.prompt.close();
+            }
+            if (v === 1) {
+              e.preventDefault();
+              let frame_ids = normalizeFormData(f.frames);
+              const data = {
+                'unified_frame_id': this.unified_frame.id, 'slowal_frame_ids': JSON.stringify(frame_ids),
+                'target_unified_frame_id': ''
+              };
+              $.ajax({
+                type: 'post',
+                url: '/' + lang + '/unifier/extract_frames_to_new_frame/',
+                dataType: 'json',
+                data: data,
+                timeout: 60000,
+                success: function (response) {
+                  show_info('Ramy zostały wydzielone do nowej ramy zunifikowanej.');
+                  this.loadFrame();
+                  let newUnifiedFrameId = response.unified_frame_id;
+                  this.currentPreviewedUnifiedFrameId = newUnifiedFrameId;
+                  $.prompt.close();
+                }.bind(this),
+                error: function (request, errorType, errorMessage) {
+                  show_error(errorType + ' (' + errorMessage + ')');
+                  $.prompt.close();
+                }
+              });
+            }
+          }.bind(this)
+        }
+      }
+      $.prompt(extract_frames_to_new_frame_popup);
+    },
+    extract_frame_to_preview_frame() {
+      if (this.currentPreviewedUnifiedFrameId !== -1 && this.currentPreviewedUnifiedFrameId !== this.unified_frame.id) {
+        let target_unified_frame_id = this.currentPreviewedUnifiedFrameId;
+        const data = {
+          'unified_frame_id': this.unified_frame.id, 'slowal_frame_ids': JSON.stringify([this.active_slowal_frame.id]),
+          'target_unified_frame_id': target_unified_frame_id
+        };
+        $.ajax({
+          type: 'post',
+          url: '/' + lang + '/unifier/extract_frames_to_new_frame/',
+          dataType: 'json',
+          data: data,
+          timeout: 60000,
+          success: function (response) {
+            show_info('Zaznaczona rama została przeniosiona.');
+            this.active_slowal_frame = null;
+            this.loadFrame();
+            this.internalForceRefresh += 1;
+          }.bind(this),
+          error: function (request, errorType, errorMessage) {
+            show_error(errorType + ' (' + errorMessage + ')');
+          }
+        });
+      }
+    },
+    changePreviewedUnifiedFrameId(unifiedFrameId) {
+      this.currentPreviewedUnifiedFrameId = unifiedFrameId;
+    },
+    swapUnifiedFrames() {
+      if (this.currentPreviewedUnifiedFrameId !== -1 && this.unified_frame.id !== this.currentPreviewedUnifiedFrameId) {
+        this.$emit("swapFrames", this.currentPreviewedUnifiedFrameId);
+      }
+    },
+    changeShowVerifiedFrames(val) {
+      this.showVerifiedFrames = val;
+      this.hidden_frames = [];
+    },
+    getArgumentCSS(argument) {
+      return (argument.role ? argument.role.str + ' ' : '') + (argument == this.active_unified_frame_argument ? 'active' : '');
+    },
+    meaningLuSelected(selectedLus) {
+      this.selectedLus = selectedLus;
+    },
+    insideFrameSelectionChanged(selectedFrameArguments) {
+      this.selectedFrameArguments = selectedFrameArguments;
+    },
+    schemataSelected(schemas) {
+      this.selectedSchemas = schemas;
+    },
+    exampleSelected(selectedExamples) {
+      this.selectedExamples = selectedExamples;
+    },
+    deleteUnifiedFrames() {
+      if (!confirm(gettext("Czy na pewno chcesz zunifikowanÄ… ramÄ™?"))) return false;
+      $.ajax({
+        type: 'post',
+        url: '/' + lang + `/unifier/delete_unified_frame/${this.unified_frame.id}/`,
+        dataType: 'json',
+        timeout: 60000,
+        success: function (response) {
+          show_info('Zunifikowana rama została usunięta.');
+          this.$emit('refreshEntriesList');
+          this.$emit("swapFrames", null);
+        }.bind(this),
+        error: function (request, errorType, errorMessage) {
+          show_error(errorType + ' (' + errorMessage + ')');
+        }
+      });
+    },
+    isReadOnlyForSuperLeksykograf() {
+      return (this.isSuperLeksykograf() && this.unified_frame.status === 'O') && this.unified_frame.assignee_username !== window.USER_USERNAME;
+    },
+    hideFrame(frame) {
+      this.hidden_frames.push(frame);
+    },
+    getSlowalReadyFrameCnt() {
+      const readyFrames = this.frames.filter(frame => frame.status == 'G');
+      return readyFrames.length;
+    },
+    frames2lexical_units(frames) {
+      const lexical_units = []
+      for (let i in frames) {
+        const frame = frames[i];
+        for (let j in frame.lexical_units) {
+          const lexical_unit = frame.lexical_units[j];
+          lexical_unit.opinion = frame.opinion;
+          lexical_unit.opinion_key = frame.opinion_key;
+          lexical_unit.frame_status = frame.status;
+          lexical_unit.frame = frame;
+          lexical_units.push(lexical_unit);
+        }
+      }
+      return lexical_units;
+    },
+    fulfill_slowal_frames_arguments_with_empty_elems(unified_frame, slowal_frames) {
+      for (let i in unified_frame.slowal_frame_mapping) {
+        const slowal_frame_mapping = unified_frame.slowal_frame_mapping[i];
+        let slowal_frame = slowal_frames.find(o => o.id === slowal_frame_mapping.slowal_frame_id);
+        if(slowal_frame != null) {
+          let new_slowal_frame_arguments = [];
+          for (let j in unified_frame.arguments) {
+            const unified_frame_argument = unified_frame.arguments[j];
+            let unified_frame_argument_mapping = slowal_frame_mapping.slowal_frame_argument_mapping.find(o => o.unified_frame_agrument_id === unified_frame_argument.id);
+            let slowal_frame_argument = null;
+            if (unified_frame_argument_mapping == null) {
+              slowal_frame_argument = {
+                'str': 'Empty',
+                'id': slowal_frame.id + '-_' + (unified_frame_argument.id),
+                'role': 'Empty',
+                'role_type': 'Empty',
+                'preferences': [],
+                'proposed_roles': [],
+              }
+            } else {
+              slowal_frame_argument = slowal_frame.arguments.find(o => o.argument_id === unified_frame_argument_mapping.slowal_frame_agrument_id);
+            }
+            new_slowal_frame_arguments.push(slowal_frame_argument)
+          }
+          slowal_frame.arguments = new_slowal_frame_arguments;
+        }
+      }
+    }
+  },
+  mounted() {
+
+    if(this.unifiedFrameId) {
+      this.loadFrame();
+    }
+
+    this.changeStatusButtonTitleToDefault();
+    if(!this.readOnly) {
+      Split(['#semantics-frames-pane', '#semantics-schemata-pane'], {
+        sizes: [40, 60],
+        minSize: 10,
+        gutterSize: 4,
+        elementStyle: (dimension, size, gutterSize) => {
+          return {
+            'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)'
+          }
+        },
+      });
+      Split(['#semantics-unified-frame-pane', '#examples'], {
+        sizes: [60, 40],
+        direction: 'vertical',
+        gutterSize: 4,
+        minSize: 10,
+      });
+    } else {
+      Split(['#semantics-unified-frame-pane-preview', '#examples-preview'], {
+        sizes: [60, 40],
+        direction: 'vertical',
+        gutterSize: 4,
+        minSize: 10,
+      });
+    }
+  }
+});
+
+export default HierarchyEdit;
+
+</script>
+
+<template>
+    <div class="col h-100 px-0 pt-0 pb-0 overflow-auto" id="semantics-frames-pane">
+      <div :id="'semantics-unified-frame-pane' + (readOnly ? '-preview' : '')" class="col w-100 p-0 overflow-auto">
+        <table v-if="!readOnly && !isReadOnlyForSuperLeksykograf()" class="table-button-menu sticky-top" cellspacing="1">
+          <tr style="background-color: white;">
+            <td id="change-title" @click="changeTitle" style="padding: 10px 15px 10px 15px; color: #000000;">Nadrama</td>
+            <td id="add-arg" @click="addArgument" style="padding: 10px 15px 10px 15px; color: #000000;">Podrama</td>
+            <td style="padding: 10px 15px 10px 15px; color: #000000;" @click="addSelectivePreference">Usiń powiązanie</td>
+            <td style="padding: 10px 15px 10px 15px; color: #000000;" @click="changeUnifiedFrameStatusToReadyOrVerified">Edytuj</td>
+          </tr>
+        </table>
+        
+        <spinner />
+        <div align="center">
+        <div align="left" style="display: table;">
+          <div class="unifiedFrame mt-3" v-bind:data-frame_id="unified_frame.id" id="unified-frame-title" v-html="unified_frame_title"></div>
+          <table v-if="unified_frame.id" id="unified-frame" class="m-0 table-borderless border border-secondary text-dark frame active">
+          <tbody>
+            <tr>
+              <template v-for="argument in unified_frame_arguments">
+              <td
+                class="argument py-2 px-1 border-top border-left border-secondary role-column"
+                    :class="getArgumentCSS(argument)"
+                    @click="unifiedFrameArgumentSelected(argument)"
+                    @mouseover="unifiedFrameArgumentHovered(argument)"
+                    @mouseleave="unifiedFrameArgumentHovered(null)"
+                >
+                {{ argument.role_type }}
+                
+                <div
+                  v-if="argument.role"
+                >
+                  [{{ argument.role.str }}]
+                </div>
+                <div v-else>
+                  <ul class="ul-role">
+                    <li v-for="proposed_role in argument.proposed_roles">
+                      {{ proposed_role.str }}
+                    </li>
+                  </ul>
+                </div>
+              </td>
+              </template>
+              
+              
+            </tr>
+            <tr>
+              <td class="preferences py-0 px-0 border-top border-left border-secondary role-column align-top"
+                  v-for='argument in unified_frame_arguments'
+                  :key='argument.id'
+              >
+                <ul class="ul-preference" v-if="argument.preferences.length > 0">
+                  <li v-for='preference in argument.preferences'>
+                    <div
+                      v-if="preference.url != null"
+                      class="preference py-2 px-1 preference-bold"
+                    >
+                      <a class="synset-plwn" v-bind:href="preference.url" target="_blank">{{ preference.str }}</a>
+                    </div>
+                    <div v-else class="preference py-2 px-1 preference-bold">{{ preference.str }}</div>
+                  </li>
+                </ul>
+                <ul class="ul-preference" v-if="unified_frame.status !== 'S'">
+                  <li v-for="preference in slowal_frames2selecional_preferencies_mapping[argument.id]">
+                    <span v-if="preference.url != null" class="preference py-2 px-1">
+                      <a class="synset-plwn" v-bind:href="preference.url" target="_blank">{{ preference.str }}</a>
+                    </span>
+                    <span v-else class="preference py-2 px-1">{{ preference.str }}</span>
+                    <info-tooltip v-if="preference.info" :text="preference.info" />
+                  </li>
+                </ul>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+        </div>
+        <div v-if="unified_frame.id" class="lu-table mt-3 mb-3">
+            <table class="m-0 table-borderless border border-secondary text-dark">
+              <tbody>
+                <tr>
+                  <th scope="row" class="py-2 px-1 text-secondary">Jednostka leksykalna</th>
+                  <th scope="row" class="py-2 px-1 text-secondary">Opinia</th>
+                  <th scope="row" class="py-2 px-1 text-secondary">Status</th>
+                </tr>
+                <tr class="preferences py-0 px-0 border-top border-left border-secondary"
+                  v-for='lexical_unit in lexical_units'
+                >
+                  <template v-if="isFrameVisible(lexical_unit.frame)">
+                    <td class="lu-cursor-pointer argument py-2 px-1 border-top border-left border-secondary"
+                        @mouseenter="lexical_unit.frame.hover=true"
+                        @mouseleave="lexical_unit.frame.hover=false"
+                        :class="lexical_unit.frame === active_slowal_frame ? 'active-lu' : lexical_unit.frame.hover ? 'lu-underline' : ''"
+                        @click="slowalFrameSelected(lexical_unit.frame)">{{ lexical_unit.str }}</td>
+                    <td class="argument py-2 px-1 border-top border-left border-secondary">
+                      <img v-bind:src="img_prefix + 'entries/img/' +lexical_unit.opinion_key + '.svg'" width="12" height="12" v-bind:alt="lexical_unit.opinion">
+                      {{ lexical_unit.opinion }}
+                    </td>
+                    <td class="argument py-2 px-1 border-top border-left border-secondary">[{{ lexical_unit.frame_status }}]</td>
+                  </template>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+      </div>
+      </div>
+      <div :id="'examples' + (readOnly ? '-preview' : '')" class="col w-100 p-0 tab-pane overflow-auto">
+         <examples-component v-if="examples"
+              :examples="examples" 
+              :frame="active_slowal_frame"
+              :frame_arguments="selectedFrameArguments"
+              :frame_arguments_or_type="frame_arguments_or_type"
+              :lus="selectedLus"
+              :schemas="selectedSchemas"
+              :key="examples"
+              @example-selected="exampleSelected"
+          />
+        </div>
+    </div>
+    <div v-if="!readOnly" class="col h-100 px-1 pt-0 pb-0 overflow-auto" style="padding-left: 0px!important; padding-right: 0px!important;" id="semantics-schemata-pane">
+      <ul class="nav nav-pills nav-justified p-1" id="entryTabs" role="tablist">
+        <li
+          v-for="tab in right_pane_tabs"
+          class="btn btn-sm btn-outline-dark nav-link mx-1"
+          :class="right_pane_tab === tab.id && 'active'"
+          @click="right_pane_tab = tab.id"
+        >
+          {{ tab.label }}
+        </li>
+      </ul>
+      <div v-if="active_slowal_frame" :class="right_pane_tab !== 'schemata' && 'd-none'">
+          <semantics-schemata-component 
+                :subentries="subentries"
+                :key="subentries"
+                :frame="active_slowal_frame"
+                :alternations="alternations"
+                :realisation_phrases="realisation_phrases"
+                :realisation_descriptions="realisation_descriptions"
+                :selectedExamples="selectedExamples"
+                @schemata-selected="schemataSelected"
+              />
+      </div>
+      <div v-if="right_pane_tab === 'frame_preview'" class="overflow-hidden" style="height: calc(100% - 43px)">
+        <hierarchy-preview
+            :key="currentPreviewedUnifiedFrameId"
+            :hierarchyEditComponent="HierarchyEdit"
+            :initialUnifiedFrameId="currentPreviewedUnifiedFrameId" 
+            @change-frame="changePreviewedUnifiedFrameId"
+            @change-preview-to-edit="swapUnifiedFrames"
+            :forceRefresh="internalForceRefresh" />
+      </div>
+      <div v-if="right_pane_tab === 'hierarchy'" class="overflow-hidden" style="height: calc(100% - 43px)">
+        <hierarchy-preview
+                :key="currentPreviewedUnifiedFrameId"
+                :hierarchyEditComponent="HierarchyEdit"
+                :initialUnifiedFrameId="currentPreviewedUnifiedFrameId"
+                @change-frame="changePreviewedUnifiedFrameId"
+                @change-preview-to-edit="swapUnifiedFrames"
+                :forceRefresh="internalForceRefresh" />
+      </div>
+      <div :class="right_pane_tab !== 'notes' && 'd-none'" id="notes-component"></div>
+    </div>
+</template>
diff --git a/frontend/src/components/unification/hierarchy/HierarchyPreview.vue b/frontend/src/components/unification/hierarchy/HierarchyPreview.vue
new file mode 100644
index 0000000..a88db17
--- /dev/null
+++ b/frontend/src/components/unification/hierarchy/HierarchyPreview.vue
@@ -0,0 +1,64 @@
+<script>
+import UnificationFramesList from "../Unification/UnificationFramesList.vue";
+
+export default {
+  props: {
+    initialUnifiedFrameId: Number,
+    initialLexicalUnitId: Number,
+    forceRefresh: Number,
+    hierarchyEditComponent: Object,
+  },
+  data () {
+    return {
+      unifiedFrameId: this.initialUnifiedFrameId
+    };
+  },
+  components: {UnificationFramesList},
+  emits: ['changeFrame', 'refreshEntriesList'],
+  methods: {
+    unifiedFrameSelected (unifiedFrameId) {
+      this.$emit('changeFrame', unifiedFrameId);
+    },
+    refreshEntriesList() {
+      this.$emit('refreshEntriesList')
+    },
+  },
+  mounted () {
+    Split(['#frame-preview-left-pane', '#frame-preview-right-pane'], {
+      sizes: [60, 40],
+      minSize: 20,
+      gutterSize: 4,
+      elementStyle: (dimension, size, gutterSize) => {
+        return {
+          'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)'
+        }
+      },
+    });
+  }
+};
+</script>
+
+<template>
+  <div class="row h-100 overflow-hidden">
+    <div :key="unifiedFrameId" class="col h-100 pr-0 pt-0 pb-0 overflow-auto" id="frame-preview-left-pane">
+      <component v-bind:is="hierarchyEditComponent"
+        v-if="unifiedFrameId !== -1"
+        :readOnly="true"
+        :unifiedFrameId="unifiedFrameId"
+        :forceRefresh="forceRefresh"
+        @refresh-entries-list="refreshEntriesList"
+      />
+      <div v-else class="h-100">
+        Brak ramy do wyświetlenia
+      </div>
+    </div>
+    <div class="col h-100 pl-1 pt-0 pb-0 overflow-auto" id="frame-preview-right-pane">
+<!--      <unification-switchable-list-->
+<!--        @unified-frame-selected="unifiedFrameSelected"-->
+<!--      />-->
+      <unification-frames-list
+            @unified-frame-selected="unifiedFrameSelected"
+      />
+    </div>
+  </div>
+</template>
diff --git a/frontend/src/components/unification/hierarchy/HierarchyRightPane.vue b/frontend/src/components/unification/hierarchy/HierarchyRightPane.vue
new file mode 100644
index 0000000..913bbf7
--- /dev/null
+++ b/frontend/src/components/unification/hierarchy/HierarchyRightPane.vue
@@ -0,0 +1,68 @@
+<script>
+import HierarchyEdit from './HierarchyEdit.vue';
+
+export default {
+  components: {HierarchyEdit},
+  props: {
+    entryId: Number,
+    lexicalUnitId: Number,
+    initialUnifiedFrameId: Number,
+  },
+  emits: ['refreshEntriesList'],
+  data() {
+    return this.getInitialData();
+  },
+  methods: {
+    getInitialData() {
+      return {
+        key: this.lexicalUnitId,
+        entryIdLocal: this.entryId,
+        unifiedFrameId: this.initialUnifiedFrameId,
+        previewedUnifiedFrameId: null
+      };
+    },
+    goToDisplay() {
+      this.unifiedFrameId = null;
+    },
+    refresh() {
+      this.key = null;
+      setTimeout(() => {
+        this.key = this.lexicalUnitId;
+      }, 0);
+    },
+    swapFrames(previewedUnifiedFrameId) {
+      this.previewedUnifiedFrameId = this.unifiedFrameId;
+      this.unifiedFrameId = previewedUnifiedFrameId;
+      this.refresh();
+    },
+    refreshEntriesList() {
+      this.$emit('refreshEntriesList');
+    }
+  },
+  watch: {
+    lexicalUnitId() {
+      Object.assign(this, this.getInitialData());
+    },
+    initialUnifiedFrameId() {
+      Object.assign(this, this.getInitialData());
+    }
+  },
+};
+</script>
+
+<template>
+  <div v-if="key || unifiedFrameId" :key="(key, entryIdLocal, unifiedFrameId)" class="row h-100 m-0 p-0 overflow-auto" id="semantics-top-pane">
+    <hierarchy-edit
+      ref="hierarchyEdit"
+      v-if="unifiedFrameId"
+      :key="unifiedFrameId"
+      :readOnly="false"
+      :unifiedFrameId="unifiedFrameId"
+      :previewedUnifiedFrameId="previewedUnifiedFrameId"
+      :initialRightPaneTab="previewedUnifiedFrameId && unifiedFrameId !== previewedUnifiedFrameId ? 'frame_preview' : 'schemata'"
+      @go-to-display="goToDisplay"
+      @swap-frames="swapFrames"
+      @refresh-entries-list="refreshEntriesList"
+    />
+  </div>
+</template>
diff --git a/frontend/src/main.js b/frontend/src/main.js
index ffa91da..d28df1b 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -5,12 +5,14 @@ import { createRouter, createWebHistory } from "vue-router";
 
 import Entries from "./components/unification/Entries/Entries.vue";
 import Unification from "./components/unification/Unification/Unification.vue";
+import Hierarchy from "./components/unification/hierarchy/Hierarchy.vue";
 
 const router = createRouter({
   history: createWebHistory(),
   routes: [
     { path: '/:lang/entries/', component: Entries },
     { path: '/:lang/entries/unification/', component: Unification },
+    { path: '/:lang/entries/hierarchy/', component: Hierarchy },
     { path: '/:pathMatch(.*)*', component: null, name: '404' },
   ]
 });
diff --git a/unifier/views.py b/unifier/views.py
index fe251b1..8a45397 100644
--- a/unifier/views.py
+++ b/unifier/views.py
@@ -92,6 +92,9 @@ def get_unified_frames(request):
         else:
             unifiedFrames = UnifiedFrame.objects.all();
 
+        if scroller_params['filter']:
+            unifiedFrames = unifiedFrames.filter(title__startswith=scroller_params['filter'])
+
         # TODO: zapytać, czy mamy zaciągać powiązane ramy zunifikowane poprzez Enrty (slowal_frame -> lexical_units -> entries -> related_entries)
         # linked_ids = set()
         # if request.session['show_linked_entries']:
@@ -120,7 +123,7 @@ def get_unified_frames(request):
                 resProcessed.append(value)
 
         ret = {
-                'draw' : scroller_params['draw'],
+                'draw': scroller_params['draw'],
                 'recordsTotal': len(resProcessed),
                 'recordsFiltered': len(resProcessed),
                 'data': resProcessed
-- 
GitLab