forked from enviPath/enviPy
Compare commits
6 Commits
feature/le
...
feat/packa
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e95837116 | |||
| debbef8158 | |||
| 2799718951 | |||
| d9e4660fd4 | |||
| 305fdc41fb | |||
| 9deca8867e |
@ -1,15 +1,15 @@
|
|||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any, Callable, List, Optional
|
from typing import Any, Callable, List, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from celery.utils.functional import LRUCache
|
from celery.utils.functional import LRUCache
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from epdb.logic import SPathway
|
from epdb.logic import SPathway
|
||||||
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
|
from epdb.models import Edge, EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||||
@ -29,7 +29,7 @@ def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
|
|||||||
log.task_id = uuid4()
|
log.task_id = uuid4()
|
||||||
log.job_name = job.__name__
|
log.job_name = job.__name__
|
||||||
log.status = "SUCCESS"
|
log.status = "SUCCESS"
|
||||||
log.done_at = datetime.now()
|
log.done_at = timezone.now()
|
||||||
log.task_result = str(x) if x else None
|
log.task_result = str(x) if x else None
|
||||||
log.save()
|
log.save()
|
||||||
|
|
||||||
|
|||||||
@ -142,6 +142,11 @@ urlpatterns = [
|
|||||||
v.package_pathway,
|
v.package_pathway,
|
||||||
name="package pathway detail",
|
name="package pathway detail",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/predict$",
|
||||||
|
v.package_predict_pathway,
|
||||||
|
name="package predict pathway",
|
||||||
|
),
|
||||||
# Pathway Nodes
|
# Pathway Nodes
|
||||||
re_path(
|
re_path(
|
||||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$",
|
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$",
|
||||||
|
|||||||
@ -374,6 +374,22 @@ def predict_pathway(request):
|
|||||||
return render(request, "predict_pathway.html", context)
|
return render(request, "predict_pathway.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@package_permission_required()
|
||||||
|
def package_predict_pathway(request, package_uuid):
|
||||||
|
"""Package-specific predict pathway view."""
|
||||||
|
if request.method != "GET":
|
||||||
|
return HttpResponseNotAllowed(["GET"])
|
||||||
|
|
||||||
|
current_user = _anonymous_or_real(request)
|
||||||
|
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
|
||||||
|
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = f"enviPath - {current_package.name} - Predict Pathway"
|
||||||
|
context["meta"]["current_package"] = current_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)
|
||||||
|
|
||||||
@ -955,6 +971,12 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
]
|
]
|
||||||
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
||||||
|
|
||||||
|
return redirect(current_model.url)
|
||||||
|
elif hidden == "retrain":
|
||||||
|
from .tasks import dispatch, retrain
|
||||||
|
|
||||||
|
dispatch(current_user, retrain, current_model.pk)
|
||||||
|
|
||||||
return redirect(current_model.url)
|
return redirect(current_model.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ meta.server_url }}/predict">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
|
href="{% if meta.current_package %}{{ meta.current_package.url }}/predict{% else %}{{ meta.server_url }}/predict{% endif %}"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Pathway</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -178,6 +178,23 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Open search modal function
|
||||||
|
function openSearchModal() {
|
||||||
|
const searchModal = document.getElementById("search_modal");
|
||||||
|
if (searchModal) {
|
||||||
|
searchModal.showModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click handler for search badge
|
||||||
|
const searchTrigger = document.getElementById("search-trigger");
|
||||||
|
if (searchTrigger) {
|
||||||
|
searchTrigger.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
openSearchModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
||||||
document.addEventListener("keydown", function (event) {
|
document.addEventListener("keydown", function (event) {
|
||||||
// Check if user is typing in an input field
|
// Check if user is typing in an input field
|
||||||
@ -198,7 +215,7 @@
|
|||||||
|
|
||||||
if (isCorrectModifier && event.key === "k") {
|
if (isCorrectModifier && event.key === "k") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
search_modal.showModal();
|
openSearchModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
{% if not public_mode %}
|
{% if not public_mode %}
|
||||||
<a href="/search" role="button">
|
<a id="search-trigger" role="button" class="cursor-pointer">
|
||||||
<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"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -2,36 +2,38 @@
|
|||||||
{% 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 relative mx-auto h-fit w-full max-w-5xl shadow-none">
|
||||||
<div
|
<div
|
||||||
class="hero min-h-[calc(100vh*0.4)] bg-gradient-to-br from-primary-800 to-primary-600"
|
class="hero from-primary-800 to-primary-600 min-h-[calc(100vh*0.4)] bg-gradient-to-br"
|
||||||
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 z-10 -translate-x-8">
|
||||||
<h2 class="text-3xl text-base-100 text-shadow-lg text-left">
|
<h2 class="text-base-100 text-left text-3xl text-shadow-lg">
|
||||||
Predict Your Pathway
|
Predict Your Pathway
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="shadow-md max-w-5xl mx-auto bg-base-200">
|
<div class="bg-base-200 mx-auto max-w-5xl shadow-md">
|
||||||
<!-- Predict Pathway Section -->
|
<!-- Predict Pathway Section -->
|
||||||
<div
|
<div
|
||||||
class="flex-col lg:flex-row-reverse w-full mx-auto -mt-32 relative z-20 mb-10 "
|
class="relative z-20 mx-auto -mt-32 mb-10 w-full flex-col lg:flex-row-reverse"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="card bg-base-100 shrink-0 shadow-xl w-3/4 mx-auto transition-all duration-300 ease-in-out"
|
class="card bg-base-100 mx-auto w-3/4 shrink-0 shadow-xl transition-all duration-300 ease-in-out"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!-- Input Mode Toggle - Fixed position outside fieldset -->
|
<div class="my-4 ml-8 flex h-fit flex-row items-center justify-start">
|
||||||
<div class="flex flex-row justify-start items-center h-fit ml-8 my-4">
|
<div class="flex items-center gap-1">
|
||||||
<div class="flex items-center gap-2">
|
<label class="swap btn btn-ghost btn-sm p-1" title="Input Mode">
|
||||||
<!-- <span class="text-sm text-neutral-500">Input Mode:</span> -->
|
|
||||||
<label class="toggle text-base-content toggle-md">
|
|
||||||
<input type="checkbox" />
|
<input type="checkbox" />
|
||||||
|
<span class="swap-on flex items-center gap-1">
|
||||||
|
<div
|
||||||
|
class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-label="smiles mode"
|
aria-label="smiles mode"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -52,6 +54,13 @@
|
|||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span class="ext-xs">SMILES</span>
|
||||||
|
</span>
|
||||||
|
<span class="swap-off flex items-center gap-1">
|
||||||
|
<div
|
||||||
|
class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-label="draw mode"
|
aria-label="draw mode"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -64,12 +73,15 @@
|
|||||||
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"
|
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>
|
||||||
|
</div>
|
||||||
|
<span class="text-base/50 text-xs">Draw</span>
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset
|
<fieldset
|
||||||
class="fieldset transition-all duration-300 ease-in-out overflow-hidden"
|
class="fieldset overflow-hidden transition-all duration-300 ease-in-out"
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
id="index-form"
|
id="index-form"
|
||||||
@ -79,29 +91,29 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div
|
<div
|
||||||
id="text-input-container"
|
id="text-input-container"
|
||||||
class="transition-all duration-300 ease-in-out opacity-100 transform scale-100"
|
class="scale-100 transform opacity-100 transition-all duration-300 ease-in-out"
|
||||||
>
|
>
|
||||||
<div class="join w-full mx-auto">
|
<div class="join mx-auto w-full">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="index-form-text-input"
|
id="index-form-text-input"
|
||||||
placeholder="canonical SMILES string"
|
placeholder="canonical SMILES string"
|
||||||
class="input grow input-md join-item"
|
class="input input-md join-item grow"
|
||||||
/>
|
/>
|
||||||
<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 mt-1 w-full">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="example-link cursor-pointer hover:text-primary"
|
class="example-link hover:text-primary cursor-pointer"
|
||||||
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"
|
title="load example"
|
||||||
>Caffeine</a
|
>Caffeine</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="example-link cursor-pointer hover:text-primary"
|
class="example-link hover:text-primary cursor-pointer"
|
||||||
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"
|
title="load example"
|
||||||
>Ibuprofen</a
|
>Ibuprofen</a
|
||||||
@ -114,7 +126,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="ketcher-container"
|
id="ketcher-container"
|
||||||
class="hidden w-full transition-all duration-300 ease-in-out opacity-0 transform scale-95"
|
class="hidden w-full scale-95 transform opacity-0 transition-all duration-300 ease-in-out"
|
||||||
>
|
>
|
||||||
<iframe
|
<iframe
|
||||||
id="index-ketcher"
|
id="index-ketcher"
|
||||||
@ -124,11 +136,13 @@
|
|||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
></iframe>
|
></iframe>
|
||||||
<button
|
<button
|
||||||
class="btn btn-lg bg-primary-950 text-primary-50 join-item w-full mt-2"
|
class="btn btn-lg bg-primary-950 text-primary-50 join-item mt-2 w-full"
|
||||||
>
|
>
|
||||||
Predict!
|
Predict!
|
||||||
</button>
|
</button>
|
||||||
<a class="label mx-auto w-full mt-1" href="#">Advanced</a>
|
<div class="mt-1 flex w-full justify-end">
|
||||||
|
<a class="label justify-end" href="/predict">Advanced</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
@ -150,18 +164,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Community News Section -->
|
<!-- Community News Section -->
|
||||||
<section class="py-16 bg-base-200 z-10 mx-8">
|
<section class="bg-base-200 z-10 mx-8 py-16">
|
||||||
<div class="max-w-7xl mx-auto px-4">
|
<div class="mx-auto max-w-7xl px-4">
|
||||||
<h2 class="h2 font-bold text-left mb-8">Community Updates</h2>
|
<h2 class="h2 mb-8 text-left font-bold">Community Updates</h2>
|
||||||
|
|
||||||
<div id="community-news-container" class="flex gap-4 justify-center">
|
<div id="community-news-container" class="flex justify-center gap-4">
|
||||||
<!-- News cards will be populated here -->
|
<!-- News cards will be populated here -->
|
||||||
<div id="loading" class="flex justify-center w-full">
|
<div id="loading" class="flex w-full justify-center">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-right mt-6">
|
<div class="mt-6 text-right">
|
||||||
<a
|
<a
|
||||||
href="https://community.envipath.org/c/announcements/10"
|
href="https://community.envipath.org/c/announcements/10"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -177,18 +191,18 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Mission Statement Section -->
|
<!-- Mission Statement Section -->
|
||||||
<section class="py-16 from-base-200 to-base-100 bg-gradient-to-b">
|
<section class="from-base-200 to-base-100 bg-gradient-to-b py-16">
|
||||||
<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
|
<img
|
||||||
src="{% static "/images/ep-rule-artwork.png" %}"
|
src="{% static "/images/ep-rule-artwork.png" %}"
|
||||||
alt="rule-based iterative tree greneration"
|
alt="rule-based iterative tree greneration"
|
||||||
class="w-full h-full object-contain"
|
class="h-full w-full object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-4 text-left w-2/3 mr-8">
|
<div class="mr-8 w-2/3 space-y-4 text-left">
|
||||||
<h2 class="h2 font-bold mb-8">About enviPath</h2>
|
<h2 class="h2 mb-8 font-bold">About enviPath</h2>
|
||||||
<p class="">
|
<p class="">
|
||||||
enviPath is a database and prediction system for the microbial
|
enviPath is a database and prediction system for the microbial
|
||||||
biotransformation of organic environmental contaminants. The
|
biotransformation of organic environmental contaminants. The
|
||||||
@ -201,7 +215,7 @@
|
|||||||
products. Explore our tools and contribute to advancing
|
products. Explore our tools and contribute to advancing
|
||||||
environmental biotransformation research.
|
environmental biotransformation research.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-row gap-4 float-right">
|
<div class="float-right flex flex-row gap-4">
|
||||||
<a href="/about" class="btn btn-ghost-neutral">Read More</a>
|
<a href="/about" class="btn btn-ghost-neutral">Read More</a>
|
||||||
<a href="/about" class="btn btn-neutral">Publications</a>
|
<a href="/about" class="btn btn-neutral">Publications</a>
|
||||||
</div>
|
</div>
|
||||||
@ -211,7 +225,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Partners Section -->
|
<!-- Partners Section -->
|
||||||
<section class="py-14 sm:py-12 bg-base-100">
|
<section class="bg-base-100 py-14 sm:py-12">
|
||||||
<div class="mx-auto px-6 lg:px-8">
|
<div class="mx-auto px-6 lg:px-8">
|
||||||
<div class="divider">
|
<div class="divider">
|
||||||
<h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2>
|
<h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2>
|
||||||
@ -222,12 +236,12 @@
|
|||||||
<img
|
<img
|
||||||
src="{% static "/images/uoa-logo-small.png" %}"
|
src="{% static "/images/uoa-logo-small.png" %}"
|
||||||
alt="The University of Auckland"
|
alt="The University of Auckland"
|
||||||
class=" max-h-20 w-full object-contain lg:col-span-1"
|
class="max-h-20 w-full object-contain lg:col-span-1"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
src="{% static "/images/logo-eawag.svg" %}"
|
src="{% static "/images/logo-eawag.svg" %}"
|
||||||
alt="Eawag"
|
alt="Eawag"
|
||||||
class=" max-h-12 w-full object-contain lg:col-span-1"
|
class="max-h-12 w-full object-contain lg:col-span-1"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
src="{% static "/images/uzh-logo.svg" %}"
|
src="{% static "/images/uzh-logo.svg" %}"
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
<div class="modal fade" tabindex="-1" id="retrain_model_modal" role="dialog" aria-labelledby="retrain_model_modal"
|
<div
|
||||||
aria-hidden="true">
|
class="modal fade"
|
||||||
|
tabindex="-1"
|
||||||
|
id="retrain_model_modal"
|
||||||
|
role="dialog"
|
||||||
|
aria-labelledby="retrain_model_modal"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -10,34 +16,40 @@
|
|||||||
<h4 class="modal-title">Retrain Model</h4>
|
<h4 class="modal-title">Retrain Model</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="retrain_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model"
|
<form
|
||||||
data-remote="true" method="post">
|
id="retrain_model_form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{{ meta.current_object.url }}"
|
||||||
|
data-remote="true"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
To reflect changes in the rule or data packages, you can use the "Retrain" button,
|
To reflect changes in the rule or data packages, you can use the
|
||||||
to let the model reflect the changes without creating a new model.
|
"Retrain" button, to let the model reflect the changes without
|
||||||
While the model is retraining, it will be unavailable for prediction.
|
creating a new model. While the model is retraining, it will be
|
||||||
|
unavailable for prediction.
|
||||||
</div>
|
</div>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="retrain">
|
<input type="hidden" name="hidden" value="retrain" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a id="retrain_model_form_submit" class="btn btn-primary" href="#">Retrain</a>
|
<a id="retrain_model_form_submit" class="btn btn-primary" href="#"
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
>Retrain</a
|
||||||
|
>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
|
$("#retrain_model_form_submit").on("click", function (e) {
|
||||||
$('#retrain_model_form_submit').on('click', function (e) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#retrain_model_form').submit();
|
$("#retrain_model_form").submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,105 +1,184 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<dialog id="search_modal" class="modal @max-sm:modal-top justify-center">
|
<dialog id="search_modal" class="modal @max-sm:modal-top justify-center">
|
||||||
<div class="modal-box w-lvw sm:w-[85vw] sm:max-w-5xl h-full sm:h-8/12 p-1" >
|
<div class="modal-box h-full w-lvw p-1 sm:h-8/12 sm:w-[85vw] sm:max-w-5xl">
|
||||||
|
|
||||||
<!-- Search Input and Mode Selector -->
|
<!-- Search Input and Mode Selector -->
|
||||||
<div class="form-control mb-4 flex-shrink-0 w-full">
|
<div class="form-control mb-4 w-full shrink-0">
|
||||||
<div class="join w-full m-0 p-3 items-center">
|
<div class="join m-0 w-full items-center p-3">
|
||||||
<label class="input join-item input-ghost grow">
|
<label class="input join-item input-ghost grow">
|
||||||
<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-search-icon lucide-search"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg>
|
<svg
|
||||||
<input type="text" autofocus
|
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-search-icon lucide-search"
|
||||||
|
>
|
||||||
|
<path d="m21 21-4.34-4.34" />
|
||||||
|
<circle cx="11" cy="11" r="8" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autofocus
|
||||||
id="modal_searchbar"
|
id="modal_searchbar"
|
||||||
placeholder="Benfuracarb"
|
placeholder="Benfuracarb"
|
||||||
class="grow" aria-label="Search" />
|
class="grow"
|
||||||
|
aria-label="Search"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- Mode Dropdown -->
|
<!-- Mode Dropdown -->
|
||||||
<div>
|
<div>
|
||||||
<button type="button"
|
<button
|
||||||
|
type="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
id="modal_mode_button"
|
id="modal_mode_button"
|
||||||
popovertarget="search_dropdown_menu" style="anchor-name:--anchor-1"
|
popovertarget="search_dropdown_menu"
|
||||||
class="btn join-item btn-ghost">
|
style="anchor-name:--1"
|
||||||
|
class="btn join-item btn-ghost"
|
||||||
|
>
|
||||||
Text
|
Text
|
||||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
class="ml-1 h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19 9l-7 7-7-7"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<ul tabindex="0"" class="dropdown dropdown-end menu bg-base-200 rounded-box z-[100] w-64 p-2 shadow-lg" popover id="search_dropdown_menu" style="position-anchor:--anchor-1">
|
<ul
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown dropdown-end menu bg-base-200 rounded-box w-64 p-2 shadow-lg"
|
||||||
|
popover
|
||||||
|
id="search_dropdown_menu"
|
||||||
|
style="position-anchor:--anchor-2"
|
||||||
|
>
|
||||||
<li class="menu-title">Text</li>
|
<li class="menu-title">Text</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="modal_dropdown_text"
|
<a
|
||||||
|
id="modal_dropdown_text"
|
||||||
class="tooltip tooltip-left"
|
class="tooltip tooltip-left"
|
||||||
data-tip="Search on object names and descriptions">
|
data-tip="Search on object names and descriptions"
|
||||||
|
>
|
||||||
Text
|
Text
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-title">SMILES</li>
|
<li class="menu-title">SMILES</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="modal_dropdown_smiles_default"
|
<a
|
||||||
|
id="modal_dropdown_smiles_default"
|
||||||
class="tooltip tooltip-left"
|
class="tooltip tooltip-left"
|
||||||
data-tip="Ignores stereochemistry and charge">
|
data-tip="Ignores stereochemistry and charge"
|
||||||
|
>
|
||||||
Default
|
Default
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="modal_dropdown_smiles_canonical"
|
<a
|
||||||
|
id="modal_dropdown_smiles_canonical"
|
||||||
class="tooltip tooltip-left"
|
class="tooltip tooltip-left"
|
||||||
data-tip="Ignores stereochemistry, preserves charge">
|
data-tip="Ignores stereochemistry, preserves charge"
|
||||||
|
>
|
||||||
Canonical
|
Canonical
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="modal_dropdown_smiles_exact"
|
<a
|
||||||
|
id="modal_dropdown_smiles_exact"
|
||||||
class="tooltip tooltip-left"
|
class="tooltip tooltip-left"
|
||||||
data-tip="Exact match for stereochemistry and charge">
|
data-tip="Exact match for stereochemistry and charge"
|
||||||
|
>
|
||||||
Exact
|
Exact
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-title">InChI</li>
|
<li class="menu-title">InChI</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="modal_dropdown_inchikey"
|
<a
|
||||||
|
id="modal_dropdown_inchikey"
|
||||||
class="tooltip tooltip-left"
|
class="tooltip tooltip-left"
|
||||||
data-tip="Search by InChIKey">
|
data-tip="Search by InChIKey"
|
||||||
|
>
|
||||||
InChIKey
|
InChIKey
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" id="modal_search_button" class="btn btn-xs btn-ghost join-item">
|
<button
|
||||||
<kbd class="kbd kbd-sm p-1 text-base-content/50">
|
type="button"
|
||||||
<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-corner-down-left-icon lucide-corner-down-left"><path d="M20 4v7a4 4 0 0 1-4 4H4"/><path d="m9 10-5 5 5 5"/></svg>
|
id="modal_search_button"
|
||||||
|
class="btn btn-xs btn-ghost join-item"
|
||||||
|
>
|
||||||
|
<kbd class="kbd kbd-sm text-base-content/50 p-1">
|
||||||
|
<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-corner-down-left-icon lucide-corner-down-left"
|
||||||
|
>
|
||||||
|
<path d="M20 4v7a4 4 0 0 1-4 4H4" />
|
||||||
|
<path d="m9 10-5 5 5 5" />
|
||||||
|
</svg>
|
||||||
</kbd>
|
</kbd>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Package Selector with Pills -->
|
<!-- Package Selector with Pills -->
|
||||||
<div class="form-control mb-4 flex-shrink-0">
|
<div class="form-control mb-4 shrink-0">
|
||||||
<!-- Pills Container -->
|
<!-- Pills Container -->
|
||||||
<div id="modal_package_pills_container"
|
<div
|
||||||
class="flex flex-wrap gap-2 p-3 border-2 border-dashed border-base-300 rounded-lg m-3 min-h-[3rem] items-center">
|
id="modal_package_pills_container"
|
||||||
|
class="border-base-300 m-3 flex min-h-12 flex-wrap items-center gap-2 rounded-lg border-2 border-dashed p-3"
|
||||||
|
>
|
||||||
<!-- Pills will be added here dynamically -->
|
<!-- Pills will be added here dynamically -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Package Dropdown Menu -->
|
<!-- Package Dropdown Menu -->
|
||||||
<ul class="dropdown dropdown-end menu bg-base-200 rounded-box z-[100] w-80 max-h-96 overflow-y-auto p-2 shadow-lg"
|
<ul
|
||||||
|
class="dropdown dropdown-center menu bg-base-200 rounded-box max-h-96 w-80 overflow-y-auto p-2 shadow-lg"
|
||||||
popover
|
popover
|
||||||
id="package_dropdown_menu"
|
id="package_dropdown_menu"
|
||||||
style="position-anchor:--anchor-packages">
|
style="position-anchor:--anchor-packages"
|
||||||
|
>
|
||||||
{% if unreviewed_packages %}
|
{% if unreviewed_packages %}
|
||||||
<li class="menu-title">Reviewed Packages</li>
|
<li class="menu-title">Reviewed Packages</li>
|
||||||
{% for obj in reviewed_packages %}
|
{% for obj in reviewed_packages %}
|
||||||
<li>
|
<li>
|
||||||
<a class="package-option flex justify-between items-center"
|
<a
|
||||||
|
class="package-option flex items-center justify-between"
|
||||||
data-package-url="{{ obj.url }}"
|
data-package-url="{{ obj.url }}"
|
||||||
data-package-name="{{ obj.name }}">
|
data-package-name="{{ obj.name }}"
|
||||||
|
>
|
||||||
<span>{{ obj.name }}</span>
|
<span>{{ obj.name }}</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 package-checkmark hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="package-checkmark hidden h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M5 13l4 4L19 7"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -107,12 +186,25 @@
|
|||||||
<li class="menu-title">Unreviewed Packages</li>
|
<li class="menu-title">Unreviewed Packages</li>
|
||||||
{% for obj in unreviewed_packages %}
|
{% for obj in unreviewed_packages %}
|
||||||
<li>
|
<li>
|
||||||
<a class="package-option flex justify-between items-center"
|
<a
|
||||||
|
class="package-option flex items-center justify-between"
|
||||||
data-package-url="{{ obj.url }}"
|
data-package-url="{{ obj.url }}"
|
||||||
data-package-name="{{ obj.name }}">
|
data-package-name="{{ obj.name }}"
|
||||||
|
>
|
||||||
<span>{{ obj.name }}</span>
|
<span>{{ obj.name }}</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 package-checkmark hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="package-checkmark hidden h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M5 13l4 4L19 7"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -121,12 +213,25 @@
|
|||||||
<li class="menu-title">Reviewed Packages</li>
|
<li class="menu-title">Reviewed Packages</li>
|
||||||
{% for obj in reviewed_packages %}
|
{% for obj in reviewed_packages %}
|
||||||
<li>
|
<li>
|
||||||
<a class="package-option flex justify-between items-center"
|
<a
|
||||||
|
class="package-option flex items-center justify-between"
|
||||||
data-package-url="{{ obj.url }}"
|
data-package-url="{{ obj.url }}"
|
||||||
data-package-name="{{ obj.name }}">
|
data-package-name="{{ obj.name }}"
|
||||||
|
>
|
||||||
<span>{{ obj.name }}</span>
|
<span>{{ obj.name }}</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 package-checkmark hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="package-checkmark hidden h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M5 13l4 4L19 7"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -136,12 +241,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading Indicator -->
|
<!-- Loading Indicator -->
|
||||||
<div id="search_loading" class="hidden justify-center py-8 flex-shrink-0">
|
<div id="search_loading" class="hidden shrink-0 justify-center py-8">
|
||||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Results Container - scrollable -->
|
<!-- Results Container - scrollable -->
|
||||||
<div id="search_results" class="flex-1 overflow-y-auto min-h-0 p-2"></div>
|
<div id="search_results" class="min-h-0 flex-1 overflow-y-auto p-2"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Backdrop to close -->
|
<!-- Backdrop to close -->
|
||||||
@ -151,7 +256,7 @@
|
|||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function () {
|
||||||
// Package Selector Module - Data-driven multiselect package selection
|
// Package Selector Module - Data-driven multiselect package selection
|
||||||
const PackageSelector = {
|
const PackageSelector = {
|
||||||
// Single source of truth: array of selected packages
|
// Single source of truth: array of selected packages
|
||||||
@ -160,7 +265,7 @@
|
|||||||
elements: {
|
elements: {
|
||||||
pillsContainer: null,
|
pillsContainer: null,
|
||||||
packageDropdown: null,
|
packageDropdown: null,
|
||||||
packageOptions: null
|
packageOptions: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -171,39 +276,48 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
cacheElements() {
|
cacheElements() {
|
||||||
this.elements.pillsContainer = document.getElementById('modal_package_pills_container');
|
this.elements.pillsContainer = document.getElementById(
|
||||||
this.elements.packageDropdown = document.getElementById('package_dropdown_menu');
|
"modal_package_pills_container",
|
||||||
this.elements.packageOptions = document.querySelectorAll('.package-option');
|
);
|
||||||
|
this.elements.packageDropdown = document.getElementById(
|
||||||
|
"package_dropdown_menu",
|
||||||
|
);
|
||||||
|
this.elements.packageOptions =
|
||||||
|
document.querySelectorAll(".package-option");
|
||||||
},
|
},
|
||||||
|
|
||||||
loadInitialSelection() {
|
loadInitialSelection() {
|
||||||
// Load pre-selected packages from server-rendered pills
|
// Load pre-selected packages from server-rendered pills
|
||||||
const existingPills = this.elements.pillsContainer.querySelectorAll('.badge');
|
const existingPills =
|
||||||
existingPills.forEach(pill => {
|
this.elements.pillsContainer.querySelectorAll(".badge");
|
||||||
|
existingPills.forEach((pill) => {
|
||||||
this.selectedPackages.push({
|
this.selectedPackages.push({
|
||||||
url: pill.dataset.packageUrl,
|
url: pill.dataset.packageUrl,
|
||||||
name: pill.dataset.packageName
|
name: pill.dataset.packageName,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// If no pills found, select all reviewed packages by default
|
// If no pills found, select all reviewed packages by default
|
||||||
if (this.selectedPackages.length === 0) {
|
if (this.selectedPackages.length === 0) {
|
||||||
// Iterate through all menu items and collect reviewed packages
|
// Iterate through all menu items and collect reviewed packages
|
||||||
const menuItems = this.elements.packageDropdown.querySelectorAll('li');
|
const menuItems =
|
||||||
|
this.elements.packageDropdown.querySelectorAll("li");
|
||||||
|
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
// Check if this is the "Unreviewed Packages" menu title
|
// Check if this is the "Unreviewed Packages" menu title
|
||||||
if (item.classList.contains('menu-title') &&
|
if (
|
||||||
item.textContent.trim() === 'Unreviewed Packages') {
|
item.classList.contains("menu-title") &&
|
||||||
|
item.textContent.trim() === "Unreviewed Packages"
|
||||||
|
) {
|
||||||
break; // Stop processing after this point
|
break; // Stop processing after this point
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for package options (only reviewed packages reach here)
|
// Check for package options (only reviewed packages reach here)
|
||||||
const packageOption = item.querySelector('.package-option');
|
const packageOption = item.querySelector(".package-option");
|
||||||
if (packageOption) {
|
if (packageOption) {
|
||||||
this.selectedPackages.push({
|
this.selectedPackages.push({
|
||||||
url: packageOption.dataset.packageUrl,
|
url: packageOption.dataset.packageUrl,
|
||||||
name: packageOption.dataset.packageName
|
name: packageOption.dataset.packageName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,8 +326,8 @@
|
|||||||
|
|
||||||
attachEventListeners() {
|
attachEventListeners() {
|
||||||
// Toggle package selection on dropdown item click
|
// Toggle package selection on dropdown item click
|
||||||
this.elements.packageOptions.forEach(option => {
|
this.elements.packageOptions.forEach((option) => {
|
||||||
option.addEventListener('click', (e) => {
|
option.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation(); // Prevent dropdown from closing
|
e.stopPropagation(); // Prevent dropdown from closing
|
||||||
const packageUrl = option.dataset.packageUrl;
|
const packageUrl = option.dataset.packageUrl;
|
||||||
@ -223,9 +337,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Remove package when X is clicked (using event delegation)
|
// Remove package when X is clicked (using event delegation)
|
||||||
this.elements.pillsContainer.addEventListener('click', (e) => {
|
this.elements.pillsContainer.addEventListener("click", (e) => {
|
||||||
if (e.target.classList.contains('package-remove-btn') || e.target.closest('.package-remove-btn')) {
|
if (
|
||||||
const pill = e.target.closest('.badge');
|
e.target.classList.contains("package-remove-btn") ||
|
||||||
|
e.target.closest(".package-remove-btn")
|
||||||
|
) {
|
||||||
|
const pill = e.target.closest(".badge");
|
||||||
if (pill) {
|
if (pill) {
|
||||||
const packageUrl = pill.dataset.packageUrl;
|
const packageUrl = pill.dataset.packageUrl;
|
||||||
this.removePackage(packageUrl);
|
this.removePackage(packageUrl);
|
||||||
@ -235,7 +352,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
togglePackageSelection(packageUrl, packageName) {
|
togglePackageSelection(packageUrl, packageName) {
|
||||||
const index = this.selectedPackages.findIndex(pkg => pkg.url === packageUrl);
|
const index = this.selectedPackages.findIndex(
|
||||||
|
(pkg) => pkg.url === packageUrl,
|
||||||
|
);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
// Remove from selection
|
// Remove from selection
|
||||||
@ -249,7 +368,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
removePackage(packageUrl) {
|
removePackage(packageUrl) {
|
||||||
const index = this.selectedPackages.findIndex(pkg => pkg.url === packageUrl);
|
const index = this.selectedPackages.findIndex(
|
||||||
|
(pkg) => pkg.url === packageUrl,
|
||||||
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.selectedPackages.splice(index, 1);
|
this.selectedPackages.splice(index, 1);
|
||||||
this.render();
|
this.render();
|
||||||
@ -264,16 +385,18 @@
|
|||||||
|
|
||||||
renderPills() {
|
renderPills() {
|
||||||
// Clear existing pills and button (except placeholder)
|
// Clear existing pills and button (except placeholder)
|
||||||
const pills = this.elements.pillsContainer.querySelectorAll('.badge');
|
const pills = this.elements.pillsContainer.querySelectorAll(".badge");
|
||||||
pills.forEach(pill => pill.remove());
|
pills.forEach((pill) => pill.remove());
|
||||||
|
|
||||||
const existingButton = this.elements.pillsContainer.querySelector('#modal_package_add_button');
|
const existingButton = this.elements.pillsContainer.querySelector(
|
||||||
|
"#modal_package_add_button",
|
||||||
|
);
|
||||||
if (existingButton) {
|
if (existingButton) {
|
||||||
existingButton.remove();
|
existingButton.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create pills from data
|
// Create pills from data
|
||||||
this.selectedPackages.forEach(pkg => {
|
this.selectedPackages.forEach((pkg) => {
|
||||||
const pill = this.createPillElement(pkg.url, pkg.name);
|
const pill = this.createPillElement(pkg.url, pkg.name);
|
||||||
this.elements.pillsContainer.appendChild(pill);
|
this.elements.pillsContainer.appendChild(pill);
|
||||||
});
|
});
|
||||||
@ -285,12 +408,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const button = document.createElement('button');
|
const button = document.createElement("button");
|
||||||
button.type = 'button';
|
button.type = "button";
|
||||||
button.id = 'modal_package_add_button';
|
button.id = "modal_package_add_button";
|
||||||
button.setAttribute('popovertarget', 'package_dropdown_menu');
|
button.setAttribute("popovertarget", "package_dropdown_menu");
|
||||||
button.style.cssText = 'anchor-name:--anchor-packages';
|
button.style.cssText = "anchor-name:--anchor-packages";
|
||||||
button.className = 'btn btn-sm btn-ghost gap-2 text-base-content/50';
|
button.className = "btn btn-sm btn-ghost gap-2 text-base-content/50";
|
||||||
|
|
||||||
button.innerHTML = `
|
button.innerHTML = `
|
||||||
<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-plus-icon lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
|
<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-plus-icon lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
|
||||||
@ -301,8 +424,8 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
createPillElement(packageUrl, packageName) {
|
createPillElement(packageUrl, packageName) {
|
||||||
const pill = document.createElement('span');
|
const pill = document.createElement("span");
|
||||||
pill.className = 'badge badge-outline gap-2 max-w-xs';
|
pill.className = "badge badge-outline gap-2 max-w-xs";
|
||||||
pill.dataset.packageUrl = packageUrl;
|
pill.dataset.packageUrl = packageUrl;
|
||||||
pill.dataset.packageName = packageName;
|
pill.dataset.packageName = packageName;
|
||||||
|
|
||||||
@ -326,35 +449,36 @@
|
|||||||
|
|
||||||
renderCheckmarks() {
|
renderCheckmarks() {
|
||||||
// Update all checkmarks based on selected packages
|
// Update all checkmarks based on selected packages
|
||||||
this.elements.packageOptions.forEach(option => {
|
this.elements.packageOptions.forEach((option) => {
|
||||||
const packageUrl = option.dataset.packageUrl;
|
const packageUrl = option.dataset.packageUrl;
|
||||||
const isSelected = this.selectedPackages.some(pkg => pkg.url === packageUrl);
|
const isSelected = this.selectedPackages.some(
|
||||||
const checkmark = option.querySelector('.package-checkmark');
|
(pkg) => pkg.url === packageUrl,
|
||||||
|
);
|
||||||
|
const checkmark = option.querySelector(".package-checkmark");
|
||||||
|
|
||||||
if (checkmark) {
|
if (checkmark) {
|
||||||
checkmark.classList.toggle('hidden', !isSelected);
|
checkmark.classList.toggle("hidden", !isSelected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
getSelectedPackages() {
|
getSelectedPackages() {
|
||||||
return this.selectedPackages.map(pkg => pkg.url);
|
return this.selectedPackages.map((pkg) => pkg.url);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Modal and Search Management
|
// Modal and Search Management
|
||||||
const modal = document.getElementById('search_modal');
|
const modal = document.getElementById("search_modal");
|
||||||
const searchbar = document.getElementById('modal_searchbar');
|
const searchbar = document.getElementById("modal_searchbar");
|
||||||
const searchButton = document.getElementById('modal_search_button');
|
const searchButton = document.getElementById("modal_search_button");
|
||||||
const modeButton = document.getElementById('modal_mode_button');
|
const modeButton = document.getElementById("modal_mode_button");
|
||||||
const resultsDiv = document.getElementById('search_results');
|
const resultsDiv = document.getElementById("search_results");
|
||||||
const loadingDiv = document.getElementById('search_loading');
|
const loadingDiv = document.getElementById("search_loading");
|
||||||
|
|
||||||
// MutationObserver to detect when modal opens
|
// MutationObserver to detect when modal opens
|
||||||
const observer = new MutationObserver((mutations) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
mutations.forEach((mutation) => {
|
mutations.forEach((mutation) => {
|
||||||
if (mutation.attributeName === 'open' && modal.open) {
|
if (mutation.attributeName === "open" && modal.open) {
|
||||||
PackageSelector.render();
|
PackageSelector.render();
|
||||||
// Delay focus to allow CSS transitions to complete (modal has 0.3s transition)
|
// Delay focus to allow CSS transitions to complete (modal has 0.3s transition)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -366,33 +490,54 @@
|
|||||||
|
|
||||||
observer.observe(modal, { attributes: true });
|
observer.observe(modal, { attributes: true });
|
||||||
|
|
||||||
// Clear results when modal closes
|
// Close modal when clicking outside (on the backdrop)
|
||||||
modal.addEventListener('close', function() {
|
// According to DaisyUI docs: https://daisyui.com/components/modal/
|
||||||
resultsDiv.innerHTML = '';
|
// The backdrop form with method="dialog" should handle closing automatically when its button is clicked.
|
||||||
loadingDiv.classList.add('hidden');
|
// We also handle clicks directly on the dialog element (backdrop area) or the backdrop form.
|
||||||
searchbar.value = '';
|
modal.addEventListener("click", function (event) {
|
||||||
|
const backdrop = modal.querySelector(".modal-backdrop");
|
||||||
|
const modalBox = modal.querySelector(".modal-box");
|
||||||
|
|
||||||
|
// Close if clicking directly on the dialog element (backdrop area)
|
||||||
|
// or on the backdrop form (but ensure we're not clicking on modal-box content)
|
||||||
|
if (
|
||||||
|
event.target === modal ||
|
||||||
|
(backdrop &&
|
||||||
|
(event.target === backdrop || backdrop.contains(event.target)) &&
|
||||||
|
!modalBox.contains(event.target))
|
||||||
|
) {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear results when modal closes
|
||||||
|
modal.addEventListener("close", function () {
|
||||||
|
resultsDiv.innerHTML = "";
|
||||||
|
loadingDiv.classList.add("hidden");
|
||||||
|
searchbar.value = "";
|
||||||
|
});
|
||||||
|
|
||||||
// Mode dropdown handlers
|
// Mode dropdown handlers
|
||||||
const dropdownMenu = document.getElementById('search_dropdown_menu');
|
const dropdownMenu = document.getElementById("search_dropdown_menu");
|
||||||
|
|
||||||
const modeButtons = [
|
const modeButtons = [
|
||||||
{ id: 'modal_dropdown_text', text: 'Text' },
|
{ id: "modal_dropdown_text", text: "Text" },
|
||||||
{ id: 'modal_dropdown_smiles_default', text: 'Default' },
|
{ id: "modal_dropdown_smiles_default", text: "Default" },
|
||||||
{ id: 'modal_dropdown_smiles_canonical', text: 'Canonical' },
|
{ id: "modal_dropdown_smiles_canonical", text: "Canonical" },
|
||||||
{ id: 'modal_dropdown_smiles_exact', text: 'Exact' },
|
{ id: "modal_dropdown_smiles_exact", text: "Exact" },
|
||||||
{ id: 'modal_dropdown_inchikey', text: 'InChIKey' }
|
{ id: "modal_dropdown_inchikey", text: "InChIKey" },
|
||||||
];
|
];
|
||||||
|
|
||||||
modeButtons.forEach(({ id, text }) => {
|
modeButtons.forEach(({ id, text }) => {
|
||||||
document.getElementById(id).addEventListener('click', function(e) {
|
document.getElementById(id).addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
modeButton.innerHTML = text + ` <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
modeButton.innerHTML =
|
||||||
|
text +
|
||||||
|
` <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
// Close dropdown using popover API
|
// Close dropdown using popover API
|
||||||
if (dropdownMenu && typeof dropdownMenu.hidePopover === 'function') {
|
if (dropdownMenu && typeof dropdownMenu.hidePopover === "function") {
|
||||||
dropdownMenu.hidePopover();
|
dropdownMenu.hidePopover();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -403,23 +548,29 @@
|
|||||||
|
|
||||||
// Search Response Handler
|
// Search Response Handler
|
||||||
function handleSearchResponse(data) {
|
function handleSearchResponse(data) {
|
||||||
resultsDiv.innerHTML = '';
|
resultsDiv.innerHTML = "";
|
||||||
|
|
||||||
function makeContent(objs) {
|
function makeContent(objs) {
|
||||||
let links = '';
|
let links = "";
|
||||||
objs.forEach(obj => {
|
objs.forEach((obj) => {
|
||||||
links += `<a href="${obj.url}" class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors">${obj.name}</a>`;
|
links += `<a href="${obj.url}" class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors">${obj.name}</a>`;
|
||||||
});
|
});
|
||||||
return links;
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
let allEmpty = true;
|
let allEmpty = true;
|
||||||
let content = '';
|
let content = "";
|
||||||
|
|
||||||
// Category order for better UX
|
// Category order for better UX
|
||||||
const categoryOrder = ['Compounds', 'Compound Structures', 'Rules', 'Reactions', 'Pathways'];
|
const categoryOrder = [
|
||||||
|
"Compounds",
|
||||||
|
"Compound Structures",
|
||||||
|
"Rules",
|
||||||
|
"Reactions",
|
||||||
|
"Pathways",
|
||||||
|
];
|
||||||
|
|
||||||
categoryOrder.forEach(key => {
|
categoryOrder.forEach((key) => {
|
||||||
if (!data[key] || data[key].length < 1) {
|
if (!data[key] || data[key].length < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -464,14 +615,14 @@
|
|||||||
const query = searchbar.value.trim();
|
const query = searchbar.value.trim();
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
console.log('Search phrase empty, won\'t do search');
|
console.log("Search phrase empty, won't do search");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selPacks = PackageSelector.getSelectedPackages();
|
const selPacks = PackageSelector.getSelectedPackages();
|
||||||
|
|
||||||
if (selPacks.length < 1) {
|
if (selPacks.length < 1) {
|
||||||
console.log('No package selected, won\'t do search');
|
console.log("No package selected, won't do search");
|
||||||
resultsDiv.innerHTML = `
|
resultsDiv.innerHTML = `
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@ -486,33 +637,33 @@
|
|||||||
const mode = modeButton.textContent.trim().toLowerCase();
|
const mode = modeButton.textContent.trim().toLowerCase();
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
selPacks.forEach(pack => params.append('packages', pack));
|
selPacks.forEach((pack) => params.append("packages", pack));
|
||||||
params.append('search', query);
|
params.append("search", query);
|
||||||
params.append('mode', mode);
|
params.append("mode", mode);
|
||||||
|
|
||||||
// Show loading
|
// Show loading
|
||||||
loadingDiv.classList.remove('hidden');
|
loadingDiv.classList.remove("hidden");
|
||||||
resultsDiv.innerHTML = '';
|
resultsDiv.innerHTML = "";
|
||||||
|
|
||||||
fetch(`{{ SERVER_BASE }}/search?${params.toString()}`, {
|
fetch(`{{ SERVER_BASE }}/search?${params.toString()}`, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json'
|
Accept: "application/json",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Search request failed');
|
throw new Error("Search request failed");
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then((result) => {
|
||||||
loadingDiv.classList.add('hidden');
|
loadingDiv.classList.add("hidden");
|
||||||
handleSearchResponse(result);
|
handleSearchResponse(result);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
loadingDiv.classList.add('hidden');
|
loadingDiv.classList.add("hidden");
|
||||||
console.error('Search error:', error);
|
console.error("Search error:", error);
|
||||||
resultsDiv.innerHTML = `
|
resultsDiv.innerHTML = `
|
||||||
<div class="alert alert-error">
|
<div class="alert alert-error">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@ -525,11 +676,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Event listeners for search
|
// Event listeners for search
|
||||||
searchButton.addEventListener('click', performSearch);
|
searchButton.addEventListener("click", performSearch);
|
||||||
searchbar.addEventListener('keydown', function(e) {
|
searchbar.addEventListener("keydown", function (e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === "Enter") {
|
||||||
performSearch(e);
|
performSearch(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -2,7 +2,13 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="mx-auto w-full p-8">
|
<div class="mx-auto w-full p-8">
|
||||||
<h1 class="h1 mb-4 text-3xl font-bold">Predict a Pathway</h1>
|
<h1 class="h1 mb-4 text-3xl font-bold">
|
||||||
|
Predict a Pathway
|
||||||
|
|
||||||
|
<span class="text-base-content/50 text-xs"
|
||||||
|
>in <strong>{{ meta.current_package.name|safe }}</strong>
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
id="predict_form"
|
id="predict_form"
|
||||||
|
|||||||
@ -1,197 +0,0 @@
|
|||||||
{% extends "framework.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div id=searchContent>
|
|
||||||
<div id="packSelector">
|
|
||||||
<label>Select Packages</label><br>
|
|
||||||
<select id="selPackages" name="selPackages" data-actions-box='true' class="selPackages" multiple
|
|
||||||
data-width='100%'>
|
|
||||||
{% if unreviewed_objects %}
|
|
||||||
<option disabled>Reviewed Packages</option>
|
|
||||||
{% endif %}
|
|
||||||
{% for obj in reviewed_objects %}
|
|
||||||
<option value="{{ obj.url }}" selected>{{ obj.name|safe }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
{% if unreviewed_objects %}
|
|
||||||
<option disabled>Unreviewed Packages</option>
|
|
||||||
{% endif %}
|
|
||||||
{% for obj in unreviewed_objects %}
|
|
||||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<p></p>
|
|
||||||
<div>
|
|
||||||
<label>Search Term</label><br>
|
|
||||||
<div class="input-group" id="index-form-bar">
|
|
||||||
<input type="text" class="form-control" id='searchbar' placeholder="Benfuracarb">
|
|
||||||
<div class="input-group-btn">
|
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
|
||||||
id="mode-button"
|
|
||||||
aria-haspopup="true" aria-expanded="false">Text <span class="caret"></span></button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li class="dropdown-header">Text</li>
|
|
||||||
<li><a id="dropdown-predict-text-text">Text</a></li>
|
|
||||||
<li class="dropdown-header">SMILES</li>
|
|
||||||
<li><a id="dropdown-search-smiles-default" data-toggle="tooltip">Default</a></li>
|
|
||||||
<li><a id="dropdown-search-smiles-canonical">Canonical</a></li>
|
|
||||||
<li><a id="dropdown-search-smiles-exact">Exact</a></li>
|
|
||||||
<li class="dropdown-header">InChI</li>
|
|
||||||
<li><a id="dropdown-search-inchi-inchikey">InChIKey</a></li>
|
|
||||||
</ul>
|
|
||||||
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="search-button">
|
|
||||||
Go!
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p></p>
|
|
||||||
<div id="results"></div>
|
|
||||||
<p></p>
|
|
||||||
<div id="loading"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function modeDropdownClicked() {
|
|
||||||
var suffix = ' <span class="caret"></span>';
|
|
||||||
var dropdownVal = $(this).text();
|
|
||||||
$('#mode-button').html(dropdownVal + suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSearchResponse(id, data) {
|
|
||||||
content = `
|
|
||||||
<div class='panel-group' id='search-accordion'>
|
|
||||||
<div class='panel panel-default'>
|
|
||||||
<div class='panel-heading' id='headingPanel' style='font-size:2rem;height: 46px'>
|
|
||||||
Results
|
|
||||||
</div>
|
|
||||||
<div id='descDiv'></div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function makeContent(objs) {
|
|
||||||
links = "";
|
|
||||||
for (idx in objs) {
|
|
||||||
obj = objs[idx];
|
|
||||||
links += `<a class='list-group-item' href='${obj.url}'>${obj.name}</a>`
|
|
||||||
}
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
allEmpty = true;
|
|
||||||
for (key in data) {
|
|
||||||
if (key === 'searchterm') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data[key].length < 1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
allEmpty = false;
|
|
||||||
content += `
|
|
||||||
<div class='panel panel-default panel-heading list-group-item' style='background-color:silver'>
|
|
||||||
<h4 class='panel-title'>
|
|
||||||
<a id='${key}_link' data-toggle='collapse' data-parent='#search-accordion' href='#${key}_panel'>
|
|
||||||
${key}
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id='${key}_panel' class='panel-collapse collapse in'>
|
|
||||||
<div class='panel-body list-group-item'>
|
|
||||||
${makeContent(data[key])}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
if (allEmpty) {
|
|
||||||
$('#' + id).append('<div class="alert alert-danger" role="alert"><p>' + "No results..." + '</p></div>');
|
|
||||||
} else {
|
|
||||||
$('#' + id).append(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function search(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
query = $("#searchbar").val()
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
// Nothing to search...
|
|
||||||
console.log("Search phrase empty, won't do search")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var selPacks = [];
|
|
||||||
$("#selPackages :selected").each(function () {
|
|
||||||
var id = this.value;
|
|
||||||
selPacks.push(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selPacks.length < 1) {
|
|
||||||
console.log("No package selected, won't do search")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mode = $('#mode-button').text().trim().toLowerCase();
|
|
||||||
|
|
||||||
var par = {};
|
|
||||||
par['packages'] = selPacks;
|
|
||||||
par['search'] = query;
|
|
||||||
par['mode'] = mode;
|
|
||||||
|
|
||||||
console.log(par);
|
|
||||||
|
|
||||||
var queryString = $.param(par, true);
|
|
||||||
|
|
||||||
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
|
|
||||||
|
|
||||||
$("#results").empty();
|
|
||||||
|
|
||||||
$.getJSON("{{ SERVER_BASE }}/search?" + queryString, function (result) {
|
|
||||||
handleSearchResponse("results", result);
|
|
||||||
$("#loading").empty();
|
|
||||||
}).fail(function (d) {
|
|
||||||
$("#loading").empty();
|
|
||||||
console.log(d.responseText);
|
|
||||||
handleError(JSON.parse(d.responseText));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
tooltips = {
|
|
||||||
'dropdown-predict-text-text': 'The inserted pattern will be searched on all enviPath object names and descriptions',
|
|
||||||
'dropdown-search-smiles-default': 'Search by SMILES, stereochemistry and charge are ignored',
|
|
||||||
'dropdown-search-smiles-canonical': 'Search by SMILES, stereochemistry is ignored but charge is preserved',
|
|
||||||
'dropdown-search-smiles-exact': 'Search by SMILES, exact match for stereochemistry and charge',
|
|
||||||
'dropdown-search-inchi-inchikey': 'Search by InChIKey',
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(tooltips).forEach(key => {
|
|
||||||
$('#' + key).tooltip({
|
|
||||||
placement: "top",
|
|
||||||
title: tooltips[key]
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#' + key).on('click', modeDropdownClicked);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#selPackages").selectpicker();
|
|
||||||
$("#search-button").on("click", search);
|
|
||||||
|
|
||||||
$("#searchbar").on("keydown", function (e) {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
search(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
{% if search_result %}
|
|
||||||
$('#searchbar').val('{{ search_result.searchterm }}')
|
|
||||||
handleSearchResponse("results", {{ search_result|safe }});
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock content %}
|
|
||||||
Reference in New Issue
Block a user