tg-prepare 1.1.0__py3-none-any.whl → 2.1.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tg-prepare might be problematic. Click here for more details.

Files changed (46) hide show
  1. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/METADATA +3 -3
  2. tg_prepare-2.1.0b1.dist-info/RECORD +54 -0
  3. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/WHEEL +1 -1
  4. tg_prepare-2.1.0b1.dist-info/projects/.secret_key +1 -0
  5. tgp_backend/__init__.py +3 -4
  6. tgp_backend/config.py +31 -0
  7. tgp_backend/directories.py +6 -8
  8. tgp_backend/nextcloud.py +40 -19
  9. tgp_backend/project.py +172 -45
  10. tgp_backend/session_manager.py +47 -0
  11. tgp_backend/util.py +73 -25
  12. tgp_ui/app.py +43 -335
  13. tgp_ui/routes/__init__.py +0 -0
  14. tgp_ui/routes/collection.py +272 -0
  15. tgp_ui/routes/data.py +228 -0
  16. tgp_ui/routes/project.py +102 -0
  17. tgp_ui/routes/publication.py +129 -0
  18. tgp_ui/routes/tabs.py +34 -0
  19. tgp_ui/routes/views.py +62 -0
  20. tgp_ui/static/css/navbar.css +92 -0
  21. tgp_ui/static/js/collectionManager.js +60 -0
  22. tgp_ui/static/js/fileManager.js +186 -0
  23. tgp_ui/static/js/main.js +32 -485
  24. tgp_ui/static/js/modalManager.js +105 -0
  25. tgp_ui/static/js/navbarManager.js +151 -0
  26. tgp_ui/static/js/projectManager.js +60 -0
  27. tgp_ui/static/js/require.js +5 -0
  28. tgp_ui/static/js/sidebarManager.js +32 -0
  29. tgp_ui/static/js/tabManager.js +79 -0
  30. tgp_ui/templates/layout.html +9 -48
  31. tgp_ui/templates/macros.html +79 -72
  32. tgp_ui/templates/project_main.html +36 -0
  33. tgp_ui/templates/project_navbar.html +81 -0
  34. tgp_ui/templates/{projects.html → projects_main.html} +13 -28
  35. tgp_ui/templates/tab_final_upload.html +29 -0
  36. tg_prepare-1.1.0.dist-info/RECORD +0 -39
  37. tgp_ui/templates/collection.html +0 -194
  38. tgp_ui/templates/file_upload.html +0 -24
  39. tgp_ui/templates/nxc_file_tree.html +0 -33
  40. tgp_ui/templates/project.html +0 -26
  41. tgp_ui/templates/storage.html +0 -49
  42. tgp_ui/templates/tei_explorer.html +0 -48
  43. tgp_ui/templates/xpath_parser_modal_content.html +0 -37
  44. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/entry_points.txt +0 -0
  45. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/licenses/LICENSE +0 -0
  46. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2023-2025 TU-Dresden (ZIH)
