[Feature] Add Predict Pathway Page in Modern UI (#188)

## Major Changes
- Predict Pathway is now a separate view as it is meant as adavanced prediction interface

## Current status
![image.png](/attachments/c5bc3f5c-cf30-4b5f-acb3-a70117a96dae)

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#188
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2025-11-12 02:38:57 +13:00
committed by jebus
parent 2b79adc2f7
commit c1553d9cd4
9 changed files with 939 additions and 530 deletions

View File

@ -48,6 +48,7 @@ urlpatterns = [
re_path(r"^user$", v.users, name="users"), re_path(r"^user$", v.users, name="users"),
re_path(r"^group$", v.groups, name="groups"), re_path(r"^group$", v.groups, name="groups"),
re_path(r"^search$", v.search, name="search"), re_path(r"^search$", v.search, name="search"),
re_path(r"^predict$", v.predict_pathway, name="predict_pathway"),
# User Detail # User Detail
re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"), re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"),
# Group Detail # Group Detail

View File

@ -362,6 +362,18 @@ def index(request):
return render(request, "index/index.html", context) return render(request, "index/index.html", context)
def predict_pathway(request):
"""Top-level predict pathway view using user's default package."""
if request.method != "GET":
return HttpResponseNotAllowed(["GET"])
context = get_base_context(request)
context["title"] = "enviPath - Predict Pathway"
context["meta"]["current_package"] = context["meta"]["user"].default_package
return render(request, "predict_pathway.html", context)
def packages(request): def packages(request):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -1301,7 +1313,7 @@ def package_compound_structures(request, package_uuid, compound_uuid):
elif request.method == "POST": elif request.method == "POST":
structure_name = request.POST.get("structure-name") structure_name = request.POST.get("structure-name")
structure_smiles = request.POST.get("structure-smiles").strip() structure_smiles = request.POST.get("structure-smiles")
structure_description = request.POST.get("structure-description") structure_description = request.POST.get("structure-description")
try: try:
@ -1483,11 +1495,11 @@ def package_rules(request, package_uuid):
# Obtain parameters as required by rule type # Obtain parameters as required by rule type
if rule_type == "SimpleAmbitRule": if rule_type == "SimpleAmbitRule":
params["smirks"] = request.POST.get("rule-smirks").strip() params["smirks"] = request.POST.get("rule-smirks")
params["reactant_filter_smarts"] = request.POST.get("rule-reactant-smarts") params["reactant_filter_smarts"] = request.POST.get("rule-reactant-smarts")
params["product_filter_smarts"] = request.POST.get("rule-product-smarts") params["product_filter_smarts"] = request.POST.get("rule-product-smarts")
elif rule_type == "SimpleRDKitRule": elif rule_type == "SimpleRDKitRule":
params["reaction_smarts"] = request.POST.get("rule-reaction-smarts").strip() params["reaction_smarts"] = request.POST.get("rule-reaction-smarts")
elif rule_type == "ParallelRule": elif rule_type == "ParallelRule":
pass pass
elif rule_type == "SequentialRule": elif rule_type == "SequentialRule":
@ -1684,7 +1696,7 @@ def package_reactions(request, package_uuid):
elif request.method == "POST": elif request.method == "POST":
reaction_name = request.POST.get("reaction-name") reaction_name = request.POST.get("reaction-name")
reaction_description = request.POST.get("reaction-description") reaction_description = request.POST.get("reaction-description")
reactions_smirks = request.POST.get("reaction-smirks").strip() reactions_smirks = request.POST.get("reaction-smirks")
educts = reactions_smirks.split(">>")[0].split(".") educts = reactions_smirks.split(">>")[0].split(".")
products = reactions_smirks.split(">>")[1].split(".") products = reactions_smirks.split(">>")[1].split(".")

View File

@ -1,6 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#predict_modal"> <a href="{{ meta.server_url }}/predict">
<span class="glyphicon glyphicon-plus"></span> New Pathway</a> <span class="glyphicon glyphicon-plus"></span> New Pathway</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,11 +1,15 @@
<!DOCTYPE html> <!doctype html>
<html data-theme="envipath"> <html data-theme="envipath">
{% load static %} {% load static %}
<head> <head>
<title>{{ title }}</title> <title>{{ title }}</title>
<meta name="csrf-token" content="{{ csrf_token }}"> <meta name="csrf-token" content="{{ csrf_token }}" />
{# Favicon #} {# Favicon #}
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/> <link
rel="shortcut icon"
type="image/png"
href="{% static 'images/favicon.ico' %}"
/>
{# Tailwind CSS disabled for legacy Bootstrap framework #} {# Tailwind CSS disabled for legacy Bootstrap framework #}
{# Pages using this framework will be migrated to framework_modern.html incrementally #} {# Pages using this framework will be migrated to framework_modern.html incrementally #}
@ -13,12 +17,20 @@
{# Legacy Bootstrap 3.3.7 - scoped to .legacy-bootstrap #} {# Legacy Bootstrap 3.3.7 - scoped to .legacy-bootstrap #}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
/>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
<link href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet"> <link
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css"> rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/js/bootstrap-select.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/js/bootstrap-select.min.js"></script>
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script> <script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
<!-- CDN END --> <!-- CDN END -->
@ -26,14 +38,15 @@
{# Bootstrap compatibility styles #} {# Bootstrap compatibility styles #}
<style> <style>
/* Ensure proper viewport behavior */ /* Ensure proper viewport behavior */
html, body { html,
body {
height: 100%; /* ensure body fills viewport */ height: 100%; /* ensure body fills viewport */
overflow-x: hidden; /* prevent horizontal scroll */ overflow-x: hidden; /* prevent horizontal scroll */
} }
</style> </style>
<script> <script>
const csrftoken = document.querySelector('[name=csrf-token]').content; const csrftoken = document.querySelector("[name=csrf-token]").content;
// Setup CSRF header for all jQuery AJAX requests // Setup CSRF header for all jQuery AJAX requests
$.ajaxSetup({ $.ajaxSetup({
@ -41,7 +54,7 @@
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken); xhr.setRequestHeader("X-CSRFToken", csrftoken);
} }
} },
}); });
</script> </script>
@ -50,7 +63,6 @@
<!-- {# EP CSS #}--> <!-- {# EP CSS #}-->
<!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>--> <!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>-->
{# General EP JS #} {# General EP JS #}
<script src="{% static 'js/pps.js' %}"></script> <script src="{% static 'js/pps.js' %}"></script>
{# Modal Steps for Stepwise Modal Wizards #} {# Modal Steps for Stepwise Modal Wizards #}
@ -59,28 +71,33 @@
{% if not debug %} {% if not debug %}
<!-- Matomo --> <!-- Matomo -->
<script> <script>
var _paq = window._paq = window._paq || []; var _paq = (window._paq = window._paq || []);
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']); _paq.push(["trackPageView"]);
_paq.push(['enableLinkTracking']); _paq.push(["enableLinkTracking"]);
(function () { (function () {
var u = "//matomo.envipath.com/"; var u = "//matomo.envipath.com/";
_paq.push(['setTrackerUrl', u + 'matomo.php']); _paq.push(["setTrackerUrl", u + "matomo.php"]);
_paq.push(['setSiteId', '{{ meta.site_id }}']); _paq.push(["setSiteId", "{{ meta.site_id }}"]);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0]; var d = document,
g = d.createElement("script"),
s = d.getElementsByTagName("script")[0];
g.async = true; g.async = true;
g.src = u + 'matomo.js'; g.src = u + "matomo.js";
s.parentNode.insertBefore(g, s); s.parentNode.insertBefore(g, s);
})(); })();
</script> </script>
<!-- End Matomo Code --> <!-- End Matomo Code -->
{% endif %} {% endif %}
</head> </head>
<body> <body>
<!-- Legacy Bootstrap navbar - isolated from Tailwind --> <!-- Legacy Bootstrap navbar - isolated from Tailwind -->
<div class="legacy-bootstrap"> <div class="legacy-bootstrap">
<nav class="navbar navbar-default navbar-inverse" style="border-radius:0px;" role="navigation"> <nav
class="navbar navbar-default navbar-inverse"
style="border-radius:0px;"
role="navigation"
>
<div class="container-fluid"> <div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display --> <!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header navbar-header-framework"> <div class="navbar-header navbar-header-framework">
@ -91,19 +108,28 @@
<!-- <span class="icon-bar"></span>--> <!-- <span class="icon-bar"></span>-->
<!-- <span class="icon-bar"></span>--> <!-- <span class="icon-bar"></span>-->
<!-- </button>--> <!-- </button>-->
<a id="pictureLink" href="{{ meta.server_url }}" class="navbar-brand"> <a
<img id="image-logo-short-white.svg" src='{% static "/images/logo-short-white.svg" %}' width="100" id="pictureLink"
alt="enviPath"> href="{{ meta.server_url }}"
class="navbar-brand"
>
<img
id="image-logo-short-white.svg"
src="{% static "/images/logo-short-white.svg" %}"
width="100"
alt="enviPath"
/>
</a> </a>
</div> </div>
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse"> <div
class="collapse navbar-collapse collapse-framework navbar-collapse-framework"
id="navbarCollapse"
>
<ul class="nav navbar-nav navbar-nav-framework"> <ul class="nav navbar-nav navbar-nav-framework">
<li> <li>
<a href="#" data-toggle="modal" data-target="#predict_modal"> <a href="{{ meta.server_url }}/predict"> Predict Pathway </a>
Predict Pathway
</a>
</li> </li>
{# <li class="dropdown">#} {# <li class="dropdown">#}
{# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#} {# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
@ -120,66 +146,148 @@
{# </li>#} {# </li>#}
{# </ul>#} {# </ul>#}
{# </li>#} {# </li>#}
<li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> <li>
<li><a href="{{ meta.server_url }}/search" id="searchLink">Search</a></li> <a href="{{ meta.server_url }}/package" id="packageLink"
<li><a href="{{ meta.server_url }}/model" id="modelLink">Modelling</a></li> >Package</a
>
</li>
<li>
<a href="{{ meta.server_url }}/search" id="searchLink"
>Search</a
>
</li>
<li>
<a href="{{ meta.server_url }}/model" id="modelLink"
>Modelling</a
>
</li>
<li class="dropdown"> <li class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Browse Data<b class="caret"></b></a> <a data-toggle="dropdown" class="dropdown-toggle" href="#"
>Browse Data<b class="caret"></b
></a>
<ul role="menu" class="dropdown-menu"> <ul role="menu" class="dropdown-menu">
<li><a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a></li> <li>
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li> <a href="{{ meta.server_url }}/pathway" id="pathwayLink"
<li><a href="{{ meta.server_url }}/compound" id="compoundLink">Compound</a></li> >Pathway</a
<li><a href="{{ meta.server_url }}/reaction" id="reactionLink">Reaction</a></li> >
<li><a href="{{ meta.server_url }}/model" id="relative-reasoningLink">Model</a></li> </li>
<li><a href="{{ meta.server_url }}/scenario" id="scenarioLink">Scenario</a></li> <li>
<a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a>
</li>
<li>
<a href="{{ meta.server_url }}/compound" id="compoundLink"
>Compound</a
>
</li>
<li>
<a href="{{ meta.server_url }}/reaction" id="reactionLink"
>Reaction</a
>
</li>
<li>
<a
href="{{ meta.server_url }}/model"
id="relative-reasoningLink"
>Model</a
>
</li>
<li>
<a href="{{ meta.server_url }}/scenario" id="scenarioLink"
>Scenario</a
>
</li>
{# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#} {# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#}
{# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#} {# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#}
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#} {# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
</ul> </ul>
</li> </li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework"> <ul
<li><a href="https://community.envipath.org/" id="communityLink">Community</a></li> class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework"
>
<li>
<a href="https://community.envipath.org/" id="communityLink"
>Community</a
>
</li>
<li class="dropdown"> <li class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a> <a data-toggle="dropdown" class="dropdown-toggle" href="#"
>Info <b class="caret"></b
></a>
<ul role="menu" class="dropdown-menu"> <ul role="menu" class="dropdown-menu">
<!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>--> <!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>-->
<li><a href="https://community.envipath.org/t/envipath-license/109" id="licenceLink">Licences</a></li> <li>
<li class="divider"></li> <a
<li><a target="_blank" href="https://wiki.envipath.org/" id="wikiLink">Documentation Wiki</a> href="https://community.envipath.org/t/envipath-license/109"
id="licenceLink"
>Licences</a
>
</li>
<li class="divider"></li>
<li>
<a
target="_blank"
href="https://wiki.envipath.org/"
id="wikiLink"
>Documentation Wiki</a
>
</li>
<li>
<a
href="#"
id="citeButton"
data-toggle="modal"
data-target="#citemodal"
>How to cite enviPath</a
>
</li> </li>
<li><a href="#" id="citeButton" data-toggle="modal" data-target="#citemodal">How to cite
enviPath</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a>Version: {{ meta.version }}</a></li> <li><a>Version: {{ meta.version }}</a></li>
</ul> </ul>
</li> </li>
{% if meta.user.username == 'anonymous' %} {% if meta.user.username == 'anonymous' %}
<li> <li>
<a href="{% url 'login' %}" id="loginButton" style="margin-right:10px">Login</a> <a
href="{% url 'login' %}"
id="loginButton"
style="margin-right:10px"
>Login</a
>
</li> </li>
{% else %} {% else %}
<li class="dropdown"> <li class="dropdown">
<a data-toggle="dropdown" id="loggedInButton" class="dropdown-toggle" id="logedInButton" <a
href="#"> data-toggle="dropdown"
id="loggedInButton"
class="dropdown-toggle"
id="logedInButton"
href="#"
>
<div id="username"> <div id="username">
{{ user.username }}<b class="caret"></b> {{ user.username }}<b class="caret"></b>
</div> </div>
</a> </a>
<ul role="menu" class="dropdown-menu"> <ul role="menu" class="dropdown-menu">
<li> <li>
<a href="{{ meta.user.url }}" id="accountbutton">My Account</a> <a href="{{ meta.user.url }}" id="accountbutton"
>My Account</a
>
</li> </li>
<li class="divider"></li> <li class="divider"></li>
<form class="navbar-form navbar-left navbar-left-framework" role="logout" <form
action="{% url 'logout' %}" method="post"> class="navbar-form navbar-left navbar-left-framework"
role="logout"
action="{% url 'logout' %}"
method="post"
>
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<input type="hidden" name="logout" value="true"> <input type="hidden" name="logout" value="true" />
</div> </div>
<button type="submit" class="btn btn-default">Logout</button> <button type="submit" class="btn btn-default">
Logout
</button>
</form> </form>
</ul> </ul>
</li> </li>
@ -210,24 +318,30 @@
</div> </div>
{% endif %} {% endif %}
{% if message %} {% if message %}
<div id="message"> <div id="message">{{ message }}</div>
{{ message }}
</div>
{% endif %} {% endif %}
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
{% if meta.url_contains_package and meta.current_package.license %} {% if meta.url_contains_package and meta.current_package.license %}
<p></p> <p></p>
<div class="panel-group" id="license_accordion"> <div class="panel-group" id="license_accordion">
<div class="panel panel-default list-group-item" style="background-color:#f5f5f5"> <div
class="panel panel-default list-group-item"
style="background-color:#f5f5f5"
>
<div class="panel-title"> <div class="panel-title">
<a data-toggle="collapse" data-parent="#licence_accordion" href="#license">License</a> <a
data-toggle="collapse"
data-parent="#licence_accordion"
href="#license"
>License</a
>
</div> </div>
</div> </div>
<div id="license" class="panel-collapse collapse in"> <div id="license" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
<a target="_blank" href="{{ meta.current_package.license.link }}"> <a target="_blank" href="{{ meta.current_package.license.link }}">
<img src="{{ meta.current_package.license.image_link }}"> <img src="{{ meta.current_package.license.image_link }}" />
</a> </a>
</div> </div>
</div> </div>
@ -244,19 +358,32 @@
<ul class="nav nav-pills nav-justified"> <ul class="nav nav-pills nav-justified">
<li> <li>
<a href="http://ml.auckland.ac.nz" target="_blank"> <a href="http://ml.auckland.ac.nz" target="_blank">
<img id="image-uoalogo" height="60" src='{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}' <img
alt="The Univserity of Auckland"/> id="image-uoalogo"
height="60"
src="{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}"
alt="The Univserity of Auckland"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="https://eawag.ch" target="_blank"> <a href="https://eawag.ch" target="_blank">
<img id="image-ealogo" height="60" src='{% static "/images/ealogo.gif" %}' alt="Eawag"/> <img
id="image-ealogo"
height="60"
src="{% static "/images/ealogo.gif" %}"
alt="Eawag"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="https://www.uzh.ch/" target="_blank"> <a href="https://www.uzh.ch/" target="_blank">
<img id="image-ufzlogo" height="60" src='{% static "/images/uzh-logo.svg" %}' <img
alt="University of Zurich"/> id="image-ufzlogo"
height="60"
src="{% static "/images/uzh-logo.svg" %}"
alt="University of Zurich"
/>
</a> </a>
</li> </li>
</ul> </ul>
@ -267,7 +394,9 @@
<div class="col-lg-12"> <div class="col-lg-12">
<ul class="nav nav-pills nav-justified"> <ul class="nav nav-pills nav-justified">
<!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>--> <!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>-->
<li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li> <li>
<a href="mailto:admin@envipath.org" target="_blank">Contact</a>
</li>
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschr&auml;nkt) &amp; Co. KG &copy;--> <!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschr&auml;nkt) &amp; Co. KG &copy;-->
<!-- {{ YEAR }}</a></li>--> <!-- {{ YEAR }}</a></li>-->
</ul> </ul>
@ -279,8 +408,8 @@
<script> <script>
$(function () { $(function () {
// Hide actionsbutton if theres no action defined // Hide actionsbutton if theres no action defined
if ($('#actionsButton ul').children().length > 0) { if ($("#actionsButton ul").children().length > 0) {
$('#actionsButton').show(); $("#actionsButton").show();
} }
}); });
</script> </script>

View File

@ -72,7 +72,7 @@
<!-- End Matomo Code --> <!-- End Matomo Code -->
{% endif %} {% endif %}
</head> </head>
<body class="min-h-screen bg-base-300"> <body class="bg-base-300 min-h-screen">
{% include "includes/navbar.html" %} {% include "includes/navbar.html" %}
{# Main Content Area #} {# Main Content Area #}
@ -81,7 +81,7 @@
{# Breadcrumbs - outside main content, optional #} {# Breadcrumbs - outside main content, optional #}
{% if breadcrumbs %} {% if breadcrumbs %}
<div id="bread" class="max-w-7xl mx-auto px-4 py-4"> <div id="bread" class="max-w-7xl mx-auto px-4 py-4">
<div class="text-sm breadcrumbs"> <div class="breadcrumbs text-sm">
<ul> <ul>
{% for elem in breadcrumbs %} {% for elem in breadcrumbs %}
{% for name, url in elem.items %} {% for name, url in elem.items %}
@ -100,7 +100,7 @@
{# Main content container - paper effect on medium+ screens #} {# Main content container - paper effect on medium+ screens #}
<div <div
id="docContent" id="docContent"
class="w-full xl:w-xl md:mx-auto md:my-8 bg-base-100 md:shadow-2xl md:rounded-lg border-2" class="bg-base-100 mx-auto md:my-8 md:max-w-6xl md:rounded-lg md:shadow-xl"
> >
{# Messages - inside paper #} {# Messages - inside paper #}
{% if message %} {% if message %}
@ -141,7 +141,7 @@
<a <a
href="https://community.envipath.org/" href="https://community.envipath.org/"
target="_blank" target="_blank"
class="flex items-center justify-center btn btn-secondary hover:btn-secondary-focus text-secondary-content text-sm shadow-lg transition-all duration-300 hover:scale-105 hover:-translate-x-1" class="btn btn-secondary hover:btn-secondary-focus text-secondary-content flex items-center justify-center text-sm shadow-lg transition-all duration-300 hover:-translate-x-1 hover:scale-105"
title="Get Help from the Community" title="Get Help from the Community"
> >
<svg <svg

View File

@ -11,7 +11,11 @@
{% if not public_mode %} {% if not public_mode %}
<div class="navbar-center hidden lg:flex"> <div class="navbar-center hidden lg:flex">
<a href="#" role="button" class="btn btn-ghost" id="predictLink" <a
href="{{ meta.server_url }}/predict"
role="button"
class="btn btn-ghost"
id="predictLink"
>Predict</a >Predict</a
> >
<!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> --> <!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> -->
@ -53,11 +57,7 @@
<div class="navbar-end"> <div class="navbar-end">
{% if not public_mode %} {% if not public_mode %}
<button <a href="/search" role="button">
type="button"
onclick="search_modal.showModal()"
class="btn btn-ghost"
>
<div <div
class="flex items-center badge badge-dash space-x-1 bg-base-200 text-base-content/50 p-2 m-1" class="flex items-center badge badge-dash space-x-1 bg-base-200 text-base-content/50 p-2 m-1"
> >
@ -78,7 +78,7 @@
</svg> </svg>
<span id="search-shortcut">⌘K</span> <span id="search-shortcut">⌘K</span>
</div> </div>
</button> </a>
{% endif %} {% endif %}
{% if meta.user.username == 'anonymous' or public_mode %} {% if meta.user.username == 'anonymous' or public_mode %}
<a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a> <a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a>

View File

@ -1,26 +1,30 @@
{% extends "framework_modern.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% block main_content %} {% block main_content %}
<!-- Hero Section with Logo and Search --> <!-- Hero Section with Logo and Search -->
<section class="hero h-fit max-w-5xl w-full shadow-none mx-auto relative"> <section class="hero h-fit max-w-5xl w-full shadow-none mx-auto relative">
<div
<div class="hero min-h-[calc(100vh*0.4)] bg-gradient-to-br from-primary-800 to-primary-600" class="hero min-h-[calc(100vh*0.4)] bg-gradient-to-br from-primary-800 to-primary-600"
style="background-image: url('{% static "/images/hero.png" %}'); background-size: cover; background-position: center;" style="background-image: url('{% static "/images/hero.png" %}'); background-size: cover; background-position: center;"
> >
<div class="hero-overlay"></div> <div class="hero-overlay"></div>
<!-- Predict Pathway text over the image --> <!-- Predict Pathway text over the image -->
<div class="absolute bottom-40 left-1/8 -translate-x-8 z-10"> <div class="absolute bottom-40 left-1/8 -translate-x-8 z-10">
<h2 class="text-3xl text-base-100 text-shadow-lg text-left">Predict Your Pathway</h2> <h2 class="text-3xl text-base-100 text-shadow-lg text-left">
Predict Your Pathway
</h2>
</div> </div>
</div> </div>
</section> </section>
<div class="shadow-md max-w-5xl mx-auto bg-base-200"> <div class="shadow-md max-w-5xl mx-auto bg-base-200">
<!-- Predict Pathway Section --> <!-- Predict Pathway Section -->
<div class="flex-col lg:flex-row-reverse w-full mx-auto -mt-32 relative z-20 mb-10 "> <div
<div class="card bg-base-100 shrink-0 shadow-xl w-3/4 mx-auto transition-all duration-300 ease-in-out"> class="flex-col lg:flex-row-reverse w-full mx-auto -mt-32 relative z-20 mb-10 "
>
<div
class="card bg-base-100 shrink-0 shadow-xl w-3/4 mx-auto transition-all duration-300 ease-in-out"
>
<div class="card-body"> <div class="card-body">
<!-- Input Mode Toggle - Fixed position outside fieldset --> <!-- Input Mode Toggle - Fixed position outside fieldset -->
<div class="flex flex-row justify-start items-center h-fit ml-8 my-4"> <div class="flex flex-row justify-start items-center h-fit ml-8 my-4">
@ -28,7 +32,12 @@
<!-- <span class="text-sm text-neutral-500">Input Mode:</span> --> <!-- <span class="text-sm text-neutral-500">Input Mode:</span> -->
<label class="toggle text-base-content toggle-md"> <label class="toggle text-base-content toggle-md">
<input type="checkbox" /> <input type="checkbox" />
<svg aria-label="smiles mode" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class="size-5"> <svg
aria-label="smiles mode"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
class="size-5"
>
<g <g
stroke-linejoin="round" stroke-linejoin="round"
stroke-linecap="round" stroke-linecap="round"
@ -36,7 +45,11 @@
fill="currentColor" fill="currentColor"
stroke="none" stroke="none"
> >
<path fill-rule="evenodd" d="M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75Z" clip-rule="evenodd" /> <path
fill-rule="evenodd"
d="M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75Z"
clip-rule="evenodd"
/>
</g> </g>
</svg> </svg>
<svg <svg
@ -47,49 +60,95 @@
stroke="none" stroke="none"
class="size-5" class="size-5"
> >
<path d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z" /> <path
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
/>
</svg> </svg>
</label> </label>
</div> </div>
</div> </div>
<fieldset class="fieldset transition-all duration-300 ease-in-out overflow-hidden"> <fieldset
<form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST"> class="fieldset transition-all duration-300 ease-in-out overflow-hidden"
>
<form
id="index-form"
action="{{ meta.current_package.url }}/pathway"
method="POST"
>
{% csrf_token %} {% csrf_token %}
<div id="text-input-container" class="transition-all duration-300 ease-in-out opacity-100 transform scale-100"> <div
id="text-input-container"
class="transition-all duration-300 ease-in-out opacity-100 transform scale-100"
>
<div class="join w-full mx-auto"> <div class="join w-full mx-auto">
<input type="text" id="index-form-text-input" placeholder="canonical SMILES string" class="input grow input-md join-item" /> <input
type="text"
id="index-form-text-input"
placeholder="canonical SMILES string"
class="input grow input-md join-item"
/>
<button class="btn btn-neutral join-item">Predict!</button> <button class="btn btn-neutral join-item">Predict!</button>
</div> </div>
<div class="label relative w-full mt-1"> <div class="label relative w-full mt-1">
<div class="flex gap-2"> <div class="flex gap-2">
<a href="#" class="example-link cursor-pointer hover:text-primary" <a
href="#"
class="example-link cursor-pointer hover:text-primary"
data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C" data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
title="load example">Caffeine</a> title="load example"
<a href="#" class="example-link cursor-pointer hover:text-primary" >Caffeine</a
>
<a
href="#"
class="example-link cursor-pointer hover:text-primary"
data-smiles="CC(C)CC1=CC=C(C=C1)C(C)C(=O)O" data-smiles="CC(C)CC1=CC=C(C=C1)C(C)C(=O)O"
title="load example">Ibuprofen</a> title="load example"
>Ibuprofen</a
>
</div> </div>
<a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#">Advanced</a> <a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#"
>Advanced</a
>
</div> </div>
</div> </div>
<div id="ketcher-container" class="hidden w-full transition-all duration-300 ease-in-out opacity-0 transform scale-95"> <div
<iframe id="index-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" id="ketcher-container"
width="100%" height="400" class="rounded-lg"></iframe> class="hidden w-full transition-all duration-300 ease-in-out opacity-0 transform scale-95"
<button class="btn btn-lg bg-primary-950 text-primary-50 join-item w-full mt-2">Predict!</button> >
<iframe
id="index-ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="400"
class="rounded-lg"
></iframe>
<button
class="btn btn-lg bg-primary-950 text-primary-50 join-item w-full mt-2"
>
Predict!
</button>
<a class="label mx-auto w-full mt-1" href="#">Advanced</a> <a class="label mx-auto w-full mt-1" href="#">Advanced</a>
</div> </div>
<input type="hidden" id="index-form-smiles" name="smiles" value="smiles"> <input
<input type="hidden" id="index-form-predict" name="predict" value="predict"> type="hidden"
<input type="hidden" id="current-action" value="predict"> id="index-form-smiles"
name="smiles"
value="smiles"
/>
<input
type="hidden"
id="index-form-predict"
name="predict"
value="predict"
/>
<input type="hidden" id="current-action" value="predict" />
</form> </form>
</fieldset> </fieldset>
</div> </div>
</div> </div>
</div> </div>
<!-- Community News Section --> <!-- Community News Section -->
<section class="py-16 bg-base-200 z-10 mx-8"> <section class="py-16 bg-base-200 z-10 mx-8">
<div class="max-w-7xl mx-auto px-4"> <div class="max-w-7xl mx-auto px-4">
@ -103,7 +162,11 @@
</div> </div>
<div class="text-right mt-6"> <div class="text-right mt-6">
<a href="https://community.envipath.org/c/announcements/10" target="_blank" class="btn btn-ghost btn-sm"> <a
href="https://community.envipath.org/c/announcements/10"
target="_blank"
class="btn btn-ghost btn-sm"
>
Read More Announcements Read More Announcements
</a> </a>
</div> </div>
@ -118,7 +181,11 @@
<div class="mx-auto px-8 md:px-12"> <div class="mx-auto px-8 md:px-12">
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<div class="w-1/3"> <div class="w-1/3">
<img src="{% static "/images/ep-rule-artwork.png" %}" alt="rule-based iterative tree greneration" class="w-full h-full object-contain" /> <img
src="{% static "/images/ep-rule-artwork.png" %}"
alt="rule-based iterative tree greneration"
class="w-full h-full object-contain"
/>
</div> </div>
<div class="space-y-4 text-left w-2/3 mr-8"> <div class="space-y-4 text-left w-2/3 mr-8">
<h2 class="h2 font-bold mb-8">About enviPath</h2> <h2 class="h2 font-bold mb-8">About enviPath</h2>
@ -129,9 +196,10 @@
observed biotransformation pathways. observed biotransformation pathways.
</p> </p>
<p class=""> <p class="">
The pathway prediction system provides different relative reasoning models The pathway prediction system provides different relative
to predict likely biotransformation pathways and products. Explore our tools reasoning models to predict likely biotransformation pathways and
and contribute to advancing environmental biotransformation research. products. Explore our tools and contribute to advancing
environmental biotransformation research.
</p> </p>
<div class="flex flex-row gap-4 float-right"> <div class="flex flex-row gap-4 float-right">
<a href="/about" class="btn btn-ghost-neutral">Read More</a> <a href="/about" class="btn btn-ghost-neutral">Read More</a>
@ -139,25 +207,36 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<!-- Partners Section --> <!-- Partners Section -->
<section class="py-14 sm:py-12 bg-base-100"> <section class="py-14 sm:py-12 bg-base-100">
<div class="mx-auto px-6 lg:px-8"> <div class="mx-auto px-6 lg:px-8">
<div class="divider"><h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2></div> <div class="divider">
<div class="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-3"> <h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2>
<img src="{% static "/images/uoa-logo-small.png" %}" alt="The University of Auckland" class=" max-h-20 w-full object-contain lg:col-span-1" /> </div>
<img src="{% static "/images/logo-eawag.svg" %}" alt="Eawag" class=" max-h-12 w-full object-contain lg:col-span-1" /> <div
<img src="{% static "/images/uzh-logo.svg" %}" alt="University of Zurich" class="2 max-h-16 w-full object-contain lg:col-span-1" /> class="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-3"
>
<img
src="{% static "/images/uoa-logo-small.png" %}"
alt="The University of Auckland"
class=" max-h-20 w-full object-contain lg:col-span-1"
/>
<img
src="{% static "/images/logo-eawag.svg" %}"
alt="Eawag"
class=" max-h-12 w-full object-contain lg:col-span-1"
/>
<img
src="{% static "/images/uzh-logo.svg" %}"
alt="University of Zurich"
class="2 max-h-16 w-full object-contain lg:col-span-1"
/>
</div> </div>
</div> </div>
</section> </section>
</div> </div>
<script language="javascript"> <script language="javascript">
@ -167,16 +246,16 @@
// Function to render Discourse topics into cards // Function to render Discourse topics into cards
function renderDiscourseTopics(topics) { function renderDiscourseTopics(topics) {
const container = document.getElementById('community-news-container'); const container = document.getElementById("community-news-container");
if (!container) return; if (!container) return;
// Clear container // Clear container
container.innerHTML = ''; container.innerHTML = "";
// Create cards for each topic // Create cards for each topic
topics.forEach(topic => { topics.forEach((topic) => {
const card = createDiscourseCard(topic); const card = createDiscourseCard(topic);
container.insertAdjacentHTML('beforeend', card); container.insertAdjacentHTML("beforeend", card);
}); });
} }
@ -223,53 +302,53 @@
// Toggle functionality with smooth animations // Toggle functionality with smooth animations
function toggleInputMode() { function toggleInputMode() {
const toggle = $('input[type="checkbox"]'); const toggle = $('input[type="checkbox"]');
const textContainer = $('#text-input-container'); const textContainer = $("#text-input-container");
const ketcherContainer = $('#ketcher-container'); const ketcherContainer = $("#ketcher-container");
const formCard = $('.card'); const formCard = $(".card");
const fieldset = $('.fieldset'); const fieldset = $(".fieldset");
if (toggle.is(':checked')) { if (toggle.is(":checked")) {
// Draw mode - show Ketcher, hide text input // Draw mode - show Ketcher, hide text input
textContainer.addClass('opacity-0 transform scale-95'); textContainer.addClass("opacity-0 transform scale-95");
textContainer.removeClass('opacity-100 transform scale-100'); textContainer.removeClass("opacity-100 transform scale-100");
// Adjust fieldset padding for Ketcher mode - reduce padding and make more compact // Adjust fieldset padding for Ketcher mode - reduce padding and make more compact
fieldset.removeClass('p-8'); fieldset.removeClass("p-8");
fieldset.addClass('p-4'); fieldset.addClass("p-4");
// Wait for fade out to complete, then hide and show new content // Wait for fade out to complete, then hide and show new content
setTimeout(() => { setTimeout(() => {
textContainer.addClass('hidden'); textContainer.addClass("hidden");
ketcherContainer.removeClass('hidden opacity-0 transform scale-95'); ketcherContainer.removeClass("hidden opacity-0 transform scale-95");
ketcherContainer.addClass('opacity-100 transform scale-100'); ketcherContainer.addClass("opacity-100 transform scale-100");
// Force re-evaluation of iframe size // Force re-evaluation of iframe size
const iframe = document.getElementById('index-ketcher'); const iframe = document.getElementById("index-ketcher");
if (iframe) { if (iframe) {
iframe.style.height = '400px'; iframe.style.height = "400px";
} }
}, 300); }, 300);
} else { } else {
// SMILES mode - show text input, hide Ketcher // SMILES mode - show text input, hide Ketcher
ketcherContainer.addClass('opacity-0 transform scale-95'); ketcherContainer.addClass("opacity-0 transform scale-95");
ketcherContainer.removeClass('opacity-100 transform scale-100'); ketcherContainer.removeClass("opacity-100 transform scale-100");
// Restore fieldset padding for text input mode // Restore fieldset padding for text input mode
fieldset.removeClass('p-4'); fieldset.removeClass("p-4");
fieldset.addClass('p-8'); fieldset.addClass("p-8");
// Wait for fade out to complete, then hide and show new content // Wait for fade out to complete, then hide and show new content
setTimeout(() => { setTimeout(() => {
ketcherContainer.addClass('hidden'); ketcherContainer.addClass("hidden");
textContainer.removeClass('hidden opacity-0 transform scale-95'); textContainer.removeClass("hidden opacity-0 transform scale-95");
textContainer.addClass('opacity-100 transform scale-100'); textContainer.addClass("opacity-100 transform scale-100");
}, 300); }, 300);
// Transfer SMILES from Ketcher to text input if available // Transfer SMILES from Ketcher to text input if available
if (window.indexKetcher && window.indexKetcher.getSmiles) { if (window.indexKetcher && window.indexKetcher.getSmiles) {
const smiles = window.indexKetcher.getSmiles(); const smiles = window.indexKetcher.getSmiles();
if (smiles && smiles.trim() !== '') { if (smiles && smiles.trim() !== "") {
$('#index-form-text-input').val(smiles); $("#index-form-text-input").val(smiles);
} }
} }
} }
@ -277,27 +356,27 @@
// Ketcher integration // Ketcher integration
function indexKetcherToTextInput() { function indexKetcherToTextInput() {
$('#index-form-smiles').val(this.ketcher.getSmiles()); $("#index-form-smiles").val(this.ketcher.getSmiles());
} }
$(function () { $(function () {
// Initialize fieldset with proper padding // Initialize fieldset with proper padding
$('.fieldset').addClass('p-8'); $(".fieldset").addClass("p-8");
// Toggle event listener // Toggle event listener
$('input[type="checkbox"]').on('change', toggleInputMode); $('input[type="checkbox"]').on("change", toggleInputMode);
// Ketcher iframe load handler // Ketcher iframe load handler
$('#index-ketcher').on('load', function() { $("#index-ketcher").on("load", function () {
const checkKetcherReady = () => { const checkKetcherReady = () => {
const win = this.contentWindow; const win = this.contentWindow;
if (win.ketcher && 'editor' in win.ketcher) { if (win.ketcher && "editor" in win.ketcher) {
window.indexKetcher = win.ketcher; window.indexKetcher = win.ketcher;
win.ketcher.editor.event.change.handlers.push({ win.ketcher.editor.event.change.handlers.push({
once: false, once: false,
priority: 0, priority: 0,
f: indexKetcherToTextInput, f: indexKetcherToTextInput,
ketcher: win.ketcher ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
@ -307,20 +386,20 @@
}); });
// Handle example link clicks // Handle example link clicks
$('.example-link').on('click', function(e) { $(".example-link").on("click", function (e) {
e.preventDefault(); e.preventDefault();
const smiles = $(this).data('smiles'); const smiles = $(this).data("smiles");
const title = $(this).attr('title'); const title = $(this).attr("title");
// Check if we're in Ketcher mode or text input mode // Check if we're in Ketcher mode or text input mode
if ($('input[type="checkbox"]').is(':checked')) { if ($('input[type="checkbox"]').is(":checked")) {
// In Ketcher mode - set the SMILES in Ketcher // In Ketcher mode - set the SMILES in Ketcher
if (window.indexKetcher && window.indexKetcher.setMolecule) { if (window.indexKetcher && window.indexKetcher.setMolecule) {
window.indexKetcher.setMolecule(smiles); window.indexKetcher.setMolecule(smiles);
} }
} else { } else {
// In text input mode - set the SMILES in the text input // In text input mode - set the SMILES in the text input
$('#index-form-text-input').val(smiles); $("#index-form-text-input").val(smiles);
} }
// Show a brief feedback // Show a brief feedback
@ -332,25 +411,25 @@
}); });
// Handle form submission on Enter // Handle form submission on Enter
$('#index-form').on("submit", function (e) { $("#index-form").on("submit", function (e) {
e.preventDefault(); e.preventDefault();
var textSmiles = ''; var textSmiles = "";
// Check if we're in Ketcher mode and extract SMILES // Check if we're in Ketcher mode and extract SMILES
if ($('input[type="checkbox"]').is(':checked') && window.indexKetcher) { if ($('input[type="checkbox"]').is(":checked") && window.indexKetcher) {
textSmiles = window.indexKetcher.getSmiles().trim(); textSmiles = window.indexKetcher.getSmiles().trim();
} else { } else {
textSmiles = $('#index-form-text-input').val().trim(); textSmiles = $("#index-form-text-input").val().trim();
} }
if (textSmiles === '') { if (textSmiles === "") {
return; return;
} }
$('#index-form-smiles').val(textSmiles); $("#index-form-smiles").val(textSmiles);
$("#index-form").attr("action", currentPackage + "/pathway"); $("#index-form").attr("action", currentPackage + "/pathway");
$("#index-form").attr("method", 'POST'); $("#index-form").attr("method", "POST");
this.submit(); this.submit();
}); });

View File

@ -0,0 +1,188 @@
{% extends "framework_modern.html" %}
{% load static %}
{% block content %}
<div class="mx-auto w-full p-8">
<h1 class="h1 mb-4 text-3xl font-bold">Predict a Pathway</h1>
<form
id="predict_form"
accept-charset="UTF-8"
action="{{ meta.current_package.url }}/pathway"
method="post"
>
{% csrf_token %}
<div class="mb-8 flex flex-col gap-8 md:flex-row md:items-end">
<fieldset class="flex flex-col gap-4 md:flex-3/4">
<label class="floating-label" for="name">
<input
type="text"
name="name"
placeholder="Name"
id="name"
class="input input-md w-full"
/>
<span>Name</span>
</label>
<label class="floating-label" for="description">
<input
type="text"
name="description"
placeholder="Description"
id="description"
class="input input-md w-full"
/>
<span>Description</span>
</label>
</fieldset>
<fieldset
class="fieldset flex shrink-0 flex-row items-start gap-3 md:flex-1/4 md:flex-col"
>
<label class="fieldset-label text-base-content/50">Mode</label>
<label class="label">
<input
type="radio"
name="predict"
id="radioPredict"
value="predict"
checked
class="radio radio-neutral"
/>
Predict
</label>
<label class="label">
<input
type="radio"
name="predict"
id="radioIncremental"
value="incremental"
class="radio radio-neutral"
/>
Incremental
</label>
<label class="label">
<input
type="radio"
name="predict"
id="radioBuild"
value="build"
class="radio radio-neutral"
/>
Build
</label>
</fieldset>
</div>
<label class="floating-label" for="predict-smiles">
<input
type="text"
name="smiles"
placeholder="SMILES"
id="predict-smiles"
class="input input-md w-full"
/>
<span>SMILES</span>
</label>
<div class="divider text-base-content/50">OR</div>
<div class="mb-8 w-full">
<label class="text-base-content/50 mb-4 text-xs font-medium"
>Draw Structure</label
>
<iframe
id="predict-ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div>
<label class="select mb-8 w-full">
<span class="label">Predictor</span>
<select id="prediction-setting" name="prediction-setting">
<option disabled>Select a Setting</option>
{% for s in meta.available_settings %}
<option
value="{{ s.url }}"
{% if s.id == meta.user.default_setting.id %}selected{% endif %}
>
{{ s.name }}{% if s.id == meta.user.default_setting.id %}
(User default)
{% endif %}
</option>
{% endfor %}
</select>
</label>
<div class="flex justify-end gap-2">
<a href="{{ meta.current_package.url }}/pathway" class="btn btn-outline"
>Cancel</a
>
<button
type="submit"
id="predict-submit-button"
class="btn btn-primary"
>
Predict
</button>
</div>
</form>
</div>
<script>
function predictKetcherToTextInput() {
$("#predict-smiles").val(this.ketcher.getSmiles());
}
$(function () {
$("#predict-ketcher").on("load", function () {
const checkKetcherReady = () => {
const win = this.contentWindow;
if (win.ketcher && "editor" in win.ketcher) {
window.predictKetcher = win.ketcher;
win.ketcher.editor.event.change.handlers.push({
once: false,
priority: 0,
f: predictKetcherToTextInput,
ketcher: win.ketcher,
});
} else {
setTimeout(checkKetcherReady, 100);
}
};
checkKetcherReady();
});
$("#predict-submit-button").on("click", function (e) {
e.preventDefault();
const button = $(this);
button.prop("disabled", true);
button.text("Predicting...");
// Get SMILES from either input or Ketcher
let smiles = $("#predict-smiles").val().trim();
// If SMILES input is empty, try to get from Ketcher
if (!smiles && window.predictKetcher) {
smiles = window.predictKetcher.getSmiles().trim();
if (smiles) {
$("#predict-smiles").val(smiles);
}
}
// Basic validation
if (!smiles) {
alert("Please enter a SMILES string or draw a structure.");
button.prop("disabled", false);
button.text("Predict");
return;
}
// Submit form
$("#predict_form").submit();
});
});
</script>
{% endblock content %}

View File

@ -105,7 +105,7 @@
</div> </div>
</div> </div>
<form method="post" action="{% url 'register' %}" class="space-y-4"> <form method="post" action="{% url 'login' %}" class="space-y-4">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="register" value="true" /> <input type="hidden" name="register" value="true" />