forked from enviPath/enviPy
This PR moves all the collection pages into the new UI in a rough push. I did not put the same amount of care into these as into search, index, and predict. ## Major changes - All modals are now migrated to a state based alpine.js implementation. - jQuery is no longer present in the base layout; ajax is replace by native fetch api - most of the pps.js is now obsolte (as I understand it; the code is not referenced any more @jebus please double check) - in-memory pagination for large result lists (set to 50; we can make that configurable later; performance degrades at around 1k) stukk a bit rough tracked in #235 ## Minor things - Sarch and index also use alpine now - The loading spinner is now CSS animated (not sure if it currently gets correctly called) ## Not done - Ihave not even cheked the admin pages. Not sure If these need migrations - The temporary migration pages still use the old template. Not sure what is supposed to happen with those? @jebus ## What I did to test - opend all pages in browse, and user ; plus all pages reachable from there. - Interacted and tested the functionality of each modal superfically with exception of the API key modal (no functional test). --- This PR is massive sorry for that; just did not want to push half-brokenn state. @jebus @liambrydon I would be glad if you could click around and try to break it :) Finally closes #133 Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#236 Co-authored-by: Tobias O <tobias.olenyi@envipath.com> Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
534 lines
20 KiB
HTML
534 lines
20 KiB
HTML
{% extends "framework_modern.html" %}
|
|
{% load static %}
|
|
{% block content %}
|
|
|
|
{# Serialize objects data for Alpine pagination #}
|
|
{# prettier-ignore-start #}
|
|
{# FIXME: This is a hack to get the objects data into the JavaScript code. #}
|
|
<script>
|
|
window.reviewedObjects = [
|
|
{% for obj in reviewed_objects %}
|
|
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
|
{% endfor %}
|
|
];
|
|
window.unreviewedObjects = [
|
|
{% for obj in unreviewed_objects %}
|
|
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
|
{% endfor %}
|
|
];
|
|
</script>
|
|
{# prettier-ignore-end #}
|
|
|
|
{% if object_type != 'package' %}
|
|
<div class="px-8 py-4">
|
|
<input
|
|
type="text"
|
|
id="object-search"
|
|
class="input input-bordered hidden w-full max-w-xs"
|
|
placeholder="Search by name"
|
|
/>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% block action_modals %}
|
|
{% if object_type == 'package' %}
|
|
{% include "modals/collections/new_package_modal.html" %}
|
|
{% include "modals/collections/import_package_modal.html" %}
|
|
{% include "modals/collections/import_legacy_package_modal.html" %}
|
|
{% elif object_type == 'compound' %}
|
|
{% include "modals/collections/new_compound_modal.html" %}
|
|
{% elif object_type == 'rule' %}
|
|
{% include "modals/collections/new_rule_modal.html" %}
|
|
{% elif object_type == 'reaction' %}
|
|
{% include "modals/collections/new_reaction_modal.html" %}
|
|
{% elif object_type == 'pathway' %}
|
|
{# {% include "modals/collections/new_pathway_modal.html" %} #}
|
|
{% elif object_type == 'node' %}
|
|
{% include "modals/collections/new_node_modal.html" %}
|
|
{% elif object_type == 'edge' %}
|
|
{% include "modals/collections/new_edge_modal.html" %}
|
|
{% elif object_type == 'scenario' %}
|
|
{% include "modals/collections/new_scenario_modal.html" %}
|
|
{% elif object_type == 'model' %}
|
|
{% include "modals/collections/new_model_modal.html" %}
|
|
{% elif object_type == 'setting' %}
|
|
{#{% include "modals/collections/new_setting_modal.html" %}#}
|
|
{% elif object_type == 'user' %}
|
|
<div></div>
|
|
{% elif object_type == 'group' %}
|
|
{% include "modals/collections/new_group_modal.html" %}
|
|
{% endif %}
|
|
{% endblock action_modals %}
|
|
|
|
<div class="px-8 py-4">
|
|
<!-- Header Section -->
|
|
<div class="card bg-base-100">
|
|
<div class="card-body px-0 py-4">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="card-title text-2xl">
|
|
{% if object_type == 'package' %}
|
|
Packages
|
|
{% elif object_type == 'compound' %}
|
|
Compounds
|
|
{% elif object_type == 'structure' %}
|
|
Compound structures
|
|
{% elif object_type == 'rule' %}
|
|
Rules
|
|
{% elif object_type == 'reaction' %}
|
|
Reactions
|
|
{% elif object_type == 'pathway' %}
|
|
Pathways
|
|
{% elif object_type == 'node' %}
|
|
Nodes
|
|
{% elif object_type == 'edge' %}
|
|
Edges
|
|
{% elif object_type == 'scenario' %}
|
|
Scenarios
|
|
{% elif object_type == 'model' %}
|
|
Model
|
|
{% elif object_type == 'setting' %}
|
|
Settings
|
|
{% elif object_type == 'user' %}
|
|
Users
|
|
{% elif object_type == 'group' %}
|
|
Groups
|
|
{% endif %}
|
|
</h2>
|
|
<div id="actionsButton" class="dropdown dropdown-end hidden">
|
|
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
class="lucide lucide-wrench"
|
|
>
|
|
<path
|
|
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
|
/>
|
|
</svg>
|
|
Actions
|
|
</div>
|
|
<ul
|
|
tabindex="-1"
|
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
|
>
|
|
{% block actions %}
|
|
{% if object_type == 'package' %}
|
|
{% include "actions/collections/package.html" %}
|
|
{% elif object_type == 'compound' %}
|
|
{% include "actions/collections/compound.html" %}
|
|
{% elif object_type == 'structure' %}
|
|
{% include "actions/collections/compound_structure.html" %}
|
|
{% elif object_type == 'rule' %}
|
|
{% include "actions/collections/rule.html" %}
|
|
{% elif object_type == 'reaction' %}
|
|
{% include "actions/collections/reaction.html" %}
|
|
{% elif object_type == 'setting' %}
|
|
{% include "actions/collections/setting.html" %}
|
|
{% elif object_type == 'scenario' %}
|
|
{% include "actions/collections/scenario.html" %}
|
|
{% elif object_type == 'model' %}
|
|
{% include "actions/collections/model.html" %}
|
|
{% elif object_type == 'pathway' %}
|
|
{% include "actions/collections/pathway.html" %}
|
|
{% elif object_type == 'node' %}
|
|
{% include "actions/collections/node.html" %}
|
|
{% elif object_type == 'edge' %}
|
|
{% include "actions/collections/edge.html" %}
|
|
{% elif object_type == 'group' %}
|
|
{% include "actions/collections/group.html" %}
|
|
{% endif %}
|
|
{% endblock %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2">
|
|
<!-- Set Text above links -->
|
|
{% if object_type == 'package' %}
|
|
<p>
|
|
A package contains pathways, rules, etc. and can reflect specific
|
|
experimental conditions.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/packages"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'compound' %}
|
|
<p>
|
|
A compound stores the structure of a molecule and can include
|
|
meta-information.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/compounds"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'structure' %}
|
|
<p>
|
|
The structures stored in this compound
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/compounds"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'rule' %}
|
|
<p>
|
|
A rule describes a biotransformation reaction template that is
|
|
defined as SMIRKS.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/Rules"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'reaction' %}
|
|
<p>
|
|
A reaction is a specific biotransformation from educt compounds to
|
|
product compounds.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/reactions"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'pathway' %}
|
|
<p>
|
|
A pathway displays the (predicted) biodegradation of a compound as
|
|
graph.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/pathways"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'node' %}
|
|
<p>
|
|
Nodes represent the (predicted) compounds in a graph.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/nodes"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'edge' %}
|
|
<p>
|
|
Edges represent the links between Nodes in a graph
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/edges"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'scenario' %}
|
|
<p>
|
|
A scenario contains meta-information that can be attached to other
|
|
data (compounds, rules, ..).
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/scenarios"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'model' %}
|
|
<p>
|
|
A model applies machine learning to limit the combinatorial
|
|
explosion.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/relative_reasoning"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'setting' %}
|
|
<p>
|
|
A setting includes configuration parameters for pathway
|
|
predictions.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/settings"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'user' %}
|
|
<p>
|
|
Register now to create own packages and to submit and manage your
|
|
data.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/users"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% elif object_type == 'group' %}
|
|
<p>
|
|
Users can team up in groups to share packages.
|
|
<a
|
|
target="_blank"
|
|
href="https://wiki.envipath.org/index.php/groups"
|
|
class="link link-primary"
|
|
>Learn more >></a
|
|
>
|
|
</p>
|
|
{% endif %}
|
|
|
|
<!-- If theres nothing to show extend the text above -->
|
|
{% if reviewed_objects and unreviewed_objects %}
|
|
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
|
|
<p class="mt-4">
|
|
Nothing found. There are two possible reasons: <br /><br />1.
|
|
There is no content yet.<br />2. You have no reading
|
|
permissions.<br /><br />Please be sure you have at least reading
|
|
permissions.
|
|
</p>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lists Container - Full Width with Reviewed on Right -->
|
|
<div class="w-full">
|
|
{% if reviewed_objects %}
|
|
{% if reviewed_objects|length > 0 %}
|
|
<!-- Reviewed -->
|
|
<div
|
|
class="collapse-arrow bg-base-200 collapse order-2 w-full"
|
|
x-data="paginatedList(window.reviewedObjects || [], { isReviewed: true, instanceId: 'reviewed' })"
|
|
>
|
|
<input type="checkbox" checked />
|
|
<div class="collapse-title text-xl font-medium">
|
|
Reviewed
|
|
<span
|
|
class="badge badge-sm badge-neutral ml-2"
|
|
x-text="totalItems"
|
|
></span>
|
|
</div>
|
|
<div class="collapse-content w-full">
|
|
<ul class="menu bg-base-100 rounded-box w-full">
|
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
|
<li>
|
|
<a :href="obj.url" class="hover:bg-base-200">
|
|
<span x-text="obj.name"></span>
|
|
<span
|
|
class="tooltip tooltip-left ml-auto"
|
|
data-tip="Reviewed"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
class="lucide lucide-star"
|
|
>
|
|
<polygon
|
|
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
<!-- Pagination Controls -->
|
|
<div
|
|
x-show="totalPages > 1"
|
|
class="mt-4 flex items-center justify-between px-2"
|
|
>
|
|
<span class="text-base-content/70 text-sm">
|
|
Showing <span x-text="showingStart"></span>-<span
|
|
x-text="showingEnd"
|
|
></span>
|
|
of <span x-text="totalItems"></span>
|
|
</span>
|
|
<div class="join">
|
|
<button
|
|
class="join-item btn btn-sm"
|
|
:disabled="currentPage === 1"
|
|
@click="prevPage()"
|
|
>
|
|
«
|
|
</button>
|
|
<template x-for="item in pageNumbers" :key="item.key">
|
|
<button
|
|
class="join-item btn btn-sm"
|
|
:class="{ 'btn-active': item.page === currentPage }"
|
|
:disabled="item.isEllipsis"
|
|
@click="!item.isEllipsis && goToPage(item.page)"
|
|
x-text="item.page"
|
|
></button>
|
|
</template>
|
|
<button
|
|
class="join-item btn btn-sm"
|
|
:disabled="currentPage === totalPages"
|
|
@click="nextPage()"
|
|
>
|
|
»
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
{% if unreviewed_objects %}
|
|
<!-- Unreviewed -->
|
|
<div
|
|
class="collapse-arrow bg-base-200 collapse order-1 w-full"
|
|
x-data="paginatedList(window.unreviewedObjects || [], { isReviewed: false, instanceId: 'unreviewed' })"
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
{% if reviewed_objects|length == 0 or object_type == 'package' %}checked{% endif %}
|
|
/>
|
|
<div class="collapse-title text-xl font-medium">
|
|
Unreviewed
|
|
<span
|
|
class="badge badge-sm badge-neutral ml-2"
|
|
x-text="totalItems"
|
|
></span>
|
|
</div>
|
|
<div class="collapse-content w-full">
|
|
<ul class="menu bg-base-100 rounded-box w-full">
|
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
|
<li>
|
|
<a
|
|
:href="obj.url"
|
|
class="hover:bg-base-200"
|
|
x-text="obj.name"
|
|
></a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
<!-- Pagination Controls -->
|
|
<div
|
|
x-show="totalPages > 1"
|
|
class="mt-4 flex items-center justify-between px-2"
|
|
>
|
|
<span class="text-base-content/70 text-sm">
|
|
Showing <span x-text="showingStart"></span>-<span
|
|
x-text="showingEnd"
|
|
></span>
|
|
of <span x-text="totalItems"></span>
|
|
</span>
|
|
<div class="join">
|
|
<button
|
|
class="join-item btn btn-sm"
|
|
:disabled="currentPage === 1"
|
|
@click="prevPage()"
|
|
>
|
|
«
|
|
</button>
|
|
<template x-for="item in pageNumbers" :key="item.key">
|
|
<button
|
|
class="join-item btn btn-sm"
|
|
:class="{ 'btn-active': item.page === currentPage }"
|
|
:disabled="item.isEllipsis"
|
|
@click="!item.isEllipsis && goToPage(item.page)"
|
|
x-text="item.page"
|
|
></button>
|
|
</template>
|
|
<button
|
|
class="join-item btn btn-sm"
|
|
:disabled="currentPage === totalPages"
|
|
@click="nextPage()"
|
|
>
|
|
»
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if objects %}
|
|
<!-- Unreviewable objects such as User / Group / Setting -->
|
|
<div class="card bg-base-100">
|
|
<div class="card-body">
|
|
<ul class="menu bg-base-200 rounded-box">
|
|
{% for obj in objects %}
|
|
{% if object_type == 'user' %}
|
|
<li>
|
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
|
>{{ obj.username }}</a
|
|
>
|
|
</li>
|
|
{% else %}
|
|
<li>
|
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
|
>{{ obj.name }}</a
|
|
>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
// Show actions button if there are actions
|
|
const actionsButton = document.getElementById("actionsButton");
|
|
const actionsList = actionsButton?.querySelector("ul");
|
|
if (actionsList && actionsList.children.length > 0) {
|
|
actionsButton?.classList.remove("hidden");
|
|
}
|
|
|
|
// Show search input and connect to Alpine pagination
|
|
const objectSearch = document.getElementById("object-search");
|
|
if (objectSearch) {
|
|
objectSearch.classList.remove("hidden");
|
|
objectSearch.addEventListener("input", function () {
|
|
const query = this.value;
|
|
// Dispatch search to all paginatedList components
|
|
document
|
|
.querySelectorAll('[x-data*="paginatedList"]')
|
|
.forEach((el) => {
|
|
if (el._x_dataStack && el._x_dataStack[0]) {
|
|
el._x_dataStack[0].search(query);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Delete form submit handler
|
|
const deleteSubmit = document.getElementById("modal-form-delete-submit");
|
|
const deleteForm = document.getElementById("modal-form-delete");
|
|
if (deleteSubmit && deleteForm) {
|
|
deleteSubmit.addEventListener("click", function (e) {
|
|
e.preventDefault();
|
|
deleteForm.submit();
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock content %}
|