3
+ # ralf.klammer@tu-dresden.de
4
+ # moritz.wilhelm@tu-dresden.de
5
+
6
+ import logging
7
+
8
+ from flask import Blueprint, render_template, request
9
+ from flask_json import json_response
10
+
11
+ from tgp_backend.project import Project, TGProject
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+ publication_routes = Blueprint("publication", __name__)
16
+
17
+
18
+ @publication_routes.route(
19
+ "/tab-prepare-upload/<string:projectname>", methods=["GET"]
20
+ )
21
+ def tab_upload(projectname):
22
+ project = Project(projectname)
23
+
24
+ return render_template(
25
+ "tabs/upload.html",
26
+ project=project,
27
+ )
28
+
29
+
30
+ # ***TAB CHECK RESULTS***
31
+ # ***********************
32
+ @publication_routes.route(
33
+ "/tab-check-result/<string:projectname>", methods=["GET"]
34
+ )
35
+ def tab_check_result(projectname):
36
+ # Erstelle das Project-Objekt
37
+ project = Project(projectname)
38
+
39
+ # Übergib das Project-Objekt an das Template
40
+ return render_template(
41
+ "tabs/check_result.html",
42
+ project=project, # Das Project-Objekt wird hier übergeben
43
+ )
44
+
45
+
46
+ class TGProjectHandler:
47
+ def __init__(self, projectname, instance):
48
+ self.project = Project(projectname)
49
+ self.tg_project = self.project.get_tgp(instance)
50
+ self.instance = instance
51
+
52
+ def _render_template(self):
53
+ return render_template(
54
+ "includes/upload_form.html",
55
+ project=self.project,
56
+ instance=self.instance,
57
+ )
58
+
59
+ def save_session_id(self, session_id):
60
+ self.tg_project.tg_session_id = session_id
61
+ return self._render_template()
62
+
63
+ def save_tg_project_id(self, tg_project_id):
64
+ self.tg_project.tg_project_id = tg_project_id
65
+ return self._render_template()
66
+
67
+ def delete_tg_project(self, tg_project_id):
68
+ self.tg_project.delete_tg_project(tg_project_id)
69
+ return self._render_template()
70
+
71
+ def create_tg_project(self, tg_projectname):
72
+ self.tg_project.create_tg_project(tg_projectname)
73
+ return self._render_template()
74
+
75
+
76
+ @publication_routes.route(
77
+ "/save_session_id/<string:projectname>/<string:instance>", methods=["POST"]
78
+ )
79
+ def save_session_id(projectname, instance):
80
+ return TGProjectHandler(projectname, instance).save_session_id(
81
+ request.form.get("tg_auth_session_id")
82
+ )
83
+
84
+
85
+ @publication_routes.route(
86
+ "/save_tg_project_id/<string:projectname>/<string:instance>/<string:tg_project_id>",
87
+ methods=["POST"],
88
+ )
89
+ def save_tg_project_id(projectname, instance, tg_project_id):
90
+ return TGProjectHandler(projectname, instance).save_tg_project_id(
91
+ tg_project_id
92
+ )
93
+
94
+
95
+ @publication_routes.route(
96
+ "/delete_tg_project_id/<string:projectname>/<string:instance>/<string:tg_project_id>",
97
+ methods=["POST"],
98
+ )
99
+ def delete_tg_project_id(projectname, instance, tg_project_id):
100
+ return TGProjectHandler(projectname, instance).delete_tg_project(
101
+ tg_project_id
102
+ )
103
+
104
+
105
+ @publication_routes.route(
106
+ "/create_tg_project/<string:projectname>/<string:instance>",
107
+ methods=["POST"],
108
+ )
109
+ def create_tg_project(projectname, instance):
110
+ return TGProjectHandler(projectname, instance).create_tg_project(
111
+ request.form.get("tg_projectname")
112
+ )
113
+
114
+
115
+ @publication_routes.route(
116
+ "/upload_project/<string:projectname>/<string:instance>", methods=["POST"]
117
+ )
118
+ def upload_project(projectname, instance):
119
+ TGProject(projectname, instance).upload_tg_project()
120
+ return json_response(response="OK")
121
+
122
+
123
+ # @publication_routes.route(
124
+ # "/get_tg_project_hits/<string:projectname>/<string:instance>/<string:tg_project_id>",
125
+ # methods=["GET"],
126
+ # )
127
+ # def get_tg_project_hits(projectname, instance, tg_project_id):
128
+ # hits = TGProject(projectname, instance).get_tg_project_hits(tg_project_id)
129
+ # return json_response(response="OK", hits=hits)
tgp_ui/routes/tabs.py ADDED
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2023-2025 TU-Dresden (ZIH)
3
+ # ralf.klammer@tu-dresden.de
4
+ # moritz.wilhelm@tu-dresden.de
5
+
6
+ import logging
7
+
8
+ from flask import Blueprint, session
9
+ from flask_json import json_response
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+ tab_manager = Blueprint("tab_manager", __name__)
14
+
15
+
16
+ @tab_manager.route("/get_last_tab/<string:projectname>", methods=["GET"])
17
+ def get_last_tab(projectname):
18
+
19
+ tabname = session.get(f"last_tab_{projectname}", None)
20
+
21
+ return json_response(status=200, initial_tab=tabname or "tab-edit-project")
22
+
23
+
24
+ @tab_manager.route(
25
+ "/set_last_tab/<string:projectname>/<string:tabname>", methods=["POST"]
26
+ )
27
+ def set_last_tab(projectname, tabname):
28
+
29
+ if tabname:
30
+ session[f"last_tab_{projectname}"] = tabname
31
+ return json_response(
32
+ response="OK",
33
+ message=f"Last tab for project '{projectname}' set to '{tabname}'",
34
+ )
tgp_ui/routes/views.py ADDED
@@ -0,0 +1,62 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2023-2025 TU-Dresden (ZIH)
3
+ # ralf.klammer@tu-dresden.de
4
+ # moritz.wilhelm@tu-dresden.de
5
+
6
+ import logging
7
+
8
+ from flask import Blueprint, render_template, request, session
9
+
10
+ from tgp_backend.project import Project
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+ main_views = Blueprint("views", __name__)
15
+
16
+
17
+ # favored route
18
+ @main_views.route("/overview", methods=["GET", "POST"])
19
+ # additional routes for compatibility
20
+ @main_views.route("/", methods=["GET"])
21
+ @main_views.route("/project", methods=["GET", "POST"])
22
+ @main_views.route("/projects", methods=["GET", "POST"])
23
+ def overview():
24
+ return render_template("projects_main.html", sub_title="Projects Overview")
25
+
26
+
27
+ @main_views.route("/project/<string:projectname>", methods=["GET", "POST"])
28
+ def project_view(projectname=None):
29
+ return render_template(
30
+ "project_main.html",
31
+ user=session.get("user", "-"),
32
+ current_project=projectname,
33
+ project=Project(projectname),
34
+ tab=request.args.get("tab"),
35
+ )
36
+
37
+
38
+ @main_views.route("/modal/container", methods=["GET"])
39
+ def modal_container():
40
+ """
41
+ Render a modal spinner template.
42
+ This is used to show a loading spinner while processing requests.
43
+ """
44
+ return render_template("modal/empty_container.html")
45
+
46
+
47
+ @main_views.route("/tabs/container", methods=["GET"])
48
+ def tab_container():
49
+ """
50
+ Render a tab spinner template.
51
+ This is used to show a loading spinner while processing requests.
52
+ """
53
+ return render_template("tabs/empty_container.html")
54
+
55
+
56
+ @main_views.route("/details/container", methods=["GET"])
57
+ def details_container():
58
+ """
59
+ Render a details spinner template.
60
+ This is used to show a loading spinner while processing requests.
61
+ """
62
+ return render_template("details/empty_container.html", show_spinner=True)
@@ -0,0 +1,92 @@
1
+ .btn-circle.btn-xl {
2
+ width: 4.2rem;
3
+ height: 4.2rem;
4
+ border-radius: 50%;
5
+ text-align: center;
6
+ font-size: 1.5rem;
7
+ display: flex;
8
+ justify-content: center;
9
+ align-items: center;
10
+ position: relative;
11
+ z-index: 1;
12
+ }
13
+
14
+ .btn-circle.btn-sm {
15
+ width: 3.2rem;
16
+ height: 3.2rem;
17
+ border-radius: 50%;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+
22
+ }
23
+
24
+ /* Animation für den Übergang */
25
+ .btn-xl.animate-to-long {
26
+ width: 25rem;
27
+ height: 4.2rem;
28
+ border-width: 0.2rem;
29
+ border-radius: 3rem;
30
+ transition: all 5.2s ease-in-out;
31
+ display: flex;
32
+ justify-content: center;
33
+ align-items: center;
34
+ }
35
+
36
+ .btn-xl.animate-to-long i .main-icon {
37
+ opacity: 0;
38
+ /* Icon ausblenden */
39
+ transition: opacity 0.3s ease-in-out;
40
+ /* Sanftes Ausblenden */
41
+ }
42
+
43
+ .line {
44
+ height: 20px;
45
+ border-radius: 10px;
46
+ position: absolute;
47
+ top: 50%;
48
+ height: 4px;
49
+ }
50
+
51
+ .line.blank {
52
+ width: 95%;
53
+ background-color: #f8f9fa;
54
+ left: 1rem;
55
+ z-index: 0;
56
+ }
57
+
58
+ .line.blue {
59
+ width: 0%;
60
+ background-color: #0d6efd;
61
+ left: 1rem;
62
+ z-index: 0.5;
63
+ transition: width 0.3s ease-in-out;
64
+ }
65
+
66
+ .btn-xl.animate-to-long {
67
+ z-index: 2;
68
+ /* Erweiterte Buttons über die Linie legen */
69
+ transition: all 0.3s ease-in-out;
70
+ }
71
+
72
+ .border-primary{
73
+ border-width: 0.2rem;
74
+ transition: all 0.2s ease-in-out;
75
+ }
76
+
77
+
78
+ /* tab-edit-other-files */
79
+ .tab-button {
80
+ width: 12rem;
81
+ height: 12rem;
82
+ color: #0d6efd;
83
+ }
84
+
85
+ .tab-button.active,
86
+ .tab-button:active {
87
+ background-color: #FFFFFF !important;
88
+ }
89
+
90
+ .big-icon {
91
+ font-size: 5rem;
92
+ }
@@ -0,0 +1,60 @@
1
+ define([], function () {
2
+ const CollectionManager = {
3
+ loadDetailsInSidebar: function () {
4
+ const self = $(this);
5
+ const url = self.data('url');
6
+ const detailsContent = $('#detailsContent');
7
+ detailsContent.load("/details/container");
8
+ self.closest('.card-body')
9
+ .find('button.btn-primary')
10
+ .addClass('btn-secondary')
11
+ .removeClass('btn-primary');
12
+
13
+ self.addClass('btn-primary').removeClass('btn-secondary');
14
+
15
+ $.get(url, function (data) {
16
+ detailsContent.html(data);
17
+ }).fail(function () {
18
+ detailsContent.html('<p class="text-danger">Failed to load details. Please try again.</p>');
19
+ });
20
+ },
21
+
22
+ addMultiInputClassifications: function (e) {
23
+ e.preventDefault();
24
+ const self = $(this);
25
+ const thisRow = self.closest('.row');
26
+ thisRow.next().clone()
27
+ .insertAfter(thisRow)
28
+ .find('input').val('');
29
+ },
30
+
31
+ removeMultiInputClassifications: function (e) {
32
+ e.preventDefault();
33
+ const self = $(this);
34
+ const thisMultiInput = self.closest('.multi-input');
35
+ thisMultiInput.remove();
36
+ },
37
+
38
+ highlightFolder: function (e) {
39
+ const self = $(this);
40
+ const selectedFolder = self.closest('.card');
41
+ selectedFolder.toggleClass('border-primary').toggleClass('border-secondary');
42
+ },
43
+
44
+ init: function () {
45
+ // Load details in sidebar
46
+ $(document).on('click', '.edit-collection-button', this.loadDetailsInSidebar);
47
+
48
+ // Add a new multi input-field (e.g., 'Basic classification', 'Rights Holder')
49
+ $(document).on('click', '.add-multi-input-classifications', this.addMultiInputClassifications);
50
+
51
+ // Remove a multi input-field (e.g., 'Basic classification', 'Rights Holder')
52
+ $(document).on('click', '.remove-multi-field-classification', this.removeMultiInputClassifications);
53
+
54
+ // Highlight folder
55
+ $(document).on('change', 'input[name="selected_folder"]', this.highlightFolder);
56
+ }
57
+ };
58
+
59
+ return CollectionManager;
60
+ });
@@ -0,0 +1,186 @@
1
+ define([], function () {
2
+ const FileManager = {
3
+ triggerFileInput: function () {
4
+ $('#fileInput').trigger('click');
5
+ },
6
+
7
+ submitFileUpload: function () {
8
+ $('#fileUploadLocal').trigger('submit');
9
+ },
10
+
11
+ // New functions from main_new.js
12
+ showPreviewOfAvatar: function () {
13
+ const uploadFile = $(this);
14
+ const files = this.files;
15
+
16
+ if (files && files.length && window.FileReader) {
17
+ const reader = new FileReader();
18
+ reader.readAsDataURL(files[0]);
19
+
20
+ reader.onloadend = function () {
21
+ uploadFile.closest("div").find('.ratio').html(`
22
+ <div class="w-100 h-100"
23
+ style="background-image: url(${this.result});
24
+ background-size: cover;
25
+ background-position: center;">
26
+ </div>
27
+ `);
28
+ };
29
+ }
30
+ },
31
+
32
+ cloneFromGit: function (e) {
33
+ e.preventDefault();
34
+ const form = $(e.target);
35
+ const button = form.find('button[type="submit"]');
36
+ const spinner = button.find('.spinner-border');
37
+ const buttonText = button.find('span:not(.spinner-border)');
38
+ const modal = form.closest('.modal');
39
+
40
+ button.prop('disabled', true);
41
+ spinner.removeClass('d-none');
42
+ buttonText.text('Cloning...');
43
+
44
+ $.ajax({
45
+ url: form.attr('action'),
46
+ method: 'POST',
47
+ data: form.serialize(),
48
+ success: function (response) {
49
+ $(modal).modal('hide');
50
+ $(form.closest('.tab-pane')).html(response);
51
+ },
52
+ error: function () {
53
+ button.removeClass('btn-primary').addClass('btn-danger');
54
+ buttonText.text('Clone Repository');
55
+ },
56
+ complete: function () {
57
+ spinner.addClass('d-none');
58
+ button.prop('disabled', false);
59
+ }
60
+ });
61
+ },
62
+
63
+ deleteFolder: function (e) {
64
+ e.preventDefault();
65
+ const button = $(this);
66
+ const itemName = button.data('item-name');
67
+ const itemType = button.data('item-type');
68
+
69
+ if (confirm(`Do you want to delete ${itemType === 'folder' ? 'the folder' : 'the file'} "${itemName}"?`)) {
70
+ $.ajax({
71
+ url: '/api/delete-folder',
72
+ method: 'DELETE',
73
+ data: {
74
+ path: button.data('item-path'),
75
+ projectname: button.data('projectname')
76
+ },
77
+ success: function (response) {
78
+ $(button.closest('.tab-pane')).html(response);
79
+ },
80
+ error: function () {
81
+ alert('Error deleting item.');
82
+ }
83
+ });
84
+ }
85
+ },
86
+
87
+ deleteXSLT: function (e) {
88
+ e.preventDefault();
89
+ const projectname = $(this).data('projectname');
90
+ const self = this;
91
+ $.ajax({
92
+ url: `/delete-xslt/${projectname}`,
93
+ method: 'DELETE',
94
+ headers: {
95
+ 'Content-Type': 'application/json'
96
+ },
97
+ success: function (response) {
98
+ FileManager.hideHint.call(self);
99
+ },
100
+ error: function () {
101
+ alert('Error deleting XSLT file.');
102
+ }
103
+ });
104
+ },
105
+
106
+ hideHint: function () {
107
+ const hint = $('.xslt-upload');
108
+ if (hint) {
109
+ hint.addClass('d-none');
110
+ }
111
+ },
112
+
113
+ selectNextcloudFolder: function () {
114
+ const self = $(this);
115
+ const new_state = self.is(':checked') ? 'checked' : '';
116
+ const checkboxes = self.closest('.list-group-item').find('.nextcloud-folder');
117
+ checkboxes
118
+ .prop("checked", new_state)
119
+ .not(':first')
120
+ .prop("disabled", true);
121
+ },
122
+
123
+ dragDropHandler: function (event) {
124
+ event.preventDefault();
125
+
126
+ const dropZone = $(event.target).closest('.js-file-upload');
127
+ const files = event.originalEvent.dataTransfer.files;
128
+ const uploadUrl = dropZone.data('url');
129
+
130
+ console.log(`Dropped ${files.length} files to upload`);
131
+
132
+ if (files.length > 0) {
133
+ // Create FormData with the dropped files
134
+ const formData = new FormData();
135
+
136
+ for (let i = 0; i < files.length; i++) {
137
+ formData.append('files', files[i]);
138
+ console.log(`File ${i + 1}: ${files[i].name}`);
139
+ }
140
+
141
+ // Upload files via AJAX
142
+ $.ajax({
143
+ url: uploadUrl,
144
+ type: 'POST',
145
+ data: formData,
146
+ processData: false,
147
+ contentType: false,
148
+ success: function (response) {
149
+ dropZone.closest('.container, .tab-pane').html(response);
150
+ },
151
+ error: function (xhr, status, error) {
152
+ console.error('Upload error:', error);
153
+ dropZone.addClass('border-danger text-danger').removeClass('border-success');
154
+ }
155
+ });
156
+ }
157
+ },
158
+
159
+ preventDefaults: function (event) {
160
+ event.preventDefault();
161
+ event.stopPropagation();
162
+ },
163
+
164
+ // Update the init function to include drag & drop event listeners
165
+ init: function () {
166
+ $(document).on('change', 'input#fileInput', this.submitFileUpload);
167
+ $(document).on('click', 'button#triggerFileInput', this.triggerFileInput);
168
+ $(document).on('click', '.delete-folder', this.deleteFolder);
169
+ $(document).on("change", ".showPreviewOfAvatar", this.showPreviewOfAvatar);
170
+ $(document).on('click', '#deleteXsltButton', this.deleteXSLT);
171
+ $(document).on('change', '.xslt-upload', this.hideHint);
172
+ $(document).on('click', 'input.nextcloud-folder', this.selectNextcloudFolder);
173
+
174
+ // Drag & Drop event listeners for elements with class 'js-file-upload'
175
+ $(document).on('dragenter', '.js-file-upload', this.preventDefaults);
176
+ $(document).on('dragover', '.js-file-upload', this.preventDefaults);
177
+ $(document).on('dragleave', '.js-file-upload', this.preventDefaults);
178
+ $(document).on('drop', '.js-file-upload', this.dragDropHandler);
179
+ $(document).on('click', '.js-file-upload', function (event) {
180
+ $('#triggerFileInput').click();
181
+ })
182
+ }
183
+ };
184
+
185
+ return FileManager;
186
+ });