forked from enviPath/enviPy
Compare commits
67 Commits
enhancemen
...
feature/fr
| Author | SHA1 | Date | |
|---|---|---|---|
| 34d5318534 | |||
| 34d3b75c6f | |||
| b9ac713cb2 | |||
| 05bee9b718 | |||
| 0d9947e6ce | |||
| e3b381ab41 | |||
| 97626337aa | |||
| 2aded2ddd7 | |||
| f5133c1980 | |||
| 7fbc49afd3 | |||
| a087a518f6 | |||
| 881e0e6798 | |||
| 2eab66e9ee | |||
| ab927b11a2 | |||
| fde60c3ad3 | |||
| 61a43da822 | |||
| 211ebfd19b | |||
| 06a6c23d05 | |||
| 3536a14e47 | |||
| 7eb4029ac9 | |||
| 7b38fc2e37 | |||
| 4834348454 | |||
| 0a52b12f02 | |||
| 14571d23a6 | |||
| ea8475f0e2 | |||
| 442d139217 | |||
| 1ba511a31d | |||
| 5d89341955 | |||
| 5f390ac2d2 | |||
| 46d21e60d2 | |||
| 13be240226 | |||
| 167a72f5a3 | |||
| 1736319bd7 | |||
| e87aae6bf7 | |||
| 253523c81f | |||
| 15809a4ccf | |||
| b7e1dac66a | |||
| 849ebbe7f8 | |||
| c5dcb36452 | |||
| 16a991220a | |||
| 05c8e130b1 | |||
| 4fd7856043 | |||
| f5efaf1b3f | |||
| 4a2ef3a237 | |||
| d097013853 | |||
| 63cc7cf460 | |||
| 9ca94eeb42 | |||
| de1dc75a12 | |||
| 43a034427d | |||
| d98b60fc57 | |||
| 86c3f9b808 | |||
| 4408a09e82 | |||
| f5889b270a | |||
| f76861a83f | |||
| 110fbe12c1 | |||
| d8dc9598ff | |||
| b7e4abae1c | |||
| 2fa0be52ef | |||
| be5ff369e0 | |||
| ee776e7d14 | |||
| f27bc56379 | |||
| 547eac3411 | |||
| 9e66169d23 | |||
| 9a43942f12 | |||
| 1bad6e4b03 | |||
| ad38704ffe | |||
| 0775ad30d4 |
@ -105,7 +105,7 @@ jobs:
|
|||||||
until pg_isready -h postgres -U postgres; do sleep 2; done
|
until pg_isready -h postgres -U postgres; do sleep 2; done
|
||||||
# until redis-cli -h redis ping; do sleep 2; done
|
# until redis-cli -h redis ping; do sleep 2; done
|
||||||
|
|
||||||
- name: Run Django Migrations
|
- name: Run Django migrations
|
||||||
run: |
|
run: |
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
python manage.py migrate --noinput
|
python manage.py migrate --noinput
|
||||||
|
|||||||
@ -87,7 +87,6 @@ TEMPLATES = [
|
|||||||
"django.template.context_processors.request",
|
"django.template.context_processors.request",
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"epdb.context_processors.package_context",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -21,7 +21,6 @@ from .models import (
|
|||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
ExternalIdentifier,
|
ExternalIdentifier,
|
||||||
JobLog,
|
JobLog,
|
||||||
License,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -63,10 +62,6 @@ class EnviFormerAdmin(EPAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LicenseAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ["cc_string", "link", "image_link"]
|
|
||||||
|
|
||||||
|
|
||||||
class CompoundAdmin(EPAdmin):
|
class CompoundAdmin(EPAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -123,7 +118,6 @@ admin.site.register(JobLog, JobLogAdmin)
|
|||||||
admin.site.register(Package, PackageAdmin)
|
admin.site.register(Package, PackageAdmin)
|
||||||
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||||
admin.site.register(EnviFormer, EnviFormerAdmin)
|
admin.site.register(EnviFormer, EnviFormerAdmin)
|
||||||
admin.site.register(License, LicenseAdmin)
|
|
||||||
admin.site.register(Compound, CompoundAdmin)
|
admin.site.register(Compound, CompoundAdmin)
|
||||||
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||||
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
"""
|
|
||||||
Context processors for enviPy application.
|
|
||||||
|
|
||||||
Context processors automatically make variables available to all templates.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .logic import PackageManager
|
|
||||||
from .models import Package
|
|
||||||
|
|
||||||
|
|
||||||
def package_context(request):
|
|
||||||
"""
|
|
||||||
Provides package data for the search modal which is included globally
|
|
||||||
in framework_modern.html.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Context dictionary with reviewed and unreviewed packages
|
|
||||||
"""
|
|
||||||
current_user = request.user
|
|
||||||
|
|
||||||
reviewed_package_qs = PackageManager.get_reviewed_packages()
|
|
||||||
|
|
||||||
unreviewed_package_qs = Package.objects.none()
|
|
||||||
|
|
||||||
# Only get user-specific packages if user is authenticated
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"reviewed_packages": reviewed_package_qs,
|
|
||||||
"unreviewed_packages": unreviewed_package_qs,
|
|
||||||
}
|
|
||||||
@ -12,16 +12,10 @@ from epdb.models import (
|
|||||||
Permission,
|
Permission,
|
||||||
User,
|
User,
|
||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
License,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument(
|
|
||||||
"-ol", "--only-licenses", action="store_true", help="Only create licenses."
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_users(self):
|
def create_users(self):
|
||||||
# Anonymous User
|
# Anonymous User
|
||||||
if not User.objects.filter(email="anon@envipath.com").exists():
|
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||||
@ -89,17 +83,6 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
return anon, admin, g, user0
|
return anon, admin, g, user0
|
||||||
|
|
||||||
def create_licenses(self):
|
|
||||||
"""Create the six default licenses supported by enviPath"""
|
|
||||||
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
|
||||||
for cc_string in cc_strings:
|
|
||||||
if not License.objects.filter(cc_string=cc_string).exists():
|
|
||||||
new_license = License()
|
|
||||||
new_license.cc_string = cc_string
|
|
||||||
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
|
||||||
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
|
||||||
new_license.save()
|
|
||||||
|
|
||||||
def import_package(self, data, owner):
|
def import_package(self, data, owner):
|
||||||
return PackageManager.import_legacy_package(
|
return PackageManager.import_legacy_package(
|
||||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||||
@ -174,10 +157,6 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
# Create licenses
|
|
||||||
self.create_licenses()
|
|
||||||
if options.get("only_licenses", False):
|
|
||||||
return
|
|
||||||
# Create users
|
# Create users
|
||||||
anon, admin, g, user0 = self.create_users()
|
anon, admin, g, user0 = self.create_users()
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-11 14:11
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0009_joblog"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="license",
|
|
||||||
name="cc_string",
|
|
||||||
field=models.TextField(default="by-nc-sa", verbose_name="CC string"),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-11 14:13
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from django.contrib.postgres.aggregates import ArrayAgg
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db.models import Min
|
|
||||||
|
|
||||||
|
|
||||||
def set_cc(apps, schema_editor):
|
|
||||||
License = apps.get_model("epdb", "License")
|
|
||||||
|
|
||||||
# For all existing licenses extract cc_string from link
|
|
||||||
for license in License.objects.all():
|
|
||||||
pattern = r"/licenses/([^/]+)/4\.0"
|
|
||||||
match = re.search(pattern, license.link)
|
|
||||||
if match:
|
|
||||||
license.cc_string = match.group(1)
|
|
||||||
license.save()
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Could not find license for {license.link}")
|
|
||||||
|
|
||||||
# Ensure we have all licenses
|
|
||||||
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
|
||||||
for cc_string in cc_strings:
|
|
||||||
if not License.objects.filter(cc_string=cc_string).exists():
|
|
||||||
new_license = License()
|
|
||||||
new_license.cc_string = cc_string
|
|
||||||
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
|
||||||
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
|
||||||
new_license.save()
|
|
||||||
|
|
||||||
# As we might have existing Licenses representing the same License,
|
|
||||||
# get min pk and all pks as a list
|
|
||||||
license_lookup_qs = License.objects.values("cc_string").annotate(
|
|
||||||
lowest_pk=Min("id"), all_pks=ArrayAgg("id", order_by=("id",))
|
|
||||||
)
|
|
||||||
|
|
||||||
license_lookup = {
|
|
||||||
row["cc_string"]: (row["lowest_pk"], row["all_pks"]) for row in license_lookup_qs
|
|
||||||
}
|
|
||||||
|
|
||||||
Packages = apps.get_model("epdb", "Package")
|
|
||||||
|
|
||||||
for k, v in license_lookup.items():
|
|
||||||
# Set min pk to all packages pointing to any of the duplicates
|
|
||||||
Packages.objects.filter(pk__in=v[1]).update(license_id=v[0])
|
|
||||||
# remove the min pk from "other" pks as we use them for deletion
|
|
||||||
v[1].remove(v[0])
|
|
||||||
# Delete redundant License objects
|
|
||||||
License.objects.filter(pk__in=v[1]).delete()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0010_license_cc_string"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [migrations.RunPython(set_cc)]
|
|
||||||
@ -655,7 +655,6 @@ class ScenarioMixin(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class License(models.Model):
|
class License(models.Model):
|
||||||
cc_string = models.TextField(blank=False, null=False, verbose_name="CC string")
|
|
||||||
link = models.URLField(blank=False, null=False, verbose_name="link")
|
link = models.URLField(blank=False, null=False, verbose_name="link")
|
||||||
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,6 @@ 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
|
||||||
|
|||||||
@ -362,18 +362,6 @@ 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)
|
||||||
|
|
||||||
@ -694,6 +682,7 @@ def search(request):
|
|||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
package_urls = request.GET.getlist("packages")
|
package_urls = request.GET.getlist("packages")
|
||||||
searchterm = request.GET.get("search", "").strip()
|
searchterm = request.GET.get("search", "").strip()
|
||||||
|
|
||||||
mode = request.GET.get("mode")
|
mode = request.GET.get("mode")
|
||||||
|
|
||||||
# add HTTP_ACCEPT check to differentiate between index and ajax call
|
# add HTTP_ACCEPT check to differentiate between index and ajax call
|
||||||
@ -794,7 +783,6 @@ def package_models(request, package_uuid):
|
|||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
log_post_params(request)
|
log_post_params(request)
|
||||||
|
|
||||||
name = request.POST.get("model-name")
|
name = request.POST.get("model-name")
|
||||||
description = request.POST.get("model-description")
|
description = request.POST.get("model-description")
|
||||||
|
|
||||||
@ -1084,7 +1072,9 @@ def package(request, package_uuid):
|
|||||||
write = request.POST.get("write") == "on"
|
write = request.POST.get("write") == "on"
|
||||||
owner = request.POST.get("owner") == "on"
|
owner = request.POST.get("owner") == "on"
|
||||||
|
|
||||||
cc_string = request.POST.get("license")
|
license = request.POST.get("license")
|
||||||
|
license_link = request.POST.get("license-link")
|
||||||
|
license_image_link = request.POST.get("license-image-link")
|
||||||
|
|
||||||
if new_package_name:
|
if new_package_name:
|
||||||
current_package.name = new_package_name
|
current_package.name = new_package_name
|
||||||
@ -1112,15 +1102,24 @@ def package(request, package_uuid):
|
|||||||
|
|
||||||
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
|
elif license is not None:
|
||||||
|
if license == "no-license":
|
||||||
|
if current_package.license is not None:
|
||||||
|
current_package.license.delete()
|
||||||
|
|
||||||
elif cc_string is not None:
|
|
||||||
cc_string = cc_string.strip()
|
|
||||||
if cc_string == "no-license": # Reset the package's license
|
|
||||||
current_package.license = None
|
current_package.license = None
|
||||||
current_package.save()
|
current_package.save()
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
else: # Get the license and assign it to the package
|
else:
|
||||||
current_package.license = License.objects.get(cc_string=cc_string)
|
if current_package.license is not None:
|
||||||
|
current_package.license.delete()
|
||||||
|
|
||||||
|
license = License()
|
||||||
|
license.link = license_link
|
||||||
|
license.image_link = license_image_link
|
||||||
|
license.save()
|
||||||
|
|
||||||
|
current_package.license = license
|
||||||
current_package.save()
|
current_package.save()
|
||||||
|
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
@ -1175,7 +1174,7 @@ def package_compounds(request, package_uuid):
|
|||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
compound_name = request.POST.get("compound-name")
|
compound_name = request.POST.get("compound-name")
|
||||||
compound_smiles = request.POST.get("compound-smiles")
|
compound_smiles = request.POST.get("compound-smiles").strip()
|
||||||
compound_description = request.POST.get("compound-description")
|
compound_description = request.POST.get("compound-description")
|
||||||
|
|
||||||
c = Compound.create(current_package, compound_smiles, compound_name, compound_description)
|
c = Compound.create(current_package, compound_smiles, compound_name, compound_description)
|
||||||
@ -1302,7 +1301,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")
|
structure_smiles = request.POST.get("structure-smiles").strip()
|
||||||
structure_description = request.POST.get("structure-description")
|
structure_description = request.POST.get("structure-description")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1484,11 +1483,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")
|
params["smirks"] = request.POST.get("rule-smirks").strip()
|
||||||
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")
|
params["reaction_smarts"] = request.POST.get("rule-reaction-smarts").strip()
|
||||||
elif rule_type == "ParallelRule":
|
elif rule_type == "ParallelRule":
|
||||||
pass
|
pass
|
||||||
elif rule_type == "SequentialRule":
|
elif rule_type == "SequentialRule":
|
||||||
@ -1685,7 +1684,8 @@ 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")
|
|
||||||
|
reactions_smirks = request.POST.get("reaction-smirks").strip()
|
||||||
educts = reactions_smirks.split(">>")[0].split(".")
|
educts = reactions_smirks.split(">>")[0].split(".")
|
||||||
products = reactions_smirks.split(">>")[1].split(".")
|
products = reactions_smirks.split(">>")[1].split(".")
|
||||||
|
|
||||||
@ -2259,7 +2259,6 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
|
|||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
log_post_params(request)
|
log_post_params(request)
|
||||||
|
|
||||||
edge_name = request.POST.get("edge-name")
|
edge_name = request.POST.get("edge-name")
|
||||||
edge_description = request.POST.get("edge-description")
|
edge_description = request.POST.get("edge-description")
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,6 @@
|
|||||||
/* Import DaisyUI plugin */
|
/* Import DaisyUI plugin */
|
||||||
@plugin "daisyui" {
|
@plugin "daisyui" {
|
||||||
logs: true;
|
logs: true;
|
||||||
exclude: rootscrollgutter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@import "./daisyui-theme.css";
|
@import "./daisyui-theme.css";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ meta.server_url }}/predict">
|
<a role="button" data-toggle="modal" data-target="#predict_modal">
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
|
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,15 +1,11 @@
|
|||||||
<!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
|
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
|
||||||
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 #}
|
||||||
@ -17,45 +13,36 @@
|
|||||||
|
|
||||||
{# 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
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||||
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
|
<link href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
||||||
href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"
|
<link rel="stylesheet"
|
||||||
rel="stylesheet"
|
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css">
|
||||||
/>
|
|
||||||
<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 -->
|
||||||
|
|
||||||
{# Bootstrap compatibility styles #}
|
{# Bootstrap compatibility styles #}
|
||||||
<style>
|
<style>
|
||||||
/* Ensure proper viewport behavior */
|
/* Ensure proper viewport behavior */
|
||||||
html,
|
html, body {
|
||||||
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({
|
||||||
beforeSend: function (xhr, settings) {
|
beforeSend: function(xhr, settings) {
|
||||||
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>
|
||||||
|
|
||||||
<!-- {# C3 CSS #}-->
|
<!-- {# C3 CSS #}-->
|
||||||
@ -63,44 +50,40 @@
|
|||||||
<!-- {# 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 #}
|
||||||
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
|
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
|
||||||
|
|
||||||
{% 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,
|
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
||||||
g = d.createElement("script"),
|
g.async = true;
|
||||||
s = d.getElementsByTagName("script")[0];
|
g.src = u + 'matomo.js';
|
||||||
g.async = true;
|
s.parentNode.insertBefore(g, s);
|
||||||
g.src = u + "matomo.js";
|
})();
|
||||||
s.parentNode.insertBefore(g, s);
|
</script>
|
||||||
})();
|
<!-- End Matomo Code -->
|
||||||
</script>
|
|
||||||
<!-- End Matomo Code -->
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</head>
|
|
||||||
<body>
|
</head>
|
||||||
<!-- Legacy Bootstrap navbar - isolated from Tailwind -->
|
<body>
|
||||||
<div class="legacy-bootstrap">
|
<!-- Legacy Bootstrap navbar - isolated from Tailwind -->
|
||||||
<nav
|
<div class="legacy-bootstrap">
|
||||||
class="navbar navbar-default navbar-inverse"
|
<nav class="navbar navbar-default navbar-inverse" style="border-radius:0px;" role="navigation">
|
||||||
style="border-radius:0px;"
|
<div class="container-fluid">
|
||||||
role="navigation"
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
>
|
<div class="navbar-header navbar-header-framework">
|
||||||
<div class="container-fluid">
|
|
||||||
<!-- Brand and toggle get grouped for better mobile display -->
|
|
||||||
<div class="navbar-header navbar-header-framework">
|
|
||||||
<!-- <button type="button" class="navbar-toggle navbar-toggle-framework" data-toggle="collapse"-->
|
<!-- <button type="button" class="navbar-toggle navbar-toggle-framework" data-toggle="collapse"-->
|
||||||
<!-- data-target="#navbarCollapse">-->
|
<!-- data-target="#navbarCollapse">-->
|
||||||
<!-- <span class="sr-only">Toggle navigation</span>-->
|
<!-- <span class="sr-only">Toggle navigation</span>-->
|
||||||
@ -108,315 +91,204 @@
|
|||||||
<!-- <span class="icon-bar"></span>-->
|
<!-- <span class="icon-bar"></span>-->
|
||||||
<!-- <span class="icon-bar"></span>-->
|
<!-- <span class="icon-bar"></span>-->
|
||||||
<!-- </button>-->
|
<!-- </button>-->
|
||||||
<a
|
<a id="pictureLink" href="{{ meta.server_url }}" class="navbar-brand">
|
||||||
id="pictureLink"
|
<img id="image-logo-short-white.svg" src='{% static "/images/logo-short-white.svg" %}' width="100"
|
||||||
href="{{ meta.server_url }}"
|
alt="enviPath">
|
||||||
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
|
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
||||||
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>
|
|
||||||
<a href="{{ meta.server_url }}/predict"> Predict Pathway </a>
|
|
||||||
</li>
|
|
||||||
{# <li class="dropdown">#}
|
|
||||||
{# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
|
|
||||||
{# <ul role="menu" class="dropdown-menu">#}
|
|
||||||
{# <li>#}
|
|
||||||
{# <a class="button" data-toggle="modal" data-target="#predict_modal">#}
|
|
||||||
{# <i class=" glyphicon glyphicon-tag"></i> Predict Pathway#}
|
|
||||||
{# </a>#}
|
|
||||||
{# </li>#}
|
|
||||||
{# <li>#}
|
|
||||||
{# <a class="button" data-toggle="modal" data-target="#batch_predict_modal">#}
|
|
||||||
{# <i class=" glyphicon glyphicon-tags"></i> Batch Prediction#}
|
|
||||||
{# </a>#}
|
|
||||||
{# </li>#}
|
|
||||||
{# </ul>#}
|
|
||||||
{# </li>#}
|
|
||||||
<li>
|
|
||||||
<a href="{{ meta.server_url }}/package" id="packageLink"
|
|
||||||
>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">
|
|
||||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#"
|
|
||||||
>Browse Data<b class="caret"></b
|
|
||||||
></a>
|
|
||||||
<ul role="menu" class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a href="{{ meta.server_url }}/pathway" id="pathwayLink"
|
|
||||||
>Pathway</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 }}/user" id="userLink">User</a></li>#}
|
|
||||||
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul
|
|
||||||
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">
|
|
||||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#"
|
|
||||||
>Info <b class="caret"></b
|
|
||||||
></a>
|
|
||||||
<ul role="menu" class="dropdown-menu">
|
|
||||||
<!--<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 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 class="divider"></li>
|
|
||||||
<li><a>Version: {{ meta.version }}</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
{% if meta.user.username == 'anonymous' %}
|
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a href="#" data-toggle="modal" data-target="#predict_modal">
|
||||||
href="{% url 'login' %}"
|
Predict Pathway
|
||||||
id="loginButton"
|
</a>
|
||||||
style="margin-right:10px"
|
|
||||||
>Login</a
|
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{# <li class="dropdown">#}
|
||||||
|
{# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
|
||||||
|
{# <ul role="menu" class="dropdown-menu">#}
|
||||||
|
{# <li>#}
|
||||||
|
{# <a class="button" data-toggle="modal" data-target="#predict_modal">#}
|
||||||
|
{# <i class=" glyphicon glyphicon-tag"></i> Predict Pathway#}
|
||||||
|
{# </a>#}
|
||||||
|
{# </li>#}
|
||||||
|
{# <li>#}
|
||||||
|
{# <a class="button" data-toggle="modal" data-target="#batch_predict_modal">#}
|
||||||
|
{# <i class=" glyphicon glyphicon-tags"></i> Batch Prediction#}
|
||||||
|
{# </a>#}
|
||||||
|
{# </li>#}
|
||||||
|
{# </ul>#}
|
||||||
|
{# </li>#}
|
||||||
|
<li><a href="{{ meta.server_url }}/package" id="packageLink">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
|
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Browse Data<b class="caret"></b></a>
|
||||||
data-toggle="dropdown"
|
<ul role="menu" class="dropdown-menu">
|
||||||
id="loggedInButton"
|
<li><a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a></li>
|
||||||
class="dropdown-toggle"
|
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li>
|
||||||
id="logedInButton"
|
<li><a href="{{ meta.server_url }}/compound" id="compoundLink">Compound</a></li>
|
||||||
href="#"
|
<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>
|
||||||
<div id="username">
|
<li><a href="{{ meta.server_url }}/scenario" id="scenarioLink">Scenario</a></li>
|
||||||
{{ user.username }}<b class="caret"></b>
|
{# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#}
|
||||||
</div>
|
{# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#}
|
||||||
</a>
|
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
|
||||||
<ul role="menu" class="dropdown-menu">
|
</ul>
|
||||||
<li>
|
|
||||||
<a href="{{ meta.user.url }}" id="accountbutton"
|
|
||||||
>My Account</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="divider"></li>
|
|
||||||
<form
|
|
||||||
class="navbar-form navbar-left navbar-left-framework"
|
|
||||||
role="logout"
|
|
||||||
action="{% url 'logout' %}"
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="hidden" name="logout" value="true" />
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-default">
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<!-- End legacy Bootstrap navbar -->
|
|
||||||
|
|
||||||
<div id="docContent" class="content container">
|
</ul>
|
||||||
{% if breadcrumbs %}
|
|
||||||
<div id="bread">
|
<ul class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework">
|
||||||
<ol class="breadcrumb">
|
<li><a href="https://community.envipath.org/" id="communityLink">Community</a></li>
|
||||||
{% for elem in breadcrumbs %}
|
<li class="dropdown">
|
||||||
{% for name, url in elem.items %}
|
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
|
||||||
{% if forloop.parentloop.last %}
|
<ul role="menu" class="dropdown-menu">
|
||||||
<li class="active">{{ name }}</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 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 class="divider"></li>
|
||||||
|
<li><a>Version: {{ meta.version }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% if meta.user.username == 'anonymous' %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'login' %}" id="loginButton" style="margin-right:10px">Login</a>
|
||||||
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>
|
<li class="dropdown">
|
||||||
<a href="{{ url }}">{{ name }}</a>
|
<a data-toggle="dropdown" id="loggedInButton" class="dropdown-toggle" id="logedInButton"
|
||||||
</li>
|
href="#">
|
||||||
|
<div id="username">
|
||||||
|
{{ user.username }}<b class="caret"></b>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<ul role="menu" class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.user.url }}" id="accountbutton">My Account</a>
|
||||||
|
</li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<form class="navbar-form navbar-left navbar-left-framework" role="logout"
|
||||||
|
action="{% url 'logout' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="hidden" name="logout" value="true">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-default">Logout</button>
|
||||||
|
</form>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
</ul>
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
{% if message %}
|
</nav>
|
||||||
<div id="message">{{ message }}</div>
|
</div>
|
||||||
{% endif %}
|
<!-- End legacy Bootstrap navbar -->
|
||||||
{% block content %}
|
|
||||||
{% endblock content %}
|
<div id="docContent" class="content container">
|
||||||
{% if meta.url_contains_package and meta.current_package.license %}
|
{% if breadcrumbs %}
|
||||||
|
<div id="bread">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
{% for elem in breadcrumbs %}
|
||||||
|
{% for name, url in elem.items %}
|
||||||
|
{% if forloop.parentloop.last %}
|
||||||
|
<li class="active">{{ name }}</li>
|
||||||
|
{% else %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url }}">{{ name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if message %}
|
||||||
|
<div id="message">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% block content %}
|
||||||
|
{% endblock content %}
|
||||||
|
{% 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
|
<div class="panel panel-default list-group-item" style="background-color:#f5f5f5">
|
||||||
class="panel panel-default list-group-item"
|
<div class="panel-title">
|
||||||
style="background-color:#f5f5f5"
|
<a data-toggle="collapse" data-parent="#licence_accordion" href="#license">License</a>
|
||||||
>
|
</div>
|
||||||
<div class="panel-title">
|
|
||||||
<a
|
|
||||||
data-toggle="collapse"
|
|
||||||
data-parent="#licence_accordion"
|
|
||||||
href="#license"
|
|
||||||
>License</a
|
|
||||||
>
|
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FOOTER - Legacy Bootstrap -->
|
<!-- FOOTER - Legacy Bootstrap -->
|
||||||
<div class="legacy-bootstrap">
|
<div class="legacy-bootstrap">
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<hr />
|
<hr/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
|
||||||
<ul class="nav nav-pills nav-justified">
|
|
||||||
<li>
|
|
||||||
<a href="http://ml.auckland.ac.nz" target="_blank">
|
|
||||||
<img
|
|
||||||
id="image-uoalogo"
|
|
||||||
height="60"
|
|
||||||
src="{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}"
|
|
||||||
alt="The Univserity of Auckland"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://eawag.ch" target="_blank">
|
|
||||||
<img
|
|
||||||
id="image-ealogo"
|
|
||||||
height="60"
|
|
||||||
src="{% static "/images/ealogo.gif" %}"
|
|
||||||
alt="Eawag"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://www.uzh.ch/" target="_blank">
|
|
||||||
<img
|
|
||||||
id="image-ufzlogo"
|
|
||||||
height="60"
|
|
||||||
src="{% static "/images/uzh-logo.svg" %}"
|
|
||||||
alt="University of Zurich"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<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="http://ml.auckland.ac.nz" target="_blank">
|
||||||
|
<img id="image-uoalogo" height="60" src='{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}'
|
||||||
|
alt="The Univserity of Auckland"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://eawag.ch" target="_blank">
|
||||||
|
<img id="image-ealogo" height="60" src='{% static "/images/ealogo.gif" %}' alt="Eawag"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.uzh.ch/" target="_blank">
|
||||||
|
<img id="image-ufzlogo" height="60" src='{% static "/images/uzh-logo.svg" %}'
|
||||||
|
alt="University of Zurich"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<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>
|
<li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li>
|
||||||
<a href="mailto:admin@envipath.org" target="_blank">Contact</a>
|
|
||||||
</li>
|
|
||||||
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschränkt) & Co. KG ©-->
|
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschränkt) & Co. KG ©-->
|
||||||
<!-- {{ YEAR }}</a></li>-->
|
<!-- {{ YEAR }}</a></li>-->
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- End legacy Bootstrap footer -->
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End legacy Bootstrap footer -->
|
||||||
|
|
||||||
<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>
|
||||||
{% block modals %}
|
{% block modals %}
|
||||||
{% include "modals/cite_modal.html" %}
|
{% include "modals/cite_modal.html" %}
|
||||||
{% include "modals/predict_modal.html" %}
|
{% include "modals/signup_modal.html" %}
|
||||||
{% include "modals/batch_predict_modal.html" %}
|
{% include "modals/predict_modal.html" %}
|
||||||
{% endblock %}
|
{% include "modals/batch_predict_modal.html" %}
|
||||||
</body>
|
{% endblock %}
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,49 +1,38 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html data-theme="envipath" lang="en">
|
<html data-theme="envipath" lang="en">
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<head>
|
<head>
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="csrf-token" content="{{ csrf_token }}" />
|
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||||
|
|
||||||
{# Favicon #}
|
{# Favicon #}
|
||||||
<link
|
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/png"
|
|
||||||
href="{% static 'images/favicon.ico' %}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{# Tailwind CSS + DaisyUI Output #}
|
{# Tailwind CSS + DaisyUI Output #}
|
||||||
<link
|
<link href="{% static 'css/output.css' %}" rel="stylesheet" type="text/css"/>
|
||||||
href="{% static 'css/output.css' %}"
|
|
||||||
rel="stylesheet"
|
|
||||||
type="text/css"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{# jQuery - Keep for compatibility with existing JS #}
|
{# jQuery - Keep for compatibility with existing JS #}
|
||||||
<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>
|
||||||
|
|
||||||
{# Font Awesome #}
|
{# Font Awesome #}
|
||||||
<link
|
<link href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
||||||
href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{# Discourse embed for community #}
|
{# Discourse embed for community #}
|
||||||
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
|
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
|
||||||
|
|
||||||
<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({
|
||||||
beforeSend: function (xhr, settings) {
|
beforeSend: function(xhr, settings) {
|
||||||
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>
|
||||||
|
|
||||||
{# General EP JS #}
|
{# General EP JS #}
|
||||||
@ -52,155 +41,136 @@
|
|||||||
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
|
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
|
||||||
|
|
||||||
{% if not debug %}
|
{% if not debug %}
|
||||||
<!-- Matomo -->
|
<!-- Matomo -->
|
||||||
<script>
|
<script>
|
||||||
var _paq = (window._paq = window._paq || []);
|
var _paq = window._paq = window._paq || [];
|
||||||
_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,
|
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
||||||
g = d.createElement("script"),
|
g.async = true;
|
||||||
s = d.getElementsByTagName("script")[0];
|
g.src = u + 'matomo.js';
|
||||||
g.async = true;
|
s.parentNode.insertBefore(g, s);
|
||||||
g.src = u + "matomo.js";
|
})();
|
||||||
s.parentNode.insertBefore(g, s);
|
</script>
|
||||||
})();
|
<!-- End Matomo Code -->
|
||||||
</script>
|
|
||||||
<!-- End Matomo Code -->
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-base-300 min-h-screen">
|
<body class="min-h-screen bg-base-300">
|
||||||
{% include "includes/navbar.html" %}
|
{% include "includes/navbar.html" %}
|
||||||
|
|
||||||
{# Main Content Area #}
|
{# Main Content Area #}
|
||||||
<main class="w-full">
|
<main class="w-full">
|
||||||
{% block main_content %}
|
{% block main_content %}
|
||||||
{# 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="breadcrumbs text-sm">
|
<div class="text-sm breadcrumbs">
|
||||||
<ul>
|
<ul>
|
||||||
{% for elem in breadcrumbs %}
|
{% for elem in breadcrumbs %}
|
||||||
{% for name, url in elem.items %}
|
{% for name, url in elem.items %}
|
||||||
{% if forloop.parentloop.last %}
|
{% if forloop.parentloop.last %}
|
||||||
<li>{{ name }}</li>
|
<li>{{ name }}</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{{ url }}">{{ name }}</a></li>
|
<li><a href="{{ url }}">{{ name }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Main content container - paper effect on medium+ screens #}
|
||||||
|
<div 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">
|
||||||
|
{# Messages - inside paper #}
|
||||||
|
{% if message %}
|
||||||
|
<div id="message" class="alert alert-info m-4">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Page content - no enforced styles #}
|
||||||
|
{% block content %}
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{# License - inside paper if present #}
|
||||||
|
{% if meta.url_contains_package and meta.current_package.license %}
|
||||||
|
<div class="collapse collapse-arrow bg-base-200 m-8">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
License
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<a target="_blank" href="{{ meta.current_package.license.link }}">
|
||||||
|
<img src="{{ meta.current_package.license.image_link }}" alt="License">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endblock main_content %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# Main content container - paper effect on medium+ screens #}
|
|
||||||
<div
|
|
||||||
id="docContent"
|
|
||||||
class="bg-base-100 mx-auto md:my-8 md:max-w-6xl md:rounded-lg md:shadow-xl"
|
|
||||||
>
|
|
||||||
{# Messages - inside paper #}
|
|
||||||
{% if message %}
|
|
||||||
<div id="message" class="alert alert-info m-4">{{ message }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# Page content - no enforced styles #}
|
|
||||||
{% block content %}
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{# License - inside paper if present #}
|
|
||||||
{% if meta.url_contains_package and meta.current_package.license %}
|
|
||||||
<div class="collapse collapse-arrow bg-base-200 m-8">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title text-xl font-medium">License</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="{{ meta.current_package.license.link }}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="{{ meta.current_package.license.image_link }}"
|
|
||||||
alt="License"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endblock main_content %}
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{% include "includes/footer.html" %}
|
{% include "includes/footer.html" %}
|
||||||
|
|
||||||
{# Floating Help Tab #}
|
{# Floating Help Tab #}
|
||||||
{% if not public_mode %}
|
{% if not public_mode %}
|
||||||
<div class="fixed right-0 top-1/2 -translate-y-1/2 z-50">
|
<div class="fixed right-0 top-1/2 -translate-y-1/2 z-50">
|
||||||
<a
|
<a href="https://community.envipath.org/" target="_blank"
|
||||||
href="https://community.envipath.org/"
|
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"
|
||||||
target="_blank"
|
title="Get Help from the Community">
|
||||||
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"
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle-question-mark-icon lucide-message-circle-question-mark"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
|
||||||
title="Get Help from the Community"
|
<path d="M12 17h.01"/>
|
||||||
>
|
</svg>
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
class="lucide lucide-message-circle-question-mark-icon lucide-message-circle-question-mark"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
|
|
||||||
/>
|
|
||||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
|
||||||
<path d="M12 17h.01" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Modals - TODO: Convert these to DaisyUI modals #}
|
||||||
{% block modals %}
|
{% block modals %}
|
||||||
{% include "modals/search_modal.html" %}
|
{# Note: These modals still use Bootstrap markup and will need conversion #}
|
||||||
|
{% include "modals/cite_modal.html" %}
|
||||||
|
{% include "modals/signup_modal.html" %}
|
||||||
|
{% include "modals/predict_modal.html" %}
|
||||||
|
{% include "modals/batch_predict_modal.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
// Hide actionsbutton if there's no action defined
|
// Hide actionsbutton if there's no action defined
|
||||||
if ($("#actionsButton ul").children().length > 0) {
|
if ($('#actionsButton ul').children().length > 0) {
|
||||||
$("#actionsButton").show();
|
$('#actionsButton').show();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
});
|
||||||
document.addEventListener("keydown", function (event) {
|
|
||||||
// Check if user is typing in an input field
|
|
||||||
const activeElement = document.activeElement;
|
|
||||||
const isInputField =
|
|
||||||
activeElement &&
|
|
||||||
(activeElement.tagName === "INPUT" ||
|
|
||||||
activeElement.tagName === "TEXTAREA" ||
|
|
||||||
activeElement.contentEditable === "true");
|
|
||||||
|
|
||||||
if (isInputField) {
|
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
||||||
return; // Don't trigger shortcut when typing in input fields
|
document.addEventListener('keydown', function(event) {
|
||||||
}
|
// Check if user is typing in an input field
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
const isInputField = activeElement && (
|
||||||
|
activeElement.tagName === 'INPUT' ||
|
||||||
|
activeElement.tagName === 'TEXTAREA' ||
|
||||||
|
activeElement.contentEditable === 'true'
|
||||||
|
);
|
||||||
|
|
||||||
// Check for Cmd+K (Mac) or Ctrl+K (Windows/Linux)
|
if (isInputField) {
|
||||||
const isMac = /Mac/.test(navigator.platform);
|
return; // Don't trigger shortcut when typing in input fields
|
||||||
const isCorrectModifier = isMac ? event.metaKey : event.ctrlKey;
|
}
|
||||||
|
|
||||||
if (isCorrectModifier && event.key === "k") {
|
// Check for Cmd+K (Mac) or Ctrl+K (Windows/Linux)
|
||||||
event.preventDefault();
|
const isMac = /Mac/.test(navigator.platform);
|
||||||
search_modal.showModal();
|
const isCorrectModifier = isMac ? event.metaKey : event.ctrlKey;
|
||||||
}
|
|
||||||
});
|
if (isCorrectModifier && event.key === 'k') {
|
||||||
|
event.preventDefault();
|
||||||
|
window.location.href = '/search';
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,147 +1,71 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{# Modern DaisyUI Navbar #}
|
{# Modern DaisyUI Navbar #}
|
||||||
<div class="navbar bg-neutral-50 text-neutral-950 shadow-lg x-50">
|
<div class="navbar bg-neutral-50 text-neutral-950 shadow-lg x-50">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a href="{{ meta.server_url }}" class="btn btn-ghost normal-case text-xl">
|
<a href="{{ meta.server_url }}" class="btn btn-ghost normal-case text-xl">
|
||||||
<svg class="h-8 fill-base-content" viewBox="0 0 104 26" role="img">
|
<svg class="h-8 fill-base-content" viewBox="0 0 104 26" role="img">
|
||||||
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
|
<use href='{% static "/images/logo-name.svg" %}#ep-logo-name' />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if not public_mode %}
|
|
||||||
<div class="navbar-center hidden lg:flex">
|
|
||||||
<a
|
|
||||||
href="{{ meta.server_url }}/predict"
|
|
||||||
role="button"
|
|
||||||
class="btn btn-ghost"
|
|
||||||
id="predictLink"
|
|
||||||
>Predict</a
|
|
||||||
>
|
|
||||||
<!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> -->
|
|
||||||
<!--<li><a href="{{ meta.server_url }}/browse" id="browseLink">Browse</a></li>-->
|
|
||||||
<div class="dropdown dropdown-center">
|
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost">Browse</div>
|
|
||||||
<ul
|
|
||||||
tabindex="-1"
|
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</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>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="navbar-end">
|
|
||||||
{% if not public_mode %}
|
{% if not public_mode %}
|
||||||
<a href="/search" role="button">
|
<div class="navbar-center hidden lg:flex">
|
||||||
<div
|
<a href="#" role="button" class="btn btn-ghost" id="predictLink">Predict</a>
|
||||||
class="flex items-center badge badge-dash space-x-1 bg-base-200 text-base-content/50 p-2 m-1"
|
<!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> -->
|
||||||
>
|
<!--<li><a href="{{ meta.server_url }}/browse" id="browseLink">Browse</a></li>-->
|
||||||
<svg
|
<div class="dropdown dropdown-center">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<div tabindex="0" role="button" class="btn btn-ghost">Browse</div>
|
||||||
width="16"
|
<ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
|
||||||
height="16"
|
<li><a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a></li>
|
||||||
viewBox="0 0 24 24"
|
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li>
|
||||||
fill="none"
|
<li><a href="{{ meta.server_url }}/compound" id="compoundLink">Compound</a></li>
|
||||||
stroke="currentColor"
|
<li><a href="{{ meta.server_url }}/reaction" id="reactionLink">Reaction</a></li>
|
||||||
stroke-width="2"
|
<li><a href="{{ meta.server_url }}/model" id="relative-reasoningLink">Model</a></li>
|
||||||
stroke-linecap="round"
|
<li><a href="{{ meta.server_url }}/scenario" id="scenarioLink">Scenario</a></li>
|
||||||
stroke-linejoin="round"
|
</ul>
|
||||||
class="lucide lucide-search-icon lucide-search"
|
|
||||||
>
|
|
||||||
<path d="m21 21-4.34-4.34" />
|
|
||||||
<circle cx="11" cy="11" r="8" />
|
|
||||||
</svg>
|
|
||||||
<span id="search-shortcut">⌘K</span>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if meta.user.username == 'anonymous' or public_mode %}
|
|
||||||
<a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a>
|
<div class="navbar-end">
|
||||||
{% else %}
|
{% if not public_mode %}
|
||||||
<div class="dropdown dropdown-end">
|
<a href="/search" role="button" >
|
||||||
<div
|
<div class="flex items-center badge badge-dash space-x-1 bg-base-200 text-base-content/50 p-2 m-1">
|
||||||
tabindex="0"
|
<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>
|
||||||
role="button"
|
<span id="search-shortcut">⌘K</span>
|
||||||
class="btn btn-ghost m-1 btn-circle"
|
</div>
|
||||||
id="loggedInButton"
|
</a>
|
||||||
>
|
{% endif %}
|
||||||
<svg
|
{% if meta.user.username == 'anonymous' or public_mode %}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a>
|
||||||
width="24"
|
{% else %}
|
||||||
height="24"
|
<div class="dropdown dropdown-end">
|
||||||
viewBox="0 0 24 24"
|
<div tabindex="0" role="button" class="btn btn-ghost m-1 btn-circle" id="loggedInButton">
|
||||||
fill="none"
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg>
|
||||||
stroke="currentColor"
|
</div>
|
||||||
stroke-width="2"
|
<ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-sm">
|
||||||
stroke-linecap="round"
|
<li><a href="{{ meta.user.url }}" id="accountbutton">Settings</a></li>
|
||||||
stroke-linejoin="round"
|
<li>
|
||||||
class="lucide lucide-circle-user-icon lucide-circle-user"
|
<form id="logoutForm" action="{% url 'logout' %}" method="post" style="display: none;">
|
||||||
>
|
{% csrf_token %}
|
||||||
<circle cx="12" cy="12" r="10" />
|
<input type="hidden" name="logout" value="true">
|
||||||
<circle cx="12" cy="10" r="3" />
|
</form>
|
||||||
<path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662" />
|
<a href="#" id="logoutButton" onclick="event.preventDefault(); document.getElementById('logoutForm').submit();">Logout</a>
|
||||||
</svg>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
<ul
|
</div>
|
||||||
tabindex="-1"
|
{% endif %}
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-sm"
|
</div>
|
||||||
>
|
|
||||||
<li><a href="{{ meta.user.url }}" id="accountbutton">Settings</a></li>
|
|
||||||
<li>
|
|
||||||
<form
|
|
||||||
id="logoutForm"
|
|
||||||
action="{% url 'logout' %}"
|
|
||||||
method="post"
|
|
||||||
style="display: none;"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="logout" value="true" />
|
|
||||||
</form>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
id="logoutButton"
|
|
||||||
onclick="event.preventDefault(); document.getElementById('logoutForm').submit();"
|
|
||||||
>Logout</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// OS-aware search shortcut display
|
// OS-aware search shortcut display
|
||||||
(function () {
|
(function() {
|
||||||
const isMac = /Mac/.test(navigator.platform);
|
const isMac = /Mac/.test(navigator.platform);
|
||||||
const shortcutElement = document.getElementById("search-shortcut");
|
const shortcutElement = document.getElementById('search-shortcut');
|
||||||
if (shortcutElement) {
|
if (shortcutElement) {
|
||||||
shortcutElement.textContent = isMac ? "⌘K" : "Ctrl+K";
|
shortcutElement.textContent = isMac ? '⌘K' : 'Ctrl+K';
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,269 +1,190 @@
|
|||||||
{% extends "framework_modern.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block main_content %}
|
{% block main_content %}
|
||||||
<!-- Hero Section with Logo and Search -->
|
|
||||||
<section class="hero h-fit max-w-5xl w-full shadow-none mx-auto relative">
|
|
||||||
<div
|
|
||||||
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;"
|
|
||||||
>
|
|
||||||
<div class="hero-overlay"></div>
|
|
||||||
<!-- Predict Pathway text over the image -->
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="shadow-md max-w-5xl mx-auto bg-base-200">
|
<!-- Hero Section with Logo and Search -->
|
||||||
<!-- Predict Pathway Section -->
|
<section class="hero h-fit max-w-5xl w-full shadow-none mx-auto relative">
|
||||||
<div
|
|
||||||
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">
|
|
||||||
<!-- 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 items-center gap-2">
|
|
||||||
<!-- <span class="text-sm text-neutral-500">Input Mode:</span> -->
|
|
||||||
<label class="toggle text-base-content toggle-md">
|
|
||||||
<input type="checkbox" />
|
|
||||||
<svg
|
|
||||||
aria-label="smiles mode"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
class="size-5"
|
|
||||||
>
|
|
||||||
<g
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-width="2"
|
|
||||||
fill="currentColor"
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
aria-label="draw mode"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
stroke="none"
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<fieldset
|
<div class="hero min-h-[calc(100vh*0.4)] bg-gradient-to-br from-primary-800 to-primary-600"
|
||||||
class="fieldset transition-all duration-300 ease-in-out overflow-hidden"
|
style="background-image: url('{% static "/images/hero.png" %}'); background-size: cover; background-position: center;"
|
||||||
>
|
|
||||||
<form
|
|
||||||
id="index-form"
|
|
||||||
action="{{ meta.current_package.url }}/pathway"
|
|
||||||
method="POST"
|
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
<div class="hero-overlay"></div>
|
||||||
<div
|
<!-- Predict Pathway text over the image -->
|
||||||
id="text-input-container"
|
<div class="absolute bottom-40 left-1/8 -translate-x-8 z-10">
|
||||||
class="transition-all duration-300 ease-in-out opacity-100 transform scale-100"
|
<h2 class="text-3xl text-base-100 text-shadow-lg text-left">Predict Your Pathway</h2>
|
||||||
>
|
</div>
|
||||||
<div class="join w-full mx-auto">
|
</div>
|
||||||
<input
|
</section>
|
||||||
type="text"
|
|
||||||
id="index-form-text-input"
|
<div class="shadow-md max-w-5xl mx-auto bg-base-200">
|
||||||
placeholder="canonical SMILES string"
|
|
||||||
class="input grow input-md join-item"
|
<!-- Predict Pathway Section -->
|
||||||
/>
|
<div class="flex-col lg:flex-row-reverse w-full mx-auto -mt-32 relative z-20 mb-10 ">
|
||||||
<button class="btn btn-neutral join-item">Predict!</button>
|
<div class="card bg-base-100 shrink-0 shadow-xl w-3/4 mx-auto transition-all duration-300 ease-in-out">
|
||||||
</div>
|
<div class="card-body">
|
||||||
<div class="label relative w-full mt-1">
|
<!-- Input Mode Toggle - Fixed position outside fieldset -->
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-row justify-start items-center h-fit ml-8 my-4">
|
||||||
<a
|
<div class="flex items-center gap-2">
|
||||||
href="#"
|
<!-- <span class="text-sm text-neutral-500">Input Mode:</span> -->
|
||||||
class="example-link cursor-pointer hover:text-primary"
|
<label class="toggle text-base-content toggle-md">
|
||||||
data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
|
<input type="checkbox" />
|
||||||
title="load example"
|
<svg aria-label="smiles mode" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class="size-5">
|
||||||
>Caffeine</a
|
<g
|
||||||
>
|
stroke-linejoin="round"
|
||||||
<a
|
stroke-linecap="round"
|
||||||
href="#"
|
stroke-width="2"
|
||||||
class="example-link cursor-pointer hover:text-primary"
|
fill="currentColor"
|
||||||
data-smiles="CC(C)CC1=CC=C(C=C1)C(C)C(=O)O"
|
stroke="none"
|
||||||
title="load example"
|
>
|
||||||
>Ibuprofen</a
|
<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>
|
||||||
</div>
|
</svg>
|
||||||
<a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#"
|
<svg
|
||||||
>Advanced</a
|
aria-label="draw mode"
|
||||||
>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</div>
|
viewBox="0 0 20 20"
|
||||||
</div>
|
fill="currentColor"
|
||||||
<div
|
stroke="none"
|
||||||
id="ketcher-container"
|
class="size-5"
|
||||||
class="hidden w-full transition-all duration-300 ease-in-out opacity-0 transform scale-95"
|
>
|
||||||
>
|
<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" />
|
||||||
<iframe
|
|
||||||
id="index-ketcher"
|
</svg>
|
||||||
src="{% static '/js/ketcher2/ketcher.html' %}"
|
</label>
|
||||||
width="100%"
|
</div>
|
||||||
height="400"
|
</div>
|
||||||
class="rounded-lg"
|
|
||||||
></iframe>
|
<fieldset class="fieldset transition-all duration-300 ease-in-out overflow-hidden">
|
||||||
<button
|
<form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST">
|
||||||
class="btn btn-lg bg-primary-950 text-primary-50 join-item w-full mt-2"
|
{% csrf_token %}
|
||||||
>
|
<div id="text-input-container" class="transition-all duration-300 ease-in-out opacity-100 transform scale-100">
|
||||||
Predict!
|
<div class="join w-full mx-auto">
|
||||||
</button>
|
<input type="text" id="index-form-text-input" placeholder="canonical SMILES string" class="input grow input-md join-item" />
|
||||||
<a class="label mx-auto w-full mt-1" href="#">Advanced</a>
|
<button class="btn btn-neutral join-item">Predict!</button>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<div class="label relative w-full mt-1" >
|
||||||
type="hidden"
|
<div class="flex gap-2">
|
||||||
id="index-form-smiles"
|
<a href="#" class="example-link cursor-pointer hover:text-primary"
|
||||||
name="smiles"
|
data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
|
||||||
value="smiles"
|
title="load example">Caffeine</a>
|
||||||
/>
|
<a href="#" class="example-link cursor-pointer hover:text-primary"
|
||||||
<input
|
data-smiles="CC(C)CC1=CC=C(C=C1)C(C)C(=O)O"
|
||||||
type="hidden"
|
title="load example">Ibuprofen</a>
|
||||||
id="index-form-predict"
|
</div>
|
||||||
name="predict"
|
<a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#">Advanced</a>
|
||||||
value="predict"
|
</div>
|
||||||
/>
|
</div>
|
||||||
<input type="hidden" id="current-action" value="predict" />
|
<div id="ketcher-container" class="hidden w-full transition-all duration-300 ease-in-out opacity-0 transform scale-95">
|
||||||
</form>
|
<iframe id="index-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
</fieldset>
|
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>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" 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>
|
||||||
|
</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">
|
||||||
<h2 class="h2 font-bold text-left mb-8">Community Updates</h2>
|
<h2 class="h2 font-bold text-left mb-8">Community Updates</h2>
|
||||||
|
|
||||||
<div id="community-news-container" class="flex gap-4 justify-center">
|
<div id="community-news-container" class="flex gap-4 justify-center">
|
||||||
<!-- 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 justify-center w-full">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-right mt-6">
|
||||||
|
<a href="https://community.envipath.org/c/announcements/10" target="_blank" class="btn btn-ghost btn-sm">
|
||||||
|
Read More Announcements
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Discourse API integration -->
|
||||||
|
<script src="{% static 'js/discourse-api.js' %}"></script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-right mt-6">
|
|
||||||
<a
|
|
||||||
href="https://community.envipath.org/c/announcements/10"
|
|
||||||
target="_blank"
|
|
||||||
class="btn btn-ghost btn-sm"
|
|
||||||
>
|
|
||||||
Read More Announcements
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Discourse API integration -->
|
|
||||||
<script src="{% static 'js/discourse-api.js' %}"></script>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Mission Statement Section -->
|
<!-- Mission Statement Section -->
|
||||||
<section class="py-16 from-base-200 to-base-100 bg-gradient-to-b">
|
<section class="py-16 from-base-200 to-base-100 bg-gradient-to-b">
|
||||||
<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" %}" alt="rule-based iterative tree greneration" class="w-full h-full object-contain" />
|
||||||
src="{% static "/images/ep-rule-artwork.png" %}"
|
</div>
|
||||||
alt="rule-based iterative tree greneration"
|
<div class="space-y-4 text-left w-2/3 mr-8">
|
||||||
class="w-full h-full object-contain"
|
<h2 class="h2 font-bold mb-8">About enviPath</h2>
|
||||||
/>
|
<p class="">
|
||||||
</div>
|
enviPath is a database and prediction system for the microbial
|
||||||
<div class="space-y-4 text-left w-2/3 mr-8">
|
biotransformation of organic environmental contaminants. The
|
||||||
<h2 class="h2 font-bold mb-8">About enviPath</h2>
|
database provides the possibility to store and view experimentally
|
||||||
<p class="">
|
observed biotransformation pathways.
|
||||||
enviPath is a database and prediction system for the microbial
|
</p>
|
||||||
biotransformation of organic environmental contaminants. The
|
<p class="">
|
||||||
database provides the possibility to store and view experimentally
|
The pathway prediction system provides different relative reasoning models
|
||||||
observed biotransformation pathways.
|
to predict likely biotransformation pathways and products. Explore our tools
|
||||||
</p>
|
and contribute to advancing environmental biotransformation research.
|
||||||
<p class="">
|
</p>
|
||||||
The pathway prediction system provides different relative
|
<div class="flex flex-row gap-4 float-right">
|
||||||
reasoning models to predict likely biotransformation pathways and
|
<a href="/about" class="btn btn-ghost-neutral">Read More</a>
|
||||||
products. Explore our tools and contribute to advancing
|
<a href="/about" class="btn btn-neutral">Publications</a>
|
||||||
environmental biotransformation research.
|
</div>
|
||||||
</p>
|
</div>
|
||||||
<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-neutral">Publications</a>
|
|
||||||
</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">
|
<div class="divider"><h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2></div>
|
||||||
<h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2>
|
<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">
|
||||||
|
<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
|
|
||||||
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>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script language="javascript">
|
|
||||||
var currentPackage = "{{ meta.current_package.url }}";
|
|
||||||
|
|
||||||
// Discourse API integration is now handled by discourse-api.js
|
|
||||||
|
|
||||||
// Function to render Discourse topics into cards
|
|
||||||
function renderDiscourseTopics(topics) {
|
|
||||||
const container = document.getElementById("community-news-container");
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
// Clear container
|
</div>
|
||||||
container.innerHTML = "";
|
|
||||||
|
|
||||||
// Create cards for each topic
|
<script language="javascript">
|
||||||
topics.forEach((topic) => {
|
var currentPackage = "{{ meta.current_package.url }}";
|
||||||
const card = createDiscourseCard(topic);
|
|
||||||
container.insertAdjacentHTML("beforeend", card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create HTML card for a topic
|
// Discourse API integration is now handled by discourse-api.js
|
||||||
function createDiscourseCard(topic) {
|
|
||||||
const date = new Date(topic.created_at).toLocaleDateString();
|
|
||||||
|
|
||||||
return `
|
// Function to render Discourse topics into cards
|
||||||
|
function renderDiscourseTopics(topics) {
|
||||||
|
const container = document.getElementById('community-news-container');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
// Clear container
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Create cards for each topic
|
||||||
|
topics.forEach(topic => {
|
||||||
|
const card = createDiscourseCard(topic);
|
||||||
|
container.insertAdjacentHTML('beforeend', card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to create HTML card for a topic
|
||||||
|
function createDiscourseCard(topic) {
|
||||||
|
const date = new Date(topic.created_at).toLocaleDateString();
|
||||||
|
|
||||||
|
return `
|
||||||
<div class="card bg-white shadow-xs hover:shadow-lg transition-shadow duration-300 h-64 w-75 flex-shrink-0">
|
<div class="card bg-white shadow-xs hover:shadow-lg transition-shadow duration-300 h-64 w-75 flex-shrink-0">
|
||||||
<div class="card-body flex flex-col h-full">
|
<div class="card-body flex flex-col h-full">
|
||||||
<h3 class="card-title leading-tight font-normal tracking-tight h-12 mb-2 line-clamp-2 text-ellipsis wrap-break-word overflow-hidden">
|
<h3 class="card-title leading-tight font-normal tracking-tight h-12 mb-2 line-clamp-2 text-ellipsis wrap-break-word overflow-hidden">
|
||||||
@ -294,146 +215,146 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
|
||||||
|
|
||||||
// Make render function globally available
|
|
||||||
window.renderDiscourseTopics = renderDiscourseTopics;
|
|
||||||
|
|
||||||
// Toggle functionality with smooth animations
|
|
||||||
function toggleInputMode() {
|
|
||||||
const toggle = $('input[type="checkbox"]');
|
|
||||||
const textContainer = $("#text-input-container");
|
|
||||||
const ketcherContainer = $("#ketcher-container");
|
|
||||||
const formCard = $(".card");
|
|
||||||
const fieldset = $(".fieldset");
|
|
||||||
|
|
||||||
if (toggle.is(":checked")) {
|
|
||||||
// Draw mode - show Ketcher, hide text input
|
|
||||||
textContainer.addClass("opacity-0 transform scale-95");
|
|
||||||
textContainer.removeClass("opacity-100 transform scale-100");
|
|
||||||
|
|
||||||
// Adjust fieldset padding for Ketcher mode - reduce padding and make more compact
|
|
||||||
fieldset.removeClass("p-8");
|
|
||||||
fieldset.addClass("p-4");
|
|
||||||
|
|
||||||
// Wait for fade out to complete, then hide and show new content
|
|
||||||
setTimeout(() => {
|
|
||||||
textContainer.addClass("hidden");
|
|
||||||
ketcherContainer.removeClass("hidden opacity-0 transform scale-95");
|
|
||||||
ketcherContainer.addClass("opacity-100 transform scale-100");
|
|
||||||
|
|
||||||
// Force re-evaluation of iframe size
|
|
||||||
const iframe = document.getElementById("index-ketcher");
|
|
||||||
if (iframe) {
|
|
||||||
iframe.style.height = "400px";
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
} else {
|
|
||||||
// SMILES mode - show text input, hide Ketcher
|
|
||||||
ketcherContainer.addClass("opacity-0 transform scale-95");
|
|
||||||
ketcherContainer.removeClass("opacity-100 transform scale-100");
|
|
||||||
|
|
||||||
// Restore fieldset padding for text input mode
|
|
||||||
fieldset.removeClass("p-4");
|
|
||||||
fieldset.addClass("p-8");
|
|
||||||
|
|
||||||
// Wait for fade out to complete, then hide and show new content
|
|
||||||
setTimeout(() => {
|
|
||||||
ketcherContainer.addClass("hidden");
|
|
||||||
textContainer.removeClass("hidden opacity-0 transform scale-95");
|
|
||||||
textContainer.addClass("opacity-100 transform scale-100");
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// Transfer SMILES from Ketcher to text input if available
|
|
||||||
if (window.indexKetcher && window.indexKetcher.getSmiles) {
|
|
||||||
const smiles = window.indexKetcher.getSmiles();
|
|
||||||
if (smiles && smiles.trim() !== "") {
|
|
||||||
$("#index-form-text-input").val(smiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ketcher integration
|
// Make render function globally available
|
||||||
function indexKetcherToTextInput() {
|
window.renderDiscourseTopics = renderDiscourseTopics;
|
||||||
$("#index-form-smiles").val(this.ketcher.getSmiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
// Toggle functionality with smooth animations
|
||||||
// Initialize fieldset with proper padding
|
function toggleInputMode() {
|
||||||
$(".fieldset").addClass("p-8");
|
const toggle = $('input[type="checkbox"]');
|
||||||
|
const textContainer = $('#text-input-container');
|
||||||
|
const ketcherContainer = $('#ketcher-container');
|
||||||
|
const formCard = $('.card');
|
||||||
|
const fieldset = $('.fieldset');
|
||||||
|
|
||||||
// Toggle event listener
|
if (toggle.is(':checked')) {
|
||||||
$('input[type="checkbox"]').on("change", toggleInputMode);
|
// Draw mode - show Ketcher, hide text input
|
||||||
|
textContainer.addClass('opacity-0 transform scale-95');
|
||||||
|
textContainer.removeClass('opacity-100 transform scale-100');
|
||||||
|
|
||||||
// Ketcher iframe load handler
|
// Adjust fieldset padding for Ketcher mode - reduce padding and make more compact
|
||||||
$("#index-ketcher").on("load", function () {
|
fieldset.removeClass('p-8');
|
||||||
const checkKetcherReady = () => {
|
fieldset.addClass('p-4');
|
||||||
const win = this.contentWindow;
|
|
||||||
if (win.ketcher && "editor" in win.ketcher) {
|
// Wait for fade out to complete, then hide and show new content
|
||||||
window.indexKetcher = win.ketcher;
|
setTimeout(() => {
|
||||||
win.ketcher.editor.event.change.handlers.push({
|
textContainer.addClass('hidden');
|
||||||
once: false,
|
ketcherContainer.removeClass('hidden opacity-0 transform scale-95');
|
||||||
priority: 0,
|
ketcherContainer.addClass('opacity-100 transform scale-100');
|
||||||
f: indexKetcherToTextInput,
|
|
||||||
ketcher: win.ketcher,
|
// Force re-evaluation of iframe size
|
||||||
|
const iframe = document.getElementById('index-ketcher');
|
||||||
|
if (iframe) {
|
||||||
|
iframe.style.height = '400px';
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
// SMILES mode - show text input, hide Ketcher
|
||||||
|
ketcherContainer.addClass('opacity-0 transform scale-95');
|
||||||
|
ketcherContainer.removeClass('opacity-100 transform scale-100');
|
||||||
|
|
||||||
|
// Restore fieldset padding for text input mode
|
||||||
|
fieldset.removeClass('p-4');
|
||||||
|
fieldset.addClass('p-8');
|
||||||
|
|
||||||
|
// Wait for fade out to complete, then hide and show new content
|
||||||
|
setTimeout(() => {
|
||||||
|
ketcherContainer.addClass('hidden');
|
||||||
|
textContainer.removeClass('hidden opacity-0 transform scale-95');
|
||||||
|
textContainer.addClass('opacity-100 transform scale-100');
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// Transfer SMILES from Ketcher to text input if available
|
||||||
|
if (window.indexKetcher && window.indexKetcher.getSmiles) {
|
||||||
|
const smiles = window.indexKetcher.getSmiles();
|
||||||
|
if (smiles && smiles.trim() !== '') {
|
||||||
|
$('#index-form-text-input').val(smiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ketcher integration
|
||||||
|
function indexKetcherToTextInput() {
|
||||||
|
$('#index-form-smiles').val(this.ketcher.getSmiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
// Initialize fieldset with proper padding
|
||||||
|
$('.fieldset').addClass('p-8');
|
||||||
|
|
||||||
|
// Toggle event listener
|
||||||
|
$('input[type="checkbox"]').on('change', toggleInputMode);
|
||||||
|
|
||||||
|
// Ketcher iframe load handler
|
||||||
|
$('#index-ketcher').on('load', function() {
|
||||||
|
const checkKetcherReady = () => {
|
||||||
|
const win = this.contentWindow;
|
||||||
|
if (win.ketcher && 'editor' in win.ketcher) {
|
||||||
|
window.indexKetcher = win.ketcher;
|
||||||
|
win.ketcher.editor.event.change.handlers.push({
|
||||||
|
once: false,
|
||||||
|
priority: 0,
|
||||||
|
f: indexKetcherToTextInput,
|
||||||
|
ketcher: win.ketcher
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(checkKetcherReady, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkKetcherReady();
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
setTimeout(checkKetcherReady, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
checkKetcherReady();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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
|
||||||
const originalText = $(this).text();
|
const originalText = $(this).text();
|
||||||
$(this).text(`loaded!`);
|
$(this).text(`loaded!`);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$(this).text(originalText);
|
$(this).text(originalText);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Discourse topics are now loaded automatically by discourse-api.js
|
// Discourse topics are now loaded automatically by discourse-api.js
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock main_content %}
|
{% endblock main_content %}
|
||||||
|
|||||||
@ -52,6 +52,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="license" name="license">
|
<input type="hidden" id="license" name="license">
|
||||||
|
<input type="hidden" id="license-link" name="license-link">
|
||||||
|
<input type="hidden" id="license-image-link" name="license-image-link">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -126,6 +128,8 @@ function cc() {
|
|||||||
|
|
||||||
$('#ccfig').append(img_tpl);
|
$('#ccfig').append(img_tpl);
|
||||||
$('#license').val(ccstr);
|
$('#license').val(ccstr);
|
||||||
|
$('#license-link').val(link);
|
||||||
|
$('#license-image-link').val(imageLink);
|
||||||
} else {
|
} else {
|
||||||
$('#ccfig').empty();
|
$('#ccfig').empty();
|
||||||
$('#set_license_form_submit').prop('disabled', true);
|
$('#set_license_form_submit').prop('disabled', true);
|
||||||
|
|||||||
@ -1,535 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
<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" >
|
|
||||||
|
|
||||||
<!-- Search Input and Mode Selector -->
|
|
||||||
<div class="form-control mb-4 flex-shrink-0 w-full">
|
|
||||||
<div class="join w-full m-0 p-3 items-center">
|
|
||||||
<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>
|
|
||||||
<input type="text" autofocus
|
|
||||||
id="modal_searchbar"
|
|
||||||
placeholder="Benfuracarb"
|
|
||||||
class="grow" aria-label="Search" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- Mode Dropdown -->
|
|
||||||
<div>
|
|
||||||
<button type="button"
|
|
||||||
tabindex="0"
|
|
||||||
id="modal_mode_button"
|
|
||||||
popovertarget="search_dropdown_menu" style="anchor-name:--anchor-1"
|
|
||||||
class="btn join-item btn-ghost">
|
|
||||||
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"/>
|
|
||||||
</svg>
|
|
||||||
</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">
|
|
||||||
<li class="menu-title">Text</li>
|
|
||||||
<li>
|
|
||||||
<a id="modal_dropdown_text"
|
|
||||||
class="tooltip tooltip-left"
|
|
||||||
data-tip="Search on object names and descriptions">
|
|
||||||
Text
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="menu-title">SMILES</li>
|
|
||||||
<li>
|
|
||||||
<a id="modal_dropdown_smiles_default"
|
|
||||||
class="tooltip tooltip-left"
|
|
||||||
data-tip="Ignores stereochemistry and charge">
|
|
||||||
Default
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a id="modal_dropdown_smiles_canonical"
|
|
||||||
class="tooltip tooltip-left"
|
|
||||||
data-tip="Ignores stereochemistry, preserves charge">
|
|
||||||
Canonical
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a id="modal_dropdown_smiles_exact"
|
|
||||||
class="tooltip tooltip-left"
|
|
||||||
data-tip="Exact match for stereochemistry and charge">
|
|
||||||
Exact
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="menu-title">InChI</li>
|
|
||||||
<li>
|
|
||||||
<a id="modal_dropdown_inchikey"
|
|
||||||
class="tooltip tooltip-left"
|
|
||||||
data-tip="Search by InChIKey">
|
|
||||||
InChIKey
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" id="modal_search_button" class="btn btn-xs btn-ghost join-item">
|
|
||||||
<kbd class="kbd kbd-sm p-1 text-base-content/50">
|
|
||||||
<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>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Package Selector with Pills -->
|
|
||||||
<div class="form-control mb-4 flex-shrink-0">
|
|
||||||
<!-- Pills Container -->
|
|
||||||
<div id="modal_package_pills_container"
|
|
||||||
class="flex flex-wrap gap-2 p-3 border-2 border-dashed border-base-300 rounded-lg m-3 min-h-[3rem] items-center">
|
|
||||||
<!-- Pills will be added here dynamically -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 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"
|
|
||||||
popover
|
|
||||||
id="package_dropdown_menu"
|
|
||||||
style="position-anchor:--anchor-packages">
|
|
||||||
{% if unreviewed_packages %}
|
|
||||||
<li class="menu-title">Reviewed Packages</li>
|
|
||||||
{% for obj in reviewed_packages %}
|
|
||||||
<li>
|
|
||||||
<a class="package-option flex justify-between items-center"
|
|
||||||
data-package-url="{{ obj.url }}"
|
|
||||||
data-package-name="{{ obj.name }}">
|
|
||||||
<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">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
<li class="menu-title">Unreviewed Packages</li>
|
|
||||||
{% for obj in unreviewed_packages %}
|
|
||||||
<li>
|
|
||||||
<a class="package-option flex justify-between items-center"
|
|
||||||
data-package-url="{{ obj.url }}"
|
|
||||||
data-package-name="{{ obj.name }}">
|
|
||||||
<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">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<li class="menu-title">Reviewed Packages</li>
|
|
||||||
{% for obj in reviewed_packages %}
|
|
||||||
<li>
|
|
||||||
<a class="package-option flex justify-between items-center"
|
|
||||||
data-package-url="{{ obj.url }}"
|
|
||||||
data-package-name="{{ obj.name }}">
|
|
||||||
<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">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading Indicator -->
|
|
||||||
<div id="search_loading" class="hidden justify-center py-8 flex-shrink-0">
|
|
||||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results Container - scrollable -->
|
|
||||||
<div id="search_results" class="flex-1 overflow-y-auto min-h-0 p-2"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Backdrop to close -->
|
|
||||||
<form method="dialog" class="modal-backdrop">
|
|
||||||
<button>close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
// Package Selector Module - Data-driven multiselect package selection
|
|
||||||
const PackageSelector = {
|
|
||||||
// Single source of truth: array of selected packages
|
|
||||||
selectedPackages: [],
|
|
||||||
|
|
||||||
elements: {
|
|
||||||
pillsContainer: null,
|
|
||||||
packageDropdown: null,
|
|
||||||
packageOptions: null
|
|
||||||
},
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.cacheElements();
|
|
||||||
this.loadInitialSelection();
|
|
||||||
this.attachEventListeners();
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
cacheElements() {
|
|
||||||
this.elements.pillsContainer = document.getElementById('modal_package_pills_container');
|
|
||||||
this.elements.packageDropdown = document.getElementById('package_dropdown_menu');
|
|
||||||
this.elements.packageOptions = document.querySelectorAll('.package-option');
|
|
||||||
},
|
|
||||||
|
|
||||||
loadInitialSelection() {
|
|
||||||
// Load pre-selected packages from server-rendered pills
|
|
||||||
const existingPills = this.elements.pillsContainer.querySelectorAll('.badge');
|
|
||||||
existingPills.forEach(pill => {
|
|
||||||
this.selectedPackages.push({
|
|
||||||
url: pill.dataset.packageUrl,
|
|
||||||
name: pill.dataset.packageName
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// If no pills found, select all reviewed packages by default
|
|
||||||
if (this.selectedPackages.length === 0) {
|
|
||||||
// Iterate through all menu items and collect reviewed packages
|
|
||||||
const menuItems = this.elements.packageDropdown.querySelectorAll('li');
|
|
||||||
|
|
||||||
for (const item of menuItems) {
|
|
||||||
// Check if this is the "Unreviewed Packages" menu title
|
|
||||||
if (item.classList.contains('menu-title') &&
|
|
||||||
item.textContent.trim() === 'Unreviewed Packages') {
|
|
||||||
break; // Stop processing after this point
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for package options (only reviewed packages reach here)
|
|
||||||
const packageOption = item.querySelector('.package-option');
|
|
||||||
if (packageOption) {
|
|
||||||
this.selectedPackages.push({
|
|
||||||
url: packageOption.dataset.packageUrl,
|
|
||||||
name: packageOption.dataset.packageName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
attachEventListeners() {
|
|
||||||
// Toggle package selection on dropdown item click
|
|
||||||
this.elements.packageOptions.forEach(option => {
|
|
||||||
option.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation(); // Prevent dropdown from closing
|
|
||||||
const packageUrl = option.dataset.packageUrl;
|
|
||||||
const packageName = option.dataset.packageName;
|
|
||||||
this.togglePackageSelection(packageUrl, packageName);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove package when X is clicked (using event delegation)
|
|
||||||
this.elements.pillsContainer.addEventListener('click', (e) => {
|
|
||||||
if (e.target.classList.contains('package-remove-btn') || e.target.closest('.package-remove-btn')) {
|
|
||||||
const pill = e.target.closest('.badge');
|
|
||||||
if (pill) {
|
|
||||||
const packageUrl = pill.dataset.packageUrl;
|
|
||||||
this.removePackage(packageUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePackageSelection(packageUrl, packageName) {
|
|
||||||
const index = this.selectedPackages.findIndex(pkg => pkg.url === packageUrl);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
// Remove from selection
|
|
||||||
this.selectedPackages.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
// Add to selection
|
|
||||||
this.selectedPackages.push({ url: packageUrl, name: packageName });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
removePackage(packageUrl) {
|
|
||||||
const index = this.selectedPackages.findIndex(pkg => pkg.url === packageUrl);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.selectedPackages.splice(index, 1);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.renderPills();
|
|
||||||
this.renderAddButton();
|
|
||||||
this.renderCheckmarks();
|
|
||||||
},
|
|
||||||
|
|
||||||
renderPills() {
|
|
||||||
// Clear existing pills and button (except placeholder)
|
|
||||||
const pills = this.elements.pillsContainer.querySelectorAll('.badge');
|
|
||||||
pills.forEach(pill => pill.remove());
|
|
||||||
|
|
||||||
const existingButton = this.elements.pillsContainer.querySelector('#modal_package_add_button');
|
|
||||||
if (existingButton) {
|
|
||||||
existingButton.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create pills from data
|
|
||||||
this.selectedPackages.forEach(pkg => {
|
|
||||||
const pill = this.createPillElement(pkg.url, pkg.name);
|
|
||||||
this.elements.pillsContainer.appendChild(pill);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderAddButton() {
|
|
||||||
// Only render button if there are packages available
|
|
||||||
if (this.elements.packageOptions.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.type = 'button';
|
|
||||||
button.id = 'modal_package_add_button';
|
|
||||||
button.setAttribute('popovertarget', 'package_dropdown_menu');
|
|
||||||
button.style.cssText = 'anchor-name:--anchor-packages';
|
|
||||||
button.className = 'btn btn-sm btn-ghost gap-2 text-base-content/50';
|
|
||||||
|
|
||||||
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>
|
|
||||||
Add Package
|
|
||||||
`;
|
|
||||||
|
|
||||||
this.elements.pillsContainer.appendChild(button);
|
|
||||||
},
|
|
||||||
|
|
||||||
createPillElement(packageUrl, packageName) {
|
|
||||||
const pill = document.createElement('span');
|
|
||||||
pill.className = 'badge badge-outline gap-2 max-w-xs';
|
|
||||||
pill.dataset.packageUrl = packageUrl;
|
|
||||||
pill.dataset.packageName = packageName;
|
|
||||||
|
|
||||||
pill.innerHTML = `
|
|
||||||
<span class="truncate" title="${packageName}">${packageName}</span>
|
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-4 w-4 cursor-pointer hover:text-error package-remove-btn flex-shrink-0 rotate-45"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none" stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M5 12h14"/><path d="M12 5v14"/>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return pill;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderCheckmarks() {
|
|
||||||
// Update all checkmarks based on selected packages
|
|
||||||
this.elements.packageOptions.forEach(option => {
|
|
||||||
const packageUrl = option.dataset.packageUrl;
|
|
||||||
const isSelected = this.selectedPackages.some(pkg => pkg.url === packageUrl);
|
|
||||||
const checkmark = option.querySelector('.package-checkmark');
|
|
||||||
|
|
||||||
if (checkmark) {
|
|
||||||
checkmark.classList.toggle('hidden', !isSelected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
getSelectedPackages() {
|
|
||||||
return this.selectedPackages.map(pkg => pkg.url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Modal and Search Management
|
|
||||||
const modal = document.getElementById('search_modal');
|
|
||||||
const searchbar = document.getElementById('modal_searchbar');
|
|
||||||
const searchButton = document.getElementById('modal_search_button');
|
|
||||||
const modeButton = document.getElementById('modal_mode_button');
|
|
||||||
const resultsDiv = document.getElementById('search_results');
|
|
||||||
const loadingDiv = document.getElementById('search_loading');
|
|
||||||
|
|
||||||
// MutationObserver to detect when modal opens
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.attributeName === 'open' && modal.open) {
|
|
||||||
PackageSelector.render();
|
|
||||||
// Delay focus to allow CSS transitions to complete (modal has 0.3s transition)
|
|
||||||
setTimeout(() => {
|
|
||||||
searchbar.focus();
|
|
||||||
}, 320);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(modal, { attributes: true });
|
|
||||||
|
|
||||||
// Clear results when modal closes
|
|
||||||
modal.addEventListener('close', function() {
|
|
||||||
resultsDiv.innerHTML = '';
|
|
||||||
loadingDiv.classList.add('hidden');
|
|
||||||
searchbar.value = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Mode dropdown handlers
|
|
||||||
const dropdownMenu = document.getElementById('search_dropdown_menu');
|
|
||||||
|
|
||||||
const modeButtons = [
|
|
||||||
{ id: 'modal_dropdown_text', text: 'Text' },
|
|
||||||
{ id: 'modal_dropdown_smiles_default', text: 'Default' },
|
|
||||||
{ id: 'modal_dropdown_smiles_canonical', text: 'Canonical' },
|
|
||||||
{ id: 'modal_dropdown_smiles_exact', text: 'Exact' },
|
|
||||||
{ id: 'modal_dropdown_inchikey', text: 'InChIKey' }
|
|
||||||
];
|
|
||||||
|
|
||||||
modeButtons.forEach(({ id, text }) => {
|
|
||||||
document.getElementById(id).addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
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"/>
|
|
||||||
</svg>`;
|
|
||||||
// Close dropdown using popover API
|
|
||||||
if (dropdownMenu && typeof dropdownMenu.hidePopover === 'function') {
|
|
||||||
dropdownMenu.hidePopover();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize Package Selector
|
|
||||||
PackageSelector.init();
|
|
||||||
|
|
||||||
// Search Response Handler
|
|
||||||
function handleSearchResponse(data) {
|
|
||||||
resultsDiv.innerHTML = '';
|
|
||||||
|
|
||||||
function makeContent(objs) {
|
|
||||||
let links = '';
|
|
||||||
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>`;
|
|
||||||
});
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
let allEmpty = true;
|
|
||||||
let content = '';
|
|
||||||
|
|
||||||
// Category order for better UX
|
|
||||||
const categoryOrder = ['Compounds', 'Compound Structures', 'Rules', 'Reactions', 'Pathways'];
|
|
||||||
|
|
||||||
categoryOrder.forEach(key => {
|
|
||||||
if (!data[key] || data[key].length < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
allEmpty = false;
|
|
||||||
|
|
||||||
content += `
|
|
||||||
<div class="collapse collapse-arrow bg-base-200 mb-2">
|
|
||||||
<input type="checkbox" checked />
|
|
||||||
<div class="collapse-title font-medium">
|
|
||||||
${key} <span class="badge badge-neutral badge-sm ml-2">${data[key].length}</span>
|
|
||||||
</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
${makeContent(data[key])}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (allEmpty) {
|
|
||||||
resultsDiv.innerHTML = `
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
|
||||||
</svg>
|
|
||||||
<span>No results found</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
resultsDiv.innerHTML = `
|
|
||||||
<div class="mb-2">
|
|
||||||
<div class="text-sm font-semibold text-base-content/70 mb-2">Results</div>
|
|
||||||
${content}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search Execution
|
|
||||||
function performSearch(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const query = searchbar.value.trim();
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
console.log('Search phrase empty, won\'t do search');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selPacks = PackageSelector.getSelectedPackages();
|
|
||||||
|
|
||||||
if (selPacks.length < 1) {
|
|
||||||
console.log('No package selected, won\'t do search');
|
|
||||||
resultsDiv.innerHTML = `
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
<span>Please select at least one package</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mode = modeButton.textContent.trim().toLowerCase();
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
selPacks.forEach(pack => params.append('packages', pack));
|
|
||||||
params.append('search', query);
|
|
||||||
params.append('mode', mode);
|
|
||||||
|
|
||||||
// Show loading
|
|
||||||
loadingDiv.classList.remove('hidden');
|
|
||||||
resultsDiv.innerHTML = '';
|
|
||||||
|
|
||||||
fetch(`{{ SERVER_BASE }}/search?${params.toString()}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Search request failed');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
loadingDiv.classList.add('hidden');
|
|
||||||
handleSearchResponse(result);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
loadingDiv.classList.add('hidden');
|
|
||||||
console.error('Search error:', error);
|
|
||||||
resultsDiv.innerHTML = `
|
|
||||||
<div class="alert alert-error">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
<span>Search failed. Please try again.</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listeners for search
|
|
||||||
searchButton.addEventListener('click', performSearch);
|
|
||||||
searchbar.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
performSearch(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
110
templates/modals/signup_modal.html
Normal file
110
templates/modals/signup_modal.html
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<div class="modal fade bs-modal-sm" id="signupmodal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-sm">
|
||||||
|
<div class="modal-content">
|
||||||
|
<br>
|
||||||
|
<div class="bs-example bs-example-tabs">
|
||||||
|
<ul id="myTab" class="nav nav-tabs">
|
||||||
|
<li class="active">
|
||||||
|
<a href="#signin" data-toggle="tab">Sign In</a>
|
||||||
|
</li>
|
||||||
|
<li class="">
|
||||||
|
<a href="#signup" data-toggle="tab">Register</a>
|
||||||
|
</li>
|
||||||
|
<li class="">
|
||||||
|
<a href="#why" data-toggle="tab">Why?</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="myTabContent" class="tab-content">
|
||||||
|
<div class="tab-pane fade active in" id="signin">
|
||||||
|
<form class="form-horizontal" method="post" action="{% url 'login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<fieldset>
|
||||||
|
<input type="hidden" name="login" id="login" value="true"/>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="username">Username:</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input required id="username" name="username" type="text" class="form-control"
|
||||||
|
placeholder="username" autocomplete="username">
|
||||||
|
</div>
|
||||||
|
<label class="control-label" for="passwordinput">Password:</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input required id="passwordinput" name="password" class="form-control"
|
||||||
|
type="password" placeholder="********" autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Button -->
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="signin"></label>
|
||||||
|
<div class="controls">
|
||||||
|
<button id="signin" name="signin" class="btn btn-success">Sign In</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Why tab -->
|
||||||
|
<div class="tab-pane fade in" id="why">
|
||||||
|
<p>After you register, you have more permissions on
|
||||||
|
this site, e.g., can create your own
|
||||||
|
packages, submit data for review, and set access
|
||||||
|
permissions to your data.</p>
|
||||||
|
<p></p>
|
||||||
|
<p>
|
||||||
|
<br> Please
|
||||||
|
contact <a href="mailto:admin@envipath.org">admin@envipath.org</a>
|
||||||
|
if you have any questions.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Register -->
|
||||||
|
<div class="tab-pane fade"
|
||||||
|
id="signup">
|
||||||
|
<div id="passwordGuideline" class="alert alert-info">
|
||||||
|
The password must contain 8 to 30 characters<br>
|
||||||
|
The following characters are allowed:
|
||||||
|
- Upper and lower case characters<br>
|
||||||
|
- Digits<br>
|
||||||
|
- Special characters _, -, +<br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="signup-action" class="form-horizontal" action="{% url 'login' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="register" id="register" value="true"/>
|
||||||
|
|
||||||
|
<label class="control-label" for="userid">Username:</label>
|
||||||
|
<input id="userid" name="username" class="form-control" type="text" placeholder="user" required autocomplete="username">
|
||||||
|
|
||||||
|
<label class="control-label" for="email">Email:</label>
|
||||||
|
<input id="email" name="email" class="form-control" type="email" placeholder="user@envipath.org" required>
|
||||||
|
|
||||||
|
<label class="control-label" for="password">Password:</label>
|
||||||
|
<input id="password" name="password" class="form-control" type="password" placeholder="********" required autocomplete="new-password">
|
||||||
|
|
||||||
|
<label class="control-label" for="rpassword">Repeat Password:</label>
|
||||||
|
<input id="rpassword" name="rpassword" class="form-control" type="password" placeholder="********" required autocomplete="new-password">
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="confirmsignup"></label>
|
||||||
|
<div class="controls">
|
||||||
|
<button id="confirmsignup" name="confirmsignup" class="btn btn-success">Sign Up
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<center>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -1,188 +0,0 @@
|
|||||||
{% 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 %}
|
|
||||||
@ -105,7 +105,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" action="{% url 'login' %}" class="space-y-4">
|
<form method="post" action="{% url 'register' %}" class="space-y-4">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="register" value="true" />
|
<input type="hidden" name="register" value="true" />
|
||||||
|
|
||||||
|
|||||||
@ -4,14 +4,7 @@ from django.test import TestCase, tag
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from epdb.logic import UserManager
|
from epdb.logic import UserManager
|
||||||
from epdb.models import (
|
from epdb.models import Package, UserPackagePermission, Permission, GroupPackagePermission, Group
|
||||||
Package,
|
|
||||||
UserPackagePermission,
|
|
||||||
Permission,
|
|
||||||
GroupPackagePermission,
|
|
||||||
Group,
|
|
||||||
License,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PackageViewTest(TestCase):
|
class PackageViewTest(TestCase):
|
||||||
@ -36,15 +29,6 @@ class PackageViewTest(TestCase):
|
|||||||
add_to_group=True,
|
add_to_group=True,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
)
|
)
|
||||||
# Create the default license set.
|
|
||||||
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
|
||||||
for cc_string in cc_strings:
|
|
||||||
if not License.objects.filter(cc_string=cc_string).exists():
|
|
||||||
new_license = License()
|
|
||||||
new_license.cc_string = cc_string
|
|
||||||
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
|
||||||
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
|
||||||
new_license.save()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.force_login(self.user1)
|
self.client.force_login(self.user1)
|
||||||
@ -204,28 +188,7 @@ class PackageViewTest(TestCase):
|
|||||||
self.client.post(package_url, {"license": "no-license"})
|
self.client.post(package_url, {"license": "no-license"})
|
||||||
|
|
||||||
self.assertIsNone(p.license)
|
self.assertIsNone(p.license)
|
||||||
|
# TODO test others
|
||||||
# Test other possible licenses
|
|
||||||
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
|
||||||
for cc_string in cc_strings:
|
|
||||||
self.client.post(package_url, {"license": cc_string})
|
|
||||||
# Without this, the instance of p doesn't have the license. However, the one retrieved with get does
|
|
||||||
p = Package.objects.get(url=package_url)
|
|
||||||
self.assertEqual(
|
|
||||||
p.license.link, f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test again to ensure that Licenses are reused
|
|
||||||
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
|
||||||
for cc_string in cc_strings:
|
|
||||||
self.client.post(package_url, {"license": cc_string})
|
|
||||||
# Without this, the instance of p doesn't have the license. However, the one retrieved with get does
|
|
||||||
p = Package.objects.get(url=package_url)
|
|
||||||
self.assertEqual(
|
|
||||||
p.license.link, f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(License.objects.count(), len(cc_strings))
|
|
||||||
|
|
||||||
def test_delete_package(self):
|
def test_delete_package(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
|
|||||||
@ -714,7 +714,6 @@ class PackageImporter:
|
|||||||
license_obj, _ = License.objects.get_or_create(
|
license_obj, _ = License.objects.get_or_create(
|
||||||
name=license_data["name"],
|
name=license_data["name"],
|
||||||
defaults={
|
defaults={
|
||||||
"cc_string": license_data.get("cc_string", ""),
|
|
||||||
"link": license_data.get("link", ""),
|
"link": license_data.get("link", ""),
|
||||||
"image_link": license_data.get("image_link", ""),
|
"image_link": license_data.get("image_link", ""),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user