Compare commits
2 Commits
develop
...
feature/en
| Author | SHA1 | Date | |
|---|---|---|---|
| 04f9c9252a | |||
| c2d45917ce |
@ -16,5 +16,3 @@ POSTGRES_PORT=
|
||||
# MAIL
|
||||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
# MATOMO
|
||||
MATOMO_SITE_ID
|
||||
|
||||
@ -1,134 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: ${{ !contains(gitea.event.pull_request.title, 'WIP') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
POSTGRES_USER: ${{ vars.POSTGRES_USER }}
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||
POSTGRES_DB: ${{ vars.POSTGRES_DB }}
|
||||
ports:
|
||||
- ${{ vars.POSTGRES_PORT}}:5432
|
||||
options: >-
|
||||
--health-cmd="pg_isready -U postgres"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
|
||||
#redis:
|
||||
# image: redis:7
|
||||
# ports:
|
||||
# - 6379:6379
|
||||
# options: >-
|
||||
# --health-cmd "redis-cli ping"
|
||||
# --health-interval=10s
|
||||
# --health-timeout=5s
|
||||
# --health-retries=5
|
||||
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
EP_DATA_DIR: /opt/enviPy/
|
||||
ALLOWED_HOSTS: 127.0.0.1,localhost
|
||||
DEBUG: True
|
||||
LOG_LEVEL: DEBUG
|
||||
MODEL_BUILDING_ENABLED: True
|
||||
APPLICABILITY_DOMAIN_ENABLED: True
|
||||
ENVIFORMER_PRESENT: True
|
||||
ENVIFORMER_DEVICE: cpu
|
||||
FLAG_CELERY_PRESENT: False
|
||||
PLUGINS_ENABLED: True
|
||||
SERVER_URL: http://localhost:8000
|
||||
ADMIN_APPROVAL_REQUIRED: True
|
||||
REGISTRATION_MANDATORY: True
|
||||
LOG_DIR: ''
|
||||
# DB
|
||||
POSTGRES_SERVICE_NAME: postgres
|
||||
POSTGRES_DB: ${{ vars.POSTGRES_DB }}
|
||||
POSTGRES_USER: ${{ vars.POSTGRES_USER }}
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||
POSTGRES_PORT: 5432
|
||||
# SENTRY
|
||||
SENTRY_ENABLED: False
|
||||
# MS ENTRA
|
||||
MS_ENTRA_ENABLED: False
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install system tools via apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y postgresql-client redis-tools openjdk-11-jre-headless
|
||||
|
||||
- name: Setup ssh
|
||||
run: |
|
||||
echo "${{ secrets.ENVIPY_CI_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan git.envipath.com >> ~/.ssh/known_hosts
|
||||
eval $(ssh-agent -s)
|
||||
ssh-add ~/.ssh/id_ed25519
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Setup venv
|
||||
run: |
|
||||
uv sync --locked --all-extras --dev
|
||||
source .venv/bin/activate
|
||||
playwright install --with-deps
|
||||
|
||||
- name: Run PNPM Commands
|
||||
run: |
|
||||
uv run python scripts/pnpm_wrapper.py install
|
||||
cat << 'EOF' > pnpm-workspace.yaml
|
||||
onlyBuiltDependencies:
|
||||
- '@parcel/watcher'
|
||||
- '@tailwindcss/oxide'
|
||||
EOF
|
||||
uv run python scripts/pnpm_wrapper.py run build
|
||||
|
||||
- name: Wait for services
|
||||
run: |
|
||||
until pg_isready -h postgres -U postgres; do sleep 2; done
|
||||
# until redis-cli -h redis ping; do sleep 2; done
|
||||
|
||||
- name: Run Django Migrations
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
python manage.py migrate --noinput
|
||||
|
||||
- name: Run frontend tests
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
python manage.py test --tag frontend
|
||||
|
||||
- name: Run Django tests
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
python manage.py test tests --exclude-tag slow --exclude-tag frontend
|
||||
6
.gitignore
vendored
@ -6,13 +6,7 @@ static/django_extensions/
|
||||
.env
|
||||
debug.log
|
||||
scratches/
|
||||
test-results/
|
||||
|
||||
data/
|
||||
|
||||
.DS_Store
|
||||
|
||||
node_modules/
|
||||
static/css/output.css
|
||||
|
||||
*.code-workspace
|
||||
|
||||
@ -8,7 +8,6 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
exclude: ^static/images/
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.13.3
|
||||
@ -21,15 +20,6 @@ repos:
|
||||
- id: ruff-format
|
||||
types_or: [python, pyi]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: prettier-jinja-templates
|
||||
name: Format Jinja templates with Prettier
|
||||
entry: pnpm exec prettier --plugin=prettier-plugin-jinja-template --parser=jinja-template --write
|
||||
language: system
|
||||
types: [file]
|
||||
files: ^templates/.*\.html$
|
||||
|
||||
# - repo: local
|
||||
# hooks:
|
||||
# - id: django-check
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-jinja-template", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "templates/**/*.html",
|
||||
"options": {
|
||||
"parser": "jinja-template"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
22
README.md
@ -7,13 +7,11 @@ These instructions will guide you through setting up the project for local devel
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.11 or later
|
||||
- [uv](https://github.com/astral-sh/uv) - Python package manager
|
||||
- **Docker and Docker Compose** - Required for running PostgreSQL database
|
||||
- [uv](https://github.com/astral-sh/uv) - A fast Python package installer and resolver.
|
||||
- **Docker and Docker Compose** - Required for running the PostgreSQL database.
|
||||
- Git
|
||||
- Make
|
||||
|
||||
> **Note:** This application requires PostgreSQL (uses `ArrayField`). Docker is the easiest way to run PostgreSQL locally.
|
||||
|
||||
> **Note:** This application requires PostgreSQL, which uses `ArrayField`. Docker is the recommended way to run PostgreSQL locally.
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
@ -25,12 +23,7 @@ Then, sync the project dependencies. This will create a virtual environment in `
|
||||
uv sync --dev
|
||||
```
|
||||
|
||||
Note on RDkit installation: if you have rdkit installed on your system globally with a different version of python, the installation will try to link against that and subsequent calls fail. Only option remove global rdkit and rerun sync.
|
||||
|
||||
---
|
||||
|
||||
The frontend requires `pnpm` to correctly display in development.
|
||||
[Install it here](https://pnpm.io/installation).
|
||||
> **Note on RDkit:** If you have a different version of rdkit installed globally, the dependency installation may fail. If this happens, please uninstall the global version and run `uv sync` again.
|
||||
|
||||
### 2. Set Up Environment File
|
||||
|
||||
@ -51,7 +44,6 @@ uv run poe setup
|
||||
```
|
||||
|
||||
This single command will:
|
||||
|
||||
1. Start the PostgreSQL database using Docker Compose.
|
||||
2. Run database migrations.
|
||||
3. Bootstrap initial data (anonymous user, default packages, models).
|
||||
@ -62,12 +54,9 @@ After setup, start the development server:
|
||||
uv run poe dev
|
||||
```
|
||||
|
||||
This will start the css-watcher as well as the django-development server,
|
||||
The application will be available at `http://localhost:8000`.
|
||||
|
||||
**Note:** The development server automatically starts a CSS watcher (`pnpm run dev`) alongside the Django server to rebuild CSS files when changes are detected. This ensures your styles are always up-to-date during development.
|
||||
|
||||
#### Other useful Poe commands
|
||||
#### Other useful Poe commands:
|
||||
|
||||
You can list all available commands by running `uv run poe --help`.
|
||||
|
||||
@ -77,7 +66,6 @@ uv run poe db-down # Stop PostgreSQL
|
||||
uv run poe migrate # Run migrations only
|
||||
uv run poe bootstrap # Bootstrap data only
|
||||
uv run poe shell # Open the Django shell
|
||||
uv run poe build # Build frontend assets and collect static files
|
||||
uv run poe clean # Remove database volumes (WARNING: destroys all data)
|
||||
```
|
||||
|
||||
|
||||
@ -49,23 +49,9 @@ INSTALLED_APPS = [
|
||||
"oauth2_provider",
|
||||
# Custom
|
||||
"epdb",
|
||||
# "migration",
|
||||
"migration",
|
||||
]
|
||||
|
||||
TENANT = os.environ.get("TENANT", "public")
|
||||
|
||||
if TENANT != "public":
|
||||
INSTALLED_APPS.append(TENANT)
|
||||
|
||||
EPDB_PACKAGE_MODEL = os.environ.get("EPDB_PACKAGE_MODEL", "epdb.Package")
|
||||
|
||||
|
||||
def GET_PACKAGE_MODEL():
|
||||
from django.apps import apps
|
||||
|
||||
return apps.get_model(EPDB_PACKAGE_MODEL)
|
||||
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
]
|
||||
@ -101,14 +87,11 @@ TEMPLATES = [
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"epdb.context_processors.package_context",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
ALLOWED_HTML_TAGS = {"b", "i", "u", "br", "em", "mark", "p", "s", "strong"}
|
||||
|
||||
WSGI_APPLICATION = "envipath.wsgi.application"
|
||||
|
||||
# Database
|
||||
@ -260,7 +243,6 @@ LOGGING = {
|
||||
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
||||
ENVIFORMER_DEVICE = os.environ.get("ENVIFORMER_DEVICE", "cpu")
|
||||
|
||||
|
||||
# If celery is not present set always eager to true which will cause delayed tasks to block until finished
|
||||
FLAG_CELERY_PRESENT = os.environ.get("FLAG_CELERY_PRESENT", "False") == "True"
|
||||
if not FLAG_CELERY_PRESENT:
|
||||
@ -361,14 +343,6 @@ LOGIN_EXEMPT_URLS = [
|
||||
"/password_reset/",
|
||||
"/reset/",
|
||||
"/microsoft/",
|
||||
"/terms",
|
||||
"/privacy",
|
||||
"/cookie-policy",
|
||||
"/about",
|
||||
"/contact",
|
||||
"/jobs",
|
||||
"/cite",
|
||||
"/legal",
|
||||
]
|
||||
|
||||
# MS AD/Entra
|
||||
@ -383,6 +357,3 @@ if MS_ENTRA_ENABLED:
|
||||
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
|
||||
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
|
||||
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
|
||||
|
||||
# Site ID 10 -> beta.envipath.org
|
||||
MATOMO_SITE_ID = os.environ.get("MATOMO_SITE_ID", "10")
|
||||
|
||||
@ -23,20 +23,12 @@ from .api import api_v1, api_legacy
|
||||
|
||||
urlpatterns = [
|
||||
path("", include("epdb.urls")),
|
||||
path("", include("migration.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
path("api/v1/", api_v1.urls),
|
||||
path("api/legacy/", api_legacy.urls),
|
||||
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
||||
]
|
||||
|
||||
if "migration" in s.INSTALLED_APPS:
|
||||
urlpatterns.append(path("", include("migration.urls")))
|
||||
|
||||
if s.MS_ENTRA_ENABLED:
|
||||
urlpatterns.append(path("", include("epauth.urls")))
|
||||
|
||||
# Custom error handlers
|
||||
handler400 = "epdb.views.handler400"
|
||||
handler403 = "epdb.views.handler403"
|
||||
handler404 = "epdb.views.handler404"
|
||||
handler500 = "epdb.views.handler500"
|
||||
|
||||
@ -1,31 +1,28 @@
|
||||
from django.conf import settings as s
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import (
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
EnviFormer,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
Group,
|
||||
GroupPackagePermission,
|
||||
JobLog,
|
||||
License,
|
||||
MLRelativeReasoning,
|
||||
Node,
|
||||
ParallelRule,
|
||||
Pathway,
|
||||
Reaction,
|
||||
Scenario,
|
||||
Setting,
|
||||
SimpleAmbitRule,
|
||||
User,
|
||||
UserPackagePermission,
|
||||
Group,
|
||||
GroupPackagePermission,
|
||||
Package,
|
||||
MLRelativeReasoning,
|
||||
EnviFormer,
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
SimpleAmbitRule,
|
||||
ParallelRule,
|
||||
Reaction,
|
||||
Pathway,
|
||||
Node,
|
||||
Edge,
|
||||
Scenario,
|
||||
Setting,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
JobLog,
|
||||
)
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
list_display = ["username", "email", "is_active"]
|
||||
@ -65,10 +62,6 @@ class EnviFormerAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class LicenseAdmin(admin.ModelAdmin):
|
||||
list_display = ["cc_string", "link", "image_link"]
|
||||
|
||||
|
||||
class CompoundAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
@ -125,7 +118,6 @@ admin.site.register(JobLog, JobLogAdmin)
|
||||
admin.site.register(Package, PackageAdmin)
|
||||
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||
admin.site.register(EnviFormer, EnviFormerAdmin)
|
||||
admin.site.register(License, LicenseAdmin)
|
||||
admin.site.register(Compound, CompoundAdmin)
|
||||
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
import logging
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EPDBConfig(AppConfig):
|
||||
@ -12,6 +7,3 @@ class EPDBConfig(AppConfig):
|
||||
|
||||
def ready(self):
|
||||
import epdb.signals # noqa: F401
|
||||
|
||||
model_name = getattr(settings, "EPDB_PACKAGE_MODEL", "epdb.Package")
|
||||
logger.info(f"Using Package model: {model_name}")
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
"""
|
||||
Context processors for enviPy application.
|
||||
|
||||
Context processors automatically make variables available to all templates.
|
||||
"""
|
||||
|
||||
from .logic import PackageManager
|
||||
from django.conf import settings as s
|
||||
|
||||
|
||||
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 = s.GET_PACKAGE_MODEL().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,
|
||||
}
|
||||
@ -1,35 +1,27 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import List, Dict, Optional, Any
|
||||
|
||||
import nh3
|
||||
from django.conf import settings as s
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from ninja import Field, Form, Router, Schema, Query
|
||||
from ninja.security import SessionAuth
|
||||
from ninja import Router, Schema, Field, Form
|
||||
|
||||
from utilities.chem import FormatConverter
|
||||
from utilities.misc import PackageExporter
|
||||
|
||||
from .logic import GroupManager, PackageManager, SettingManager, UserManager, SearchManager
|
||||
from .logic import PackageManager, UserManager, SettingManager
|
||||
from .models import (
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
EPModel,
|
||||
Node,
|
||||
Pathway,
|
||||
Reaction,
|
||||
Rule,
|
||||
Scenario,
|
||||
SimpleAmbitRule,
|
||||
Package,
|
||||
User,
|
||||
UserPackagePermission,
|
||||
ParallelRule,
|
||||
Rule,
|
||||
Reaction,
|
||||
Scenario,
|
||||
Pathway,
|
||||
Node,
|
||||
Edge,
|
||||
SimpleAmbitRule,
|
||||
)
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
|
||||
def _anonymous_or_real(request):
|
||||
if request.user.is_authenticated and not request.user.is_anonymous:
|
||||
@ -37,7 +29,8 @@ def _anonymous_or_real(request):
|
||||
return get_user_model().objects.get(username="anonymous")
|
||||
|
||||
|
||||
router = Router(auth=SessionAuth(csrf=False))
|
||||
# router = Router(auth=SessionAuth())
|
||||
router = Router()
|
||||
|
||||
|
||||
class Error(Schema):
|
||||
@ -125,16 +118,13 @@ class SimpleEdge(SimpleObject):
|
||||
identifier: str = "edge"
|
||||
|
||||
|
||||
class SimpleModel(SimpleObject):
|
||||
identifier: str = "relative-reasoning"
|
||||
|
||||
|
||||
################
|
||||
# Login/Logout #
|
||||
################
|
||||
@router.post("/", response={200: SimpleUser, 403: Error}, auth=None)
|
||||
@router.post("/", response={200: SimpleUser, 403: Error})
|
||||
def login(request, loginusername: Form[str], loginpassword: Form[str]):
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth import login
|
||||
|
||||
email = User.objects.get(username=loginusername).email
|
||||
user = authenticate(username=email, password=loginpassword)
|
||||
@ -177,13 +167,9 @@ class UserSchema(Schema):
|
||||
return SettingManager.get_all_settings(obj)
|
||||
|
||||
|
||||
class Me(Schema):
|
||||
whoami: str | None = None
|
||||
|
||||
|
||||
@router.get("/user", response={200: UserWrapper, 403: Error})
|
||||
def get_users(request, me: Query[Me]):
|
||||
if me.whoami:
|
||||
def get_users(request, whoami: str = None):
|
||||
if whoami:
|
||||
return {"user": [request.user]}
|
||||
else:
|
||||
return {"user": User.objects.all()}
|
||||
@ -200,61 +186,6 @@ def get_user(request, user_uuid):
|
||||
}
|
||||
|
||||
|
||||
class Search(Schema):
|
||||
packages: List[str] = Field(alias="packages[]")
|
||||
search: str
|
||||
method: str
|
||||
|
||||
|
||||
@router.get("/search", response={200: Any, 403: Error})
|
||||
def search(request, search: Query[Search]):
|
||||
try:
|
||||
packs = []
|
||||
for package in search.packages:
|
||||
packs.append(PackageManager.get_package_by_url(request.user, package))
|
||||
|
||||
method = None
|
||||
|
||||
if search.method == "text":
|
||||
method = "text"
|
||||
elif search.method == "inchikey":
|
||||
method = "inchikey"
|
||||
elif search.method == "defaultSmiles":
|
||||
method = "default"
|
||||
elif search.method == "canonicalSmiles":
|
||||
method = "canonical"
|
||||
elif search.method == "exactSmiles":
|
||||
method = "exact"
|
||||
|
||||
if method is None:
|
||||
raise ValueError(f"Search method {search.method} is not supported!")
|
||||
|
||||
search_res = SearchManager.search(packs, search.search, method)
|
||||
res = {}
|
||||
if "Compounds" in search_res:
|
||||
res["compound"] = search_res["Compounds"]
|
||||
|
||||
if "Compound Structures" in search_res:
|
||||
res["structure"] = search_res["Compound Structures"]
|
||||
|
||||
if "Reaction" in search_res:
|
||||
res["reaction"] = search_res["Reaction"]
|
||||
|
||||
if "Pathway" in search_res:
|
||||
res["pathway"] = search_res["Pathway"]
|
||||
|
||||
if "Rules" in search_res:
|
||||
res["rule"] = search_res["Rules"]
|
||||
|
||||
for key in res:
|
||||
for v in res[key]:
|
||||
v["id"] = v["url"].replace("simple-ambit-rule", "simple-rule")
|
||||
|
||||
return res
|
||||
except ValueError as e:
|
||||
return 403, {"message": f"Search failed due to {e}"}
|
||||
|
||||
|
||||
###########
|
||||
# Package #
|
||||
###########
|
||||
@ -320,110 +251,67 @@ def get_packages(request):
|
||||
}
|
||||
|
||||
|
||||
class GetPackage(Schema):
|
||||
exportAsJson: str | None = None
|
||||
|
||||
|
||||
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema | Any, 403: Error})
|
||||
def get_package(request, package_uuid, gp: Query[GetPackage]):
|
||||
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema, 403: Error})
|
||||
def get_package(request, package_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if gp.exportAsJson and gp.exportAsJson.strip() == "true":
|
||||
return PackageExporter(p).do_export()
|
||||
|
||||
return p
|
||||
return PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Getting Package with id {package_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
class CreatePackage(Schema):
|
||||
packageName: str
|
||||
packageDescription: str | None = None
|
||||
|
||||
|
||||
@router.post("/package")
|
||||
def create_packages(
|
||||
request,
|
||||
p: Form[CreatePackage],
|
||||
request, packageName: Form[str], packageDescription: Optional[str] = Form(None)
|
||||
):
|
||||
try:
|
||||
if p.packageName.strip() == "":
|
||||
if packageName.strip() == "":
|
||||
raise ValueError("Package name cannot be empty!")
|
||||
|
||||
new_pacakge = PackageManager.create_package(
|
||||
request.user, p.packageName, p.packageDescription
|
||||
)
|
||||
new_pacakge = PackageManager.create_package(request.user, packageName, packageDescription)
|
||||
return redirect(new_pacakge.url)
|
||||
except ValueError as e:
|
||||
return 400, {"message": str(e)}
|
||||
|
||||
|
||||
class UpdatePackage(Schema):
|
||||
packageDescription: str | None = None
|
||||
hiddenMethod: str | None = None
|
||||
permissions: str | None = None
|
||||
ppsURI: str | None = None
|
||||
read: str | None = None
|
||||
write: str | None = None
|
||||
|
||||
|
||||
@router.post("/package/{uuid:package_uuid}", response={200: PackageSchema | Any, 400: Error})
|
||||
def update_package(request, package_uuid, pack: Form[UpdatePackage]):
|
||||
def update_package(
|
||||
request,
|
||||
package_uuid,
|
||||
packageDescription: Optional[str] = Form(None),
|
||||
hiddenMethod: Optional[str] = Form(None),
|
||||
exportAsJson: Optional[str] = Form(None),
|
||||
permissions: Optional[str] = Form(None),
|
||||
ppsURI: Optional[str] = Form(None),
|
||||
read: Optional[str] = Form(None),
|
||||
write: Optional[str] = Form(None),
|
||||
):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if pack.hiddenMethod:
|
||||
if pack.hiddenMethod == "DELETE":
|
||||
if hiddenMethod:
|
||||
if hiddenMethod == "DELETE":
|
||||
p.delete()
|
||||
|
||||
elif pack.packageDescription is not None:
|
||||
description = nh3.clean(pack.packageDescription, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
if description:
|
||||
p.description = description
|
||||
elif packageDescription and packageDescription.strip() != "":
|
||||
p.description = packageDescription
|
||||
p.save()
|
||||
return HttpResponse(status=200)
|
||||
else:
|
||||
raise ValueError("Package description cannot be empty!")
|
||||
elif all([pack.permissions, pack.ppsURI, pack.read]):
|
||||
if "group" in pack.ppsURI:
|
||||
grantee = GroupManager.get_group_lp(pack.ppsURI)
|
||||
else:
|
||||
grantee = UserManager.get_user_lp(pack.ppsURI)
|
||||
return
|
||||
elif exportAsJson == "true":
|
||||
pack_json = PackageManager.export_package(
|
||||
p, include_models=False, include_external_identifiers=False
|
||||
)
|
||||
return pack_json
|
||||
elif all([permissions, ppsURI, read]):
|
||||
PackageManager.update_permissions
|
||||
elif all([permissions, ppsURI, write]):
|
||||
pass
|
||||
|
||||
PackageManager.grant_read(request.user, p, grantee)
|
||||
return HttpResponse(status=200)
|
||||
elif all([pack.permissions, pack.ppsURI, pack.write]):
|
||||
if "group" in pack.ppsURI:
|
||||
grantee = GroupManager.get_group_lp(pack.ppsURI)
|
||||
else:
|
||||
grantee = UserManager.get_user_lp(pack.ppsURI)
|
||||
|
||||
PackageManager.grant_write(request.user, p, grantee)
|
||||
return HttpResponse(status=200)
|
||||
except ValueError as e:
|
||||
return 400, {"message": str(e)}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}")
|
||||
def delete_package(request, package_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.administrable(request.user, p):
|
||||
p.delete()
|
||||
return redirect(f"{s.SERVER_URL}/package")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Package!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Package with id {package_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
################################
|
||||
# Compound / CompoundStructure #
|
||||
################################
|
||||
@ -621,83 +509,6 @@ def get_package_compound_structure(request, package_uuid, compound_uuid, structu
|
||||
}
|
||||
|
||||
|
||||
class CreateCompound(Schema):
|
||||
compoundSmiles: str
|
||||
compoundName: str | None = None
|
||||
compoundDescription: str | None = None
|
||||
inchi: str | None = None
|
||||
|
||||
|
||||
@router.post("/package/{uuid:package_uuid}/compound")
|
||||
def create_package_compound(
|
||||
request,
|
||||
package_uuid,
|
||||
c: Form[CreateCompound],
|
||||
):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
# inchi is not used atm
|
||||
c = Compound.create(
|
||||
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
||||
)
|
||||
return redirect(c.url)
|
||||
except ValueError as e:
|
||||
return 400, {"message": str(e)}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}")
|
||||
def delete_compound(request, package_uuid, compound_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
c = Compound.objects.get(package=p, uuid=compound_uuid)
|
||||
c.delete()
|
||||
return redirect(f"{p.url}/compound")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Compound!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Compound with id {compound_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}"
|
||||
)
|
||||
def delete_compound_structure(request, package_uuid, compound_uuid, structure_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
c = Compound.objects.get(package=p, uuid=compound_uuid)
|
||||
cs = CompoundStructure.objects.get(compound=c, uuid=structure_uuid)
|
||||
|
||||
# Check if we have to delete the compound as no structure is left
|
||||
if len(cs.compound.structures.all()) == 1:
|
||||
# This will delete the structure as well
|
||||
c.delete()
|
||||
return redirect(p.url + "/compound")
|
||||
else:
|
||||
if cs.normalized_structure:
|
||||
c.delete()
|
||||
return redirect(p.url + "/compound")
|
||||
else:
|
||||
if c.default_structure == cs:
|
||||
cs.delete()
|
||||
c.default_structure = c.structures.all().first()
|
||||
return redirect(c.url + "/structure")
|
||||
else:
|
||||
cs.delete()
|
||||
return redirect(c.url + "/structure")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this CompoundStructure!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting CompoundStructure with id {compound_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
#########
|
||||
# Rules #
|
||||
#########
|
||||
@ -861,73 +672,6 @@ def _get_package_rule(request, package_uuid, rule_uuid):
|
||||
|
||||
|
||||
# POST
|
||||
class CreateSimpleRule(Schema):
|
||||
smirks: str
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
reactantFilterSmarts: str | None = None
|
||||
productFilterSmarts: str | None = None
|
||||
immediate: str | None = None
|
||||
rdkitrule: str | None = None
|
||||
|
||||
|
||||
@router.post("/package/{uuid:package_uuid}/simple-rule")
|
||||
def create_package_simple_rule(
|
||||
request,
|
||||
package_uuid,
|
||||
r: Form[CreateSimpleRule],
|
||||
):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if r.rdkitrule and r.rdkitrule.strip() == "true":
|
||||
raise ValueError("Not yet implemented!")
|
||||
else:
|
||||
sr = SimpleAmbitRule.create(
|
||||
p, r.name, r.description, r.smirks, r.reactantFilterSmarts, r.productFilterSmarts
|
||||
)
|
||||
|
||||
return redirect(sr.url)
|
||||
|
||||
except ValueError as e:
|
||||
return 400, {"message": str(e)}
|
||||
|
||||
|
||||
class CreateParallelRule(Schema):
|
||||
simpleRules: str
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
reactantFilterSmarts: str | None = None
|
||||
productFilterSmarts: str | None = None
|
||||
immediate: str | None = None
|
||||
|
||||
|
||||
@router.post("/package/{uuid:package_uuid}/parallel-rule")
|
||||
def create_package_parallel_rule(
|
||||
request,
|
||||
package_uuid,
|
||||
r: Form[CreateParallelRule],
|
||||
):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
srs = SimpleRule.objects.filter(package=p, url__in=r.simpleRules)
|
||||
|
||||
if srs.count() != len(r.simpleRules):
|
||||
raise ValueError(
|
||||
f"Not all SimpleRules could be found in Package with id {package_uuid}!"
|
||||
)
|
||||
|
||||
sr = ParallelRule.create(
|
||||
p, list(srs), r.name, r.description, r.reactantFilterSmarts, r.productFilterSmarts
|
||||
)
|
||||
|
||||
return redirect(sr.url)
|
||||
|
||||
except ValueError as e:
|
||||
return 400, {"message": str(e)}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}
|
||||
)
|
||||
@ -977,41 +721,6 @@ def _post_package_rule(request, package_uuid, rule_uuid, compound: Form[str]):
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}")
|
||||
def delete_rule(request, package_uuid, rule_uuid):
|
||||
return _delete_rule(request, package_uuid, rule_uuid)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}",
|
||||
)
|
||||
def delete_simple_rule(request, package_uuid, rule_uuid):
|
||||
return _delete_rule(request, package_uuid, rule_uuid)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}",
|
||||
)
|
||||
def delete_parallel_rule(request, package_uuid, rule_uuid):
|
||||
return _delete_rule(request, package_uuid, rule_uuid)
|
||||
|
||||
|
||||
def _delete_rule(request, package_uuid, rule_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
r = Rule.objects.get(package=p, uuid=rule_uuid)
|
||||
r.delete()
|
||||
return redirect(f"{p.url}/rule")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Rule!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Rule with id {rule_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
############
|
||||
# Reaction #
|
||||
############
|
||||
@ -1100,82 +809,6 @@ def get_package_reaction(request, package_uuid, reaction_uuid):
|
||||
}
|
||||
|
||||
|
||||
class CreateReaction(Schema):
|
||||
reactionName: str | None = None
|
||||
reactionDescription: str | None = None
|
||||
smirks: str | None = None
|
||||
educt: str | None = None
|
||||
product: str | None = None
|
||||
rule: str | None = None
|
||||
|
||||
|
||||
@router.post("/package/{uuid:package_uuid}/reaction")
|
||||
def create_package_reaction(
|
||||
request,
|
||||
package_uuid,
|
||||
r: Form[CreateReaction],
|
||||
):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if r.smirks is None and (r.educt is None or r.product is None):
|
||||
raise ValueError("Either SMIRKS or educt/product must be provided")
|
||||
|
||||
if r.smirks is not None and (r.educt is not None and r.product is not None):
|
||||
raise ValueError("SMIRKS and educt/product provided!")
|
||||
|
||||
rule = None
|
||||
if r.rule:
|
||||
try:
|
||||
rule = Rule.objects.get(package=p, url=r.rule)
|
||||
except Rule.DoesNotExist:
|
||||
raise ValueError(f"Rule with id {r.rule} does not exist!")
|
||||
|
||||
if r.educt is not None:
|
||||
try:
|
||||
educt_cs = CompoundStructure.objects.get(compound__package=p, url=r.educt)
|
||||
except CompoundStructure.DoesNotExist:
|
||||
raise ValueError(f"Compound with id {r.educt} does not exist!")
|
||||
|
||||
try:
|
||||
product_cs = CompoundStructure.objects.get(compound__package=p, url=r.product)
|
||||
except CompoundStructure.DoesNotExist:
|
||||
raise ValueError(f"Compound with id {r.product} does not exist!")
|
||||
|
||||
new_r = Reaction.create(
|
||||
p, r.reactionName, r.reactionDescription, [educt_cs], [product_cs], rule
|
||||
)
|
||||
else:
|
||||
educts = r.smirks.split(">>")[0].split("\\.")
|
||||
products = r.smirks.split(">>")[1].split("\\.")
|
||||
|
||||
new_r = Reaction.create(
|
||||
p, r.reactionName, r.reactionDescription, educts, products, rule
|
||||
)
|
||||
|
||||
return redirect(new_r.url)
|
||||
|
||||
except ValueError as e:
|
||||
return 400, {"message": str(e)}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/reaction/{uuid:reaction_uuid}")
|
||||
def delete_reaction(request, package_uuid, reaction_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
r = Reaction.objects.get(package=p, uuid=reaction_uuid)
|
||||
r.delete()
|
||||
return redirect(f"{p.url}/reaction")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Reaction!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Reaction with id {reaction_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
############
|
||||
# Scenario #
|
||||
############
|
||||
@ -1190,7 +823,7 @@ class ScenarioSchema(Schema):
|
||||
description: str = Field(None, alias="description")
|
||||
id: str = Field(None, alias="url")
|
||||
identifier: str = "scenario"
|
||||
linkedTo: List[Dict[str, str]] = Field([], alias="linked_to")
|
||||
linkedTo: List[Dict[str, str]] = Field({}, alias="linked_to")
|
||||
name: str = Field(None, alias="name")
|
||||
pathways: List["SimplePathway"] = Field([], alias="related_pathways")
|
||||
relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios")
|
||||
@ -1241,38 +874,6 @@ def get_package_scenario(request, package_uuid, scenario_uuid):
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/scenario")
|
||||
def delete_scenarios(request, package_uuid, scenario_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
scens = Scenario.objects.filter(package=p)
|
||||
scens.delete()
|
||||
return redirect(f"{p.url}/scenario")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete Scenarios!")
|
||||
except ValueError:
|
||||
return 403, {"message": "Deleting Scenarios failed due to insufficient rights!"}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/scenario/{uuid:scenario_uuid}")
|
||||
def delete_scenario(request, package_uuid, scenario_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
scen = Scenario.objects.get(package=p, uuid=scenario_uuid)
|
||||
scen.delete()
|
||||
return redirect(f"{p.url}/scenario")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Scenario!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Scenario with id {scenario_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
###########
|
||||
# Pathway #
|
||||
###########
|
||||
@ -1412,67 +1013,46 @@ def get_package_pathway(request, package_uuid, pathway_uuid):
|
||||
}
|
||||
|
||||
|
||||
class CreatePathway(Schema):
|
||||
smilesinput: str
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
rootOnly: str | None = None
|
||||
selectedSetting: str | None = None
|
||||
|
||||
|
||||
@router.post("/package/{uuid:package_uuid}/pathway")
|
||||
def create_pathway(
|
||||
request,
|
||||
package_uuid,
|
||||
pw: Form[CreatePathway],
|
||||
smilesinput: Form[str],
|
||||
name: Optional[str] = Form(None),
|
||||
description: Optional[str] = Form(None),
|
||||
rootOnly: Optional[str] = Form(None),
|
||||
selectedSetting: Optional[str] = Form(None),
|
||||
):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
stand_smiles = FormatConverter.standardize(pw.smilesinput.strip())
|
||||
stand_smiles = FormatConverter.standardize(smilesinput.strip())
|
||||
|
||||
new_pw = Pathway.create(p, stand_smiles, name=pw.name, description=pw.description)
|
||||
pw = Pathway.create(p, stand_smiles, name=name, description=description)
|
||||
|
||||
pw_mode = "predict"
|
||||
if pw.rootOnly and pw.rootOnly.strip() == "true":
|
||||
if rootOnly and rootOnly == "true":
|
||||
pw_mode = "build"
|
||||
|
||||
new_pw.kv.update({"mode": pw_mode})
|
||||
new_pw.save()
|
||||
pw.kv.update({"mode": pw_mode})
|
||||
pw.save()
|
||||
|
||||
if pw_mode == "predict":
|
||||
setting = request.user.prediction_settings()
|
||||
|
||||
if pw.selectedSetting:
|
||||
setting = SettingManager.get_setting_by_url(request.user, pw.selectedSetting)
|
||||
if selectedSetting:
|
||||
setting = SettingManager.get_setting_by_url(request.user, selectedSetting)
|
||||
|
||||
new_pw.setting = setting
|
||||
new_pw.save()
|
||||
pw.setting = setting
|
||||
pw.save()
|
||||
|
||||
from .tasks import dispatch, predict
|
||||
from .tasks import predict
|
||||
|
||||
dispatch(request.user, predict, new_pw.pk, setting.pk, limit=-1)
|
||||
predict.delay(pw.pk, setting.pk, limit=-1)
|
||||
|
||||
return redirect(new_pw.url)
|
||||
return redirect(pw.url)
|
||||
except ValueError as e:
|
||||
return 400, {"message": str(e)}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
||||
def delete_pathway(request, package_uuid, pathway_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||
pw.delete()
|
||||
return redirect(f"{p.url}/pathway")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this pathway!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Pathway with id {pathway_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
print(e)
|
||||
|
||||
|
||||
########
|
||||
@ -1563,52 +1143,6 @@ def get_package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
|
||||
}
|
||||
|
||||
|
||||
class CreateNode(Schema):
|
||||
nodeAsSmiles: str
|
||||
nodeName: str | None = None
|
||||
nodeReason: str | None = None
|
||||
nodeDepth: str | None = None
|
||||
|
||||
|
||||
@router.post(
|
||||
"/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/node",
|
||||
response={200: str | Any, 403: Error},
|
||||
)
|
||||
def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||
|
||||
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
||||
node_depth = int(n.nodeDepth)
|
||||
else:
|
||||
node_depth = -1
|
||||
|
||||
n = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
||||
|
||||
return redirect(n.url)
|
||||
except ValueError:
|
||||
return 403, {"message": "Adding node failed!"}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/node/{uuid:node_uuid}")
|
||||
def delete_node(request, package_uuid, pathway_uuid, node_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||
n = Node.objects.get(pathway=pw, uuid=node_uuid)
|
||||
n.delete()
|
||||
return redirect(f"{pw.url}/node")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Node!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Node with id {node_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
########
|
||||
# Edge #
|
||||
########
|
||||
@ -1672,200 +1206,6 @@ def get_package_pathway_edge(request, package_uuid, pathway_uuid, edge_uuid):
|
||||
}
|
||||
|
||||
|
||||
class CreateEdge(Schema):
|
||||
edgeAsSmirks: str | None = None
|
||||
educts: str | None = None # Node URIs comma sep
|
||||
products: str | None = None # Node URIs comma sep
|
||||
multistep: str | None = None
|
||||
edgeReason: str | None = None
|
||||
|
||||
|
||||
@router.post(
|
||||
"/package/{uuid:package_uuid}/üathway/{uuid:pathway_uuid}/edge",
|
||||
response={200: str | Any, 403: Error},
|
||||
)
|
||||
def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||
|
||||
if e.edgeAsSmirks is None and (e.educts is None or e.products is None):
|
||||
raise ValueError("Either SMIRKS or educt/product must be provided")
|
||||
|
||||
if e.edgeAsSmirks is not None and (e.educts is not None and e.products is not None):
|
||||
raise ValueError("SMIRKS and educt/product provided!")
|
||||
|
||||
educts = []
|
||||
products = []
|
||||
|
||||
if e.edgeAsSmirks:
|
||||
for ed in e.edgeAsSmirks.split(">>")[0].split("\\."):
|
||||
educts.append(Node.objects.get(pathway=pw, default_node_label__smiles=ed))
|
||||
|
||||
for pr in e.edgeAsSmirks.split(">>")[1].split("\\."):
|
||||
products.append(Node.objects.get(pathway=pw, default_node_label__smiles=pr))
|
||||
else:
|
||||
for ed in e.educts.split(","):
|
||||
educts.append(Node.objects.get(pathway=pw, url=ed.strip()))
|
||||
|
||||
for pr in e.products.split(","):
|
||||
products.append(Node.objects.get(pathway=pw, url=pr.strip()))
|
||||
|
||||
new_e = Edge.create(
|
||||
pathway=pw,
|
||||
start_nodes=educts,
|
||||
end_nodes=products,
|
||||
rule=None,
|
||||
name=e.name,
|
||||
description=e.edgeReason,
|
||||
)
|
||||
|
||||
return redirect(new_e.url)
|
||||
except ValueError:
|
||||
return 403, {"message": "Adding node failed!"}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/edge/{uuid:edge_uuid}")
|
||||
def delete_edge(request, package_uuid, pathway_uuid, edge_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||
e = Edge.objects.get(pathway=pw, uuid=edge_uuid)
|
||||
e.delete()
|
||||
return redirect(f"{pw.url}/edge")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Edge!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Edge with id {edge_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
#########
|
||||
# Model #
|
||||
#########
|
||||
class ModelWrapper(Schema):
|
||||
relative_reasoning: List["SimpleModel"] = Field(..., alias="relative-reasoning")
|
||||
|
||||
|
||||
class ModelSchema(Schema):
|
||||
aliases: List[str] = Field([], alias="aliases")
|
||||
description: str = Field(None, alias="description")
|
||||
evalPackages: List["SimplePackage"] = Field([])
|
||||
id: str = Field(None, alias="url")
|
||||
identifier: str = "relative-reasoning"
|
||||
# "info" : {
|
||||
# "Accuracy (Single-Gen)" : "0.5932962678936605" ,
|
||||
# "Area under PR-Curve (Single-Gen)" : "0.5654653182134282" ,
|
||||
# "Area under ROC-Curve (Single-Gen)" : "0.8178302405034772" ,
|
||||
# "Precision (Single-Gen)" : "0.6978730822873083" ,
|
||||
# "Probability Threshold" : "0.5" ,
|
||||
# "Recall/Sensitivity (Single-Gen)" : "0.4484149210261006"
|
||||
# } ,
|
||||
name: str = Field(None, alias="name")
|
||||
pathwayPackages: List["SimplePackage"] = Field([])
|
||||
reviewStatus: str = Field(None, alias="review_status")
|
||||
rulePackages: List["SimplePackage"] = Field([])
|
||||
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
||||
status: str
|
||||
statusMessage: str
|
||||
threshold: str
|
||||
type: str
|
||||
|
||||
|
||||
@router.get("/model", response={200: ModelWrapper, 403: Error})
|
||||
def get_models(request):
|
||||
pass
|
||||
|
||||
|
||||
@router.get("/package/{uuid:package_uuid}/model", response={200: ModelWrapper, 403: Error})
|
||||
def get_package_models(request, package_uuid, model_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
return EPModel.objects.filter(package=p)
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Getting Reaction with id {model_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
class Classify(Schema):
|
||||
smiles: str | None = None
|
||||
|
||||
|
||||
@router.get(
|
||||
"/package/{uuid:package_uuid}/model/{uuid:model_uuid}",
|
||||
response={200: ModelSchema | Any, 403: Error, 400: Error},
|
||||
)
|
||||
def get_model(request, package_uuid, model_uuid, c: Query[Classify]):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
mod = EPModel.objects.get(package=p, uuid=model_uuid)
|
||||
|
||||
if c.smiles:
|
||||
if c.smiles == "":
|
||||
return 400, {"message": "Received empty SMILES"}
|
||||
|
||||
try:
|
||||
stand_smiles = FormatConverter.standardize(c.smiles)
|
||||
except ValueError:
|
||||
return 400, {"message": f'"{c.smiles}" is not a valid SMILES'}
|
||||
|
||||
from epdb.tasks import dispatch_eager, predict_simple
|
||||
|
||||
pred_res = dispatch_eager(request.user, predict_simple, mod.pk, stand_smiles)
|
||||
|
||||
result = []
|
||||
|
||||
for pr in pred_res:
|
||||
if len(pr) > 0:
|
||||
products = []
|
||||
for prod_set in pr.product_sets:
|
||||
products.append(tuple([x for x in prod_set]))
|
||||
|
||||
res = {
|
||||
"probability": pr.probability,
|
||||
"products": list(set(products)),
|
||||
}
|
||||
|
||||
if pr.rule:
|
||||
res["id"] = pr.rule.url
|
||||
res["identifier"] = pr.rule.get_rule_identifier()
|
||||
res["name"] = pr.rule.name
|
||||
res["reviewStatus"] = (
|
||||
"reviewed" if pr.rule.package.reviewed else "unreviewed"
|
||||
)
|
||||
|
||||
result.append(res)
|
||||
|
||||
return result
|
||||
|
||||
return mod
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Getting Reaction with id {model_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/package/{uuid:package_uuid}/model/{uuid:model_uuid}")
|
||||
def delete_model(request, package_uuid, model_uuid):
|
||||
try:
|
||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||
|
||||
if PackageManager.writable(request.user, p):
|
||||
m = EPModel.objects.get(package=p, uuid=model_uuid)
|
||||
m.delete()
|
||||
return redirect(f"{p.url}/model")
|
||||
else:
|
||||
raise ValueError("You do not have the rights to delete this Model!")
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Deleting Model with id {model_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
###########
|
||||
# Setting #
|
||||
###########
|
||||
|
||||
122
epdb/logic.py
@ -1,40 +1,38 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional, Set, Union
|
||||
import logging
|
||||
import json
|
||||
from typing import Union, List, Optional, Set, Dict, Any
|
||||
from uuid import UUID
|
||||
|
||||
import nh3
|
||||
from django.conf import settings as s
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
from django.conf import settings as s
|
||||
from pydantic import ValidationError
|
||||
|
||||
from epdb.models import (
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
EnzymeLink,
|
||||
EPModel,
|
||||
Group,
|
||||
GroupPackagePermission,
|
||||
Node,
|
||||
Pathway,
|
||||
Permission,
|
||||
Reaction,
|
||||
Rule,
|
||||
Setting,
|
||||
User,
|
||||
Package,
|
||||
UserPackagePermission,
|
||||
GroupPackagePermission,
|
||||
Permission,
|
||||
Group,
|
||||
Setting,
|
||||
EPModel,
|
||||
UserSettingPermission,
|
||||
Rule,
|
||||
Pathway,
|
||||
Node,
|
||||
Edge,
|
||||
Compound,
|
||||
Reaction,
|
||||
CompoundStructure,
|
||||
EnzymeLink,
|
||||
)
|
||||
from utilities.chem import FormatConverter
|
||||
from utilities.misc import PackageExporter, PackageImporter
|
||||
from utilities.misc import PackageImporter, PackageExporter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
|
||||
class EPDBURLParser:
|
||||
UUID_PATTERN = r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||
@ -187,12 +185,6 @@ class UserManager(object):
|
||||
def create_user(
|
||||
username, email, password, set_setting=True, add_to_group=True, *args, **kwargs
|
||||
):
|
||||
# Clean for potential XSS
|
||||
clean_username = nh3.clean(username).strip()
|
||||
clean_email = nh3.clean(email).strip()
|
||||
if clean_username != username or clean_email != email:
|
||||
# This will be caught by the try in view.py/register
|
||||
raise ValueError("Invalid username or password")
|
||||
# avoid circular import :S
|
||||
from .tasks import send_registration_mail
|
||||
|
||||
@ -270,9 +262,8 @@ class GroupManager(object):
|
||||
@staticmethod
|
||||
def create_group(current_user, name, description):
|
||||
g = Group()
|
||||
# Clean for potential XSS
|
||||
g.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
g.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
g.name = name
|
||||
g.description = description
|
||||
g.owner = current_user
|
||||
g.save()
|
||||
|
||||
@ -527,13 +518,8 @@ class PackageManager(object):
|
||||
@transaction.atomic
|
||||
def create_package(current_user, name: str, description: str = None):
|
||||
p = Package()
|
||||
|
||||
# Clean for potential XSS
|
||||
p.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
if description is not None and description.strip() != "":
|
||||
p.description = nh3.clean(description.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
p.name = name
|
||||
p.description = description
|
||||
p.save()
|
||||
|
||||
up = UserPackagePermission()
|
||||
@ -579,39 +565,30 @@ class PackageManager(object):
|
||||
else:
|
||||
_ = perm_cls.objects.update_or_create(defaults={"permission": new_perm}, **data)
|
||||
|
||||
@staticmethod
|
||||
def grant_read(caller: User, package: Package, grantee: Union[User, Group]):
|
||||
PackageManager.update_permissions(caller, package, grantee, Permission.READ[0])
|
||||
|
||||
@staticmethod
|
||||
def grant_write(caller: User, package: Package, grantee: Union[User, Group]):
|
||||
PackageManager.update_permissions(caller, package, grantee, Permission.WRITE[0])
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def import_legacy_package(
|
||||
data: dict, owner: User, keep_ids=False, add_import_timestamp=True, trust_reviewed=False
|
||||
):
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from envipy_additional_information import AdditionalInformationConverter
|
||||
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
from .models import (
|
||||
Package,
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
Node,
|
||||
SimpleRule,
|
||||
SimpleAmbitRule,
|
||||
ParallelRule,
|
||||
Pathway,
|
||||
Reaction,
|
||||
Scenario,
|
||||
SequentialRule,
|
||||
SequentialRuleOrdering,
|
||||
SimpleAmbitRule,
|
||||
SimpleRule,
|
||||
Reaction,
|
||||
Pathway,
|
||||
Node,
|
||||
Edge,
|
||||
Scenario,
|
||||
)
|
||||
from envipy_additional_information import AdditionalInformationConverter
|
||||
|
||||
pack = Package()
|
||||
pack.uuid = UUID(data["id"].split("/")[-1]) if keep_ids else uuid4()
|
||||
@ -1117,29 +1094,28 @@ class SettingManager(object):
|
||||
model: EPModel = None,
|
||||
model_threshold: float = None,
|
||||
):
|
||||
new_s = Setting()
|
||||
# Clean for potential XSS
|
||||
new_s.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
new_s.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
new_s.max_nodes = max_nodes
|
||||
new_s.max_depth = max_depth
|
||||
new_s.model = model
|
||||
new_s.model_threshold = model_threshold
|
||||
s = Setting()
|
||||
s.name = name
|
||||
s.description = description
|
||||
s.max_nodes = max_nodes
|
||||
s.max_depth = max_depth
|
||||
s.model = model
|
||||
s.model_threshold = model_threshold
|
||||
|
||||
new_s.save()
|
||||
s.save()
|
||||
|
||||
if rule_packages is not None:
|
||||
for r in rule_packages:
|
||||
new_s.rule_packages.add(r)
|
||||
new_s.save()
|
||||
s.rule_packages.add(r)
|
||||
s.save()
|
||||
|
||||
usp = UserSettingPermission()
|
||||
usp.user = user
|
||||
usp.setting = new_s
|
||||
usp.setting = s
|
||||
usp.permission = Permission.ALL[0]
|
||||
usp.save()
|
||||
|
||||
return new_s
|
||||
return s
|
||||
|
||||
@staticmethod
|
||||
def get_default_setting(user: User):
|
||||
@ -1568,7 +1544,7 @@ class SPathway(object):
|
||||
if self.prediction_setting.model.app_domain:
|
||||
app_domain_assessment = self.prediction_setting.model.app_domain.assess(
|
||||
sub.smiles
|
||||
)
|
||||
)[0]
|
||||
|
||||
if self.persist is not None:
|
||||
n = self.snode_persist_lookup[sub]
|
||||
@ -1601,7 +1577,9 @@ class SPathway(object):
|
||||
if self.prediction_setting.model:
|
||||
if self.prediction_setting.model.app_domain:
|
||||
app_domain_assessment = (
|
||||
self.prediction_setting.model.app_domain.assess(c)
|
||||
self.prediction_setting.model.app_domain.assess(c)[
|
||||
0
|
||||
]
|
||||
)
|
||||
|
||||
self.smiles_to_node[c] = SNode(
|
||||
|
||||
@ -12,16 +12,10 @@ from epdb.models import (
|
||||
Permission,
|
||||
User,
|
||||
ExternalDatabase,
|
||||
License,
|
||||
)
|
||||
|
||||
|
||||
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):
|
||||
# Anonymous User
|
||||
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||
@ -89,17 +83,6 @@ class Command(BaseCommand):
|
||||
|
||||
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):
|
||||
return PackageManager.import_legacy_package(
|
||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||
@ -174,10 +157,6 @@ class Command(BaseCommand):
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
# Create licenses
|
||||
self.create_licenses()
|
||||
if options.get("only_licenses", False):
|
||||
return
|
||||
# Create users
|
||||
anon, admin, g, user0 = self.create_users()
|
||||
|
||||
|
||||
@ -2,9 +2,7 @@ from django.conf import settings as s
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import EnviFormer, MLRelativeReasoning
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import MLRelativeReasoning, EnviFormer, Package
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@ -77,13 +75,11 @@ class Command(BaseCommand):
|
||||
return packages
|
||||
|
||||
# Iteratively create models in options["model_names"]
|
||||
print(
|
||||
f"Creating models: {options['model_names']}\n"
|
||||
print(f"Creating models: {options['model_names']}\n"
|
||||
f"Data packages: {options['data_packages']}\n"
|
||||
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
||||
f"Eval Packages: {options['eval_packages']}\n"
|
||||
f"Threshold: {options['threshold']:.2f}"
|
||||
)
|
||||
f"Threshold: {options['threshold']:.2f}")
|
||||
data_packages = decode_packages(options["data_packages"])
|
||||
eval_packages = decode_packages(options["eval_packages"])
|
||||
rule_packages = decode_packages(options["rule_packages"])
|
||||
@ -94,7 +90,7 @@ class Command(BaseCommand):
|
||||
pack,
|
||||
data_packages=data_packages,
|
||||
eval_packages=eval_packages,
|
||||
threshold=options["threshold"],
|
||||
threshold=options['threshold'],
|
||||
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
||||
f"evaluated on {options['eval_packages']}.",
|
||||
@ -105,7 +101,7 @@ class Command(BaseCommand):
|
||||
rule_packages=rule_packages,
|
||||
data_packages=data_packages,
|
||||
eval_packages=eval_packages,
|
||||
threshold=options["threshold"],
|
||||
threshold=options['threshold'],
|
||||
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
||||
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
|
||||
|
||||
@ -8,9 +8,7 @@ from django.conf import settings as s
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from epdb.models import EnviFormer
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
from epdb.models import EnviFormer, Package
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
from django.apps import apps
|
||||
from django.conf import settings as s
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import F, JSONField, TextField, Value
|
||||
from django.db.models.functions import Cast, Replace
|
||||
|
||||
from django.db.models import F, Value, TextField, JSONField
|
||||
from django.db.models.functions import Replace, Cast
|
||||
|
||||
from epdb.models import EnviPathModel
|
||||
|
||||
@ -23,13 +23,10 @@ class Command(BaseCommand):
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
print("Localizing urls for Package")
|
||||
Package.objects.update(url=Replace(F("url"), Value(options["old"]), Value(options["new"])))
|
||||
|
||||
MODELS = [
|
||||
"User",
|
||||
"Group",
|
||||
"Package",
|
||||
"Compound",
|
||||
"CompoundStructure",
|
||||
"Pathway",
|
||||
|
||||
@ -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)]
|
||||
548
epdb/models.py
@ -1,22 +1,19 @@
|
||||
import csv
|
||||
import io
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, List, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from celery import shared_task
|
||||
from celery.utils.functional import LRUCache
|
||||
from django.conf import settings as s
|
||||
from django.utils import timezone
|
||||
|
||||
from epdb.logic import SPathway
|
||||
from epdb.models import Edge, EPModel, JobLog, Node, Pathway, Rule, Setting, User
|
||||
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
|
||||
def get_ml_model(model_pk: int):
|
||||
if model_pk not in ML_CACHE:
|
||||
@ -32,7 +29,7 @@ def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
|
||||
log.task_id = uuid4()
|
||||
log.job_name = job.__name__
|
||||
log.status = "SUCCESS"
|
||||
log.done_at = timezone.now()
|
||||
log.done_at = datetime.now()
|
||||
log.task_result = str(x) if x else None
|
||||
log.save()
|
||||
|
||||
@ -175,6 +172,7 @@ def predict(
|
||||
|
||||
except Exception as e:
|
||||
pw.kv.update({"status": "failed"})
|
||||
pw.kv.update(**{"error": str(e)})
|
||||
pw.save()
|
||||
|
||||
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||
|
||||
15
epdb/urls.py
@ -48,7 +48,6 @@ urlpatterns = [
|
||||
re_path(r"^user$", v.users, name="users"),
|
||||
re_path(r"^group$", v.groups, name="groups"),
|
||||
re_path(r"^search$", v.search, name="search"),
|
||||
re_path(r"^predict$", v.predict_pathway, name="predict_pathway"),
|
||||
# User Detail
|
||||
re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"),
|
||||
# Group Detail
|
||||
@ -142,11 +141,6 @@ urlpatterns = [
|
||||
v.package_pathway,
|
||||
name="package pathway detail",
|
||||
),
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/predict$",
|
||||
v.package_predict_pathway,
|
||||
name="package predict pathway",
|
||||
),
|
||||
# Pathway Nodes
|
||||
re_path(
|
||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$",
|
||||
@ -199,13 +193,4 @@ urlpatterns = [
|
||||
re_path(r"^jobs", v.jobs, name="jobs"),
|
||||
# OAuth Stuff
|
||||
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
||||
# Static Pages
|
||||
re_path(r"^terms$", v.static_terms_of_use, name="terms_of_use"),
|
||||
re_path(r"^privacy$", v.static_privacy_policy, name="privacy_policy"),
|
||||
re_path(r"^cookie-policy$", v.static_cookie_policy, name="cookie_policy"),
|
||||
re_path(r"^about$", v.static_about_us, name="about_us"),
|
||||
re_path(r"^contact$", v.static_contact_support, name="contact_support"),
|
||||
re_path(r"^careers$", v.static_careers, name="careers"),
|
||||
re_path(r"^cite$", v.static_cite, name="cite"),
|
||||
re_path(r"^legal$", v.static_legal, name="legal"),
|
||||
]
|
||||
|
||||
371
epdb/views.py
@ -1,12 +1,11 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
from typing import List, Dict, Any
|
||||
|
||||
import nh3
|
||||
from django.conf import settings as s
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
|
||||
from django.shortcuts import render, redirect
|
||||
from django.urls import reverse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from envipy_additional_information import NAME_MAPPING
|
||||
@ -15,46 +14,44 @@ from oauth2_provider.decorators import protected_resource
|
||||
from utilities.chem import FormatConverter, IndigoUtils
|
||||
from utilities.decorators import package_permission_required
|
||||
from utilities.misc import HTMLGenerator
|
||||
|
||||
from .logic import (
|
||||
EPDBURLParser,
|
||||
GroupManager,
|
||||
PackageManager,
|
||||
SearchManager,
|
||||
SettingManager,
|
||||
UserManager,
|
||||
SettingManager,
|
||||
SearchManager,
|
||||
EPDBURLParser,
|
||||
)
|
||||
from .models import (
|
||||
APIToken,
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
EnviFormer,
|
||||
EnzymeLink,
|
||||
EPModel,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
Group,
|
||||
Package,
|
||||
GroupPackagePermission,
|
||||
JobLog,
|
||||
License,
|
||||
MLRelativeReasoning,
|
||||
Node,
|
||||
Pathway,
|
||||
Permission,
|
||||
Group,
|
||||
CompoundStructure,
|
||||
Compound,
|
||||
Reaction,
|
||||
Rule,
|
||||
Pathway,
|
||||
Node,
|
||||
EPModel,
|
||||
EnviFormer,
|
||||
MLRelativeReasoning,
|
||||
RuleBasedRelativeReasoning,
|
||||
Scenario,
|
||||
SimpleAmbitRule,
|
||||
User,
|
||||
APIToken,
|
||||
UserPackagePermission,
|
||||
Permission,
|
||||
License,
|
||||
User,
|
||||
Edge,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
EnzymeLink,
|
||||
JobLog,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
|
||||
def log_post_params(request):
|
||||
if s.DEBUG:
|
||||
@ -62,26 +59,6 @@ def log_post_params(request):
|
||||
logger.debug(f"{k}\t{v}")
|
||||
|
||||
|
||||
def get_error_handler_context(request, for_user=None) -> Dict[str, Any]:
|
||||
current_user = _anonymous_or_real(request)
|
||||
|
||||
if for_user:
|
||||
current_user = for_user
|
||||
|
||||
ctx = {
|
||||
"title": "enviPath",
|
||||
"meta": {
|
||||
"site_id": s.MATOMO_SITE_ID,
|
||||
"version": "0.0.1",
|
||||
"server_url": s.SERVER_URL,
|
||||
"user": current_user,
|
||||
"enabled_features": s.FLAGS,
|
||||
"debug": s.DEBUG,
|
||||
},
|
||||
}
|
||||
return ctx
|
||||
|
||||
|
||||
def error(request, message: str, detail: str, code: int = 400):
|
||||
context = get_base_context(request)
|
||||
error_context = {
|
||||
@ -96,48 +73,6 @@ def error(request, message: str, detail: str, code: int = 400):
|
||||
return render(request, "errors/error.html", context, status=code)
|
||||
|
||||
|
||||
def handler400(request, exception):
|
||||
"""Custom 400 Bad Request error handler"""
|
||||
context = get_error_handler_context(request)
|
||||
context["public_mode"] = True
|
||||
return render(request, "errors/400_bad_request.html", context, status=400)
|
||||
|
||||
|
||||
def handler403(request, exception):
|
||||
"""Custom 403 Forbidden error handler"""
|
||||
context = get_error_handler_context(request)
|
||||
context["public_mode"] = True
|
||||
return render(request, "errors/403_access_denied.html", context, status=403)
|
||||
|
||||
|
||||
def handler404(request, exception):
|
||||
"""Custom 404 Not Found error handler"""
|
||||
context = get_error_handler_context(request)
|
||||
context["public_mode"] = True
|
||||
return render(request, "errors/404_not_found.html", context, status=404)
|
||||
|
||||
|
||||
def handler500(request):
|
||||
"""Custom 500 Internal Server Error handler"""
|
||||
context = get_error_handler_context(request)
|
||||
|
||||
error_context = {}
|
||||
error_context["error_message"] = "Internal Server Error"
|
||||
error_context["error_detail"] = "An unexpected error occurred. Please try again later."
|
||||
|
||||
if request.headers.get("Accept") == "application/json":
|
||||
return JsonResponse(error_context, status=500)
|
||||
|
||||
context["public_mode"] = True
|
||||
context["error_code"] = 500
|
||||
context["error_description"] = (
|
||||
"We encountered an unexpected error while processing your request. Our team has been notified and is working to resolve the issue."
|
||||
)
|
||||
context.update(**error_context)
|
||||
|
||||
return render(request, "errors/error.html", context, status=500)
|
||||
|
||||
|
||||
def login(request):
|
||||
context = get_base_context(request)
|
||||
|
||||
@ -147,13 +82,10 @@ def login(request):
|
||||
return render(request, "static/login.html", context)
|
||||
|
||||
elif request.method == "POST":
|
||||
from django.contrib.auth import authenticate, login
|
||||
|
||||
username = request.POST.get("username").strip()
|
||||
if username != request.POST.get("username"):
|
||||
context["message"] = "Login failed!"
|
||||
return render(request, "static/login.html", context)
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth import login
|
||||
|
||||
username = request.POST.get("username")
|
||||
password = request.POST.get("password")
|
||||
|
||||
# Get email for username and check if the account is active
|
||||
@ -168,7 +100,6 @@ def login(request):
|
||||
except get_user_model().DoesNotExist:
|
||||
context["message"] = "Login failed!"
|
||||
return render(request, "static/login.html", context)
|
||||
|
||||
try:
|
||||
user = authenticate(username=email, password=password)
|
||||
except Exception:
|
||||
@ -206,14 +137,9 @@ def register(request):
|
||||
context = get_base_context(request)
|
||||
|
||||
if request.method == "GET":
|
||||
# Redirect to unified login page with signup tab
|
||||
next_url = request.GET.get("next", "")
|
||||
redirect_url = reverse("login") + "#signup"
|
||||
|
||||
if next_url:
|
||||
redirect_url += f"?next={next_url}"
|
||||
|
||||
return redirect(redirect_url)
|
||||
context["title"] = "enviPath"
|
||||
context["next"] = request.GET.get("next", "")
|
||||
return render(request, "static/register.html", context)
|
||||
elif request.method == "POST":
|
||||
context["title"] = "enviPath"
|
||||
if next := request.POST.get("next"):
|
||||
@ -226,18 +152,18 @@ def register(request):
|
||||
|
||||
if not (username and email and password):
|
||||
context["message"] = "Invalid username/email/password"
|
||||
return render(request, "static/login.html", context)
|
||||
return render(request, "static/register.html", context)
|
||||
|
||||
if password != rpassword or password == "":
|
||||
context["message"] = "Registration failed, provided passwords differ!"
|
||||
return render(request, "static/login.html", context)
|
||||
return render(request, "static/register.html", context)
|
||||
|
||||
try:
|
||||
u = UserManager.create_user(username, email, password)
|
||||
logger.info(f"Created user {u.username} ({u.pk})")
|
||||
except Exception:
|
||||
context["message"] = "Registration failed! Couldn't create User Account."
|
||||
return render(request, "static/login.html", context)
|
||||
return render(request, "static/register.html", context)
|
||||
|
||||
if s.ADMIN_APPROVAL_REQUIRED:
|
||||
context["success_message"] = (
|
||||
@ -254,8 +180,8 @@ def register(request):
|
||||
|
||||
|
||||
def editable(request, user):
|
||||
# if user.is_superuser:
|
||||
# return True
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
url = request.build_absolute_uri(request.path)
|
||||
if PackageManager.is_package_url(url):
|
||||
@ -311,7 +237,6 @@ def get_base_context(request, for_user=None) -> Dict[str, Any]:
|
||||
"enabled_features": s.FLAGS,
|
||||
"debug": s.DEBUG,
|
||||
"external_databases": ExternalDatabase.get_databases(),
|
||||
"site_id": s.MATOMO_SITE_ID,
|
||||
},
|
||||
}
|
||||
|
||||
@ -425,34 +350,6 @@ def index(request):
|
||||
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)
|
||||
|
||||
|
||||
@package_permission_required()
|
||||
def package_predict_pathway(request, package_uuid):
|
||||
"""Package-specific predict pathway view."""
|
||||
if request.method != "GET":
|
||||
return HttpResponseNotAllowed(["GET"])
|
||||
|
||||
current_user = _anonymous_or_real(request)
|
||||
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
|
||||
|
||||
context = get_base_context(request)
|
||||
context["title"] = f"enviPath - {current_package.name} - Predict Pathway"
|
||||
context["meta"]["current_package"] = current_package
|
||||
|
||||
return render(request, "predict_pathway.html", context)
|
||||
|
||||
|
||||
def packages(request):
|
||||
current_user = _anonymous_or_real(request)
|
||||
|
||||
@ -772,7 +669,7 @@ def search(request):
|
||||
|
||||
if request.method == "GET":
|
||||
package_urls = request.GET.getlist("packages")
|
||||
searchterm = request.GET.get("search", "").strip()
|
||||
searchterm = request.GET.get("search")
|
||||
mode = request.GET.get("mode")
|
||||
|
||||
# add HTTP_ACCEPT check to differentiate between index and ajax call
|
||||
@ -935,7 +832,7 @@ def package_models(request, package_uuid):
|
||||
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
|
||||
)
|
||||
|
||||
from .tasks import build_model, dispatch
|
||||
from .tasks import dispatch, build_model
|
||||
|
||||
dispatch(current_user, build_model, mod.pk)
|
||||
|
||||
@ -969,10 +866,9 @@ def package_model(request, package_uuid, model_uuid):
|
||||
if classify:
|
||||
from epdb.tasks import dispatch_eager, predict_simple
|
||||
|
||||
pred_res = dispatch_eager(
|
||||
current_user, predict_simple, current_model.pk, stand_smiles
|
||||
)
|
||||
res = dispatch_eager(current_user, predict_simple, current_model.pk, stand_smiles)
|
||||
|
||||
pred_res = current_model.predict(stand_smiles)
|
||||
res = []
|
||||
|
||||
for pr in pred_res:
|
||||
@ -995,7 +891,7 @@ def package_model(request, package_uuid, model_uuid):
|
||||
return JsonResponse(res, safe=False)
|
||||
|
||||
else:
|
||||
app_domain_assessment = current_model.app_domain.assess(stand_smiles)
|
||||
app_domain_assessment = current_model.app_domain.assess(stand_smiles)[0]
|
||||
return JsonResponse(app_domain_assessment, safe=False)
|
||||
|
||||
context = get_base_context(request)
|
||||
@ -1035,24 +931,12 @@ def package_model(request, package_uuid, model_uuid):
|
||||
]
|
||||
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
||||
|
||||
return redirect(current_model.url)
|
||||
elif hidden == "retrain":
|
||||
from .tasks import dispatch, retrain
|
||||
|
||||
dispatch(current_user, retrain, current_model.pk)
|
||||
|
||||
return redirect(current_model.url)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
else:
|
||||
# TODO: Move cleaning to property updater
|
||||
name = request.POST.get("model-name")
|
||||
if name is not None:
|
||||
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
description = request.POST.get("model-description")
|
||||
if description is not None:
|
||||
description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
name = request.POST.get("model-name", "").strip()
|
||||
description = request.POST.get("model-description", "").strip()
|
||||
|
||||
if any([name, description]):
|
||||
if name:
|
||||
@ -1132,7 +1016,9 @@ def package(request, package_uuid):
|
||||
return redirect(s.SERVER_URL + "/package")
|
||||
elif hidden == "publish-package":
|
||||
for g in Group.objects.filter(public=True):
|
||||
PackageManager.grant_read(current_user, current_package, g)
|
||||
PackageManager.update_permissions(
|
||||
current_user, current_package, g, Permission.READ[0]
|
||||
)
|
||||
return redirect(current_package.url)
|
||||
elif hidden == "copy":
|
||||
object_to_copy = request.POST.get("object_to_copy")
|
||||
@ -1152,23 +1038,17 @@ def package(request, package_uuid):
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
# TODO: Move cleaning to property updater
|
||||
new_package_name = request.POST.get("package-name")
|
||||
if new_package_name is not None:
|
||||
new_package_name = nh3.clean(new_package_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
new_package_description = request.POST.get("package-description")
|
||||
if new_package_description is not None:
|
||||
new_package_description = nh3.clean(
|
||||
new_package_description, tags=s.ALLOWED_HTML_TAGS
|
||||
).strip()
|
||||
|
||||
grantee_url = request.POST.get("grantee")
|
||||
read = request.POST.get("read") == "on"
|
||||
write = request.POST.get("write") == "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:
|
||||
current_package.name = new_package_name
|
||||
@ -1196,15 +1076,24 @@ def package(request, package_uuid):
|
||||
|
||||
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
||||
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.save()
|
||||
return redirect(current_package.url)
|
||||
else: # Get the license and assign it to the package
|
||||
current_package.license = License.objects.get(cc_string=cc_string)
|
||||
else:
|
||||
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()
|
||||
|
||||
return redirect(current_package.url)
|
||||
@ -1312,16 +1201,8 @@ def package_compound(request, package_uuid, compound_uuid):
|
||||
|
||||
return JsonResponse({"success": current_compound.url})
|
||||
|
||||
# TODO: Move cleaning to property updater
|
||||
new_compound_name = request.POST.get("compound-name")
|
||||
if new_compound_name is not None:
|
||||
new_compound_name = nh3.clean(new_compound_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
new_compound_description = request.POST.get("compound-description")
|
||||
if new_compound_description is not None:
|
||||
new_compound_description = nh3.clean(
|
||||
new_compound_description, tags=s.ALLOWED_HTML_TAGS
|
||||
).strip()
|
||||
new_compound_name = request.POST.get("compound-name", "").strip()
|
||||
new_compound_description = request.POST.get("compound-description", "").strip()
|
||||
|
||||
if new_compound_name:
|
||||
current_compound.name = new_compound_name
|
||||
@ -1457,16 +1338,8 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
# TODO: Move cleaning to property updater
|
||||
new_structure_name = request.POST.get("compound-structure-name")
|
||||
if new_structure_name is not None:
|
||||
new_structure_name = nh3.clean(new_structure_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
new_structure_description = request.POST.get("compound-structure-description")
|
||||
if new_structure_description is not None:
|
||||
new_structure_description = nh3.clean(
|
||||
new_structure_description, tags=s.ALLOWED_HTML_TAGS
|
||||
).strip()
|
||||
new_structure_name = request.POST.get("compound-structure-name", "").strip()
|
||||
new_structure_description = request.POST.get("compound-structure-description", "").strip()
|
||||
|
||||
if new_structure_name:
|
||||
current_structure.name = new_structure_name
|
||||
@ -1673,14 +1546,8 @@ def package_rule(request, package_uuid, rule_uuid):
|
||||
|
||||
return JsonResponse({"success": current_rule.url})
|
||||
|
||||
# TODO: Move cleaning to property updater
|
||||
rule_name = request.POST.get("rule-name")
|
||||
if rule_name is not None:
|
||||
rule_name = nh3.clean(rule_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
rule_description = request.POST.get("rule-description")
|
||||
if rule_description is not None:
|
||||
rule_description = nh3.clean(rule_description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
rule_name = request.POST.get("rule-name", "").strip()
|
||||
rule_description = request.POST.get("rule-description", "").strip()
|
||||
|
||||
if rule_name:
|
||||
current_rule.name = rule_name
|
||||
@ -1770,6 +1637,7 @@ def package_reactions(request, package_uuid):
|
||||
reaction_name = request.POST.get("reaction-name")
|
||||
reaction_description = request.POST.get("reaction-description")
|
||||
reactions_smirks = request.POST.get("reaction-smirks")
|
||||
|
||||
educts = reactions_smirks.split(">>")[0].split(".")
|
||||
products = reactions_smirks.split(">>")[1].split(".")
|
||||
|
||||
@ -1830,16 +1698,8 @@ def package_reaction(request, package_uuid, reaction_uuid):
|
||||
|
||||
return JsonResponse({"success": current_reaction.url})
|
||||
|
||||
# TODO: Move cleaning to property updater
|
||||
new_reaction_name = request.POST.get("reaction-name")
|
||||
if new_reaction_name is not None:
|
||||
new_reaction_name = nh3.clean(new_reaction_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
new_reaction_description = request.POST.get("reaction-description")
|
||||
if new_reaction_description is not None:
|
||||
new_reaction_description = nh3.clean(
|
||||
new_reaction_description, tags=s.ALLOWED_HTML_TAGS
|
||||
).strip()
|
||||
new_reaction_name = request.POST.get("reaction-name", "").strip()
|
||||
new_reaction_description = request.POST.get("reaction-description", "").strip()
|
||||
|
||||
if new_reaction_name:
|
||||
current_reaction.name = new_reaction_name
|
||||
@ -1916,9 +1776,8 @@ def package_pathways(request, package_uuid):
|
||||
|
||||
name = request.POST.get("name")
|
||||
description = request.POST.get("description")
|
||||
|
||||
smiles = request.POST.get("smiles", "").strip()
|
||||
pw_mode = request.POST.get("predict", "predict").strip()
|
||||
smiles = request.POST.get("smiles", "").strip()
|
||||
|
||||
if "smiles" in request.POST and smiles == "":
|
||||
return error(
|
||||
@ -1927,6 +1786,8 @@ def package_pathways(request, package_uuid):
|
||||
"Pathway prediction failed due to missing or empty SMILES",
|
||||
)
|
||||
|
||||
smiles = smiles.strip()
|
||||
|
||||
try:
|
||||
stand_smiles = FormatConverter.standardize(smiles)
|
||||
except ValueError:
|
||||
@ -2085,14 +1946,8 @@ def package_pathway(request, package_uuid, pathway_uuid):
|
||||
|
||||
return JsonResponse({"success": current_pathway.url})
|
||||
|
||||
# TODO: Move cleaning to property updater
|
||||
pathway_name = request.POST.get("pathway-name")
|
||||
if pathway_name is not None:
|
||||
pathway_name = nh3.clean(pathway_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
pathway_description = request.POST.get("pathway-description")
|
||||
if pathway_description is not None:
|
||||
pathway_description = nh3.clean(pathway_description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
if any([pathway_name, pathway_description]):
|
||||
if pathway_name is not None and pathway_name.strip() != "":
|
||||
@ -2180,8 +2035,8 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
|
||||
elif request.method == "POST":
|
||||
node_name = request.POST.get("node-name")
|
||||
node_description = request.POST.get("node-description")
|
||||
node_smiles = request.POST.get("node-smiles")
|
||||
|
||||
node_smiles = request.POST.get("node-smiles").strip()
|
||||
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
||||
|
||||
return redirect(current_pathway.url)
|
||||
@ -2346,7 +2201,6 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
|
||||
|
||||
edge_name = request.POST.get("edge-name")
|
||||
edge_description = request.POST.get("edge-description")
|
||||
|
||||
edge_substrates = request.POST.getlist("edge-substrates")
|
||||
edge_products = request.POST.getlist("edge-products")
|
||||
|
||||
@ -2433,7 +2287,7 @@ def package_scenarios(request, package_uuid):
|
||||
"all", False
|
||||
):
|
||||
scens = Scenario.objects.filter(package=current_package).order_by("name")
|
||||
res = [{"name": s_.name, "url": s_.url, "uuid": s_.uuid} for s_ in scens]
|
||||
res = [{"name": s.name, "url": s.url, "uuid": s.uuid} for s in scens]
|
||||
return JsonResponse(res, safe=False)
|
||||
|
||||
context = get_base_context(request)
|
||||
@ -2471,9 +2325,9 @@ def package_scenarios(request, package_uuid):
|
||||
context["unreviewed_objects"] = unreviewed_scenario_qs
|
||||
|
||||
from envipy_additional_information import (
|
||||
SEDIMENT_ADDITIONAL_INFORMATION,
|
||||
SLUDGE_ADDITIONAL_INFORMATION,
|
||||
SOIL_ADDITIONAL_INFORMATION,
|
||||
SEDIMENT_ADDITIONAL_INFORMATION,
|
||||
)
|
||||
|
||||
context["scenario_types"] = {
|
||||
@ -2481,21 +2335,21 @@ def package_scenarios(request, package_uuid):
|
||||
"name": "soil",
|
||||
"widgets": [
|
||||
HTMLGenerator.generate_html(ai, prefix=f"soil_{0}")
|
||||
for ai in [x for sv in SOIL_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||
for ai in [x for s in SOIL_ADDITIONAL_INFORMATION.values() for x in s]
|
||||
],
|
||||
},
|
||||
"Sludge Data": {
|
||||
"name": "sludge",
|
||||
"widgets": [
|
||||
HTMLGenerator.generate_html(ai, prefix=f"sludge_{0}")
|
||||
for ai in [x for sv in SLUDGE_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||
for ai in [x for s in SLUDGE_ADDITIONAL_INFORMATION.values() for x in s]
|
||||
],
|
||||
},
|
||||
"Water-Sediment System Data": {
|
||||
"name": "sediment",
|
||||
"widgets": [
|
||||
HTMLGenerator.generate_html(ai, prefix=f"sediment_{0}")
|
||||
for ai in [x for sv in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||
for ai in [x for s in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in s]
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -2510,7 +2364,6 @@ def package_scenarios(request, package_uuid):
|
||||
|
||||
scenario_name = request.POST.get("scenario-name")
|
||||
scenario_description = request.POST.get("scenario-description")
|
||||
|
||||
scenario_date_year = request.POST.get("scenario-date-year")
|
||||
scenario_date_month = request.POST.get("scenario-date-month")
|
||||
scenario_date_day = request.POST.get("scenario-date-day")
|
||||
@ -2524,9 +2377,9 @@ def package_scenarios(request, package_uuid):
|
||||
scenario_type = request.POST.get("scenario-type")
|
||||
|
||||
additional_information = HTMLGenerator.build_models(request.POST.dict())
|
||||
additional_information = [x for sv in additional_information.values() for x in sv]
|
||||
additional_information = [x for s in additional_information.values() for x in s]
|
||||
|
||||
new_scen = Scenario.create(
|
||||
s = Scenario.create(
|
||||
current_package,
|
||||
name=scenario_name,
|
||||
description=scenario_description,
|
||||
@ -2535,7 +2388,7 @@ def package_scenarios(request, package_uuid):
|
||||
additional_information=additional_information,
|
||||
)
|
||||
|
||||
return redirect(new_scen.url)
|
||||
return redirect(s.url)
|
||||
else:
|
||||
return HttpResponseNotAllowed(
|
||||
[
|
||||
@ -2835,7 +2688,6 @@ def settings(request):
|
||||
|
||||
name = request.POST.get("prediction-setting-name")
|
||||
description = request.POST.get("prediction-setting-description")
|
||||
|
||||
new_default = request.POST.get("prediction-setting-new-default", "off") == "on"
|
||||
|
||||
max_nodes = min(
|
||||
@ -2986,60 +2838,3 @@ def userinfo(request):
|
||||
"email_verified": user.is_active,
|
||||
}
|
||||
return JsonResponse(res)
|
||||
|
||||
|
||||
# Static Pages
|
||||
def static_terms_of_use(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - Terms of Use"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/terms_of_use.html", context)
|
||||
|
||||
|
||||
def static_privacy_policy(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - Privacy Policy"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/privacy_policy.html", context)
|
||||
|
||||
|
||||
def static_cookie_policy(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - Cookie Policy"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/cookie_policy.html", context)
|
||||
|
||||
|
||||
def static_about_us(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - About Us"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/about_us.html", context)
|
||||
|
||||
|
||||
def static_contact_support(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - Contact & Support"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/contact.html", context)
|
||||
|
||||
|
||||
def static_careers(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - Careers"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/careers.html", context)
|
||||
|
||||
|
||||
def static_cite(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - How to Cite"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/cite.html", context)
|
||||
|
||||
|
||||
def static_legal(request):
|
||||
context = get_base_context(request)
|
||||
context["title"] = "enviPath - Legal Information"
|
||||
context["public_mode"] = True
|
||||
return render(request, "static/legal.html", context)
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
import gzip
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings as s
|
||||
from django.http import HttpResponseNotAllowed
|
||||
from django.shortcuts import render
|
||||
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import Rule, SimpleAmbitRule, Package, CompoundStructure
|
||||
from epdb.views import get_base_context, _anonymous_or_real
|
||||
from utilities.chem import FormatConverter
|
||||
|
||||
|
||||
from rdkit import Chem
|
||||
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||
|
||||
from epdb.models import CompoundStructure, Rule, SimpleAmbitRule
|
||||
from epdb.views import get_base_context
|
||||
from utilities.chem import FormatConverter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
|
||||
def normalize_smiles(smiles):
|
||||
m1 = Chem.MolFromSmiles(smiles)
|
||||
@ -56,7 +59,9 @@ def run_both_engines(SMILES, SMIRKS):
|
||||
set(
|
||||
[
|
||||
normalize_smiles(str(x))
|
||||
for x in FormatConverter.sanitize_smiles([str(s) for s in all_rdkit_prods])[0]
|
||||
for x in FormatConverter.sanitize_smiles(
|
||||
[str(s) for s in all_rdkit_prods]
|
||||
)[0]
|
||||
]
|
||||
)
|
||||
)
|
||||
@ -80,7 +85,8 @@ def migration(request):
|
||||
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
||||
)
|
||||
ALL_SMILES = [
|
||||
cs.smiles for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
||||
cs.smiles
|
||||
for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
||||
]
|
||||
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
||||
|
||||
@ -136,7 +142,9 @@ def migration(request):
|
||||
)
|
||||
|
||||
for r in migration_status["results"]:
|
||||
r["detail_url"] = r["detail_url"].replace("http://localhost:8000", s.SERVER_URL)
|
||||
r["detail_url"] = r["detail_url"].replace(
|
||||
"http://localhost:8000", s.SERVER_URL
|
||||
)
|
||||
|
||||
context.update(**migration_status)
|
||||
|
||||
@ -144,6 +152,8 @@ def migration(request):
|
||||
|
||||
|
||||
def migration_detail(request, package_uuid, rule_uuid):
|
||||
current_user = _anonymous_or_real(request)
|
||||
|
||||
if request.method == "GET":
|
||||
context = get_base_context(request)
|
||||
|
||||
@ -225,7 +235,9 @@ def compare(request):
|
||||
context["smirks"] = (
|
||||
"[#1,#6:6][#7;X3;!$(NC1CC1)!$([N][C]=O)!$([!#8]CNC=O):1]([#1,#6:7])[#6;A;X4:2][H:3]>>[#1,#6:6][#7;X3:1]([#1,#6:7])[H:3].[#6;A:2]=O"
|
||||
)
|
||||
context["smiles"] = "C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N"
|
||||
context["smiles"] = (
|
||||
"C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N"
|
||||
)
|
||||
return render(request, "compare.html", context)
|
||||
|
||||
elif request.method == "POST":
|
||||
|
||||
25
package.json
@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "envipy",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "enviPath UI - Tailwind CSS + DaisyUI",
|
||||
"scripts": {
|
||||
"dev": "tailwindcss -i static/css/input.css -o static/css/output.css --watch=always",
|
||||
"build": "tailwindcss -i static/css/input.css -o static/css/output.css --minify"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "^4.1.16",
|
||||
"@tailwindcss/postcss": "^4.1.16",
|
||||
"daisyui": "^5.4.3",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-jinja-template": "^2.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||
"tailwindcss": "^4.1.16"
|
||||
},
|
||||
"keywords": [
|
||||
"django",
|
||||
"tailwindcss",
|
||||
"daisyui"
|
||||
]
|
||||
}
|
||||
740
pnpm-lock.yaml
generated
@ -1,740 +0,0 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@tailwindcss/cli':
|
||||
specifier: ^4.1.16
|
||||
version: 4.1.16
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.1.16
|
||||
version: 4.1.16
|
||||
daisyui:
|
||||
specifier: ^5.4.3
|
||||
version: 5.4.3
|
||||
postcss:
|
||||
specifier: ^8.5.6
|
||||
version: 8.5.6
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
prettier-plugin-jinja-template:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0(prettier@3.6.2)
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1(prettier@3.6.2)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.16
|
||||
version: 4.1.16
|
||||
|
||||
packages:
|
||||
|
||||
'@alloc/quick-lru@5.2.0':
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.1':
|
||||
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.1':
|
||||
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.1':
|
||||
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.1':
|
||||
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher@2.5.1':
|
||||
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@tailwindcss/cli@4.1.16':
|
||||
resolution: {integrity: sha512-dsnANPrh2ZooHyZ/8uJhc9ecpcYtufToc21NY09NS9vF16rxPCjJ8dP7TUAtPqlUJTHSmRkN2hCdoYQIlgh4fw==}
|
||||
hasBin: true
|
||||
|
||||
'@tailwindcss/node@4.1.16':
|
||||
resolution: {integrity: sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==}
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.1.16':
|
||||
resolution: {integrity: sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.1.16':
|
||||
resolution: {integrity: sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.1.16':
|
||||
resolution: {integrity: sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.1.16':
|
||||
resolution: {integrity: sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
|
||||
resolution: {integrity: sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
|
||||
resolution: {integrity: sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.16':
|
||||
resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.16':
|
||||
resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.16':
|
||||
resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.16':
|
||||
resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
bundledDependencies:
|
||||
- '@napi-rs/wasm-runtime'
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- '@tybys/wasm-util'
|
||||
- '@emnapi/wasi-threads'
|
||||
- tslib
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
|
||||
resolution: {integrity: sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.1.16':
|
||||
resolution: {integrity: sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide@4.1.16':
|
||||
resolution: {integrity: sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@tailwindcss/postcss@4.1.16':
|
||||
resolution: {integrity: sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
daisyui@5.4.3:
|
||||
resolution: {integrity: sha512-dfDCJnN4utErGoWfElgdEE252FtfHV9Mxj5Dq1+JzUq3nVkluWdF3JYykP0Xy/y/yArnPXYztO1tLNCow3kjmg==}
|
||||
|
||||
detect-libc@1.0.3:
|
||||
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
enhanced-resolve@5.18.3:
|
||||
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
fill-range@7.1.1:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
lightningcss-darwin-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-darwin-x64@1.30.2:
|
||||
resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-freebsd-x64@1.30.2:
|
||||
resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||
resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss-win32-x64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.30.2:
|
||||
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mri@1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prettier-plugin-jinja-template@2.1.0:
|
||||
resolution: {integrity: sha512-mzoCp2Oy9BDSug80fw3B3J4n4KQj1hRvoQOL1akqcDKBb5nvYxrik9zUEDs4AEJ6nK7QDTGoH0y9rx7AlnQ78Q==}
|
||||
peerDependencies:
|
||||
prettier: ^3.0.0
|
||||
|
||||
prettier-plugin-tailwindcss@0.7.1:
|
||||
resolution: {integrity: sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==}
|
||||
engines: {node: '>=20.19'}
|
||||
peerDependencies:
|
||||
'@ianvs/prettier-plugin-sort-imports': '*'
|
||||
'@prettier/plugin-hermes': '*'
|
||||
'@prettier/plugin-oxc': '*'
|
||||
'@prettier/plugin-pug': '*'
|
||||
'@shopify/prettier-plugin-liquid': '*'
|
||||
'@trivago/prettier-plugin-sort-imports': '*'
|
||||
'@zackad/prettier-plugin-twig': '*'
|
||||
prettier: ^3.0
|
||||
prettier-plugin-astro: '*'
|
||||
prettier-plugin-css-order: '*'
|
||||
prettier-plugin-jsdoc: '*'
|
||||
prettier-plugin-marko: '*'
|
||||
prettier-plugin-multiline-arrays: '*'
|
||||
prettier-plugin-organize-attributes: '*'
|
||||
prettier-plugin-organize-imports: '*'
|
||||
prettier-plugin-sort-imports: '*'
|
||||
prettier-plugin-svelte: '*'
|
||||
peerDependenciesMeta:
|
||||
'@ianvs/prettier-plugin-sort-imports':
|
||||
optional: true
|
||||
'@prettier/plugin-hermes':
|
||||
optional: true
|
||||
'@prettier/plugin-oxc':
|
||||
optional: true
|
||||
'@prettier/plugin-pug':
|
||||
optional: true
|
||||
'@shopify/prettier-plugin-liquid':
|
||||
optional: true
|
||||
'@trivago/prettier-plugin-sort-imports':
|
||||
optional: true
|
||||
'@zackad/prettier-plugin-twig':
|
||||
optional: true
|
||||
prettier-plugin-astro:
|
||||
optional: true
|
||||
prettier-plugin-css-order:
|
||||
optional: true
|
||||
prettier-plugin-jsdoc:
|
||||
optional: true
|
||||
prettier-plugin-marko:
|
||||
optional: true
|
||||
prettier-plugin-multiline-arrays:
|
||||
optional: true
|
||||
prettier-plugin-organize-attributes:
|
||||
optional: true
|
||||
prettier-plugin-organize-imports:
|
||||
optional: true
|
||||
prettier-plugin-sort-imports:
|
||||
optional: true
|
||||
prettier-plugin-svelte:
|
||||
optional: true
|
||||
|
||||
prettier@3.6.2:
|
||||
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
tailwindcss@4.1.16:
|
||||
resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==}
|
||||
|
||||
tapable@2.3.0:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.1':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher@2.5.1':
|
||||
dependencies:
|
||||
detect-libc: 1.0.3
|
||||
is-glob: 4.0.3
|
||||
micromatch: 4.0.8
|
||||
node-addon-api: 7.1.1
|
||||
optionalDependencies:
|
||||
'@parcel/watcher-android-arm64': 2.5.1
|
||||
'@parcel/watcher-darwin-arm64': 2.5.1
|
||||
'@parcel/watcher-darwin-x64': 2.5.1
|
||||
'@parcel/watcher-freebsd-x64': 2.5.1
|
||||
'@parcel/watcher-linux-arm-glibc': 2.5.1
|
||||
'@parcel/watcher-linux-arm-musl': 2.5.1
|
||||
'@parcel/watcher-linux-arm64-glibc': 2.5.1
|
||||
'@parcel/watcher-linux-arm64-musl': 2.5.1
|
||||
'@parcel/watcher-linux-x64-glibc': 2.5.1
|
||||
'@parcel/watcher-linux-x64-musl': 2.5.1
|
||||
'@parcel/watcher-win32-arm64': 2.5.1
|
||||
'@parcel/watcher-win32-ia32': 2.5.1
|
||||
'@parcel/watcher-win32-x64': 2.5.1
|
||||
|
||||
'@tailwindcss/cli@4.1.16':
|
||||
dependencies:
|
||||
'@parcel/watcher': 2.5.1
|
||||
'@tailwindcss/node': 4.1.16
|
||||
'@tailwindcss/oxide': 4.1.16
|
||||
enhanced-resolve: 5.18.3
|
||||
mri: 1.2.0
|
||||
picocolors: 1.1.1
|
||||
tailwindcss: 4.1.16
|
||||
|
||||
'@tailwindcss/node@4.1.16':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
enhanced-resolve: 5.18.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.1.16
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.1.16':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide@4.1.16':
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64': 4.1.16
|
||||
'@tailwindcss/oxide-darwin-arm64': 4.1.16
|
||||
'@tailwindcss/oxide-darwin-x64': 4.1.16
|
||||
'@tailwindcss/oxide-freebsd-x64': 4.1.16
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.16
|
||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.16
|
||||
'@tailwindcss/oxide-linux-arm64-musl': 4.1.16
|
||||
'@tailwindcss/oxide-linux-x64-gnu': 4.1.16
|
||||
'@tailwindcss/oxide-linux-x64-musl': 4.1.16
|
||||
'@tailwindcss/oxide-wasm32-wasi': 4.1.16
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.16
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.16
|
||||
|
||||
'@tailwindcss/postcss@4.1.16':
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
'@tailwindcss/node': 4.1.16
|
||||
'@tailwindcss/oxide': 4.1.16
|
||||
postcss: 8.5.6
|
||||
tailwindcss: 4.1.16
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
daisyui@5.4.3: {}
|
||||
|
||||
detect-libc@1.0.3: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
enhanced-resolve@5.18.3:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.0
|
||||
|
||||
fill-range@7.1.1:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-arm64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-x64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-freebsd-x64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-x64-msvc@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss@1.30.2:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
lightningcss-android-arm64: 1.30.2
|
||||
lightningcss-darwin-arm64: 1.30.2
|
||||
lightningcss-darwin-x64: 1.30.2
|
||||
lightningcss-freebsd-x64: 1.30.2
|
||||
lightningcss-linux-arm-gnueabihf: 1.30.2
|
||||
lightningcss-linux-arm64-gnu: 1.30.2
|
||||
lightningcss-linux-arm64-musl: 1.30.2
|
||||
lightningcss-linux-x64-gnu: 1.30.2
|
||||
lightningcss-linux-x64-musl: 1.30.2
|
||||
lightningcss-win32-arm64-msvc: 1.30.2
|
||||
lightningcss-win32-x64-msvc: 1.30.2
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
micromatch@4.0.8:
|
||||
dependencies:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.1
|
||||
|
||||
mri@1.2.0: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
prettier-plugin-jinja-template@2.1.0(prettier@3.6.2):
|
||||
dependencies:
|
||||
prettier: 3.6.2
|
||||
|
||||
prettier-plugin-tailwindcss@0.7.1(prettier@3.6.2):
|
||||
dependencies:
|
||||
prettier: 3.6.2
|
||||
|
||||
prettier@3.6.2: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
tailwindcss@4.1.16: {}
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
@ -27,14 +27,12 @@ dependencies = [
|
||||
"scikit-learn>=1.6.1",
|
||||
"sentry-sdk[django]>=2.32.0",
|
||||
"setuptools>=80.8.0",
|
||||
"nh3==0.3.2",
|
||||
"polars==1.35.1",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.4" }
|
||||
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.2" }
|
||||
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
|
||||
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7" }
|
||||
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"}
|
||||
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
|
||||
|
||||
[project.optional-dependencies]
|
||||
@ -45,8 +43,6 @@ dev = [
|
||||
"poethepoet>=0.37.0",
|
||||
"pre-commit>=4.3.0",
|
||||
"ruff>=0.13.3",
|
||||
"pytest-playwright>=0.7.1",
|
||||
"pytest-django>=4.11.1",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
@ -68,39 +64,23 @@ docstring-code-format = true
|
||||
|
||||
[tool.poe.tasks]
|
||||
# Main tasks
|
||||
setup = { sequence = [
|
||||
"db-up",
|
||||
"migrate",
|
||||
"bootstrap",
|
||||
], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
||||
dev = { cmd = "uv run python scripts/dev_server.py", help = "Start the development server with CSS watcher", deps = [
|
||||
"db-up",
|
||||
"js-deps",
|
||||
] }
|
||||
build = { sequence = [
|
||||
"build-frontend",
|
||||
"collectstatic",
|
||||
], help = "Build frontend assets and collect static files" }
|
||||
setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
||||
dev = { cmd = "python manage.py runserver", help = "Start the development server", deps = ["db-up"] }
|
||||
|
||||
# Database tasks
|
||||
db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" }
|
||||
db-down = { cmd = "docker compose -f docker-compose.dev.yml down", help = "Stop PostgreSQL database" }
|
||||
|
||||
# Frontend tasks
|
||||
js-deps = { cmd = "uv run python scripts/pnpm_wrapper.py install", help = "Install frontend dependencies" }
|
||||
|
||||
# Full cleanup tasks
|
||||
clean = { sequence = [
|
||||
"clean-db",
|
||||
], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
|
||||
clean = { sequence = ["clean-db"], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
|
||||
clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." }
|
||||
|
||||
# Django tasks
|
||||
migrate = { cmd = "uv run python manage.py migrate", help = "Run database migrations" }
|
||||
migrate = { cmd = "python manage.py migrate", help = "Run database migrations" }
|
||||
bootstrap = { shell = """
|
||||
echo "Bootstrapping initial data..."
|
||||
echo "This will take a bit ⏱️. Get yourself some coffee..."
|
||||
uv run python manage.py bootstrap
|
||||
python manage.py bootstrap
|
||||
echo "✓ Bootstrap complete"
|
||||
echo ""
|
||||
echo "Default admin credentials:"
|
||||
@ -108,16 +88,4 @@ echo " Username: admin"
|
||||
echo " Email: admin@envipath.com"
|
||||
echo " Password: SuperSafe"
|
||||
""", help = "Bootstrap initial data (anonymous user, packages, models)" }
|
||||
shell = { cmd = "uv run python manage.py shell", help = "Open Django shell" }
|
||||
|
||||
|
||||
build-frontend = { cmd = "uv run python scripts/pnpm_wrapper.py run build", help = "Build frontend assets using pnpm", deps = [
|
||||
"js-deps",
|
||||
] } # Build tasks
|
||||
|
||||
|
||||
collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = [
|
||||
"build-frontend",
|
||||
] }
|
||||
|
||||
frontend-test-setup = { cmd = "playwright install", help = "Install the browsers required for frontend testing" }
|
||||
shell = { cmd = "python manage.py shell", help = "Open Django shell" }
|
||||
|
||||
@ -1,201 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cross-platform development server script.
|
||||
Starts pnpm CSS watcher and Django dev server, handling cleanup on exit.
|
||||
Works on both Windows and Unix systems.
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def find_pnpm():
|
||||
"""
|
||||
Find pnpm executable on the system.
|
||||
Returns the path to pnpm or None if not found.
|
||||
"""
|
||||
# Try to find pnpm using shutil.which
|
||||
# On Windows, this will find pnpm.cmd if it's in PATH
|
||||
pnpm_path = shutil.which("pnpm")
|
||||
|
||||
if pnpm_path:
|
||||
return pnpm_path
|
||||
|
||||
# On Windows, also try pnpm.cmd explicitly
|
||||
if sys.platform == "win32":
|
||||
pnpm_cmd = shutil.which("pnpm.cmd")
|
||||
if pnpm_cmd:
|
||||
return pnpm_cmd
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class DevServerManager:
|
||||
"""Manages background processes for development server."""
|
||||
|
||||
def __init__(self):
|
||||
self.processes = []
|
||||
self._cleanup_registered = False
|
||||
|
||||
def start_process(self, command, description, shell=False):
|
||||
"""Start a background process and return the process object."""
|
||||
print(f"Starting {description}...")
|
||||
try:
|
||||
if shell:
|
||||
# Use shell=True for commands that need shell interpretation
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
else:
|
||||
# Split command into list for subprocess
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
self.processes.append((process, description))
|
||||
print(f"✓ Started {description} (PID: {process.pid})")
|
||||
return process
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to start {description}: {e}", file=sys.stderr)
|
||||
self.cleanup()
|
||||
sys.exit(1)
|
||||
|
||||
def cleanup(self):
|
||||
"""Terminate all running processes."""
|
||||
if not self.processes:
|
||||
return
|
||||
|
||||
print("\nShutting down...")
|
||||
for process, description in self.processes:
|
||||
if process.poll() is None: # Process is still running
|
||||
try:
|
||||
# Try graceful termination first
|
||||
if sys.platform == "win32":
|
||||
process.terminate()
|
||||
else:
|
||||
process.send_signal(signal.SIGTERM)
|
||||
|
||||
# Wait up to 5 seconds for graceful shutdown
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Force kill if graceful shutdown failed
|
||||
if sys.platform == "win32":
|
||||
process.kill()
|
||||
else:
|
||||
process.send_signal(signal.SIGKILL)
|
||||
process.wait()
|
||||
|
||||
print(f"✓ {description} stopped")
|
||||
except Exception as e:
|
||||
print(f"✗ Error stopping {description}: {e}", file=sys.stderr)
|
||||
|
||||
self.processes.clear()
|
||||
|
||||
def register_cleanup(self):
|
||||
"""Register cleanup handlers for various exit scenarios."""
|
||||
if self._cleanup_registered:
|
||||
return
|
||||
|
||||
self._cleanup_registered = True
|
||||
|
||||
# Register atexit handler (works on all platforms)
|
||||
atexit.register(self.cleanup)
|
||||
|
||||
# Register signal handlers (Unix only)
|
||||
if sys.platform != "win32":
|
||||
signal.signal(signal.SIGINT, self._signal_handler)
|
||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||
|
||||
def _signal_handler(self, signum, frame):
|
||||
"""Handle Unix signals."""
|
||||
self.cleanup()
|
||||
sys.exit(0)
|
||||
|
||||
def wait_for_process(self, process, description):
|
||||
"""Wait for a process to finish and handle its output."""
|
||||
try:
|
||||
# Stream output from the process
|
||||
for line in iter(process.stdout.readline, ""):
|
||||
if line:
|
||||
print(f"[{description}] {line.rstrip()}")
|
||||
|
||||
process.wait()
|
||||
return process.returncode
|
||||
except KeyboardInterrupt:
|
||||
# Handle Ctrl+C
|
||||
self.cleanup()
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"Error waiting for {description}: {e}", file=sys.stderr)
|
||||
self.cleanup()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
manager = DevServerManager()
|
||||
manager.register_cleanup()
|
||||
|
||||
# Find pnpm executable
|
||||
pnpm_path = find_pnpm()
|
||||
if not pnpm_path:
|
||||
print("Error: pnpm not found in PATH.", file=sys.stderr)
|
||||
print("\nPlease install pnpm:", file=sys.stderr)
|
||||
print(" Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
|
||||
print(" Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Determine shell usage based on platform
|
||||
use_shell = sys.platform == "win32"
|
||||
|
||||
# Start pnpm CSS watcher
|
||||
# Use the found pnpm path to ensure it works on Windows
|
||||
pnpm_command = f'"{pnpm_path}" run dev' if use_shell else [pnpm_path, "run", "dev"]
|
||||
manager.start_process(
|
||||
pnpm_command,
|
||||
"CSS watcher",
|
||||
shell=use_shell,
|
||||
)
|
||||
|
||||
# Give pnpm a moment to start
|
||||
time.sleep(1)
|
||||
|
||||
# Start Django dev server
|
||||
django_process = manager.start_process(
|
||||
["uv", "run", "python", "manage.py", "runserver"],
|
||||
"Django server",
|
||||
shell=False,
|
||||
)
|
||||
|
||||
print("\nDevelopment servers are running. Press Ctrl+C to stop.\n")
|
||||
|
||||
try:
|
||||
# Wait for Django server (main process)
|
||||
# If Django exits, we should clean up everything
|
||||
return_code = manager.wait_for_process(django_process, "Django")
|
||||
|
||||
# If Django exited unexpectedly, clean up and exit
|
||||
if return_code != 0:
|
||||
manager.cleanup()
|
||||
sys.exit(return_code)
|
||||
except KeyboardInterrupt:
|
||||
# Ctrl+C was pressed
|
||||
manager.cleanup()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cross-platform pnpm command wrapper.
|
||||
Finds pnpm correctly on Windows (handles pnpm.cmd) and Unix systems.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def find_pnpm():
|
||||
"""
|
||||
Find pnpm executable on the system.
|
||||
Returns the path to pnpm or None if not found.
|
||||
"""
|
||||
# Try to find pnpm using shutil.which
|
||||
# On Windows, this will find pnpm.cmd if it's in PATH
|
||||
pnpm_path = shutil.which("pnpm")
|
||||
|
||||
if pnpm_path:
|
||||
return pnpm_path
|
||||
|
||||
# On Windows, also try pnpm.cmd explicitly
|
||||
if sys.platform == "win32":
|
||||
pnpm_cmd = shutil.which("pnpm.cmd")
|
||||
if pnpm_cmd:
|
||||
return pnpm_cmd
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point - execute pnpm with provided arguments."""
|
||||
pnpm_path = find_pnpm()
|
||||
|
||||
if not pnpm_path:
|
||||
print("Error: pnpm not found in PATH.", file=sys.stderr)
|
||||
print("\nPlease install pnpm:", file=sys.stderr)
|
||||
print(" Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
|
||||
print(" Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Get all arguments passed to this script
|
||||
args = sys.argv[1:]
|
||||
|
||||
# Execute pnpm with the provided arguments
|
||||
try:
|
||||
sys.exit(subprocess.call([pnpm_path] + args))
|
||||
except KeyboardInterrupt:
|
||||
# Handle Ctrl+C gracefully
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
print(f"Error executing pnpm: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,84 +0,0 @@
|
||||
/**
|
||||
* DaisyUI Themes - Generated by Style Dictionary
|
||||
* Theme mappings defined in tokens/daisyui-themes.json
|
||||
*/
|
||||
|
||||
/* Light theme (default) */
|
||||
@plugin "daisyui/theme" {
|
||||
name: "envipath";
|
||||
default: true;
|
||||
color-scheme: light;
|
||||
|
||||
--color-base-100: var(--color-neutral-50);
|
||||
--color-base-200: var(--color-neutral-100);
|
||||
--color-base-300: var(--color-neutral-200);
|
||||
--color-base-content: var(--color-neutral-900);
|
||||
--color-primary: var(--color-primary-500);
|
||||
--color-primary-content: var(--color-primary-50);
|
||||
--color-secondary: var(--color-secondary-500);
|
||||
--color-secondary-content: var(--color-secondary-50);
|
||||
--color-accent: var(--color-accent-500);
|
||||
--color-accent-content: var(--color-accent-50);
|
||||
--color-neutral: var(--color-neutral-950);
|
||||
--color-neutral-content: var(--color-neutral-100);
|
||||
--color-info: var(--color-info-500);
|
||||
--color-info-content: var(--color-info-950);
|
||||
--color-success: var(--color-success-500);
|
||||
--color-success-content: var(--color-success-950);
|
||||
--color-warning: var(--color-warning-500);
|
||||
--color-warning-content: var(--color-warning-950);
|
||||
--color-error: var(--color-error-500);
|
||||
--color-error-content: var(--color-error-950);
|
||||
|
||||
/* border radius */
|
||||
--radius-selector: 1rem;
|
||||
--radius-field: 0.25rem;
|
||||
--radius-box: 0.5rem;
|
||||
|
||||
/* base sizes */
|
||||
--size-selector: 0.25rem;
|
||||
--size-field: 0.25rem;
|
||||
|
||||
/* border size */
|
||||
--border: 1px;
|
||||
|
||||
/* effects */
|
||||
--depth: 1;
|
||||
--noise: 0;
|
||||
}
|
||||
|
||||
/* Dark theme (prefers-color-scheme: dark) */
|
||||
@plugin "daisyui/theme" {
|
||||
name: "envipath-dark";
|
||||
prefersdark: true;
|
||||
color-scheme: dark;
|
||||
|
||||
--color-primary: var(--color-primary-400);
|
||||
--color-primary-content: var(--color-neutral-950);
|
||||
--color-secondary: var(--color-secondary-400);
|
||||
--color-secondary-content: var(--color-neutral-950);
|
||||
--color-accent: var(--color-primary-500);
|
||||
--color-accent-content: var(--color-neutral-950);
|
||||
--color-neutral: var(--color-neutral-300);
|
||||
--color-neutral-content: var(--color-neutral-900);
|
||||
--color-base-100: var(--color-neutral-900);
|
||||
--color-base-200: var(--color-neutral-800);
|
||||
--color-base-300: var(--color-neutral-700);
|
||||
--color-base-content: var(--color-neutral-50);
|
||||
--color-info: var(--color-primary-400);
|
||||
--color-info-content: var(--color-neutral-950);
|
||||
--color-success: var(--color-success-400);
|
||||
--color-success-content: var(--color-neutral-950);
|
||||
--color-warning: var(--color-warning-400);
|
||||
--color-warning-content: var(--color-neutral-950);
|
||||
--color-error: var(--color-error-400);
|
||||
--color-error-content: var(--color-neutral-950);
|
||||
--radius-selector: 1rem;
|
||||
--radius-field: 0.25rem;
|
||||
--radius-box: 0.5rem;
|
||||
--size-selector: 0.25rem;
|
||||
--size-field: 0.25rem;
|
||||
--border: 1px;
|
||||
--depth: 1;
|
||||
--noise: 0;
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* fira-code-latin-wght-normal */
|
||||
@font-face {
|
||||
font-family: 'Fira Code Variable';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 300 700;
|
||||
src: url(https://cdn.jsdelivr.net/fontsource/fonts/fira-code:vf@latest/latin-wght-normal.woff2) format('woff2-variations');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
|
||||
/* inter-latin-wght-normal */
|
||||
@font-face {
|
||||
font-family: 'Inter Variable';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 100 900;
|
||||
src: url(https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2) format('woff2-variations');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
|
||||
|
||||
/* Tell Tailwind where to find Django templates and Python files */
|
||||
@source "../../templates";
|
||||
|
||||
/* Custom theme configuration - must come before plugins */
|
||||
@import "./theme.css";
|
||||
|
||||
/* Import DaisyUI plugin */
|
||||
@plugin "daisyui" {
|
||||
logs: true;
|
||||
exclude: rootscrollgutter;
|
||||
}
|
||||
|
||||
@import "./daisyui-theme.css";
|
||||
|
||||
/* Loading Spinner - Benzene Ring */
|
||||
.loading-spinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.loading-spinner svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.loading-spinner .hexagon,
|
||||
.loading-spinner .double-bonds {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
/**
|
||||
* Tailwind v4 Theme - Generated by Style Dictionary
|
||||
* This creates Tailwind utility classes from design tokens
|
||||
*/
|
||||
|
||||
@theme {
|
||||
/* Colors */
|
||||
--color-primary-50: oklch(0.98 0.02 201);
|
||||
--color-primary-100: oklch(0.96 0.04 203);
|
||||
--color-primary-200: oklch(0.92 0.08 205);
|
||||
--color-primary-300: oklch(0.87 0.12 207);
|
||||
--color-primary-400: oklch(0.80 0.13 212);
|
||||
--color-primary-500: oklch(0.71 0.13 215);
|
||||
--color-primary-600: oklch(0.61 0.11 222);
|
||||
--color-primary-700: oklch(0.52 0.09 223);
|
||||
--color-primary-800: oklch(0.45 0.08 224);
|
||||
--color-primary-900: oklch(0.40 0.07 227);
|
||||
--color-primary-950: oklch(0.30 0.05 230);
|
||||
--color-secondary-50: oklch(0.98 0.02 166);
|
||||
--color-secondary-100: oklch(0.95 0.05 163);
|
||||
--color-secondary-200: oklch(0.90 0.09 164);
|
||||
--color-secondary-300: oklch(0.85 0.13 165);
|
||||
--color-secondary-400: oklch(0.77 0.15 163);
|
||||
--color-secondary-500: oklch(0.70 0.15 162);
|
||||
--color-secondary-600: oklch(0.60 0.13 163);
|
||||
--color-secondary-700: oklch(0.51 0.10 166);
|
||||
--color-secondary-800: oklch(0.43 0.09 167);
|
||||
--color-secondary-900: oklch(0.38 0.07 169);
|
||||
--color-secondary-950: oklch(0.26 0.05 173);
|
||||
--color-success-50: oklch(0.98 0.02 156);
|
||||
--color-success-100: oklch(0.96 0.04 157);
|
||||
--color-success-200: oklch(0.93 0.08 156);
|
||||
--color-success-300: oklch(0.87 0.14 154);
|
||||
--color-success-400: oklch(0.80 0.18 152);
|
||||
--color-success-500: oklch(0.72 0.19 150);
|
||||
--color-success-600: oklch(0.63 0.17 149);
|
||||
--color-success-700: oklch(0.53 0.14 150);
|
||||
--color-success-800: oklch(0.45 0.11 151);
|
||||
--color-success-900: oklch(0.39 0.09 153);
|
||||
--color-success-950: oklch(0.27 0.06 153);
|
||||
--color-warning-50: oklch(0.99 0.03 102);
|
||||
--color-warning-100: oklch(0.97 0.07 103);
|
||||
--color-warning-200: oklch(0.95 0.12 102);
|
||||
--color-warning-300: oklch(0.91 0.17 98);
|
||||
--color-warning-400: oklch(0.86 0.17 92);
|
||||
--color-warning-500: oklch(0.80 0.16 86);
|
||||
--color-warning-600: oklch(0.68 0.14 76);
|
||||
--color-warning-700: oklch(0.55 0.12 66);
|
||||
--color-warning-800: oklch(0.48 0.10 62);
|
||||
--color-warning-900: oklch(0.42 0.09 58);
|
||||
--color-warning-950: oklch(0.29 0.06 54);
|
||||
--color-error-50: oklch(0.97 0.01 17);
|
||||
--color-error-100: oklch(0.94 0.03 18);
|
||||
--color-error-200: oklch(0.88 0.06 18);
|
||||
--color-error-300: oklch(0.81 0.10 20);
|
||||
--color-error-400: oklch(0.71 0.17 22);
|
||||
--color-error-500: oklch(0.64 0.21 25);
|
||||
--color-error-600: oklch(0.58 0.22 27);
|
||||
--color-error-700: oklch(0.51 0.19 28);
|
||||
--color-error-800: oklch(0.44 0.16 27);
|
||||
--color-error-900: oklch(0.40 0.13 26);
|
||||
--color-error-950: oklch(0.26 0.09 26);
|
||||
--color-neutral-50: oklch(0.98 0.00 248);
|
||||
--color-neutral-100: oklch(0.97 0.01 248);
|
||||
--color-neutral-200: oklch(0.93 0.01 256);
|
||||
--color-neutral-300: oklch(0.87 0.02 253);
|
||||
--color-neutral-400: oklch(0.71 0.04 257);
|
||||
--color-neutral-500: oklch(0.55 0.04 257);
|
||||
--color-neutral-600: oklch(0.45 0.04 257);
|
||||
--color-neutral-700: oklch(0.37 0.04 257);
|
||||
--color-neutral-800: oklch(0.28 0.04 260);
|
||||
--color-neutral-900: oklch(0.28 0.04 260);
|
||||
--color-neutral-950: oklch(0.28 0.04 260);
|
||||
|
||||
/* Spacing */
|
||||
--spacing-0: 0;
|
||||
--spacing-1: 0.25rem;
|
||||
--spacing-2: 0.5rem;
|
||||
--spacing-3: 0.75rem;
|
||||
--spacing-4: 1rem;
|
||||
--spacing-5: 1.25rem;
|
||||
--spacing-6: 1.5rem;
|
||||
--spacing-7: 1.75rem;
|
||||
--spacing-8: 2rem;
|
||||
--spacing-10: 2.5rem;
|
||||
--spacing-12: 3rem;
|
||||
--spacing-16: 4rem;
|
||||
--spacing-20: 5rem;
|
||||
--spacing-24: 6rem;
|
||||
--spacing-32: 8rem;
|
||||
--spacing-40: 10rem;
|
||||
--spacing-48: 12rem;
|
||||
--spacing-56: 14rem;
|
||||
--spacing-64: 16rem;
|
||||
|
||||
/* Typography */
|
||||
--font-family-sans: 'Inter Variable', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-family-mono: 'Fira Code Variable', 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
|
||||
--font-family-base: 'Inter Variable', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
--font-size-2xl: 1.5rem;
|
||||
--font-size-3xl: 1.875rem;
|
||||
--font-size-4xl: 2.25rem;
|
||||
--font-size-5xl: 3rem;
|
||||
--font-size-6xl: 3.75rem;
|
||||
--font-size-7xl: 4.5rem;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 21 KiB |
225
static/images/logo-long.svg
Normal file
@ -0,0 +1,225 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="314.98749"
|
||||
height="28.8125"
|
||||
id="svg3004"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata3010"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3008" /><g
|
||||
transform="matrix(1.25,0,0,-1.25,0,28.8125)"
|
||||
id="g3012"><g
|
||||
transform="scale(0.1,0.1)"
|
||||
id="g3014"><path
|
||||
d="m 957.473,175.816 0,-4.296 -18.453,0 0,-48.614 -5.04,0 0,48.614 -18.378,0 0,4.296 41.871,0"
|
||||
id="path3016"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 969.695,175.816 0,-22.968 31.425,0 0,22.968 5.04,0 0,-52.91 -5.04,0 0,25.637 -31.425,0 0,-25.637 -5.039,0 0,52.91 5.039,0"
|
||||
id="path3018"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1055.58,175.816 0,-4.296 -31.49,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.53,0"
|
||||
id="path3020"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1124.58,175.816 0,-4.296 -31.5,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.54,0"
|
||||
id="path3022"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1139.76,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
||||
id="path3024"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1188.15,175.816 17.19,-47.355 0.15,0 17.06,47.355 5.34,0 -19.65,-52.91 -5.86,0 -19.56,52.91 5.33,0"
|
||||
id="path3026"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1235.15,122.906 5.043,0 0,52.9102 -5.043,0 0,-52.9102 z"
|
||||
id="path3028"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1277.3,150.691 c 1.54,0 2.99,0.243 4.38,0.711 1.39,0.469 2.59,1.145 3.63,2.036 1.04,0.886 1.86,1.968 2.48,3.222 0.63,1.258 0.92,2.703 0.92,4.336 0,3.262 -0.94,5.824 -2.81,7.703 -1.88,1.875 -4.74,2.821 -8.6,2.821 l -18.82,0 0,-20.829 18.82,0 z m 0.38,25.125 c 2.16,0 4.23,-0.269 6.19,-0.816 1.95,-0.543 3.65,-1.367 5.1,-2.48 1.46,-1.118 2.62,-2.54 3.49,-4.297 0.86,-1.758 1.29,-3.821 1.29,-6.192 0,-3.359 -0.86,-6.273 -2.6,-8.742 -1.72,-2.473 -4.29,-4.055 -7.69,-4.746 l 0,-0.145 c 1.73,-0.25 3.16,-0.703 4.29,-1.367 1.14,-0.668 2.06,-1.527 2.78,-2.558 0.72,-1.035 1.23,-2.239 1.56,-3.594 0.31,-1.363 0.53,-2.832 0.62,-4.41 0.05,-0.887 0.1,-1.977 0.16,-3.262 0.05,-1.285 0.15,-2.582 0.29,-3.891 0.15,-1.312 0.38,-2.543 0.71,-3.711 0.32,-1.156 0.74,-2.054 1.3,-2.699 l -5.56,0 c -0.29,0.496 -0.54,1.098 -0.7,1.809 -0.18,0.723 -0.31,1.461 -0.37,2.234 -0.08,0.762 -0.14,1.512 -0.2,2.254 -0.05,0.742 -0.1,1.387 -0.14,1.926 -0.09,1.875 -0.26,3.742 -0.49,5.598 -0.22,1.851 -0.69,3.503 -1.4,4.961 -0.72,1.46 -1.76,2.632 -3.11,3.523 -1.36,0.887 -3.23,1.281 -5.6,1.191 l -19.12,0 0,-23.496 -5.03,0 0,52.91 24.23,0"
|
||||
id="path3030"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1308.46,140.879 c 0.77,-2.793 1.95,-5.289 3.56,-7.484 1.61,-2.2 3.66,-3.965 6.19,-5.301 2.52,-1.336 5.54,-2.004 9.04,-2.004 3.51,0 6.51,0.668 9,2.004 2.5,1.336 4.55,3.101 6.15,5.301 1.6,2.195 2.8,4.691 3.56,7.484 0.77,2.789 1.15,5.613 1.15,8.48 0,2.914 -0.38,5.758 -1.15,8.52 -0.76,2.769 -1.96,5.25 -3.56,7.449 -1.6,2.199 -3.65,3.969 -6.15,5.297 -2.49,1.336 -5.49,2.008 -9,2.008 -3.5,0 -6.52,-0.672 -9.04,-2.008 -2.53,-1.328 -4.58,-3.098 -6.19,-5.297 -1.61,-2.199 -2.79,-4.68 -3.56,-7.449 -0.75,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.4,-5.691 1.15,-8.48 z m -4.63,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.14,1.504 6.79,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.71,-3.531 7.78,-6.078 2.08,-2.547 3.64,-5.469 4.67,-8.777 1.05,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.51,-7.136 -1.56,-10.449 -1.03,-3.308 -2.59,-6.226 -4.67,-8.738 -2.07,-2.52 -4.67,-4.531 -7.78,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.14,0 -7.79,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.54,6.793 -1.54,10.449 0,3.657 0.5,7.141 1.54,10.454"
|
||||
id="path3032"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1367.77,175.816 30.84,-44.757 0.15,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.62,0"
|
||||
id="path3034"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1423.89,175.816 18.3,-46.386 18.22,46.386 7.41,0 0,-52.91 -5.03,0 0,45.723 -0.15,0 -18.09,-45.723 -4.74,0 -18.15,45.723 -0.15,0 0,-45.723 -5.04,0 0,52.91 7.42,0"
|
||||
id="path3036"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1517.1,175.816 0,-4.296 -31.48,0 0,-19.122 29.48,0 0,-4.293 -29.48,0 0,-20.898 31.86,0 0,-4.301 -36.9,0 0,52.91 36.52,0"
|
||||
id="path3038"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1532.29,175.816 30.82,-44.757 0.14,0 0,44.757 5.03,0 0,-52.91 -5.62,0 -30.81,44.754 -0.15,0 0,-44.754 -5.03,0 0,52.91 5.62,0"
|
||||
id="path3040"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1617.34,175.816 0,-4.296 -18.44,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.86,0"
|
||||
id="path3042"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1647.8,143.656 -10.22,27.121 -10.6,-27.121 20.82,0 z m -7.18,32.16 20.74,-52.91 -5.4,0 -6.46,16.449 -24.07,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.64,0"
|
||||
id="path3044"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1673.44,175.816 0,-48.609 29.65,0 0,-4.301 -34.68,0 0,52.91 5.03,0"
|
||||
id="path3046"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1769.74,165.254 c -1.02,1.605 -2.25,2.953 -3.71,4.043 -1.46,1.082 -3.06,1.914 -4.81,2.48 -1.76,0.567 -3.6,0.856 -5.52,0.856 -3.51,0 -6.53,-0.672 -9.05,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.77,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.38,-5.691 1.15,-8.48 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.54,-2.004 9.05,-2.004 2.46,0 4.69,0.449 6.66,1.336 1.98,0.89 3.68,2.101 5.12,3.633 1.43,1.523 2.59,3.324 3.48,5.371 0.89,2.05 1.45,4.261 1.71,6.633 l 5.03,0 c -0.35,-3.258 -1.11,-6.204 -2.3,-8.821 -1.18,-2.617 -2.7,-4.84 -4.59,-6.664 -1.88,-1.824 -4.08,-3.238 -6.63,-4.223 -2.54,-0.992 -5.37,-1.492 -8.48,-1.492 -4.17,0 -7.81,0.766 -10.93,2.266 -3.15,1.512 -5.76,3.523 -7.83,6.043 -2.07,2.512 -3.62,5.43 -4.65,8.738 -1.04,3.313 -1.57,6.793 -1.57,10.449 0,3.657 0.53,7.141 1.57,10.454 1.03,3.308 2.58,6.23 4.65,8.777 2.07,2.547 4.68,4.57 7.83,6.078 3.12,1.504 6.76,2.254 10.93,2.254 2.52,0 4.97,-0.371 7.38,-1.106 2.39,-0.738 4.56,-1.843 6.51,-3.296 1.95,-1.461 3.58,-3.254 4.89,-5.372 1.3,-2.125 2.13,-4.574 2.47,-7.335 l -5.04,0 c -0.43,2.019 -1.16,3.839 -2.17,5.441"
|
||||
id="path3048"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1791.01,140.879 c 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.53,-2.004 9.04,-2.004 3.51,0 6.5,0.668 9.01,2.004 2.49,1.336 4.54,3.101 6.14,5.301 1.61,2.195 2.79,4.691 3.57,7.484 0.75,2.789 1.14,5.613 1.14,8.48 0,2.914 -0.39,5.758 -1.14,8.52 -0.78,2.769 -1.96,5.25 -3.57,7.449 -1.6,2.199 -3.65,3.969 -6.14,5.297 -2.51,1.336 -5.5,2.008 -9.01,2.008 -3.51,0 -6.52,-0.672 -9.04,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.78,-2.762 -1.16,-5.606 -1.16,-8.52 0,-2.867 0.38,-5.691 1.16,-8.48 z m -4.64,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.13,1.504 6.77,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.72,-3.531 7.79,-6.078 2.07,-2.547 3.62,-5.469 4.66,-8.777 1.04,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.52,-7.136 -1.56,-10.449 -1.04,-3.308 -2.59,-6.226 -4.66,-8.738 -2.07,-2.52 -4.68,-4.531 -7.79,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.16,0 -7.8,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.56,6.793 -1.56,10.449 0,3.657 0.52,7.141 1.56,10.454"
|
||||
id="path3050"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1850.33,175.816 30.82,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.64,0"
|
||||
id="path3052"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1935.39,175.816 0,-4.296 -18.45,0 0,-48.614 -5.04,0 0,48.614 -18.37,0 0,4.296 41.86,0"
|
||||
id="path3054"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1965.86,143.656 -10.23,27.121 -10.6,-27.121 20.83,0 z m -7.21,32.16 20.76,-52.91 -5.41,0 -6.44,16.449 -24.08,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.62,0"
|
||||
id="path3056"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1993.71,175.816 18.3,-46.386 18.24,46.386 7.41,0 0,-52.91 -5.04,0 0,45.723 -0.15,0 -18.09,-45.723 -4.73,0 -18.16,45.723 -0.14,0 0,-45.723 -5.05,0 0,52.91 7.41,0"
|
||||
id="path3058"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2050.78,122.906 5.0273,0 0,52.9102 -5.0273,0 0,-52.9102 z"
|
||||
id="path3060"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2074.63,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.63,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
||||
id="path3062"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2151.78,143.656 -10.24,27.121 -10.58,-27.121 20.82,0 z m -7.2,32.16 20.76,-52.91 -5.41,0 -6.45,16.449 -24.09,0 -6.36,-16.449 -5.34,0 21.27,52.91 5.62,0"
|
||||
id="path3064"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2177.93,175.816 30.82,-44.757 0.16,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.84,44.754 -0.14,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
||||
id="path3066"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2263.01,175.816 0,-4.296 -18.46,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.88,0"
|
||||
id="path3068"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 945.785,32.6602 c 1.875,0 3.656,0.1562 5.336,0.4804 1.676,0.3164 3.16,0.8985 4.445,1.7383 1.286,0.8477 2.301,1.9649 3.043,3.375 0.739,1.4102 1.11,3.1719 1.11,5.2969 0,3.4101 -1.203,5.9648 -3.602,7.668 -2.387,1.7031 -5.836,2.5585 -10.332,2.5585 l -17.344,0 0,-21.1171 17.344,0 z m 0,25.414 c 2.028,0 3.778,0.2344 5.262,0.711 1.48,0.4609 2.719,1.1015 3.711,1.9218 0.98,0.8125 1.726,1.7696 2.215,2.8555 0.5,1.082 0.742,2.2422 0.742,3.4766 0,6.6211 -3.977,9.9375 -11.93,9.9375 l -17.344,0 0,-18.9024 17.344,0 z m 0,23.1953 c 2.223,0 4.36,-0.2109 6.41,-0.6289 2.051,-0.4218 3.852,-1.1328 5.41,-2.1484 1.559,-1.0156 2.805,-2.3438 3.743,-4 0.937,-1.6563 1.406,-3.7227 1.406,-6.1914 0,-1.3867 -0.219,-2.7305 -0.668,-4.0391 -0.445,-1.3086 -1.074,-2.4961 -1.887,-3.5547 -0.808,-1.0625 -1.781,-1.9687 -2.89,-2.7031 -1.114,-0.7422 -2.36,-1.2617 -3.739,-1.5586 l 0,-0.1523 c 3.403,-0.4453 6.121,-1.8321 8.145,-4.1797 2.031,-2.3477 3.043,-5.25 3.043,-8.711 0,-0.8398 -0.074,-1.7851 -0.227,-2.8515 -0.144,-1.0625 -0.437,-2.1524 -0.886,-3.2617 -0.446,-1.1133 -1.086,-2.2149 -1.93,-3.2969 -0.836,-1.0859 -1.957,-2.0391 -3.363,-2.8516 -1.414,-0.8203 -3.141,-1.4883 -5.192,-2.0039 -2.051,-0.5195 -4.508,-0.7812 -7.375,-0.7812 l -22.379,0 0,52.914 22.379,0"
|
||||
id="path3070"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 975.426,28.3555 5.04297,0 0,52.9141 -5.04297,0 0,-52.9141 z"
|
||||
id="path3072"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 997.105,46.3281 c 0.762,-2.789 1.95,-5.2812 3.555,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.53,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.01,2.0039 2.5,1.3281 4.54,3.0976 6.15,5.2969 1.61,2.1992 2.79,4.6914 3.55,7.4804 0.77,2.793 1.16,5.6211 1.16,8.4844 0,2.918 -0.39,5.7578 -1.16,8.5273 -0.76,2.7618 -1.94,5.2461 -3.55,7.4454 -1.61,2.2031 -3.65,3.9648 -6.15,5.3007 -2.49,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.51,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.605,-2.1993 -2.793,-4.6836 -3.555,-7.4454 -0.773,-2.7695 -1.152,-5.6093 -1.152,-8.5273 0,-2.8633 0.379,-5.6914 1.152,-8.4844 z m -4.632,18.9336 c 1.035,3.3125 2.59,6.2383 4.668,8.7813 2.074,2.5429 4.679,4.5742 7.819,6.0781 3.13,1.5078 6.77,2.2578 10.92,2.2578 4.15,0 7.79,-0.75 10.9,-2.2578 3.11,-1.5039 5.71,-3.5352 7.78,-6.0781 2.08,-2.543 3.63,-5.4688 4.67,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.67,-8.7461 -2.07,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.11,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.15,0 -7.79,0.7617 -10.92,2.2656 -3.14,1.5079 -5.745,3.5196 -7.819,6.0391 -2.078,2.5156 -3.633,5.4297 -4.668,8.7461 -1.035,3.3008 -1.559,6.7812 -1.559,10.4414 0,3.6602 0.524,7.1445 1.559,10.4492"
|
||||
id="path3074"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1087.1,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
|
||||
id="path3076"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1118.15,56.1484 c 1.53,0 2.99,0.2383 4.37,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.04,0.8907 1.86,1.9688 2.49,3.2227 0.61,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.95,5.836 -2.82,7.7031 -1.88,1.8829 -4.75,2.8282 -8.6,2.8282 l -18.82,0 0,-20.8282 18.82,0 z m 0.37,25.1211 c 2.17,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.65,-1.3633 5.11,-2.4765 1.46,-1.1133 2.62,-2.543 3.49,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.16,-0.7032 4.3,-1.3672 1.14,-0.668 2.06,-1.5235 2.78,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.33,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.3,-3.8868 0.15,-1.3125 0.38,-2.5468 0.71,-3.707 0.31,-1.1562 0.74,-2.0586 1.29,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.7,1.8203 -0.17,0.7187 -0.3,1.4531 -0.37,2.2265 -0.07,0.7618 -0.14,1.5118 -0.19,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.48,5.6016 -0.22,1.8555 -0.69,3.5 -1.41,4.9609 -0.71,1.4532 -1.75,2.6289 -3.11,3.5235 -1.36,0.8906 -3.22,1.2812 -5.59,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
||||
id="path3078"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1174.25,49.1055 -10.23,27.125 -10.6,-27.125 20.83,0 z m -7.19,32.164 20.75,-52.914 -5.41,0 -6.45,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.63,0"
|
||||
id="path3080"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1200.41,81.2695 30.83,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.63,0 -30.84,44.7578 -0.15,0 0,-44.7578 -5.04,0 0,52.914 5.64,0"
|
||||
id="path3082"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1252.87,38.9609 c 0.9,-1.8281 2.12,-3.289 3.67,-4.375 1.57,-1.0898 3.4,-1.8671 5.52,-2.3359 2.12,-0.4727 4.4,-0.7031 6.83,-0.7031 1.37,0 2.89,0.1914 4.52,0.5976 1.62,0.3907 3.14,1.0196 4.55,1.8828 1.42,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.41,3.0039 1.41,4.9258 0,1.4843 -0.33,2.7695 -1.01,3.8593 -0.66,1.086 -1.53,1.9961 -2.58,2.7383 -1.07,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.55,0.8555 -3.78,1.1524 l -11.78,2.8789 c -1.54,0.4062 -3.02,0.8906 -4.49,1.4922 -1.44,0.5898 -2.72,1.375 -3.81,2.3672 -1.09,0.9843 -1.95,2.2031 -2.63,3.6289 -0.67,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.25,2.7929 0.74,4.5234 0.49,1.7266 1.42,3.3594 2.79,4.8906 1.35,1.5274 3.21,2.8243 5.59,3.8907 2.37,1.0625 5.4,1.5898 9.1,1.5898 2.62,0 5.13,-0.3398 7.49,-1.0351 2.38,-0.6915 4.47,-1.7305 6.22,-3.1133 1.8,-1.3789 3.21,-3.0977 4.26,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.56,3.7969 -1.37,5.3047 -0.82,1.5039 -1.88,2.7617 -3.19,3.7773 -1.3,1.0117 -2.81,1.7852 -4.53,2.3008 -1.7,0.5195 -3.49,0.7773 -5.37,0.7773 -1.72,0 -3.39,-0.1914 -5,-0.5586 -1.61,-0.371 -3.01,-0.9609 -4.22,-1.7812 -1.21,-0.8164 -2.18,-1.8906 -2.93,-3.2227 -0.74,-1.332 -1.11,-2.9882 -1.11,-4.9648 0,-1.2305 0.22,-2.3086 0.64,-3.2227 0.42,-0.914 0.98,-1.6953 1.72,-2.332 0.75,-0.6484 1.61,-1.1601 2.56,-1.5547 0.98,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.88,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.27,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.47,-1.1133 -1.14,-2.2344 -2,-3.3711 -0.87,-1.1368 -2.06,-2.1602 -3.56,-3.0782 -1.51,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.88,-0.8516 -8,-0.8516 -3.11,0 -6,0.3594 -8.68,1.0781 -2.65,0.7188 -4.94,1.8125 -6.81,3.2969 -1.88,1.4844 -3.32,3.3789 -4.34,5.707 -1,2.3204 -1.43,5.1055 -1.3,8.375 l 5.05,0 c -0.06,-2.7148 0.37,-4.9921 1.25,-6.8164"
|
||||
id="path3084"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1331.72,81.2695 0,-4.2929 -28.53,0 0,-19.1211 25.35,0 0,-4.3008 -25.35,0 0,-25.1992 -5.04,0 0,52.914 33.57,0"
|
||||
id="path3086"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1343.54,46.3281 c 0.78,-2.789 1.95,-5.2812 3.55,-7.4804 1.6,-2.1993 3.68,-3.9688 6.2,-5.2969 2.51,-1.3399 5.53,-2.0039 9.03,-2.0039 3.51,0 6.51,0.664 9.01,2.0039 2.5,1.3281 4.55,3.0976 6.15,5.2969 1.6,2.1992 2.78,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.78,2.7618 -1.96,5.2461 -3.56,7.4454 -1.6,2.2031 -3.65,3.9648 -6.15,5.3007 -2.5,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.52,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.2,-5.3007 -1.6,-2.1993 -2.77,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.37,-5.6914 1.14,-8.4844 z m -4.63,18.9336 c 1.03,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.69,4.5742 7.82,6.0781 3.14,1.5078 6.79,2.2578 10.93,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.78,-6.0781 2.09,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.57,-6.789 1.57,-10.4492 0,-3.6602 -0.53,-7.1406 -1.57,-10.4414 -1.03,-3.3164 -2.57,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.73,3.5196 -7.82,6.0391 -2.07,2.5156 -3.63,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
|
||||
id="path3088"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1421.16,56.1484 c 1.54,0 3,0.2383 4.39,0.7071 1.37,0.4687 2.58,1.1484 3.62,2.0351 1.04,0.8907 1.87,1.9688 2.49,3.2227 0.61,1.2617 0.91,2.7109 0.91,4.332 0,3.2656 -0.93,5.836 -2.81,7.7031 -1.88,1.8829 -4.73,2.8282 -8.6,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.37,25.1211 c 2.18,0 4.24,-0.2695 6.18,-0.8203 1.96,-0.539 3.67,-1.3633 5.12,-2.4765 1.47,-1.1133 2.63,-2.543 3.49,-4.3008 0.86,-1.7578 1.3,-3.8125 1.3,-6.1875 0,-3.3594 -0.87,-6.2696 -2.6,-8.7461 -1.72,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.74,-0.25 3.17,-0.7032 4.3,-1.3672 1.13,-0.668 2.06,-1.5235 2.78,-2.5586 0.72,-1.0391 1.24,-2.2344 1.56,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.16,-1.3125 0.38,-2.5468 0.7,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.31,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.29,1.4531 -0.37,2.2265 -0.08,0.7618 -0.13,1.5118 -0.18,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.25,3.7461 -0.48,5.6016 -0.22,1.8555 -0.7,3.5 -1.41,4.9609 -0.73,1.4532 -1.76,2.6289 -3.12,3.5235 -1.36,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.13,0 0,-23.5 -5.03,0 0,52.914 24.23,0"
|
||||
id="path3090"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1456.21,81.2695 18.3,-46.3906 18.24,46.3906 7.41,0 0,-52.914 -5.04,0 0,45.7265 -0.15,0 -18.08,-45.7265 -4.74,0 -18.17,45.7265 -0.13,0 0,-45.7265 -5.05,0 0,52.914 7.41,0"
|
||||
id="path3092"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1541.21,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.74,-52.914 -5.42,0 -6.42,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.62,0"
|
||||
id="path3094"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1593,81.2695 0,-4.2929 -18.47,0 0,-48.6211 -5.04,0 0,48.6211 -18.37,0 0,4.2929 41.88,0"
|
||||
id="path3096"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1600.55,28.3555 5.0391,0 0,52.9141 -5.0391,0 0,-52.9141 z"
|
||||
id="path3098"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1622.23,46.3281 c 0.76,-2.789 1.94,-5.2812 3.55,-7.4804 1.6,-2.1993 3.67,-3.9688 6.19,-5.2969 2.52,-1.3399 5.53,-2.0039 9.04,-2.0039 3.5,0 6.5,0.664 9.01,2.0039 2.48,1.3281 4.54,3.0976 6.14,5.2969 1.61,2.1992 2.8,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.76,2.7618 -1.95,5.2461 -3.56,7.4454 -1.6,2.2031 -3.66,3.9648 -6.14,5.3007 -2.51,1.3321 -5.51,2 -9.01,2 -3.51,0 -6.52,-0.6679 -9.04,-2 -2.52,-1.3359 -4.59,-3.0976 -6.19,-5.3007 -1.61,-2.1993 -2.79,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.16,-5.6093 -1.16,-8.5273 0,-2.8633 0.39,-5.6914 1.16,-8.4844 z m -4.64,18.9336 c 1.03,3.3125 2.6,6.2383 4.68,8.7813 2.07,2.5429 4.67,4.5742 7.8,6.0781 3.14,1.5078 6.79,2.2578 10.94,2.2578 4.16,0 7.78,-0.75 10.88,-2.2578 3.13,-1.5039 5.72,-3.5352 7.8,-6.0781 2.07,-2.543 3.62,-5.4688 4.66,-8.7813 1.03,-3.3047 1.55,-6.789 1.55,-10.4492 0,-3.6602 -0.52,-7.1406 -1.55,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.8,-6.0391 -3.1,-1.5039 -6.72,-2.2656 -10.88,-2.2656 -4.15,0 -7.8,0.7617 -10.94,2.2656 -3.13,1.5079 -5.73,3.5196 -7.8,6.0391 -2.08,2.5156 -3.65,5.4297 -4.68,8.7461 -1.03,3.3008 -1.55,6.7812 -1.55,10.4414 0,3.6602 0.52,7.1445 1.55,10.4492"
|
||||
id="path3100"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1681.55,81.2695 30.82,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.64,0 -30.82,44.7578 -0.15,0 0,-44.7578 -5.03,0 0,52.914 5.63,0"
|
||||
id="path3102"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1775.58,55.332 c 3.51,0 6.35,0.8946 8.52,2.6719 2.17,1.7774 3.26,4.4922 3.26,8.1484 0,3.6602 -1.09,6.3711 -3.26,8.1485 -2.17,1.7851 -5.01,2.6758 -8.52,2.6758 l -17.35,0 0,-21.6446 17.35,0 z m 1.12,25.9375 c 2.36,0 4.51,-0.3359 6.43,-0.9961 1.94,-0.6679 3.58,-1.6562 4.99,-2.9648 1.37,-1.3125 2.43,-2.9063 3.17,-4.7852 0.74,-1.875 1.11,-4.0039 1.11,-6.3711 0,-2.3671 -0.37,-4.4921 -1.11,-6.371 -0.74,-1.8829 -1.8,-3.4766 -3.17,-4.7852 -1.41,-1.3047 -3.05,-2.293 -4.99,-2.9609 -1.92,-0.6641 -4.07,-0.9961 -6.43,-0.9961 l -18.47,0 0,-22.6836 -5.04,0 0,52.914 23.51,0"
|
||||
id="path3104"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1824.93,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.76,-52.914 -5.41,0 -6.45,16.457 -24.09,0 -6.38,-16.457 -5.33,0 21.28,52.914 5.62,0"
|
||||
id="path3106"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1876.73,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
|
||||
id="path3108"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1888.96,81.2695 0,-22.9687 31.41,0 0,22.9687 5.04,0 0,-52.914 -5.04,0 0,25.6445 -31.41,0 0,-25.6445 -5.04,0 0,52.914 5.04,0"
|
||||
id="path3110"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 1938.38,81.2695 12.01,-46.3125 0.15,0 12.9,46.3125 6.3,0 12.96,-46.3125 0.15,0 12.07,46.3125 5.04,0 -14.59,-52.914 -5.34,0 -13.42,47.3515 -0.14,0 -13.34,-47.3515 -5.48,0 -14.67,52.914 5.4,0"
|
||||
id="path3112"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2034.73,49.1055 -10.24,27.125 -10.59,-27.125 20.83,0 z m -7.2,32.164 20.75,-52.914 -5.41,0 -6.44,16.457 -24.09,0 -6.37,-16.457 -5.34,0 21.27,52.914 5.63,0"
|
||||
id="path3114"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2043.84,81.2695 5.93,0 17.41,-26.8281 17.33,26.8281 6.01,0 -20.9,-31.125 0,-21.789 -5.04,0 0,21.789 -20.74,31.125"
|
||||
id="path3116"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2144.01,56.1484 c 1.55,0 3,0.2383 4.38,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.05,0.8907 1.88,1.9688 2.48,3.2227 0.62,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.94,5.836 -2.81,7.7031 -1.89,1.8829 -4.75,2.8282 -8.61,2.8282 l -18.81,0 0,-20.8282 18.81,0 z m 0.38,25.1211 c 2.18,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.66,-1.3633 5.1,-2.4765 1.47,-1.1133 2.63,-2.543 3.5,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.7,-4.7383 l 0,-0.1484 c 1.72,-0.25 3.15,-0.7032 4.28,-1.3672 1.15,-0.668 2.07,-1.5235 2.79,-2.5586 0.72,-1.0391 1.24,-2.2344 1.55,-3.5977 0.32,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.15,-1.3125 0.39,-2.5468 0.71,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.3,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.3,1.4531 -0.38,2.2265 -0.07,0.7618 -0.13,1.5118 -0.18,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.49,5.6016 -0.22,1.8555 -0.68,3.5 -1.39,4.9609 -0.73,1.4532 -1.76,2.6289 -3.13,3.5235 -1.35,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.11,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
||||
id="path3118"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2208.34,81.2695 0,-4.2929 -31.49,0 0,-19.1211 29.49,0 0,-4.3008 -29.49,0 0,-20.8945 31.87,0 0,-4.3047 -36.91,0 0,52.914 36.53,0"
|
||||
id="path3120"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2221.6,38.9609 c 0.9,-1.8281 2.13,-3.289 3.66,-4.375 1.58,-1.0898 3.4,-1.8671 5.53,-2.3359 2.12,-0.4727 4.41,-0.7031 6.82,-0.7031 1.38,0 2.9,0.1914 4.53,0.5976 1.62,0.3907 3.13,1.0196 4.55,1.8828 1.41,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.4,3.0039 1.4,4.9258 0,1.4843 -0.32,2.7695 -0.99,3.8593 -0.66,1.086 -1.55,1.9961 -2.59,2.7383 -1.06,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.54,0.8555 -3.78,1.1524 l -11.77,2.8789 c -1.55,0.4062 -3.03,0.8906 -4.5,1.4922 -1.45,0.5898 -2.73,1.375 -3.82,2.3672 -1.08,0.9843 -1.95,2.2031 -2.62,3.6289 -0.66,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.24,2.7929 0.74,4.5234 0.5,1.7266 1.42,3.3594 2.78,4.8906 1.36,1.5274 3.23,2.8243 5.59,3.8907 2.38,1.0625 5.41,1.5898 9.12,1.5898 2.61,0 5.11,-0.3398 7.48,-1.0351 2.37,-0.6915 4.46,-1.7305 6.24,-3.1133 1.77,-1.3789 3.19,-3.0977 4.24,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.55,3.7969 -1.38,5.3047 -0.81,1.5039 -1.87,2.7617 -3.18,3.7773 -1.31,1.0117 -2.82,1.7852 -4.53,2.3008 -1.71,0.5195 -3.48,0.7773 -5.37,0.7773 -1.73,0 -3.4,-0.1914 -5.01,-0.5586 -1.6,-0.371 -3,-0.9609 -4.21,-1.7812 -1.21,-0.8164 -2.19,-1.8906 -2.94,-3.2227 -0.74,-1.332 -1.1,-2.9882 -1.1,-4.9648 0,-1.2305 0.21,-2.3086 0.63,-3.2227 0.43,-0.914 0.99,-1.6953 1.73,-2.332 0.75,-0.6484 1.62,-1.1601 2.56,-1.5547 0.97,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.87,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.26,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.48,-1.1133 -1.15,-2.2344 -2.01,-3.3711 -0.86,-1.1368 -2.05,-2.1602 -3.55,-3.0782 -1.5,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.89,-0.8516 -8.01,-0.8516 -3.11,0 -5.99,0.3594 -8.67,1.0781 -2.66,0.7188 -4.94,1.8125 -6.8,3.2969 -1.89,1.4844 -3.33,3.3789 -4.36,5.707 -0.99,2.3204 -1.43,5.1055 -1.29,8.375 l 5.04,0 c -0.05,-2.7148 0.38,-4.9921 1.26,-6.8164"
|
||||
id="path3122"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2270.24,46.3281 c 0.79,-2.789 1.96,-5.2812 3.57,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.52,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.02,2.0039 2.49,1.3281 4.54,3.0976 6.15,5.2969 1.6,2.1992 2.77,4.6914 3.55,7.4804 0.77,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.38,5.7578 -1.15,8.5273 -0.78,2.7618 -1.95,5.2461 -3.55,7.4454 -1.61,2.2031 -3.66,3.9648 -6.15,5.3007 -2.5,1.3321 -5.51,2 -9.02,2 -3.5,0 -6.52,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.61,-2.1993 -2.78,-4.6836 -3.57,-7.4454 -0.75,-2.7695 -1.13,-5.6093 -1.13,-8.5273 0,-2.8633 0.38,-5.6914 1.13,-8.4844 z m -4.62,18.9336 c 1.04,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.68,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.92,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.79,-6.0781 2.07,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.03,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.09,-2.5195 -4.68,-4.5312 -7.79,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.78,0.7617 -10.92,2.2656 -3.15,1.5079 -5.74,3.5196 -7.83,6.0391 -2.07,2.5156 -3.62,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
|
||||
id="path3124"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2328.53,81.2695 0,-32.7539 c 0,-3.0625 0.35,-5.664 1.03,-7.8242 0.69,-2.1406 1.71,-3.8984 3.05,-5.2578 1.33,-1.3555 2.97,-2.3477 4.88,-2.9648 1.93,-0.6172 4.11,-0.9219 6.52,-0.9219 2.47,0 4.67,0.3047 6.61,0.9219 1.93,0.6171 3.55,1.6093 4.88,2.9648 1.34,1.3594 2.35,3.1172 3.04,5.2578 0.69,2.1602 1.04,4.7617 1.04,7.8242 l 0,32.7539 5.04,0 0,-33.8672 c 0,-2.7148 -0.38,-5.2968 -1.14,-7.7382 -0.77,-2.4493 -1.99,-4.5899 -3.65,-6.418 -1.66,-1.8242 -3.76,-3.2695 -6.36,-4.332 -2.6,-1.0586 -5.74,-1.5938 -9.46,-1.5938 -3.65,0 -6.77,0.5352 -9.37,1.5938 -2.59,1.0625 -4.71,2.5078 -6.37,4.332 -1.66,1.8281 -2.87,3.9687 -3.63,6.418 -0.77,2.4414 -1.13,5.0234 -1.13,7.7382 l 0,33.8672 5.02,0"
|
||||
id="path3126"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2400.87,56.1484 c 1.51,0 2.99,0.2383 4.36,0.7071 1.39,0.4687 2.59,1.1484 3.63,2.0351 1.03,0.8907 1.86,1.9688 2.49,3.2227 0.62,1.2617 0.92,2.7109 0.92,4.332 0,3.2656 -0.94,5.836 -2.82,7.7031 -1.86,1.8829 -4.73,2.8282 -8.58,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.36,25.1211 c 2.18,0 4.23,-0.2695 6.2,-0.8203 1.94,-0.539 3.64,-1.3633 5.11,-2.4765 1.45,-1.1133 2.61,-2.543 3.47,-4.3008 0.88,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.85,-6.2696 -2.58,-8.7461 -1.73,-2.4688 -4.29,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.17,-0.7032 4.31,-1.3672 1.11,-0.668 2.05,-1.5235 2.77,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.11,-1.9766 0.16,-3.2656 0.04,-1.2852 0.15,-2.5782 0.29,-3.8868 0.14,-1.3125 0.38,-2.5468 0.7,-3.707 0.31,-1.1562 0.75,-2.0586 1.3,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.71,1.8203 -0.16,0.7187 -0.29,1.4531 -0.36,2.2265 -0.09,0.7618 -0.14,1.5118 -0.19,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.09,1.875 -0.27,3.7461 -0.47,5.6016 -0.23,1.8555 -0.69,3.5 -1.42,4.9609 -0.71,1.4532 -1.75,2.6289 -3.1,3.5235 -1.37,0.8906 -3.23,1.2812 -5.6,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
||||
id="path3128"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2465.16,70.7109 c -1.02,1.6094 -2.27,2.9493 -3.71,4.0391 -1.47,1.0859 -3.08,1.9141 -4.82,2.4844 -1.75,0.5625 -3.59,0.8515 -5.53,0.8515 -3.5,0 -6.51,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.18,-5.3007 -1.62,-2.1993 -2.8,-4.6836 -3.58,-7.4454 -0.76,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.38,-5.6914 1.14,-8.4844 0.78,-2.789 1.96,-5.2812 3.58,-7.4804 1.58,-2.1993 3.66,-3.9688 6.18,-5.2969 2.52,-1.3399 5.53,-2.0039 9.03,-2.0039 2.47,0 4.7,0.4453 6.66,1.332 2,0.8906 3.7,2.1016 5.13,3.6289 1.44,1.5274 2.6,3.3281 3.48,5.3711 0.9,2.0508 1.46,4.2695 1.71,6.6367 l 5.04,0 c -0.35,-3.2578 -1.13,-6.1953 -2.3,-8.8203 -1.19,-2.6172 -2.72,-4.8359 -4.59,-6.668 -1.88,-1.8281 -4.09,-3.2304 -6.63,-4.2226 -2.56,-0.9844 -5.37,-1.4844 -8.5,-1.4844 -4.15,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.74,3.5196 -7.83,6.0391 -2.05,2.5156 -3.62,5.4297 -4.65,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492 1.03,3.3125 2.6,6.2383 4.65,8.7813 2.09,2.5429 4.7,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.93,2.2578 2.52,0 4.97,-0.3711 7.38,-1.1094 2.39,-0.7382 4.57,-1.8398 6.52,-3.2968 1.95,-1.461 3.57,-3.25 4.89,-5.3711 1.31,-2.1289 2.14,-4.5703 2.48,-7.3399 l -5.04,0 c -0.45,2.0274 -1.17,3.8399 -2.17,5.4492"
|
||||
id="path3130"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 2519.59,81.2695 0,-4.2929 -31.5,0 0,-19.1211 29.5,0 0,-4.3008 -29.5,0 0,-20.8945 31.86,0 0,-4.3047 -36.9,0 0,52.914 36.54,0"
|
||||
id="path3132"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 109.109,130.879 c -2.171,6.246 -5.242,11.781 -9.2145,16.601 -3.9765,4.825 -8.8007,8.7 -14.4765,11.637 -5.6758,2.938 -12.1133,4.395 -19.2969,4.395 -7.375,0 -13.9023,-1.457 -19.5781,-4.395 -5.6797,-2.937 -10.5039,-6.812 -14.4766,-11.637 -3.9726,-4.82 -7.1406,-10.41 -9.5078,-16.75 -2.3633,-6.332 -3.9258,-12.816 -4.6758,-19.433 l 94.7732,0 c -0.179,6.812 -1.371,13.348 -3.547,19.582 z M 20.5703,76.2539 c 1.8008,-6.9141 4.6875,-13.1055 8.6563,-18.5937 3.9765,-5.4883 8.9922,-10.0235 15.0429,-13.6133 6.0547,-3.6016 13.336,-5.3985 21.8516,-5.3985 13.0586,0 23.2734,3.4102 30.6484,10.2188 7.3755,6.8008 12.4885,15.8867 15.3205,27.2344 l 17.883,0 C 126.184,59.457 119.234,46.5898 109.109,37.5195 98.9922,28.4258 84.6602,23.8906 66.1211,23.8906 c -11.5352,0 -21.5234,2.043 -29.9336,6.1133 C 27.7578,34.0664 20.9023,39.6445 15.6094,46.7422 10.3125,53.8281 6.39063,62.0586 3.83203,71.4336 1.28125,80.7891 0,90.6719 0,101.086 c 0,9.641 1.28125,19.098 3.83203,28.371 2.5586,9.27 6.48047,17.551 11.77737,24.828 5.2929,7.285 12.1484,13.153 20.5781,17.606 8.4102,4.437 18.3984,6.664 29.9336,6.664 11.7266,0 21.7539,-2.375 30.0859,-7.102 8.32,-4.719 15.078,-10.922 20.285,-18.578 5.196,-7.66 8.938,-16.461 11.203,-26.395 2.278,-9.937 3.219,-20 2.84,-30.2222 l -112.6522,0 c 0,-6.4336 0.8867,-13.1055 2.6875,-20.0039"
|
||||
id="path3134"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 157.859,174.297 0,-25.258 0.571,0 c 3.41,8.895 9.457,16.039 18.164,21.426 8.695,5.398 18.254,8.09 28.66,8.09 10.219,0 18.777,-1.325 25.68,-3.977 6.91,-2.648 12.441,-6.379 16.601,-11.203 4.16,-4.824 7.098,-10.738 8.797,-17.734 1.699,-7.008 2.555,-14.864 2.555,-23.555 l 0,-94.2188 -17.875,0 0,91.3708 c 0,6.246 -0.567,12.059 -1.703,17.461 -1.141,5.387 -3.121,10.067 -5.957,14.047 -2.844,3.969 -6.676,7.09 -11.5,9.359 -4.821,2.274 -10.829,3.407 -18.02,3.407 -7.191,0 -13.578,-1.278 -19.152,-3.828 -5.578,-2.555 -10.309,-6.055 -14.192,-10.5 -3.879,-4.442 -6.91,-9.743 -9.082,-15.895 -2.172,-6.156 -3.355,-12.82 -3.547,-20.004 l 0,-85.4178 -17.871,0 0,146.4298 17.871,0"
|
||||
id="path3136"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 274.938,174.297 45.976,-128.5509 0.566,0 45.407,128.5509 18.449,0 -54.77,-146.4298 -19.019,0 -56.465,146.4298 19.856,0"
|
||||
id="path3138"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 409.707,174.297 0,-146.4298 -17.875,0 0,146.4298 17.875,0 z m 0,56.187 0,-28.664 -17.875,0 0,28.664 17.875,0"
|
||||
id="path3140"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 487.02,104.863 c 3.683,0 7.222,0.27 10.632,0.809 3.407,0.547 6.403,1.601 8.993,3.172 2.589,1.566 4.668,3.785 6.238,6.648 1.558,2.852 2.347,6.613 2.347,11.235 0,4.636 -0.789,8.39 -2.347,11.25 -1.57,2.859 -3.649,5.074 -6.238,6.64 -2.59,1.571 -5.586,2.629 -8.993,3.172 -3.41,0.547 -6.949,0.813 -10.632,0.813 l -24.934,0 0,-43.739 24.934,0 z m 8.793,68.68 c 9.128,0 16.898,-1.32 23.304,-3.988 6.406,-2.66 11.613,-6.16 15.633,-10.524 4.023,-4.359 6.961,-9.34 8.797,-14.922 1.84,-5.589 2.758,-11.379 2.758,-17.382 0,-5.852 -0.918,-11.614 -2.758,-17.27 -1.836,-5.652 -4.774,-10.664 -8.797,-15.0273 -4.02,-4.3633 -9.227,-7.8672 -15.633,-10.5234 -6.406,-2.6602 -14.176,-3.9844 -23.304,-3.9844 l -33.727,0 0,-52.336 -32.102,0 0,145.9571 65.829,0"
|
||||
id="path3142"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 614.273,76.7539 c -1.835,-0.6211 -3.812,-1.1289 -5.929,-1.5351 -2.114,-0.4102 -4.324,-0.75 -6.641,-1.0196 -2.316,-0.2773 -4.637,-0.6211 -6.953,-1.0273 -2.176,-0.4102 -4.328,-0.9571 -6.437,-1.6328 -2.114,-0.6836 -3.958,-1.6016 -5.52,-2.7618 -1.566,-1.1562 -2.832,-2.625 -3.777,-4.3945 -0.957,-1.7773 -1.438,-4.0234 -1.438,-6.7461 0,-2.5937 0.481,-4.7695 1.438,-6.5429 0.945,-1.7696 2.246,-3.168 3.879,-4.1915 1.636,-1.0234 3.543,-1.7382 5.726,-2.1484 2.176,-0.4101 4.426,-0.6094 6.746,-0.6094 5.723,0 10.145,0.9532 13.285,2.8672 3.133,1.9024 5.45,4.1875 6.953,6.8438 1.497,2.6562 2.418,5.3476 2.758,8.0742 0.336,2.7226 0.516,4.9101 0.516,6.543 l 0,10.8398 c -1.234,-1.1055 -2.766,-1.9492 -4.606,-2.5586 z m -57.339,40.9841 c 3,4.496 6.812,8.106 11.449,10.832 4.637,2.723 9.844,4.672 15.637,5.828 5.789,1.157 11.617,1.735 17.48,1.735 5.316,0 10.691,-0.375 16.145,-1.125 5.457,-0.75 10.425,-2.211 14.921,-4.395 4.504,-2.175 8.18,-5.207 11.043,-9.093 2.86,-3.883 4.297,-9.032 4.297,-15.434 l 0,-54.9922 c 0,-4.7774 0.27,-9.336 0.817,-13.6954 0.539,-4.3632 1.496,-7.6328 2.859,-9.8125 l -29.437,0 c -0.543,1.6329 -0.989,3.3008 -1.329,5.0118 -0.343,1.6992 -0.582,3.4414 -0.718,5.2109 -4.633,-4.7734 -10.082,-8.1133 -16.348,-10.0156 -6.273,-1.9063 -12.676,-2.8672 -19.219,-2.8672 -5.043,0 -9.746,0.6172 -14.105,1.8398 -4.367,1.2266 -8.18,3.1367 -11.449,5.7305 -3.27,2.5859 -5.825,5.8594 -7.668,9.8125 -1.84,3.9492 -2.758,8.6523 -2.758,14.1016 0,5.9961 1.058,10.9375 3.168,14.8242 2.109,3.8789 4.836,6.9726 8.179,9.2969 3.34,2.3164 7.157,4.0585 11.446,5.2148 4.297,1.1562 8.617,2.0742 12.98,2.7539 4.364,0.6836 8.656,1.2266 12.879,1.6367 4.227,0.4102 7.973,1.0235 11.25,1.8438 3.266,0.8125 5.856,2.0117 7.762,3.582 1.91,1.5664 2.793,3.8477 2.664,6.8435 0,3.137 -0.516,5.617 -1.535,7.461 -1.028,1.844 -2.395,3.266 -4.09,4.293 -1.707,1.02 -3.68,1.699 -5.93,2.039 -2.25,0.344 -4.672,0.516 -7.258,0.516 -5.718,0 -10.222,-1.223 -13.488,-3.68 -3.277,-2.453 -5.183,-6.543 -5.726,-12.265 l -29.032,0 c 0.41,6.816 2.118,12.46 5.114,16.968"
|
||||
id="path3144"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 720.172,133.277 0,-19.422 -21.254,0 0,-52.3355 c 0,-4.9023 0.812,-8.1718 2.449,-9.8047 1.637,-1.6406 4.903,-2.4609 9.817,-2.4609 1.632,0 3.195,0.0703 4.699,0.2031 1.496,0.1328 2.926,0.3438 4.289,0.6172 l 0,-22.4883 c -2.453,-0.414 -5.176,-0.6836 -8.176,-0.8203 -2.996,-0.1289 -5.926,-0.1992 -8.789,-0.1992 -4.5,0 -8.754,0.3047 -12.773,0.918 -4.024,0.6094 -7.563,1.8086 -10.629,3.5781 -3.071,1.7695 -5.489,4.293 -7.254,7.5586 -1.781,3.2773 -2.664,7.5703 -2.664,12.8828 l 0,62.3511 -17.578,0 0,19.422 17.578,0 0,31.684 29.031,0 0,-31.684 21.254,0"
|
||||
id="path3146"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 755.918,173.543 0,-54.984 0.613,0 c 3.684,6.125 8.387,10.586 14.11,13.379 5.722,2.796 11.312,4.195 16.761,4.195 7.77,0 14.137,-1.059 19.114,-3.164 4.972,-2.114 8.894,-5.043 11.754,-8.793 2.863,-3.75 4.871,-8.317 6.031,-13.699 1.152,-5.383 1.734,-11.3481 1.734,-17.8832 l 0,-65.0079 -29.027,0 0,59.6954 c 0,8.7148 -1.363,15.2227 -4.086,19.5157 -2.731,4.293 -7.567,6.433 -14.516,6.433 -7.902,0 -13.633,-2.343 -17.172,-7.039 -3.543,-4.703 -5.316,-12.441 -5.316,-23.2144 l 0,-55.3907 -29.023,0 0,145.9571 29.023,0"
|
||||
id="path3148"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
||||
d="m 863.082,0 21.875,0 0,190.547 -21.875,0 0,-190.547 z"
|
||||
id="path3150"
|
||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104 26" role="img" fill="currentColor">
|
||||
<path id="ep-logo-name" d="M13.638 12.451a6.613 6.613 0 0 0-1.152-2.075 5.687 5.687 0 0 0-1.81-1.455c-.708-.369-1.513-.55-2.411-.55-.922 0-1.739.181-2.447.55a5.702 5.702 0 0 0-1.81 1.455 7.21 7.21 0 0 0-1.188 2.092 10.28 10.28 0 0 0-.586 2.43h11.848a8.013 8.013 0 0 0-.444-2.447zM2.571 19.277a6.885 6.885 0 0 0 1.082 2.324 6.187 6.187 0 0 0 1.88 1.702c.757.452 1.667.676 2.732.676 1.633 0 2.91-.427 3.83-1.277.923-.852 1.563-1.987 1.917-3.405h2.234c-.474 2.08-1.342 3.689-2.608 4.824-1.264 1.135-3.056 1.702-5.373 1.702-1.442 0-2.69-.254-3.742-.765-1.053-.507-1.91-1.203-2.572-2.092C1.29 22.082.8 21.052.48 19.88A13.991 13.991 0 0 1 0 16.174c0-1.206.16-2.388.479-3.547a9.56 9.56 0 0 1 1.472-3.103c.662-.91 1.519-1.643 2.572-2.2 1.051-.554 2.3-.833 3.742-.833 1.466 0 2.72.296 3.76.887A7.487 7.487 0 0 1 14.562 9.7c.65.959 1.118 2.058 1.401 3.3.284 1.243.402 2.5.354 3.777H2.234c0 .806.113 1.638.337 2.5M19.732 7.024v3.156h.07c.428-1.113 1.184-2.004 2.271-2.678a6.654 6.654 0 0 1 3.584-1.01c1.277 0 2.346.163 3.21.495.862.332 1.555.799 2.075 1.402.52.603.887 1.342 1.1 2.216.212.877.32 1.858.32 2.945v11.777h-2.235v-11.42c0-.782-.071-1.51-.214-2.184-.142-.673-.39-1.26-.745-1.757a3.61 3.61 0 0 0-1.436-1.17c-.603-.283-1.354-.425-2.253-.425-.898 0-1.697.16-2.395.479a5.221 5.221 0 0 0-1.772 1.31 6.034 6.034 0 0 0-1.136 1.988 8.128 8.128 0 0 0-.444 2.5v10.679h-2.234V7.024h2.234M34.367 7.024l5.747 16.067h.071L45.86 7.024h2.307l-6.846 18.303h-2.378L31.885 7.024h2.482M51.214 7.024v18.303h-2.235V7.024h2.235zm0-7.024V3.58h-2.235V0h2.235M61.037 15.703c.46 0 .902-.034 1.33-.103.424-.068.799-.2 1.122-.395.325-.195.584-.474.78-.833.196-.356.294-.825.294-1.404 0-.578-.098-1.047-.294-1.406a2.162 2.162 0 0 0-.78-.83 3.104 3.104 0 0 0-1.123-.395 8.39 8.39 0 0 0-1.329-.103H57.92v5.469h3.117zm1.099-8.587c1.14 0 2.112.166 2.913.498.801.335 1.452.772 1.953 1.316.503.545.871 1.167 1.1 1.866a6.89 6.89 0 0 1 .346 2.172c0 .733-.115 1.453-.346 2.159a5.043 5.043 0 0 1-1.1 1.88c-.501.544-1.152.984-1.953 1.316-.8.332-1.772.498-2.913.498H57.92v6.54h-4.013V7.116h8.229M76.944 19.216a5.26 5.26 0 0 1-.742.19c-.264.052-.54.096-.83.13-.29.034-.579.076-.87.127-.27.051-.54.12-.804.205-.263.085-.494.2-.69.344-.195.144-.354.33-.472.55-.12.222-.18.502-.18.844 0 .323.06.596.18.818.118.22.28.396.485.523.205.129.443.217.716.268.272.051.553.076.844.076.715 0 1.267-.117 1.66-.357.392-.239.68-.525.869-.857.187-.332.303-.668.344-1.008a6.93 6.93 0 0 0 .065-.818v-1.355a1.615 1.615 0 0 1-.575.32zm-7.168-5.124a4.345 4.345 0 0 1 1.43-1.353 6.257 6.257 0 0 1 1.956-.73 11.148 11.148 0 0 1 2.185-.215c.664 0 1.336.047 2.018.14a6.193 6.193 0 0 1 1.866.549 3.69 3.69 0 0 1 1.38 1.137c.356.486.537 1.128.537 1.93v6.874c0 .596.033 1.167.102 1.712.066.544.186.954.357 1.225h-3.68a5.23 5.23 0 0 1-.256-1.277 4.735 4.735 0 0 1-2.043 1.253 8.261 8.261 0 0 1-2.402.356c-.63 0-1.219-.076-1.763-.23a4.016 4.016 0 0 1-1.432-.715 3.34 3.34 0 0 1-.958-1.228c-.23-.493-.344-1.081-.344-1.762 0-.75.131-1.368.395-1.853a3.324 3.324 0 0 1 1.023-1.163 4.648 4.648 0 0 1 1.43-.651 15.515 15.515 0 0 1 1.623-.345 27.623 27.623 0 0 1 1.61-.202c.527-.052.996-.13 1.406-.232.408-.1.732-.252.97-.447.239-.195.349-.48.333-.857 0-.39-.065-.7-.192-.933a1.406 1.406 0 0 0-.511-.534 2.014 2.014 0 0 0-.741-.257 6.208 6.208 0 0 0-.907-.063c-.715 0-1.278.151-1.686.459-.41.308-.648.818-.716 1.533h-3.63c.052-.852.265-1.557.64-2.121M90.181 12.15v2.427h-2.657v6.543c0 .613.101 1.02.306 1.226.205.205.613.308 1.227.308.204 0 .399-.01.587-.027.188-.015.366-.042.537-.076v2.81a8.619 8.619 0 0 1-1.023.103c-.373.017-.74.024-1.098.024a10.41 10.41 0 0 1-1.597-.115 3.728 3.728 0 0 1-1.328-.446 2.356 2.356 0 0 1-.907-.945c-.222-.41-.333-.945-.333-1.61v-7.795h-2.198v-2.426h2.198V8.19h3.629v3.96h2.657M94.703 7.116v6.873h.075c.462-.764 1.05-1.323 1.764-1.672.716-.35 1.415-.523 2.096-.523.97 0 1.767.132 2.388.396.622.263 1.113.63 1.47 1.098.358.47.609 1.04.754 1.712.144.674.217 1.418.217 2.236v8.125h-3.629v-7.46c0-1.09-.17-1.905-.511-2.44-.34-.537-.945-.805-1.814-.805-.988 0-1.704.293-2.146.88-.443.587-.664 1.556-.664 2.901v6.924h-3.628V7.116h3.628" class="logo-main"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@ -1,3 +0,0 @@
|
||||
<svg fill="currentColor" id="ep-logo-square" role="img" viewBox="0 0 65 65" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M26.538 25.283c-.532-1.519-1.274-2.861-2.24-4.037a11.163 11.163 0 0 0-3.522-2.828c-1.381-.712-2.948-1.07-4.697-1.07-1.791 0-3.379.358-4.76 1.07a11.11 11.11 0 0 0-3.522 2.828c-.966 1.176-1.737 2.533-2.308 4.07a19.839 19.839 0 0 0-1.139 4.728h23.047a15.559 15.559 0 0 0-.86-4.76zM5 38.57c.44 1.685 1.143 3.189 2.11 4.527.966 1.333 2.187 2.436 3.656 3.31 1.475.875 3.243 1.308 5.313 1.308 3.178 0 5.665-.83 7.456-2.485 1.793-1.65 3.037-3.867 3.726-6.626h4.35c-.922 4.054-2.613 7.179-5.073 9.39-2.46 2.208-5.947 3.315-10.46 3.315-2.802 0-5.233-.502-7.274-1.489-2.056-.986-3.721-2.348-5.01-4.072-1.284-1.724-2.241-3.725-2.861-6.005C.313 37.465 0 35.058 0 32.529c0-2.343.313-4.649.933-6.9.62-2.254 1.577-4.267 2.861-6.04 1.289-1.772 2.954-3.197 5.01-4.281 2.041-1.08 4.472-1.616 7.275-1.616 2.856 0 5.293.571 7.32 1.724 2.026 1.147 3.666 2.66 4.931 4.521 1.265 1.86 2.177 3.999 2.725 6.416a27.9 27.9 0 0 1 .692 7.348H4.35c0 1.567.215 3.188.65 4.868M49.301 31.456c.914 0 1.793-.07 2.638-.202.845-.136 1.586-.4 2.23-.78.641-.391 1.159-.942 1.548-1.656.387-.702.582-1.64.582-2.782 0-1.148-.195-2.08-.582-2.79-.39-.707-.907-1.26-1.547-1.65-.645-.386-1.386-.65-2.231-.78a16.554 16.554 0 0 0-2.638-.206h-6.176v10.846h6.176zm2.184-17.032c2.26 0 4.189.331 5.776.995 1.592.66 2.88 1.524 3.877 2.608a10.007 10.007 0 0 1 2.177 3.696c.46 1.393.684 2.828.684 4.313 0 1.455-.224 2.88-.684 4.28a9.955 9.955 0 0 1-2.177 3.727c-.997 1.079-2.285 1.953-3.877 2.607-1.587.664-3.516.992-5.776.992h-8.36v12.974h-7.964V14.424h16.324" class="logo-main"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 17 KiB |
BIN
static/images/uoa.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
static/images/wait.gif
Normal file
|
After Width: | Height: | Size: 814 B |
@ -1,265 +0,0 @@
|
||||
/**
|
||||
* Alpine.js Components for enviPath
|
||||
*
|
||||
* This module provides reusable Alpine.js data components for modals,
|
||||
* form validation, and form submission.
|
||||
*/
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
/**
|
||||
* Modal Form Component
|
||||
*
|
||||
* Provides form validation using HTML5 Constraint Validation API,
|
||||
* loading states for submission, and error message management.
|
||||
*
|
||||
* Basic Usage:
|
||||
* <dialog x-data="modalForm()" @close="reset()">
|
||||
* <form id="my-form">
|
||||
* <input name="field" required>
|
||||
* </form>
|
||||
* <button @click="submit('my-form')" :disabled="isSubmitting">Submit</button>
|
||||
* </dialog>
|
||||
*
|
||||
* With Custom State:
|
||||
* <dialog x-data="modalForm({ state: { selectedItem: '', imageUrl: '' } })" @close="reset()">
|
||||
* <select x-model="selectedItem" @change="updateImagePreview(selectedItem + '?image=svg')">
|
||||
* <img :src="imageUrl" x-show="imageUrl">
|
||||
* </dialog>
|
||||
*
|
||||
* With AJAX:
|
||||
* <button @click="submitAsync('my-form', { onSuccess: (data) => console.log(data) })">
|
||||
*/
|
||||
Alpine.data('modalForm', (options = {}) => ({
|
||||
isSubmitting: false,
|
||||
errors: {},
|
||||
// Spread custom initial state from options
|
||||
...(options.state || {}),
|
||||
|
||||
/**
|
||||
* Validate a single field using HTML5 Constraint Validation API
|
||||
* @param {HTMLElement} field - The input/select/textarea element
|
||||
*/
|
||||
validateField(field) {
|
||||
const name = field.name || field.id;
|
||||
if (!name) return;
|
||||
|
||||
if (!field.validity.valid) {
|
||||
this.errors[name] = field.validationMessage;
|
||||
} else {
|
||||
delete this.errors[name];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear error for a field (call on input)
|
||||
* @param {HTMLElement} field - The input element
|
||||
*/
|
||||
clearError(field) {
|
||||
const name = field.name || field.id;
|
||||
if (name && this.errors[name]) {
|
||||
delete this.errors[name];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get error message for a field
|
||||
* @param {string} name - Field name
|
||||
* @returns {string|undefined} Error message or undefined
|
||||
*/
|
||||
getError(name) {
|
||||
return this.errors[name];
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if form has any errors
|
||||
* @returns {boolean} True if there are errors
|
||||
*/
|
||||
hasErrors() {
|
||||
return Object.keys(this.errors).length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate all fields in a form
|
||||
* @param {string} formId - The form element ID
|
||||
* @returns {boolean} True if form is valid
|
||||
*/
|
||||
validateAll(formId) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return false;
|
||||
|
||||
this.errors = {};
|
||||
const fields = form.querySelectorAll('input, select, textarea');
|
||||
|
||||
fields.forEach(field => {
|
||||
if (field.name && !field.validity.valid) {
|
||||
this.errors[field.name] = field.validationMessage;
|
||||
}
|
||||
});
|
||||
|
||||
return !this.hasErrors();
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate that two password fields match
|
||||
* @param {string} password1Id - ID of first password field
|
||||
* @param {string} password2Id - ID of second password field
|
||||
* @returns {boolean} True if passwords match
|
||||
*/
|
||||
validatePasswordMatch(password1Id, password2Id) {
|
||||
const pw1 = document.getElementById(password1Id);
|
||||
const pw2 = document.getElementById(password2Id);
|
||||
|
||||
if (!pw1 || !pw2) return false;
|
||||
|
||||
if (pw1.value !== pw2.value) {
|
||||
this.errors[pw2.name || password2Id] = 'Passwords do not match';
|
||||
pw2.setCustomValidity('Passwords do not match');
|
||||
return false;
|
||||
}
|
||||
|
||||
delete this.errors[pw2.name || password2Id];
|
||||
pw2.setCustomValidity('');
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit a form with loading state
|
||||
* @param {string} formId - The form element ID
|
||||
*/
|
||||
submit(formId) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return;
|
||||
|
||||
// Validate before submit
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set action to current URL if empty
|
||||
if (!form.action || form.action === window.location.href + '#') {
|
||||
form.action = window.location.href;
|
||||
}
|
||||
|
||||
// Set loading state and submit
|
||||
this.isSubmitting = true;
|
||||
form.submit();
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit form via AJAX (fetch)
|
||||
* @param {string} formId - The form element ID
|
||||
* @param {Object} options - Options { onSuccess, onError, closeOnSuccess }
|
||||
*/
|
||||
async submitAsync(formId, options = {}) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return;
|
||||
|
||||
// Validate before submit
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSubmitting = true;
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const response = await fetch(form.action || window.location.href, {
|
||||
method: form.method || 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
|
||||
if (response.ok) {
|
||||
if (options.onSuccess) {
|
||||
options.onSuccess(data);
|
||||
}
|
||||
|
||||
if (data.redirect || data.success) {
|
||||
window.location.href = data.redirect || data.success;
|
||||
} else if (options.closeOnSuccess) {
|
||||
this.$el.closest('dialog')?.close();
|
||||
}
|
||||
} else {
|
||||
const errorMsg = data.error || data.message || `Error: ${response.status}`;
|
||||
this.errors['_form'] = errorMsg;
|
||||
|
||||
if (options.onError) {
|
||||
options.onError(errorMsg, data);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.errors['_form'] = error.message;
|
||||
|
||||
if (options.onError) {
|
||||
options.onError(error.message);
|
||||
}
|
||||
} finally {
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set form action URL dynamically
|
||||
* @param {string} formId - The form element ID
|
||||
* @param {string} url - The URL to set as action
|
||||
*/
|
||||
setFormAction(formId, url) {
|
||||
const form = document.getElementById(formId);
|
||||
if (form) {
|
||||
form.action = url;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update image preview
|
||||
* @param {string} url - Image URL (with query params)
|
||||
* @param {string} targetId - Target element ID for the image
|
||||
*/
|
||||
updateImagePreview(url) {
|
||||
// Store URL for reactive binding with :src
|
||||
this.imageUrl = url;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset form state (call on modal close)
|
||||
* Resets to initial state from options
|
||||
*/
|
||||
reset() {
|
||||
this.isSubmitting = false;
|
||||
this.errors = {};
|
||||
this.imageUrl = '';
|
||||
|
||||
// Reset custom state to initial values
|
||||
if (options.state) {
|
||||
Object.keys(options.state).forEach(key => {
|
||||
this[key] = options.state[key];
|
||||
});
|
||||
}
|
||||
|
||||
// Call custom reset handler if provided
|
||||
if (options.onReset) {
|
||||
options.onReset.call(this);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* Simple Modal Component (no form)
|
||||
*
|
||||
* For modals that don't need form validation.
|
||||
*
|
||||
* Usage:
|
||||
* <dialog x-data="modal()">
|
||||
* <button @click="$el.closest('dialog').close()">Close</button>
|
||||
* </dialog>
|
||||
*/
|
||||
Alpine.data('modal', () => ({
|
||||
// Placeholder for simple modals that may need state later
|
||||
}));
|
||||
});
|
||||
@ -1,133 +0,0 @@
|
||||
/**
|
||||
* Alpine.js Pagination Component
|
||||
*
|
||||
* Provides client-side pagination for large lists.
|
||||
*/
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('paginatedList', (initialItems = [], options = {}) => ({
|
||||
allItems: initialItems,
|
||||
filteredItems: [],
|
||||
currentPage: 1,
|
||||
perPage: options.perPage || 50,
|
||||
searchQuery: '',
|
||||
isReviewed: options.isReviewed || false,
|
||||
instanceId: options.instanceId || Math.random().toString(36).substring(2, 9),
|
||||
|
||||
init() {
|
||||
this.filteredItems = this.allItems;
|
||||
},
|
||||
|
||||
get totalPages() {
|
||||
return Math.ceil(this.filteredItems.length / this.perPage);
|
||||
},
|
||||
|
||||
get paginatedItems() {
|
||||
const start = (this.currentPage - 1) * this.perPage;
|
||||
const end = start + this.perPage;
|
||||
return this.filteredItems.slice(start, end);
|
||||
},
|
||||
|
||||
get totalItems() {
|
||||
return this.filteredItems.length;
|
||||
},
|
||||
|
||||
get showingStart() {
|
||||
if (this.totalItems === 0) return 0;
|
||||
return (this.currentPage - 1) * this.perPage + 1;
|
||||
},
|
||||
|
||||
get showingEnd() {
|
||||
return Math.min(this.currentPage * this.perPage, this.totalItems);
|
||||
},
|
||||
|
||||
search(query) {
|
||||
this.searchQuery = query.toLowerCase();
|
||||
if (this.searchQuery === '') {
|
||||
this.filteredItems = this.allItems;
|
||||
} else {
|
||||
this.filteredItems = this.allItems.filter(item =>
|
||||
item.name.toLowerCase().includes(this.searchQuery)
|
||||
);
|
||||
}
|
||||
this.currentPage = 1;
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++;
|
||||
}
|
||||
},
|
||||
|
||||
prevPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--;
|
||||
}
|
||||
},
|
||||
|
||||
goToPage(page) {
|
||||
if (page >= 1 && page <= this.totalPages) {
|
||||
this.currentPage = page;
|
||||
}
|
||||
},
|
||||
|
||||
get pageNumbers() {
|
||||
const pages = [];
|
||||
const total = this.totalPages;
|
||||
const current = this.currentPage;
|
||||
|
||||
// Handle empty case
|
||||
if (total === 0) {
|
||||
return pages;
|
||||
}
|
||||
|
||||
if (total <= 7) {
|
||||
// Show all pages if 7 or fewer
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||
}
|
||||
} else {
|
||||
// More than 7 pages - show first, last, and sliding window around current
|
||||
// Always show first page
|
||||
pages.push({ page: 1, isEllipsis: false, key: `${this.instanceId}-page-1` });
|
||||
|
||||
// Determine the start and end of the middle range
|
||||
let rangeStart, rangeEnd;
|
||||
|
||||
if (current <= 4) {
|
||||
// Near the beginning: show pages 2-5
|
||||
rangeStart = 2;
|
||||
rangeEnd = 5;
|
||||
} else if (current >= total - 3) {
|
||||
// Near the end: show last 4 pages before the last page
|
||||
rangeStart = total - 4;
|
||||
rangeEnd = total - 1;
|
||||
} else {
|
||||
// In the middle: show current page and one on each side
|
||||
rangeStart = current - 1;
|
||||
rangeEnd = current + 1;
|
||||
}
|
||||
|
||||
// Add ellipsis before range if there's a gap
|
||||
if (rangeStart > 2) {
|
||||
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-start` });
|
||||
}
|
||||
|
||||
// Add pages in the range
|
||||
for (let i = rangeStart; i <= rangeEnd; i++) {
|
||||
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||
}
|
||||
|
||||
// Add ellipsis after range if there's a gap
|
||||
if (rangeEnd < total - 1) {
|
||||
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-end` });
|
||||
}
|
||||
|
||||
// Always show last page
|
||||
pages.push({ page: total, isEllipsis: false, key: `${this.instanceId}-page-${total}` });
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
}));
|
||||
});
|
||||
@ -1,145 +0,0 @@
|
||||
/**
|
||||
* Search Modal Alpine.js Component
|
||||
*
|
||||
* Provides package selection, search mode switching, and results display
|
||||
* for the search modal.
|
||||
*/
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
/**
|
||||
* Search Modal Component
|
||||
*
|
||||
* Usage:
|
||||
* <dialog x-data="searchModal()" @close="reset()">
|
||||
* ...
|
||||
* </dialog>
|
||||
*/
|
||||
Alpine.data('searchModal', () => ({
|
||||
// Package selector state
|
||||
selectedPackages: [],
|
||||
|
||||
// Search state
|
||||
searchMode: 'text',
|
||||
searchModeLabel: 'Text',
|
||||
query: '',
|
||||
|
||||
// Results state
|
||||
results: null,
|
||||
isSearching: false,
|
||||
error: null,
|
||||
|
||||
// Initialize on modal open
|
||||
init() {
|
||||
// Load reviewed packages by default
|
||||
this.loadInitialSelection();
|
||||
|
||||
// Watch for modal open to focus searchbar
|
||||
this.$watch('$el.open', (open) => {
|
||||
if (open) {
|
||||
setTimeout(() => {
|
||||
this.$refs.searchbar.focus();
|
||||
}, 320);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadInitialSelection() {
|
||||
// Select all reviewed packages by default
|
||||
const menuItems = this.$refs.packageDropdown.querySelectorAll('li');
|
||||
|
||||
for (const item of menuItems) {
|
||||
// Stop at 'Unreviewed Packages' section
|
||||
if (item.classList.contains('menu-title') &&
|
||||
item.textContent.trim() === 'Unreviewed Packages') {
|
||||
break;
|
||||
}
|
||||
|
||||
const packageOption = item.querySelector('.package-option');
|
||||
if (packageOption) {
|
||||
this.selectedPackages.push({
|
||||
url: packageOption.dataset.packageUrl,
|
||||
name: packageOption.dataset.packageName
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
togglePackage(url, name) {
|
||||
const index = this.selectedPackages.findIndex(pkg => pkg.url === url);
|
||||
|
||||
if (index !== -1) {
|
||||
this.selectedPackages.splice(index, 1);
|
||||
} else {
|
||||
this.selectedPackages.push({ url, name });
|
||||
}
|
||||
},
|
||||
|
||||
removePackage(url) {
|
||||
const index = this.selectedPackages.findIndex(pkg => pkg.url === url);
|
||||
if (index !== -1) {
|
||||
this.selectedPackages.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
isPackageSelected(url) {
|
||||
return this.selectedPackages.some(pkg => pkg.url === url);
|
||||
},
|
||||
|
||||
setSearchMode(mode, label) {
|
||||
this.searchMode = mode;
|
||||
this.searchModeLabel = label;
|
||||
this.$refs.modeDropdown.hidePopover();
|
||||
},
|
||||
|
||||
async performSearch(serverBase) {
|
||||
if (!this.query.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedPackages.length < 1) {
|
||||
this.results = { error: 'no_packages' };
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
this.selectedPackages.forEach(pkg => params.append('packages', pkg.url));
|
||||
params.append('search', this.query.trim());
|
||||
params.append('mode', this.searchModeLabel.toLowerCase());
|
||||
|
||||
this.isSearching = true;
|
||||
this.results = null;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${serverBase}/search?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Search request failed');
|
||||
}
|
||||
|
||||
this.results = await response.json();
|
||||
} catch (err) {
|
||||
console.error('Search error:', err);
|
||||
this.error = 'Search failed. Please try again.';
|
||||
} finally {
|
||||
this.isSearching = false;
|
||||
}
|
||||
},
|
||||
|
||||
hasResults() {
|
||||
if (!this.results || this.results.error) return false;
|
||||
const categories = ['Compounds', 'Compound Structures', 'Rules', 'Reactions', 'Pathways'];
|
||||
return categories.some(cat => this.results[cat] && this.results[cat].length > 0);
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.query = '';
|
||||
this.results = null;
|
||||
this.error = null;
|
||||
this.isSearching = false;
|
||||
}
|
||||
}));
|
||||
});
|
||||
@ -1,173 +0,0 @@
|
||||
/**
|
||||
* Discourse API Integration for enviPath Community
|
||||
* Handles fetching topics from the Discourse forum API
|
||||
*/
|
||||
|
||||
class DiscourseAPI {
|
||||
constructor() {
|
||||
this.baseUrl = 'https://community.envipath.org';
|
||||
this.categoryId = 10; // Announcements category
|
||||
this.limit = 3; // Number of topics to fetch
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch topics from Discourse API
|
||||
* @param {number} limit - Number of topics to fetch
|
||||
* @returns {Promise<Array>} Array of topic objects
|
||||
*/
|
||||
async fetchTopics(limit = this.limit) {
|
||||
try {
|
||||
const url = `${this.baseUrl}/c/announcements/${this.categoryId}.json`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return this.processTopics(data.topic_list.topics, limit);
|
||||
} catch (error) {
|
||||
console.error('Error fetching Discourse topics:', error);
|
||||
return this.getFallbackTopics();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process raw Discourse topics into standardized format
|
||||
* @param {Array} topics - Raw topics from Discourse API
|
||||
* @param {number} limit - Number of topics to return
|
||||
* @returns {Array} Processed topics
|
||||
*/
|
||||
processTopics(topics, limit) {
|
||||
return topics
|
||||
.slice(0, limit)
|
||||
.map(topic => ({
|
||||
id: topic.id,
|
||||
title: topic.title,
|
||||
excerpt: this.extractExcerpt(topic.excerpt),
|
||||
url: `${this.baseUrl}/t/${topic.slug}/${topic.id}`,
|
||||
replies: topic.reply_count,
|
||||
views: topic.views,
|
||||
created_at: topic.created_at,
|
||||
category: 'Announcements',
|
||||
category_id: this.categoryId,
|
||||
author: topic.last_poster_username,
|
||||
author_avatar: this.getAvatarUrl(topic.last_poster_username)
|
||||
}))
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // Latest first
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract excerpt from topic content
|
||||
* @param {string} excerpt - Raw excerpt from Discourse
|
||||
* @returns {string} Cleaned excerpt
|
||||
*/
|
||||
extractExcerpt(excerpt) {
|
||||
if (!excerpt) return 'No preview available yet';
|
||||
|
||||
// Remove HTML tags and clean up; collapse whitespace; do not add manual ellipsis
|
||||
const cleaned = excerpt
|
||||
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
||||
.replace(/ /g, ' ') // Replace with spaces
|
||||
.replace(/&/g, '&') // Replace & with &
|
||||
.replace(/</g, '<') // Replace < with <
|
||||
.replace(/>/g, '>') // Replace > with >
|
||||
.replace(/\s+/g, ' ') // Collapse all whitespace/newlines
|
||||
.trim();
|
||||
|
||||
// Check if excerpt is empty after cleaning
|
||||
return cleaned || 'No preview available yet';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get avatar URL for user
|
||||
* @param {string} username - Username
|
||||
* @returns {string} Avatar URL
|
||||
*/
|
||||
getAvatarUrl(username) {
|
||||
if (!username) return `${this.baseUrl}/letter_avatar_proxy/v4/letter/u/1.png`;
|
||||
return `${this.baseUrl}/user_avatar/${this.baseUrl.replace('https://', '')}/${username}/40/1_1.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fallback topics when API fails
|
||||
* @returns {Array} Fallback topics
|
||||
*/
|
||||
getFallbackTopics() {
|
||||
return [
|
||||
{
|
||||
id: 110,
|
||||
title: "enviPath Beta Update: Major Improvements to Prediction, Analysis & Collaboration!",
|
||||
excerpt: "We're excited to announce major updates to the enviPath beta platform! This release includes significant improvements to our prediction algorithms, enhanced analysis tools, and new collaboration features that will make environmental biotransformation research more accessible and efficient.",
|
||||
url: "https://community.envipath.org/t/envipath-beta-update-major-improvements-to-prediction-analysis-collaboration/110",
|
||||
replies: 0,
|
||||
views: 16,
|
||||
created_at: "2025-09-23T00:00:00Z",
|
||||
category: "Announcements",
|
||||
category_id: 10,
|
||||
author: "wicker",
|
||||
author_avatar: "https://community.envipath.org/user_avatar/community.envipath.org/wicker/40/1_1.png"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date for display
|
||||
* @param {string} dateString - ISO date string
|
||||
* @returns {string} Formatted date
|
||||
*/
|
||||
formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load topics and call render function
|
||||
* @param {string} containerId - ID of container element
|
||||
* @param {Function} renderCallback - Function to render topics
|
||||
*/
|
||||
async loadTopics(containerId = 'community-news-container', renderCallback = null) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
console.error(`Container with ID '${containerId}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide loading spinner
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
|
||||
try {
|
||||
const topics = await this.fetchTopics();
|
||||
|
||||
if (renderCallback && typeof renderCallback === 'function') {
|
||||
renderCallback(topics);
|
||||
} else {
|
||||
// Default rendering - just log topics
|
||||
console.log('Topics loaded:', topics);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading topics:', error);
|
||||
container.innerHTML = '<p class="text-neutral">No updates found. Head over to the <a href="https://community.envipath.org" target="_blank" class="link link-primary">community</a> to see the latest discussions.</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other scripts
|
||||
window.DiscourseAPI = DiscourseAPI;
|
||||
|
||||
// Auto-initialize if container exists
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (document.getElementById('community-news-container')) {
|
||||
const discourseAPI = new DiscourseAPI();
|
||||
discourseAPI.loadTopics('community-news-container', function(topics) {
|
||||
// This will be handled by the template's render function
|
||||
if (window.renderDiscourseTopics) {
|
||||
window.renderDiscourseTopics(topics);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
1169
static/js/pps.js
229
static/js/pw.js
@ -1,21 +1,14 @@
|
||||
console.log("loaded pw.js")
|
||||
|
||||
function predictFromNode(url) {
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
|
||||
},
|
||||
body: new URLSearchParams({node: url})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
$.post("", {node: url})
|
||||
.done(function (data) {
|
||||
console.log("Success:", data);
|
||||
window.location.href = data.success;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error:", error);
|
||||
.fail(function (xhr, status, error) {
|
||||
console.error("Error:", xhr.status, xhr.responseText);
|
||||
// show user-friendly message or log error
|
||||
});
|
||||
}
|
||||
|
||||
@ -110,9 +103,6 @@ function draw(pathway, elem) {
|
||||
}
|
||||
|
||||
function dragstarted(event, d) {
|
||||
// Prevent zoom pan when dragging nodes
|
||||
event.sourceEvent.stopPropagation();
|
||||
|
||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
@ -127,9 +117,6 @@ function draw(pathway, elem) {
|
||||
}
|
||||
|
||||
function dragged(event, d) {
|
||||
// Prevent zoom pan when dragging nodes
|
||||
event.sourceEvent.stopPropagation();
|
||||
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
|
||||
@ -140,9 +127,6 @@ function draw(pathway, elem) {
|
||||
}
|
||||
|
||||
function dragended(event, d) {
|
||||
// Prevent zoom pan when dragging nodes
|
||||
event.sourceEvent.stopPropagation();
|
||||
|
||||
if (!event.active) simulation.alphaTarget(0);
|
||||
|
||||
// Mark that dragging has ended
|
||||
@ -208,153 +192,52 @@ function draw(pathway, elem) {
|
||||
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
|
||||
}
|
||||
|
||||
// Wait before showing popup (ms)
|
||||
// Wait one second before showing popup
|
||||
var popupWaitBeforeShow = 1000;
|
||||
// Keep Popup at least for one second
|
||||
var popushowAtLeast = 1000;
|
||||
|
||||
// Custom popover element
|
||||
let popoverTimeout = null;
|
||||
function pop_show_e(element) {
|
||||
var e = element;
|
||||
setTimeout(function () {
|
||||
if ($(e).is(':hover')) { // if element is still hovered
|
||||
$(e).popover("show");
|
||||
|
||||
function createPopover() {
|
||||
const popover = document.createElement('div');
|
||||
popover.id = 'custom-popover';
|
||||
popover.className = 'fixed z-50';
|
||||
popover.style.cssText = `
|
||||
background: #ffffff;
|
||||
border: 1px solid #d1d5db;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
max-width: 320px;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 150ms ease-in-out, visibility 150ms ease-in-out;
|
||||
pointer-events: auto;
|
||||
`;
|
||||
popover.setAttribute('role', 'tooltip');
|
||||
popover.innerHTML = `
|
||||
<div class="font-semibold mb-2 popover-title" style="font-weight: 600; margin-bottom: 0.5rem;"></div>
|
||||
<div class="text-sm popover-content" style="font-size: 0.875rem;"></div>
|
||||
`;
|
||||
|
||||
// Add styles for content images
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#custom-popover img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0.5rem 0;
|
||||
// workaround to set fixed positions
|
||||
pop = $(e).attr("aria-describedby")
|
||||
h = $('#' + pop).height();
|
||||
$('#' + pop).attr("style", `position: fixed; top: ${clientY - (h / 2.0)}px; left: ${clientX + 10}px; margin: 0px; max-width: 1000px; display: block;`)
|
||||
setTimeout(function () {
|
||||
var close = setInterval(function () {
|
||||
if (!$(".popover:hover").length // mouse outside popover
|
||||
&& !$(e).is(':hover')) { // mouse outside element
|
||||
$(e).popover('hide');
|
||||
clearInterval(close);
|
||||
}
|
||||
#custom-popover a {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
}, 100);
|
||||
}, popushowAtLeast);
|
||||
}
|
||||
#custom-popover a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
if (!document.getElementById('popover-styles')) {
|
||||
style.id = 'popover-styles';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Keep popover open when hovering over it
|
||||
popover.addEventListener('mouseenter', () => {
|
||||
if (popoverTimeout) {
|
||||
clearTimeout(popoverTimeout);
|
||||
popoverTimeout = null;
|
||||
}
|
||||
});
|
||||
|
||||
popover.addEventListener('mouseleave', () => {
|
||||
hidePopover();
|
||||
});
|
||||
|
||||
document.body.appendChild(popover);
|
||||
return popover;
|
||||
}
|
||||
|
||||
function getPopover() {
|
||||
return document.getElementById('custom-popover') || createPopover();
|
||||
}
|
||||
|
||||
function showPopover(element, title, content) {
|
||||
const popover = getPopover();
|
||||
popover.querySelector('.popover-title').textContent = title;
|
||||
popover.querySelector('.popover-content').innerHTML = content;
|
||||
|
||||
// Make visible to measure
|
||||
popover.style.visibility = 'hidden';
|
||||
popover.style.opacity = '0';
|
||||
popover.style.display = 'block';
|
||||
|
||||
// Smart positioning - avoid viewport overflow
|
||||
const padding = 10;
|
||||
const popoverRect = popover.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
let left = clientX + 15;
|
||||
let top = clientY - (popoverRect.height / 2);
|
||||
|
||||
// Prevent right overflow
|
||||
if (left + popoverRect.width > viewportWidth - padding) {
|
||||
left = clientX - popoverRect.width - 15;
|
||||
}
|
||||
|
||||
// Prevent bottom overflow
|
||||
if (top + popoverRect.height > viewportHeight - padding) {
|
||||
top = viewportHeight - popoverRect.height - padding;
|
||||
}
|
||||
|
||||
// Prevent top overflow
|
||||
if (top < padding) {
|
||||
top = padding;
|
||||
}
|
||||
|
||||
popover.style.top = `${top}px`;
|
||||
popover.style.left = `${left}px`;
|
||||
popover.style.visibility = 'visible';
|
||||
popover.style.opacity = '1';
|
||||
|
||||
currentElement = element;
|
||||
}
|
||||
|
||||
function hidePopover() {
|
||||
const popover = getPopover();
|
||||
popover.style.opacity = '0';
|
||||
popover.style.visibility = 'hidden';
|
||||
currentElement = null;
|
||||
}, popupWaitBeforeShow);
|
||||
}
|
||||
|
||||
function pop_add(objects, title, contentFunction) {
|
||||
objects.each(function (d) {
|
||||
const element = this;
|
||||
objects.attr("id", "pop")
|
||||
.attr("data-container", "body")
|
||||
.attr("data-toggle", "popover")
|
||||
.attr("data-placement", "right")
|
||||
.attr("title", title);
|
||||
|
||||
element.addEventListener('mouseenter', () => {
|
||||
if (popoverTimeout) clearTimeout(popoverTimeout);
|
||||
|
||||
popoverTimeout = setTimeout(() => {
|
||||
if (element.matches(':hover')) {
|
||||
const content = contentFunction(d);
|
||||
showPopover(element, title, content);
|
||||
}
|
||||
}, popupWaitBeforeShow);
|
||||
objects.each(function (d, i) {
|
||||
options = {trigger: "manual", html: true, animation: false};
|
||||
this_ = this;
|
||||
var p = $(this).popover(options).on("mouseenter", function () {
|
||||
pop_show_e(this);
|
||||
});
|
||||
|
||||
element.addEventListener('mouseleave', () => {
|
||||
if (popoverTimeout) {
|
||||
clearTimeout(popoverTimeout);
|
||||
popoverTimeout = null;
|
||||
}
|
||||
|
||||
// Delay hide to allow moving to popover
|
||||
setTimeout(() => {
|
||||
const popover = getPopover();
|
||||
if (!popover.matches(':hover') && !element.matches(':hover')) {
|
||||
hidePopover();
|
||||
}
|
||||
}, 100);
|
||||
p.on("show.bs.popover", function (e) {
|
||||
// this is to dynamically ajdust the content and bounds of the popup
|
||||
p.attr('data-content', contentFunction(d));
|
||||
p.data("bs.popover").setContent();
|
||||
p.data("bs.popover").tip().css({"max-width": "1000px"});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -372,7 +255,7 @@ function draw(pathway, elem) {
|
||||
}
|
||||
}
|
||||
|
||||
popupContent += "<img src='" + n.image + "'><br>"
|
||||
popupContent += "<img src='" + n.image + "' width='" + 20 * nodeRadius + "'><br>"
|
||||
if (n.scenarios.length > 0) {
|
||||
popupContent += '<b>Half-lives and related scenarios:</b><br>'
|
||||
for (var s of n.scenarios) {
|
||||
@ -382,7 +265,7 @@ function draw(pathway, elem) {
|
||||
|
||||
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
|
||||
if (pathway.isIncremental && isLeaf) {
|
||||
popupContent += '<br><a class="btn btn-primary btn-sm mt-2" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
|
||||
popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
|
||||
}
|
||||
|
||||
return popupContent;
|
||||
@ -402,7 +285,7 @@ function draw(pathway, elem) {
|
||||
popupContent += adcontent;
|
||||
}
|
||||
|
||||
popupContent += "<img src='" + e.image + "'><br>"
|
||||
popupContent += "<img src='" + e.image + "' width='" + 20 * nodeRadius + "'><br>"
|
||||
if (e.reaction_probability) {
|
||||
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
|
||||
}
|
||||
@ -425,23 +308,6 @@ function draw(pathway, elem) {
|
||||
});
|
||||
|
||||
const zoomable = d3.select("#zoomable");
|
||||
const svg = d3.select("#pwsvg");
|
||||
const container = d3.select("#vizdiv");
|
||||
|
||||
// Set explicit SVG dimensions for proper zoom behavior
|
||||
svg.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
// Add background rectangle FIRST to enable pan/zoom on empty space
|
||||
// This must be inserted before zoomable group so it's behind everything
|
||||
svg.insert("rect", "#zoomable")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("fill", "transparent")
|
||||
.attr("pointer-events", "all")
|
||||
.style("cursor", "grab");
|
||||
|
||||
// Zoom Funktion aktivieren
|
||||
const zoom = d3.zoom()
|
||||
@ -450,12 +316,7 @@ function draw(pathway, elem) {
|
||||
zoomable.attr("transform", event.transform);
|
||||
});
|
||||
|
||||
// Apply zoom to the SVG element - this enables wheel zoom
|
||||
svg.call(zoom);
|
||||
|
||||
// Also apply zoom to container to catch events that might not reach SVG
|
||||
// This ensures drag-to-pan works even when clicking on empty space
|
||||
container.call(zoom);
|
||||
d3.select("svg").call(zoom);
|
||||
|
||||
nodes = pathway['nodes'];
|
||||
links = pathway['links'];
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_compound_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Compound</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_compound_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Compound</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_compound_structure_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_compound_structure_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_edge_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Edge</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_edge_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Edge</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,8 +1,4 @@
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_group_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Group</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_group_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Group</a>
|
||||
</li>
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Model</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_model_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Model</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_node_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Node</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_node_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Node</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,25 +1,12 @@
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_package_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Package</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_package_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Package</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('import_package_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#import_package_modal">
|
||||
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-import"></span> Import Package from legacy
|
||||
JSON</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#import_legacy_package_modal">
|
||||
<span class="glyphicon glyphicon-import"></span> Import Package from legacy JSON</a>
|
||||
</li>
|
||||
@ -1,9 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
href="{% if meta.current_package %}{{ meta.current_package.url }}/predict{% else %}{{ meta.server_url }}/predict{% endif %}"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Pathway</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#predict_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_reaction_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Reaction</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_reaction_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Reaction</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_rule_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Rule</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_rule_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Rule</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span> New Scenario</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_scenario_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span> New Scenario</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,10 +1,6 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_setting_modal').showModal(); return false;"
|
||||
>
|
||||
<span class="glyphicon glyphicon-plus"></span>New Setting</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_setting_modal">
|
||||
<span class="glyphicon glyphicon-plus"></span>New Setting</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,60 +1,32 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_compound_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_compound_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('add_structure_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Add Structure</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#add_structure_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Add Structure</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
||||
</li>
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,42 +1,22 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_compound_structure_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_compound_structure_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,26 +1,14 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_group_member_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Group</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Group</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,34 +1,18 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_model_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Model</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_model_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Model</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('evaluate_model_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#evaluate_model_modal">
|
||||
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('retrain_model_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#retrain_model_modal">
|
||||
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Model</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Model</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,34 +1,18 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_node_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Node</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_node_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Node</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Node</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Node</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,50 +1,26 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_package_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Package</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_package_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Package</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_package_permissions_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_package_permissions_modal">
|
||||
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('publish_package_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#publish_package_modal">
|
||||
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('export_package_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#export_package_modal">
|
||||
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_license_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-duplicate"></i> License</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_license_modal">
|
||||
<i class="glyphicon glyphicon-duplicate"></i> License</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Package</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Package</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,104 +1,59 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('add_pathway_node_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Add Compound</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#add_pathway_node_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Add Compound</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('add_pathway_edge_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('download_pathway_csv_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('download_pathway_image_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button" data-toggle="modal" data-target="#download_pathway_csv_modal">
|
||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button" data-toggle="modal" data-target="#download_pathway_image_modal">
|
||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a>
|
||||
</li>
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('identify_missing_rules_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing
|
||||
Rules</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#identify_missing_rules_modal">
|
||||
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing Rules</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('edit_pathway_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
{# <li>#}
|
||||
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
|
||||
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
|
||||
{# </li>#}
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#delete_pathway_node_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#delete_pathway_edge_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,52 +1,28 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_reaction_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
||||
</li>
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,44 +1,24 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_rule_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_rule_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
||||
</li>
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@ -1,26 +1,14 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('add_additional_information_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#add_additional_information_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('update_scenario_additional_information_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#update_scenario_additional_information_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a
|
||||
>
|
||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,38 +1,22 @@
|
||||
{% if meta.can_edit %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_user_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-edit"></i> Update</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_user_modal">
|
||||
<i class="glyphicon glyphicon-edit"></i> Update</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('edit_password_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-lock"></i> Update Password</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#edit_password_modal">
|
||||
<i class="glyphicon glyphicon-lock"></i> Update Password</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
onclick="document.getElementById('new_prediction_setting_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#new_prediction_setting_modal">
|
||||
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a>
|
||||
</li>
|
||||
{# <li>#}
|
||||
{# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
|
||||
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
|
||||
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
|
||||
{# </li>#}
|
||||
<li>
|
||||
<a
|
||||
class="button"
|
||||
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||
>
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Account</a
|
||||
>
|
||||
<a role="button" data-toggle="modal" data-target="#generic_delete_modal">
|
||||
<i class="glyphicon glyphicon-trash"></i> Delete Account</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -1,17 +1,12 @@
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div id="searchContent">
|
||||
|
||||
<div id=searchContent>
|
||||
<form id="admin-form" action="{{ SERVER_BASE }}/admin" method="post">
|
||||
<div class="form-group">
|
||||
<label for="textarea">Query</label>
|
||||
<textarea
|
||||
id="textarea"
|
||||
class="form-control"
|
||||
rows="10"
|
||||
placeholder="Paste query here"
|
||||
required
|
||||
>
|
||||
<textarea id="textarea" class="form-control" rows="10" placeholder="Paste query here" required>
|
||||
PREFIX pps: <http://localhost:8080/vocabulary#>
|
||||
SELECT ?name (count(?objId) as ?xcnt)
|
||||
WHERE {
|
||||
@ -20,29 +15,32 @@ WHERE {
|
||||
?packageId pps:reviewStatus 'reviewed' .
|
||||
?packageId pps:pathway ?objId .
|
||||
} GROUP BY ?name
|
||||
</textarea
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
<button id="submit" type="button" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
<p></p>
|
||||
</div>
|
||||
<div id="results"></div>
|
||||
<div id="loading"></div>
|
||||
<script>
|
||||
$(function () {
|
||||
$("#submit").on("click", function () {
|
||||
</div>
|
||||
<div id="results">
|
||||
</div>
|
||||
<div id="loading"></div>
|
||||
</div>
|
||||
<script>
|
||||
$(function() {
|
||||
$('#submit').on('click', function() {
|
||||
|
||||
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
|
||||
|
||||
data = {
|
||||
query: $("#textarea").val(),
|
||||
};
|
||||
"query": $("#textarea").val()
|
||||
}
|
||||
|
||||
$.post("{{ SERVER_BASE }}/expire", data, function (result) {
|
||||
$.post("{{ SERVER_BASE }}/expire", data, function(result) {
|
||||
$("#loading").empty();
|
||||
queryResultToTable("results", result);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,52 +1,49 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% load envipytags %}
|
||||
{% block content %}
|
||||
<div class="space-y-2 p-4">
|
||||
<!-- Header Section -->
|
||||
<div class="card bg-base-100">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl">User Prediction Jobs</h2>
|
||||
<p class="mt-2">Job Logs Desc</p>
|
||||
|
||||
<div class="panel-group" id="reviewListAccordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||
Jobs
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
Job Logs Desc
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Jobs -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Recent Jobs</div>
|
||||
<div class="collapse-content" id="job-content">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table-zebra table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Queued</th>
|
||||
<th>Done</th>
|
||||
<th>Result</th>
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="job-accordion-link" data-toggle="collapse" data-parent="#job-accordion" href="#jobs">
|
||||
Jobs
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="jobs"
|
||||
class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item" id="job-content">
|
||||
<table class="table table-bordered table-hover">
|
||||
<tr style="background-color: rgba(0, 0, 0, 0.08);">
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Queued</th>
|
||||
<th scope="col">Done</th>
|
||||
<th scope="col">Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for job in jobs %}
|
||||
<tr>
|
||||
{% if meta.user.is_superuser %}
|
||||
<td>
|
||||
<a href="{{ job.user.url }}">{{ job.user.username }}</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{{ job.task_id }}</td>
|
||||
<td>{{ job.job_name }}</td>
|
||||
<td>{{ job.status }}</td>
|
||||
<td>{{ job.created }}</td>
|
||||
<td>{{ job.done_at }}</td>
|
||||
{% if job.task_result and job.task_result|is_url == True %}
|
||||
<td>
|
||||
<a href="{{ job.task_result }}" class="link link-primary"
|
||||
>Result</a
|
||||
>
|
||||
</td>
|
||||
<td><a href="{{ job.task_result }}">Result</a></td>
|
||||
{% elif job.task_result %}
|
||||
<td>{{ job.task_result|slice:"40" }}...</td>
|
||||
{% else %}
|
||||
@ -58,31 +55,17 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if objects %}
|
||||
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<ul class="menu bg-base-200 rounded-box">
|
||||
<ul class='list-group'>
|
||||
{% for obj in objects %}
|
||||
{% if object_type == 'user' %}
|
||||
<li>
|
||||
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||
>{{ obj.username }}</a
|
||||
>
|
||||
</li>
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
|
||||
{% else %}
|
||||
<li>
|
||||
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||
>{{ obj.name }}</a
|
||||
>
|
||||
</li>
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,32 +1,20 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
|
||||
{# Serialize objects data for Alpine pagination #}
|
||||
{# prettier-ignore-start #}
|
||||
{# FIXME: This is a hack to get the objects data into the JavaScript code. #}
|
||||
<script>
|
||||
window.reviewedObjects = [
|
||||
{% for obj in reviewed_objects %}
|
||||
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
window.unreviewedObjects = [
|
||||
{% for obj in unreviewed_objects %}
|
||||
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
</script>
|
||||
{# prettier-ignore-end #}
|
||||
|
||||
{% if object_type != 'package' %}
|
||||
<div class="px-8 py-4">
|
||||
<input
|
||||
type="text"
|
||||
id="object-search"
|
||||
class="input input-bordered hidden w-full max-w-xs"
|
||||
placeholder="Search by name"
|
||||
/>
|
||||
<div>
|
||||
<div id="load-all-error" style="display: none;">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
Getting objects failed!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="text" id="object-search" class="form-control" placeholder="Search by name"
|
||||
style="display: none;">
|
||||
<p></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -60,12 +48,9 @@
|
||||
{% endif %}
|
||||
{% endblock action_modals %}
|
||||
|
||||
<div class="px-8 py-4">
|
||||
<!-- Header Section -->
|
||||
<div class="card bg-base-100">
|
||||
<div class="card-body px-0 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="card-title text-2xl">
|
||||
<div class="panel-group" id="reviewListAccordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||
{% if object_type == 'package' %}
|
||||
Packages
|
||||
{% elif object_type == 'compound' %}
|
||||
@ -93,31 +78,13 @@
|
||||
{% elif object_type == 'group' %}
|
||||
Groups
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div id="actionsButton" class="dropdown dropdown-end hidden">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-wrench"
|
||||
>
|
||||
<path
|
||||
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
||||
/>
|
||||
</svg>
|
||||
Actions
|
||||
</div>
|
||||
<ul
|
||||
tabindex="-1"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
||||
>
|
||||
<div id="actionsButton"
|
||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||
aria-haspopup="true" aria-expanded="false"><span
|
||||
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
|
||||
style="padding-right:1em"></span></a>
|
||||
<ul id="actionsList" class="dropdown-menu">
|
||||
{% block actions %}
|
||||
{% if object_type == 'package' %}
|
||||
{% include "actions/collections/package.html" %}
|
||||
@ -148,386 +115,205 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="panel-body">
|
||||
<!-- Set Text above links -->
|
||||
{% if object_type == 'package' %}
|
||||
<p>
|
||||
A package contains pathways, rules, etc. and can reflect specific
|
||||
experimental conditions.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/packages"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A package contains pathways, rules, etc. and can reflect specific experimental
|
||||
conditions. <a target="_blank" href="https://wiki.envipath.org/index.php/packages" role="button">Learn
|
||||
more >></a></p>
|
||||
{% elif object_type == 'compound' %}
|
||||
<p>
|
||||
A compound stores the structure of a molecule and can include
|
||||
meta-information.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/compounds"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A compound stores the structure of a molecule and can include meta-information.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/compounds" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'structure' %}
|
||||
<p>
|
||||
The structures stored in this compound
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/compounds"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>The structures stored in this compound
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/compounds" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'rule' %}
|
||||
<p>
|
||||
A rule describes a biotransformation reaction template that is
|
||||
defined as SMIRKS.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/Rules"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A rule describes a biotransformation reaction template that is defined as SMIRKS.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/Rules" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'reaction' %}
|
||||
<p>
|
||||
A reaction is a specific biotransformation from educt compounds to
|
||||
product compounds.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/reactions"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A reaction is a specific biotransformation from educt compounds to product compounds.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/reactions" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'pathway' %}
|
||||
<p>
|
||||
A pathway displays the (predicted) biodegradation of a compound as
|
||||
graph.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/pathways"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A pathway displays the (predicted) biodegradation of a compound as graph.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/pathways" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'node' %}
|
||||
<p>
|
||||
Nodes represent the (predicted) compounds in a graph.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/nodes"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>Nodes represent the (predicted) compounds in a graph.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/nodes" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'edge' %}
|
||||
<p>
|
||||
Edges represent the links between Nodes in a graph
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/edges"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>Edges represent the links between Nodes in a graph
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/edges" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'scenario' %}
|
||||
<p>
|
||||
A scenario contains meta-information that can be attached to other
|
||||
data (compounds, rules, ..).
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/scenarios"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A scenario contains meta-information that can be attached to other data (compounds, rules, ..).
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/scenarios" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'model' %}
|
||||
<p>
|
||||
A model applies machine learning to limit the combinatorial
|
||||
explosion.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/relative_reasoning"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A model applies machine learning to limit the combinatorial explosion.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/relative_reasoning" role="button">Learn
|
||||
more
|
||||
>></a></p>
|
||||
{% elif object_type == 'setting' %}
|
||||
<p>
|
||||
A setting includes configuration parameters for pathway
|
||||
predictions.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/settings"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A setting includes configuration parameters for pathway predictions.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/settings" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'user' %}
|
||||
<p>
|
||||
Register now to create own packages and to submit and manage your
|
||||
data.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/users"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>Register now to create own packages and to submit and manage your data.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/users" role="button">Learn more
|
||||
>></a></p>
|
||||
{% elif object_type == 'group' %}
|
||||
<p>
|
||||
Users can team up in groups to share packages.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/groups"
|
||||
class="link link-primary"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>Users can team up in groups to share packages.
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/groups" role="button">Learn more
|
||||
>></a></p>
|
||||
{% endif %}
|
||||
|
||||
<!-- If theres nothing to show extend the text above -->
|
||||
{% if reviewed_objects and unreviewed_objects %}
|
||||
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
|
||||
<p class="mt-4">
|
||||
Nothing found. There are two possible reasons: <br /><br />1.
|
||||
There is no content yet.<br />2. You have no reading
|
||||
permissions.<br /><br />Please be sure you have at least reading
|
||||
permissions.
|
||||
</p>
|
||||
<p>Nothing found. There are two possible reasons: <br><br>1. There is no content yet.<br>2. You have no
|
||||
reading permissions.<br><br>Please be sure you have at least reading permissions.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lists Container - Full Width with Reviewed on Right -->
|
||||
<div class="w-full">
|
||||
{% if reviewed_objects %}
|
||||
{% if reviewed_objects|length > 0 %}
|
||||
<!-- Reviewed -->
|
||||
<div
|
||||
class="collapse-arrow bg-base-200 collapse order-2 w-full"
|
||||
x-data="paginatedList(window.reviewedObjects || [], { isReviewed: true, instanceId: 'reviewed' })"
|
||||
>
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Reviewed
|
||||
<span
|
||||
class="badge badge-sm badge-neutral ml-2"
|
||||
x-text="totalItems"
|
||||
></span>
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
<h4 class="panel-title">
|
||||
<a id="ReviewedLink" data-toggle="collapse" data-parent="#reviewListAccordion"
|
||||
href="#Reviewed">Reviewed</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="collapse-content w-full">
|
||||
<ul class="menu bg-base-100 rounded-box w-full">
|
||||
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||
<li>
|
||||
<a :href="obj.url" class="hover:bg-base-200">
|
||||
<span x-text="obj.name"></span>
|
||||
<span
|
||||
class="tooltip tooltip-left ml-auto"
|
||||
data-tip="Reviewed"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-star"
|
||||
>
|
||||
<polygon
|
||||
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
||||
/>
|
||||
</svg>
|
||||
<div id="Reviewed" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item" id="ReviewedContent">
|
||||
{% if object_type == 'package' %}
|
||||
{% for obj in reviewed_objects %}
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
||||
style="float:right" data-toggle="tooltip"
|
||||
data-placement="top" title="" data-original-title="Reviewed">
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<!-- Pagination Controls -->
|
||||
<div
|
||||
x-show="totalPages > 1"
|
||||
class="mt-4 flex items-center justify-between px-2"
|
||||
>
|
||||
<span class="text-base-content/70 text-sm">
|
||||
Showing <span x-text="showingStart"></span>-<span
|
||||
x-text="showingEnd"
|
||||
></span>
|
||||
of <span x-text="totalItems"></span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for obj in reviewed_objects|slice:":50" %}
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}{# <i>({{ obj.package.name }})</i> #}
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
||||
style="float:right" data-toggle="tooltip"
|
||||
data-placement="top" title="" data-original-title="Reviewed">
|
||||
</span>
|
||||
<div class="join">
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:disabled="currentPage === 1"
|
||||
@click="prevPage()"
|
||||
>
|
||||
«
|
||||
</button>
|
||||
<template x-for="item in pageNumbers" :key="item.key">
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:class="{ 'btn-active': item.page === currentPage }"
|
||||
:disabled="item.isEllipsis"
|
||||
@click="!item.isEllipsis && goToPage(item.page)"
|
||||
x-text="item.page"
|
||||
></button>
|
||||
</template>
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="nextPage()"
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if unreviewed_objects %}
|
||||
<!-- Unreviewed -->
|
||||
<div
|
||||
class="collapse-arrow bg-base-200 collapse order-1 w-full"
|
||||
x-data="paginatedList(window.unreviewedObjects || [], { isReviewed: false, instanceId: 'unreviewed' })"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
{% if reviewed_objects|length == 0 or object_type == 'package' %}checked{% endif %}
|
||||
/>
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Unreviewed
|
||||
<span
|
||||
class="badge badge-sm badge-neutral ml-2"
|
||||
x-text="totalItems"
|
||||
></span>
|
||||
</div>
|
||||
<div class="collapse-content w-full">
|
||||
<ul class="menu bg-base-100 rounded-box w-full">
|
||||
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||
<li>
|
||||
<a
|
||||
:href="obj.url"
|
||||
class="hover:bg-base-200"
|
||||
x-text="obj.name"
|
||||
></a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<!-- Pagination Controls -->
|
||||
<div
|
||||
x-show="totalPages > 1"
|
||||
class="mt-4 flex items-center justify-between px-2"
|
||||
>
|
||||
<span class="text-base-content/70 text-sm">
|
||||
Showing <span x-text="showingStart"></span>-<span
|
||||
x-text="showingEnd"
|
||||
></span>
|
||||
of <span x-text="totalItems"></span>
|
||||
</span>
|
||||
<div class="join">
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:disabled="currentPage === 1"
|
||||
@click="prevPage()"
|
||||
>
|
||||
«
|
||||
</button>
|
||||
<template x-for="item in pageNumbers" :key="item.key">
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:class="{ 'btn-active': item.page === currentPage }"
|
||||
:disabled="item.isEllipsis"
|
||||
@click="!item.isEllipsis && goToPage(item.page)"
|
||||
x-text="item.page"
|
||||
></button>
|
||||
</template>
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="nextPage()"
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"><h4
|
||||
class="panel-title"><a id="UnreviewedLink" data-toggle="collapse" data-parent="#unReviewListAccordion"
|
||||
href="#Unreviewed">Unreviewed</a></h4></div>
|
||||
<div id="Unreviewed" class="panel-collapse collapse {% if reviewed_objects|length == 0 or object_type == 'package' %}in{% endif %}">
|
||||
<div class="panel-body list-group-item" id="UnreviewedContent">
|
||||
{% if object_type == 'package' %}
|
||||
{% for obj in unreviewed_objects %}
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for obj in unreviewed_objects|slice:":50" %}
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if objects %}
|
||||
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||
<div class="card bg-base-100">
|
||||
<div class="card-body">
|
||||
<ul class="menu bg-base-200 rounded-box">
|
||||
<ul class='list-group'>
|
||||
{% for obj in objects %}
|
||||
{% if object_type == 'user' %}
|
||||
<li>
|
||||
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||
>{{ obj.username }}</a
|
||||
>
|
||||
</li>
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
|
||||
{% else %}
|
||||
<li>
|
||||
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||
>{{ obj.name }}</a
|
||||
>
|
||||
</li>
|
||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<style>
|
||||
.spinner-widget {
|
||||
position: fixed; /* stays in place on scroll */
|
||||
bottom: 20px; /* distance from bottom */
|
||||
right: 20px; /* distance from right */
|
||||
z-index: 9999; /* above most elements */
|
||||
width: 60px; /* adjust to gif size */
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.spinner-widget img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="load-all-loading" class="spinner-widget" style="display: none">
|
||||
<img id="loading-gif" src="{% static '/images/wait.gif' %}" alt="Loading...">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Show actions button if there are actions
|
||||
const actionsButton = document.getElementById("actionsButton");
|
||||
const actionsList = actionsButton?.querySelector("ul");
|
||||
if (actionsList && actionsList.children.length > 0) {
|
||||
actionsButton?.classList.remove("hidden");
|
||||
$(function () {
|
||||
|
||||
$('#object-search').show();
|
||||
|
||||
{% if object_type != 'package' and object_type != 'user' and object_type != 'group' %}
|
||||
{% if reviewed_objects|length > 50 or unreviewed_objects|length > 50 %}
|
||||
$('#load-all-loading').show()
|
||||
|
||||
setTimeout(function () {
|
||||
$('#load-all-error').hide();
|
||||
|
||||
$.getJSON('?all=true', function (resp) {
|
||||
$('#ReviewedContent').empty();
|
||||
$('#UnreviewedContent').empty();
|
||||
|
||||
for (o in resp.objects) {
|
||||
obj = resp.objects[o];
|
||||
if (obj.reviewed) {
|
||||
$('#ReviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + ' <span class="glyphicon glyphicon-star" aria-hidden="true" style="float:right" data-toggle="tooltip" data-placement="top" title="" data-original-title="Reviewed"></span></a>');
|
||||
} else {
|
||||
$('#UnreviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + '</a>');
|
||||
}
|
||||
}
|
||||
|
||||
// Show search input and connect to Alpine pagination
|
||||
const objectSearch = document.getElementById("object-search");
|
||||
if (objectSearch) {
|
||||
objectSearch.classList.remove("hidden");
|
||||
objectSearch.addEventListener("input", function () {
|
||||
const query = this.value;
|
||||
// Dispatch search to all paginatedList components
|
||||
document
|
||||
.querySelectorAll('[x-data*="paginatedList"]')
|
||||
.forEach((el) => {
|
||||
if (el._x_dataStack && el._x_dataStack[0]) {
|
||||
el._x_dataStack[0].search(query);
|
||||
}
|
||||
$('#load-all-loading').hide();
|
||||
$('#load-remaining').hide();
|
||||
}).fail(function (resp) {
|
||||
$('#load-all-loading').hide();
|
||||
$('#load-all-error').show();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Delete form submit handler
|
||||
const deleteSubmit = document.getElementById("modal-form-delete-submit");
|
||||
const deleteForm = document.getElementById("modal-form-delete");
|
||||
if (deleteSubmit && deleteForm) {
|
||||
deleteSubmit.addEventListener("click", function (e) {
|
||||
}, 2500);
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
$('#modal-form-delete-submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
deleteForm.submit();
|
||||
$('#modal-form-delete').submit();
|
||||
});
|
||||
}
|
||||
|
||||
$('#object-search').on('keyup', function () {
|
||||
let query = $(this).val().toLowerCase();
|
||||
$('a.list-group-item').each(function () {
|
||||
let text = $(this).text().toLowerCase();
|
||||
$(this).toggle(text.indexOf(query) !== -1);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@ -4,32 +4,16 @@
|
||||
<div>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="smiles"
|
||||
name="smiles"
|
||||
placeholder="SMILES"
|
||||
value="{{ smiles }}"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="smiles"
|
||||
name="smirks"
|
||||
placeholder="SMIRKS"
|
||||
value="{{ smirks }}"
|
||||
/>
|
||||
<input type="text" class="form-control" id="smiles" name="smiles" placeholder="SMILES"
|
||||
value="{{ smiles }}"/>
|
||||
<input type="text" class="form-control" id="smiles" name="smirks" placeholder="SMIRKS"
|
||||
value="{{ smirks }}"/>
|
||||
<button type="submit" class="btn btn-primary">Test</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if result %}
|
||||
{{ smiles }}
|
||||
<p></p>
|
||||
<img
|
||||
width="400"
|
||||
src="{% url 'depict' %}?smiles={{ smiles|urlencode }}"
|
||||
/><br />
|
||||
{{ smiles }}<p></p>
|
||||
<img width='400' src='{% url 'depict' %}?smiles={{ smiles|urlencode }}'><br>
|
||||
<p></p>
|
||||
{% if rule %}
|
||||
{{ smirks }}
|
||||
@ -38,7 +22,9 @@
|
||||
<p></p>
|
||||
{{ rule.products_smarts }}
|
||||
<p></p>
|
||||
<div>{{ rule.as_svg|safe }}</div>
|
||||
<div>
|
||||
{{ rule.as_svg|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<h2>Diff</h2>
|
||||
{% if diff %}
|
||||
@ -52,22 +38,16 @@
|
||||
<div class="col-md-6">
|
||||
<h2>Ambit</h2>
|
||||
{% for p in ambit_res %}
|
||||
{{ p }}<br />
|
||||
<img
|
||||
width="400"
|
||||
src="{% url 'depict' %}?smiles={{ p|urlencode }}"
|
||||
/><br />
|
||||
{{ p }}<br>
|
||||
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h2>RDKit</h2>
|
||||
{% for p in rdkit_res %}
|
||||
{{ p }}<br />
|
||||
<img
|
||||
width="400"
|
||||
src="{% url 'depict' %}?smiles={{ p|urlencode }}"
|
||||
/><br />
|
||||
{{ p }}<br>
|
||||
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,77 +1,15 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="alert alert-error mb-6 shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
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>
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-lg font-bold">Bad Request</h3>
|
||||
<p class="text-sm">The request you sent was invalid or malformed.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4 text-2xl">What happened?</h2>
|
||||
<p class="text-base-content/70 mb-4">
|
||||
The server couldn't process your request because it contains invalid
|
||||
data or parameters.
|
||||
</p>
|
||||
<div class="card-actions mt-6 justify-end">
|
||||
<a href="/" class="btn btn-primary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Go Home
|
||||
</a>
|
||||
<a
|
||||
<div class="alert alert-error" role="alert">
|
||||
<h4 class="alert-heading">Bad Request!</h4>
|
||||
<p>Lorem</p>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
You can find out more about permissions in our <a target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/packages"
|
||||
target="_blank"
|
||||
class="btn btn-outline"
|
||||
>
|
||||
Learn More
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="ml-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
role="button">Wiki >></a></p>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,80 +1,15 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="alert alert-warning mb-6 shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
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>
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-lg font-bold">Access Denied</h3>
|
||||
<p class="text-sm">
|
||||
You don't have permission to access this resource.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4 text-2xl">Permission Required</h2>
|
||||
<p class="text-base-content/70 mb-4">
|
||||
You need the appropriate permissions to access this content. If you
|
||||
believe this is an error, please contact the package owner or
|
||||
administrator.
|
||||
</p>
|
||||
<div class="card-actions mt-6 justify-end">
|
||||
<a href="/" class="btn btn-primary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Go Home
|
||||
</a>
|
||||
<a
|
||||
<div class="alert alert-error" role="alert">
|
||||
<h4 class="alert-heading">Access Denied!</h4>
|
||||
<p>Access to X denied. </p>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
You can find out more about permissions in our <a target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/packages"
|
||||
target="_blank"
|
||||
class="btn btn-outline"
|
||||
>
|
||||
Learn More
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="ml-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
role="button">Wiki >></a></p>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,77 +1,15 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="alert alert-info mb-6 shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
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>
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-lg font-bold">Page Not Found</h3>
|
||||
<p class="text-sm">The page you're looking for doesn't exist.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4 text-2xl">404 Error</h2>
|
||||
<p class="text-base-content/70 mb-4">
|
||||
The page or resource you requested could not be found. It may have
|
||||
been moved, deleted, or the URL might be incorrect.
|
||||
</p>
|
||||
<div class="card-actions mt-6 justify-end">
|
||||
<a href="/" class="btn btn-primary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Go Home
|
||||
</a>
|
||||
<a
|
||||
<div class="alert alert-error" role="alert">
|
||||
<h4 class="alert-heading">Not Found!</h4>
|
||||
<p>Does not exist</p>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
You can find out more about permissions in our <a target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/packages"
|
||||
target="_blank"
|
||||
class="btn btn-outline"
|
||||
>
|
||||
Learn More
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="ml-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
role="button">Wiki >></a></p>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,76 +1,13 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="alert alert-error mb-6 shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-lg font-bold">
|
||||
{{ error_message|default:"An Error Occurred" }}
|
||||
</h3>
|
||||
<p class="text-sm">
|
||||
{{ error_detail|default:"Something went wrong. Please try again later." }}
|
||||
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">{{ error_message }}</h4>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
{{ error_detail }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4 text-2xl">Oops! Something went wrong</h2>
|
||||
<p class="text-base-content/70 mb-4">
|
||||
{{ error_description|default:"We encountered an unexpected error while processing your request. Our team has been notified and is working to resolve the issue." }}
|
||||
</p>
|
||||
<div class="card-actions mt-6 justify-end">
|
||||
<a href="/" class="btn btn-primary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Go Home
|
||||
</a>
|
||||
<button onclick="window.history.back()" class="btn btn-outline">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
/>
|
||||
</svg>
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,81 +1,12 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="alert alert-warning mb-6 shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
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>
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-lg font-bold">Account Not Activated</h3>
|
||||
<p class="text-sm">Your account is pending activation.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">Your account has not been activated yet!</h4>
|
||||
<p>Your account has not been activated yet. If you have questions <a href="mailto:admin@envipath.org">contact
|
||||
us.</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4 text-2xl">Account Activation Required</h2>
|
||||
<p class="text-base-content/70 mb-4">
|
||||
Your account has not been activated yet. An administrator needs to
|
||||
approve your account before you can access all features. This
|
||||
process typically takes 24-48 hours.
|
||||
</p>
|
||||
<div class="divider"></div>
|
||||
<p class="text-base-content/70 mb-4">
|
||||
If you have questions or believe this is an error, please
|
||||
<a href="mailto:admin@envipath.org" class="link link-primary"
|
||||
>contact us</a
|
||||
>.
|
||||
</p>
|
||||
<div class="card-actions mt-6 justify-end">
|
||||
<a href="/" class="btn btn-primary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Go Home
|
||||
</a>
|
||||
<a href="mailto:admin@envipath.org" class="btn btn-outline">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Contact Admin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,68 +1,46 @@
|
||||
<!doctype html>
|
||||
<html data-theme="envipath">
|
||||
{% load static %}
|
||||
<head>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
{% load static %}
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<meta name="csrf-token" content="{{ csrf_token }}" />
|
||||
{# Favicon #}
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/png"
|
||||
href="{% static 'images/favicon.ico' %}"
|
||||
/>
|
||||
|
||||
{# Tailwind CSS disabled for legacy Bootstrap framework #}
|
||||
{# Pages using this framework will be migrated to framework_modern.html incrementally #}
|
||||
{# <link href="{% static 'css/output.css' %}" rel="stylesheet" type="text/css"/> #}
|
||||
|
||||
{# 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>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
|
||||
/>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
|
||||
<link
|
||||
href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
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://community.envipath.org/javascripts/embed-topics.js"></script>
|
||||
<!-- CDN END -->
|
||||
|
||||
{# Bootstrap compatibility styles #}
|
||||
<style>
|
||||
/* Ensure proper viewport behavior */
|
||||
html,
|
||||
body {
|
||||
html, body {
|
||||
height: 100%; /* ensure body fills viewport */
|
||||
overflow-x: hidden; /* prevent horizontal scroll */
|
||||
}
|
||||
</style>
|
||||
|
||||
{# TODO use bundles from bootstrap 3.3.7 #}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||
<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>
|
||||
<link href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/js/bootstrap-select.min.js"></script>
|
||||
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
|
||||
<!-- CDN END -->
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
<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
|
||||
$.ajaxSetup({
|
||||
beforeSend: function (xhr, settings) {
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{# Favicon #}
|
||||
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
|
||||
<!-- {# C3 CSS #}-->
|
||||
<!-- <link id="css-c3" href="{% static 'css/c3.css' %}" rel="stylesheet" type="text/css"/>-->
|
||||
<!-- {# EP CSS #}-->
|
||||
<!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>-->
|
||||
|
||||
|
||||
{# General EP JS #}
|
||||
<script src="{% static 'js/pps.js' %}"></script>
|
||||
{# Modal Steps for Stepwise Modal Wizards #}
|
||||
@ -71,33 +49,26 @@
|
||||
{% if not debug %}
|
||||
<!-- Matomo -->
|
||||
<script>
|
||||
var _paq = (window._paq = window._paq || []);
|
||||
var _paq = window._paq = window._paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(["trackPageView"]);
|
||||
_paq.push(["enableLinkTracking"]);
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function () {
|
||||
var u = "//matomo.envipath.com/";
|
||||
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||
_paq.push(["setSiteId", "{{ meta.site_id }}"]);
|
||||
var d = document,
|
||||
g = d.createElement("script"),
|
||||
s = d.getElementsByTagName("script")[0];
|
||||
_paq.push(['setTrackerUrl', u + 'matomo.php']);
|
||||
_paq.push(['setSiteId', '10']);
|
||||
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
||||
g.async = true;
|
||||
g.src = u + "matomo.js";
|
||||
g.src = u + 'matomo.js';
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Matomo Code -->
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<!-- Legacy Bootstrap navbar - isolated from Tailwind -->
|
||||
<div class="legacy-bootstrap">
|
||||
<nav
|
||||
class="navbar navbar-default navbar-inverse"
|
||||
style="border-radius:0px;"
|
||||
role="navigation"
|
||||
>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-default navbar-inverse" style="border-radius:0px;" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header navbar-header-framework">
|
||||
@ -108,173 +79,96 @@
|
||||
<!-- <span class="icon-bar"></span>-->
|
||||
<!-- <span class="icon-bar"></span>-->
|
||||
<!-- </button>-->
|
||||
<a
|
||||
id="pictureLink"
|
||||
href="{{ meta.server_url }}"
|
||||
class="navbar-brand"
|
||||
>
|
||||
<img
|
||||
id="image-logo-short-white.svg"
|
||||
src="{% static "/images/logo-short-white.svg" %}"
|
||||
width="100"
|
||||
alt="enviPath"
|
||||
/>
|
||||
<a id="pictureLink" href="{{ meta.server_url }}" class="navbar-brand">
|
||||
<img id="image-logo-short-white.svg" src='{% static "/images/logo-short-white.svg" %}' width="100"
|
||||
alt="enviPath">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div
|
||||
class="navbar-collapse collapse-framework navbar-collapse-framework collapse"
|
||||
id="navbarCollapse"
|
||||
>
|
||||
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
||||
<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 }}/model" id="modelLink"
|
||||
>Modelling</a
|
||||
>
|
||||
<a href="#" data-toggle="modal" data-target="#predict_modal">
|
||||
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>
|
||||
<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 }}/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>
|
||||
<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>
|
||||
<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><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><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
|
||||
>
|
||||
<a href="#signup" id="loginButton" data-toggle="modal" data-target="#signupmodal"
|
||||
style="margin-right:10px">Login</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="dropdown">
|
||||
<a
|
||||
data-toggle="dropdown"
|
||||
id="loggedInButton"
|
||||
class="dropdown-toggle"
|
||||
id="logedInButton"
|
||||
href="#"
|
||||
>
|
||||
<a data-toggle="dropdown" id="loggedInButton" class="dropdown-toggle" id="logedInButton"
|
||||
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
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<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" />
|
||||
<input type="hidden" name="logout" value="true">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">
|
||||
Logout
|
||||
</button>
|
||||
<button type="submit" class="btn btn-default">Logout</button>
|
||||
</form>
|
||||
</ul>
|
||||
</li>
|
||||
@ -282,11 +176,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- End legacy Bootstrap navbar -->
|
||||
|
||||
<div id="docContent" class="content container">
|
||||
</nav>
|
||||
<div id="docContent" class="content container">
|
||||
{% if breadcrumbs %}
|
||||
<div id="bread">
|
||||
<ol class="breadcrumb">
|
||||
@ -305,100 +196,81 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if message %}
|
||||
<div id="message">{{ message }}</div>
|
||||
<div id="message">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
{% if meta.url_contains_package and meta.current_package.license %}
|
||||
<p></p>
|
||||
<div class="panel-group" id="license_accordion">
|
||||
<div
|
||||
class="panel panel-default list-group-item"
|
||||
style="background-color:#f5f5f5"
|
||||
>
|
||||
<div class="panel panel-default list-group-item" style="background-color:#f5f5f5">
|
||||
<div class="panel-title">
|
||||
<a
|
||||
data-toggle="collapse"
|
||||
data-parent="#licence_accordion"
|
||||
href="#license"
|
||||
>License</a
|
||||
>
|
||||
<a data-toggle="collapse" data-parent="#licence_accordion" href="#license">License</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="license" class="panel-collapse in collapse">
|
||||
<div id="license" class="panel-collapse collapse in">
|
||||
<div class="panel-body list-group-item">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER - Legacy Bootstrap -->
|
||||
<div class="legacy-bootstrap">
|
||||
<div class="container text-center">
|
||||
<hr />
|
||||
<!-- FOOTER -->
|
||||
<div class="container text-center">
|
||||
<hr/>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<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>
|
||||
<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="mailto:admin@envipath.org" target="_blank">Contact</a>
|
||||
</li>
|
||||
<li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li>
|
||||
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschränkt) & Co. KG ©-->
|
||||
<!-- {{ YEAR }}</a></li>-->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End legacy Bootstrap footer -->
|
||||
|
||||
<script>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
// Hide actionsbutton if theres no action defined
|
||||
if ($("#actionsButton ul").children().length > 0) {
|
||||
$("#actionsButton").show();
|
||||
if ($('#actionsButton ul').children().length > 0) {
|
||||
$('#actionsButton').show();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</script>
|
||||
{% block modals %}
|
||||
{% include "modals/cite_modal.html" %}
|
||||
{% include "modals/signup_modal.html" %}
|
||||
{% include "modals/predict_modal.html" %}
|
||||
{% include "modals/batch_predict_modal.html" %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,219 +0,0 @@
|
||||
<!doctype html>
|
||||
<html data-theme="envipath" lang="en">
|
||||
{% load static %}
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="csrf-token" content="{{ csrf_token }}" />
|
||||
|
||||
{# Favicon #}
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/png"
|
||||
href="{% static 'images/favicon.ico' %}"
|
||||
/>
|
||||
|
||||
{# Tailwind CSS + DaisyUI Output #}
|
||||
<link
|
||||
href="{% static 'css/output.css' %}"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
|
||||
{# Alpine.js - For reactive components #}
|
||||
<script
|
||||
defer
|
||||
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
||||
></script>
|
||||
<script src="{% static 'js/alpine/index.js' %}"></script>
|
||||
<script src="{% static 'js/alpine/search.js' %}"></script>
|
||||
<script src="{% static 'js/alpine/pagination.js' %}"></script>
|
||||
|
||||
{# Font Awesome #}
|
||||
<link
|
||||
href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
{# Discourse embed for community #}
|
||||
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
|
||||
|
||||
<script>
|
||||
const csrftoken = document.querySelector("[name=csrf-token]").content;
|
||||
</script>
|
||||
|
||||
{# General EP JS #}
|
||||
<script src="{% static 'js/pps.js' %}"></script>
|
||||
|
||||
{% if not debug %}
|
||||
<!-- Matomo -->
|
||||
<script>
|
||||
var _paq = (window._paq = window._paq || []);
|
||||
_paq.push(["trackPageView"]);
|
||||
_paq.push(["enableLinkTracking"]);
|
||||
(function () {
|
||||
var u = "//matomo.envipath.com/";
|
||||
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||
_paq.push(["setSiteId", "{{ meta.site_id }}"]);
|
||||
var d = document,
|
||||
g = d.createElement("script"),
|
||||
s = d.getElementsByTagName("script")[0];
|
||||
g.async = true;
|
||||
g.src = u + "matomo.js";
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Matomo Code -->
|
||||
{% endif %}
|
||||
</head>
|
||||
<body class="bg-base-300 min-h-screen">
|
||||
{% include "includes/navbar.html" %}
|
||||
|
||||
{# Main Content Area #}
|
||||
<main class="w-full">
|
||||
{% block main_content %}
|
||||
{# Breadcrumbs - outside main content, optional #}
|
||||
{% if breadcrumbs %}
|
||||
<div id="bread" class="mx-auto max-w-7xl px-4 py-4">
|
||||
<div class="breadcrumbs text-sm">
|
||||
<ul>
|
||||
{% for elem in breadcrumbs %}
|
||||
{% for name, url in elem.items %}
|
||||
{% if forloop.parentloop.last %}
|
||||
<li>{{ name }}</li>
|
||||
{% else %}
|
||||
<li><a href="{{ url }}">{{ name }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% 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-arrow bg-base-200 collapse p-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>
|
||||
|
||||
{% include "includes/footer.html" %}
|
||||
|
||||
{# Floating Help Tab #}
|
||||
{% if not public_mode %}
|
||||
<div class="fixed top-1/2 right-0 z-50 -translate-y-1/2">
|
||||
<a
|
||||
href="https://community.envipath.org/"
|
||||
target="_blank"
|
||||
class="btn btn-secondary hover:btn-secondary-focus text-secondary-content flex items-center justify-center text-sm shadow-lg transition-all duration-300 hover:-translate-x-1 hover:scale-105"
|
||||
title="Get Help from the Community"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block modals %}
|
||||
{% include "modals/search_modal.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Show actions button if there are actions defined
|
||||
const actionsButtonUl = document.querySelector("#actionsButton ul");
|
||||
if (actionsButtonUl && actionsButtonUl.children.length > 0) {
|
||||
document.getElementById("actionsButton").style.display = "";
|
||||
}
|
||||
});
|
||||
|
||||
// Open search modal function
|
||||
function openSearchModal() {
|
||||
const searchModal = document.getElementById("search_modal");
|
||||
if (searchModal) {
|
||||
searchModal.showModal();
|
||||
}
|
||||
}
|
||||
|
||||
// Click handler for search badge
|
||||
const searchTrigger = document.getElementById("search-trigger");
|
||||
if (searchTrigger) {
|
||||
searchTrigger.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
openSearchModal();
|
||||
});
|
||||
}
|
||||
|
||||
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
||||
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) {
|
||||
return; // Don't trigger shortcut when typing in input fields
|
||||
}
|
||||
|
||||
// Check for Cmd+K (Mac) or Ctrl+K (Windows/Linux)
|
||||
const isMac = /Mac/.test(navigator.platform);
|
||||
const isCorrectModifier = isMac ? event.metaKey : event.ctrlKey;
|
||||
|
||||
if (isCorrectModifier && event.key === "k") {
|
||||
event.preventDefault();
|
||||
openSearchModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,94 +0,0 @@
|
||||
{% load static %}
|
||||
<div class="bg-base-300 text-base-content mx-auto mt-10 lg:max-w-5xl">
|
||||
<footer class="footer sm:footer-horizontal p-10">
|
||||
{% if not public_mode %}
|
||||
<nav>
|
||||
<h6 class="footer-title">Services</h6>
|
||||
<a class="link link-hover" href="/predict">Predict</a>
|
||||
<a class="link link-hover" href="/package">Packages</a>
|
||||
{% if user.is_authenticated %}
|
||||
<a class="link link-hover" href="/model">Your Collections</a>
|
||||
{% endif %}
|
||||
<a
|
||||
href="https://wiki.envipath.org/"
|
||||
target="_blank"
|
||||
class="link link-hover"
|
||||
>Documentation</a
|
||||
>
|
||||
</nav>
|
||||
{% endif %}
|
||||
<nav>
|
||||
<h6 class="footer-title">Company</h6>
|
||||
<a class="link link-hover" href="/about">About us</a>
|
||||
<a class="link link-hover" href="/contact">Contact us</a>
|
||||
<a class="link link-hover" href="/careers">Careers</a>
|
||||
<a class="link link-hover" href="/legal">Legal</a>
|
||||
</nav>
|
||||
<nav>
|
||||
<h6 class="footer-title">Legal</h6>
|
||||
<a class="link link-hover" href="/terms">Terms of use</a>
|
||||
<a class="link link-hover" href="/privacy">Privacy policy</a>
|
||||
<a class="link link-hover" href="/cookie-policy">Cookie policy</a>
|
||||
<a class="link link-hover" href="/cite">Cite enviPath</a>
|
||||
</nav>
|
||||
</footer>
|
||||
<footer class="footer border-t-2 border-neutral-300 px-10 py-4">
|
||||
<div class="flex w-full flex-row items-start justify-between">
|
||||
<aside class="grid-flow-col items-center">
|
||||
<svg
|
||||
class="fill-neutral-content m-2 h-14 flex-shrink-0"
|
||||
viewbox="0 0 65 65"
|
||||
>
|
||||
<use
|
||||
href="{% static "/images/logo-square.svg" %}#ep-logo-square"
|
||||
></use>
|
||||
</svg>
|
||||
|
||||
enviPath Ltd.
|
||||
<br />
|
||||
Biodegredation prediction since 2015.
|
||||
</aside>
|
||||
<aside class="text-base-200 mt-2 text-sm">
|
||||
<span class="text-xs tracking-tight">Version</span>
|
||||
<span class="text-base font-bold">{{ meta.version }}</span>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="md:place-self-center md:justify-self-end">
|
||||
<div class="grid grid-flow-col gap-4">
|
||||
<a href="https://www.youtube.com/@envipath7231" target="_blank">
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 fill-current"
|
||||
>
|
||||
<title>YouTube</title>
|
||||
<path
|
||||
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://community.envipath.org/" target="_blank">
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 fill-current"
|
||||
>
|
||||
<title>Discourse</title>
|
||||
<path
|
||||
d="M12.103 0C18.666 0 24 5.485 24 11.997c0 6.51-5.33 11.99-11.9 11.99L0 24V11.79C0 5.28 5.532 0 12.103 0zm.116 4.563c-2.593-.003-4.996 1.352-6.337 3.57-1.33 2.208-1.387 4.957-.148 7.22L4.4 19.61l4.794-1.074c2.745 1.225 5.965.676 8.136-1.39 2.17-2.054 2.86-5.228 1.737-7.997-1.135-2.778-3.84-4.59-6.84-4.585h-.008z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/envipath/" target="_blank">
|
||||
<img
|
||||
src="{% static "/images/linkedin.png" %}"
|
||||
alt="LinkedIn"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
</div>
|
||||
@ -1,277 +0,0 @@
|
||||
{% load static %}
|
||||
{# Modern DaisyUI Navbar with Mobile Drawer Menu #}
|
||||
<div class="drawer drawer-mobile">
|
||||
<input id="drawer-toggle" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col">
|
||||
{# Navbar #}
|
||||
<div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg">
|
||||
<div class="navbar-start">
|
||||
{# Hamburger menu button - visible on mobile, hidden on desktop #}
|
||||
{% if not public_mode %}
|
||||
<label
|
||||
for="drawer-toggle"
|
||||
class="btn btn-square btn-ghost drawer-button lg:hidden"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="inline-block h-5 w-5 stroke-current"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
></path>
|
||||
</svg>
|
||||
</label>
|
||||
{% endif %}
|
||||
<a
|
||||
href="{{ meta.server_url }}"
|
||||
class="btn btn-ghost text-xl normal-case"
|
||||
>
|
||||
<svg class="fill-base-content h-8" viewBox="0 0 104 26" role="img">
|
||||
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if not public_mode %}
|
||||
{# Desktop menu - hidden on mobile, visible on desktop #}
|
||||
<div class="navbar-center hidden lg:flex">
|
||||
<a
|
||||
href="{{ meta.server_url }}/predict"
|
||||
role="button"
|
||||
class="btn btn-ghost"
|
||||
id="predictLink"
|
||||
>Predict</a
|
||||
>
|
||||
<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 }}/package" id="packageLink"
|
||||
>Package</a
|
||||
>
|
||||
</li>
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
<div class="navbar-end">
|
||||
{% if not public_mode %}
|
||||
<a id="search-trigger" role="button" class="cursor-pointer">
|
||||
<div
|
||||
class="badge badge-dash bg-base-200 text-base-content/50 m-1 flex items-center space-x-1 p-2"
|
||||
>
|
||||
<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>
|
||||
<span id="search-shortcut">⌘K</span>
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if meta.user.username == 'anonymous' or public_mode %}
|
||||
<a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a>
|
||||
{% else %}
|
||||
<div class="dropdown dropdown-end">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="btn btn-ghost btn-circle m-1"
|
||||
id="loggedInButton"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
<ul
|
||||
tabindex="-1"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-sm"
|
||||
>
|
||||
<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>
|
||||
{# Mobile drawer menu - slides in from the left #}
|
||||
<div class="drawer-side">
|
||||
<label for="drawer-toggle" class="drawer-overlay"></label>
|
||||
<ul class="menu min-h-full w-80 bg-base-200 p-4 text-base-content">
|
||||
{# Drawer header with close button #}
|
||||
<li class="mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-bold text-lg">Menu</span>
|
||||
<label
|
||||
for="drawer-toggle"
|
||||
class="btn btn-sm btn-circle btn-ghost"
|
||||
aria-label="Close menu"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
{% if not public_mode %}
|
||||
{# Predict link #}
|
||||
<li>
|
||||
<a
|
||||
href="{{ meta.server_url }}/predict"
|
||||
class="text-lg"
|
||||
id="predictLinkMobile"
|
||||
>Predict</a
|
||||
>
|
||||
</li>
|
||||
{# Browse menu with submenu #}
|
||||
<li>
|
||||
<details>
|
||||
<summary class="text-lg">Browse</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ meta.server_url }}/package" id="packageLinkMobile"
|
||||
>Package</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ meta.server_url }}/pathway" id="pathwayLinkMobile"
|
||||
>Pathway</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ meta.server_url }}/rule" id="ruleLinkMobile"
|
||||
>Rule</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ meta.server_url }}/compound" id="compoundLinkMobile"
|
||||
>Compound</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ meta.server_url }}/reaction" id="reactionLinkMobile"
|
||||
>Reaction</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="{{ meta.server_url }}/model"
|
||||
id="relative-reasoningLinkMobile"
|
||||
>Model</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ meta.server_url }}/scenario" id="scenarioLinkMobile"
|
||||
>Scenario</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// OS-aware search shortcut display
|
||||
(function () {
|
||||
const isMac = /Mac/.test(navigator.platform);
|
||||
const shortcutElement = document.getElementById("search-shortcut");
|
||||
if (shortcutElement) {
|
||||
shortcutElement.textContent = isMac ? "⌘K" : "Ctrl+K";
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@ -1,424 +1,186 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% extends "framework.html" %}
|
||||
{% load static %}
|
||||
{% block main_content %}
|
||||
<!-- Hero Section with Logo and Search -->
|
||||
<section class="hero relative mx-auto h-fit w-full max-w-5xl shadow-none">
|
||||
<div
|
||||
class="hero from-primary-800 to-primary-600 min-h-[calc(100vh*0.4)] bg-gradient-to-br"
|
||||
style="background-image: url('{% static "/images/hero.png" %}'); background-size: cover; background-position: center;"
|
||||
>
|
||||
<div class="hero-overlay"></div>
|
||||
<!-- Predict Pathway text over the image -->
|
||||
<div class="absolute bottom-40 left-1/8 -translate-x-8">
|
||||
<h2 class="text-base-100 text-left text-3xl text-shadow-lg">
|
||||
Predict Your Pathway
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="bg-base-200 mx-auto max-w-5xl shadow-md">
|
||||
<!-- Predict Pathway Section -->
|
||||
<div
|
||||
class="relative mx-auto -mt-32 mb-10 w-full flex-col lg:flex-row-reverse"
|
||||
>
|
||||
<div
|
||||
class="card bg-base-100 mx-auto w-3/4 shrink-0 shadow-xl transition-all duration-300 ease-in-out"
|
||||
x-data="{
|
||||
drawMode: false,
|
||||
smiles: '',
|
||||
loadExample(smilesStr, linkEl) {
|
||||
if (this.drawMode && window.indexKetcher && window.indexKetcher.setMolecule) {
|
||||
window.indexKetcher.setMolecule(smilesStr);
|
||||
} else {
|
||||
this.smiles = smilesStr;
|
||||
}
|
||||
const original = linkEl.textContent;
|
||||
linkEl.textContent = 'loaded!';
|
||||
setTimeout(() => linkEl.textContent = original, 1000);
|
||||
},
|
||||
syncFromKetcher() {
|
||||
const ketcher = getKetcherInstance('index-ketcher');
|
||||
if (ketcher && ketcher.getSmiles) {
|
||||
try {
|
||||
const s = ketcher.getSmiles();
|
||||
if (s && s.trim()) this.smiles = s;
|
||||
} catch (err) {
|
||||
console.error('Failed to sync from Ketcher:', err);
|
||||
}
|
||||
}
|
||||
},
|
||||
submitForm() {
|
||||
let finalSmiles = '';
|
||||
if (this.drawMode) {
|
||||
const ketcher = getKetcherInstance('index-ketcher');
|
||||
if (ketcher && ketcher.getSmiles) {
|
||||
try {
|
||||
finalSmiles = ketcher.getSmiles().trim();
|
||||
} catch (err) {
|
||||
console.error('Failed to get SMILES from Ketcher:', err);
|
||||
alert('Unable to extract structure. Please try again or switch to SMILES input.');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
alert('The drawing editor is still loading. Please wait and try again.');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
finalSmiles = this.smiles.trim();
|
||||
}
|
||||
if (!finalSmiles) {
|
||||
alert('Please enter a SMILES string or draw a structure.');
|
||||
return;
|
||||
}
|
||||
document.getElementById('index-form-smiles').value = finalSmiles;
|
||||
document.getElementById('index-form').submit();
|
||||
}
|
||||
}"
|
||||
x-init="$watch('drawMode', value => { if (!value) syncFromKetcher(); })"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="my-4 ml-8 flex h-fit flex-row items-center justify-start">
|
||||
<div class="flex items-center gap-1">
|
||||
<label class="swap btn btn-ghost btn-sm p-1" title="Input Mode">
|
||||
<input type="checkbox" x-model="drawMode" />
|
||||
<span class="swap-on flex items-center gap-1">
|
||||
<div
|
||||
class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1"
|
||||
>
|
||||
<svg
|
||||
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>
|
||||
</div>
|
||||
<span class="ext-xs">SMILES</span>
|
||||
</span>
|
||||
<span class="swap-off flex items-center gap-1">
|
||||
<div
|
||||
class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1"
|
||||
>
|
||||
<svg
|
||||
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>
|
||||
</div>
|
||||
<span class="text-base/50 text-xs">Draw</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset
|
||||
class="fieldset overflow-hidden transition-all duration-300 ease-in-out"
|
||||
:class="drawMode ? 'p-4' : 'p-8'"
|
||||
>
|
||||
<form
|
||||
id="index-form"
|
||||
action="{{ meta.current_package.url }}/pathway"
|
||||
method="POST"
|
||||
@submit.prevent="submitForm()"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<div
|
||||
id="text-input-container"
|
||||
x-show="!drawMode"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
>
|
||||
<div class="join mx-auto w-full">
|
||||
<input
|
||||
type="text"
|
||||
id="index-form-text-input"
|
||||
placeholder="canonical SMILES string"
|
||||
class="input input-md join-item grow"
|
||||
x-model="smiles"
|
||||
/>
|
||||
<button class="btn btn-neutral join-item">Predict!</button>
|
||||
</div>
|
||||
<div class="label relative mt-1 w-full">
|
||||
<div class="flex gap-2">
|
||||
<a
|
||||
href="#"
|
||||
class="example-link hover:text-primary cursor-pointer"
|
||||
title="load example"
|
||||
@click.prevent="loadExample('CN1C=NC2=C1C(=O)N(C(=O)N2C)C', $el)"
|
||||
>Caffeine</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="example-link hover:text-primary cursor-pointer"
|
||||
title="load example"
|
||||
@click.prevent="loadExample('CC(C)CC1=CC=C(C=C1)C(C)C(=O)O', $el)"
|
||||
>Ibuprofen</a
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
class="absolute top-0 left-[calc(100%-5.4rem)]"
|
||||
href="/predict"
|
||||
>Advanced</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="ketcher-container"
|
||||
x-show="drawMode"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="w-full"
|
||||
>
|
||||
<iframe
|
||||
id="index-ketcher"
|
||||
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||
width="100%"
|
||||
height="400"
|
||||
class="rounded-lg"
|
||||
></iframe>
|
||||
<button
|
||||
class="btn btn-lg bg-primary-950 text-primary-50 join-item mt-2 w-full"
|
||||
>
|
||||
Predict!
|
||||
{% block content %}
|
||||
<!-- TODO rename ids as well as remove pathways if modal is closed!-->
|
||||
<div class="modal fade" tabindex="-1" id="foundMatching" role="dialog" aria-labelledby="foundModal"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
<div class="mt-1 flex w-full justify-end">
|
||||
<a class="label justify-end" href="/predict">Advanced</a>
|
||||
<h4 class="modal-title" id="newPackMod">Found Pathway in Database</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>We found at least one pathway in the database with the given root
|
||||
compound. Do you want to open any of the existing pathways or
|
||||
predict a new one? To open an existing pathway, simply click
|
||||
on the pathway, to predict a new one, click Predict. The predicted
|
||||
pathway might differ from the ones in the database due to the
|
||||
settings used in the prediction.</p>
|
||||
<div id="foundPathways"></div>
|
||||
</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 class="modal-footer">
|
||||
<a id="modal-predict" class="btn btn-primary" href="#">Predict</a>
|
||||
<button type="button" id="cancel-predict" class="btn btn-default" data-dismiss="modal">Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Community News Section -->
|
||||
<section class="bg-base-200 z-10 mx-8 py-16">
|
||||
<div class="mx-auto max-w-7xl px-4">
|
||||
<h2 class="h2 mb-8 text-left font-bold">Community Updates</h2>
|
||||
|
||||
<div id="community-news-container" class="flex justify-center gap-4">
|
||||
<!-- News cards will be populated here -->
|
||||
<div id="loading" class="flex w-full justify-center">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-right">
|
||||
<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>
|
||||
|
||||
<!-- Mission Statement Section -->
|
||||
<section class="from-base-200 to-base-100 bg-gradient-to-b py-16">
|
||||
<div class="mx-auto px-8 md:px-12">
|
||||
<div class="flex flex-row gap-4">
|
||||
<div class="w-1/3">
|
||||
<img
|
||||
src="{% static "/images/ep-rule-artwork.png" %}"
|
||||
alt="rule-based iterative tree greneration"
|
||||
class="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-8 w-2/3 space-y-4 text-left">
|
||||
<h2 class="h2 mb-8 font-bold">About enviPath</h2>
|
||||
<p class="">
|
||||
enviPath is a database and prediction system for the microbial
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<div class="jumbotron">
|
||||
<h1>
|
||||
<img id="image-logo-long" class="img-responsive" alt="enviPath" width="1000ex"
|
||||
src='{% static "/images/logo-long.svg" %}'/>
|
||||
</h1>
|
||||
<p>enviPath is a database and prediction system for the microbial
|
||||
biotransformation of organic environmental contaminants. The
|
||||
database provides the possibility to store and view experimentally
|
||||
observed biotransformation pathways.
|
||||
observed biotransformation pathways. The pathway prediction system
|
||||
provides different relative reasoning models to predict likely biotransformation
|
||||
pathways and products. You can try it out below.
|
||||
</p>
|
||||
<p class="">
|
||||
The pathway prediction system provides different relative
|
||||
reasoning models to predict likely biotransformation pathways and
|
||||
products. Explore our tools and contribute to advancing
|
||||
environmental biotransformation research.
|
||||
<p>
|
||||
<a class="btn" style="background-color:#222222;color:#9d9d9d" role="button" target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/Main_Page">Learn more >></a>
|
||||
</p>
|
||||
<div class="float-right flex flex-row gap-4">
|
||||
<a href="/about" class="btn btn-ghost-neutral">Read More</a>
|
||||
<a href="/about" class="btn btn-neutral">Publications</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Partners Section -->
|
||||
<section class="bg-base-100 py-14 sm:py-12">
|
||||
<div class="mx-auto px-6 lg:px-8">
|
||||
<div class="divider">
|
||||
<h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2>
|
||||
</div>
|
||||
<div
|
||||
class="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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="loading"></div>
|
||||
<div class="col-xs-4">
|
||||
<d-topics-list discourse-url="https://community.envipath.org" per-page="10" category="10"
|
||||
template="complete"></d-topics-list>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="input-group" id="index-form-bar">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<iframe id="index-form-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
|
||||
height="510"></iframe>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="text" class="form-control" id='index-form-text-input'
|
||||
placeholder="Enter a SMILES to predict a Pathway or type something to search">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false" id="action-button">Predict <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a id="dropdown-predict">Predict</a></li>
|
||||
<li><a id="dropdown-search">Search</a></li>
|
||||
</ul>
|
||||
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="index-form-smiles" name="smiles" value="smiles">
|
||||
<input type="hidden" id="index-form-predict" name="predict" value="predict">
|
||||
</form>
|
||||
</div>
|
||||
<p></p>
|
||||
<script language="javascript">
|
||||
var currentPackage = "{{ meta.current_package.url }}";
|
||||
|
||||
// Helper function to safely get Ketcher instance from iframe
|
||||
function getKetcherInstance(iframeId) {
|
||||
const ketcherFrame = document.getElementById(iframeId);
|
||||
if (!ketcherFrame) {
|
||||
console.error("Ketcher iframe not found:", iframeId);
|
||||
return null;
|
||||
function goButtonClicked() {
|
||||
$(this).prop("disabled", true);
|
||||
|
||||
var action = $('#action-button').text().trim();
|
||||
|
||||
var textSmiles = $('#index-form-text-input').val().trim();
|
||||
|
||||
if (textSmiles === '') {
|
||||
$(this).prop("disabled", false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
"contentWindow" in ketcherFrame &&
|
||||
ketcherFrame.contentWindow.ketcher
|
||||
) {
|
||||
return ketcherFrame.contentWindow.ketcher;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Cannot access Ketcher iframe - possible CORS issue:",
|
||||
err,
|
||||
);
|
||||
var ketcherSmiles = getKetcher('index-form-ketcher').getSmiles().trim();
|
||||
|
||||
if (action !== 'Search' && ketcherSmiles !== '' && textSmiles !== ketcherSmiles) {
|
||||
console.log("Ketcher and TextInput differ!");
|
||||
}
|
||||
|
||||
return null;
|
||||
if (action === 'Search') {
|
||||
var par = {};
|
||||
par['search'] = textSmiles;
|
||||
par['mode'] = 'text';
|
||||
var queryString = $.param(par, true);
|
||||
window.location.href = "/search?" + queryString;
|
||||
} else {
|
||||
$('#index-form-smiles').val(textSmiles);
|
||||
$('#index-form').submit();
|
||||
}
|
||||
}
|
||||
|
||||
// Discourse API integration is now handled by discourse-api.js
|
||||
function actionDropdownClicked() {
|
||||
var suffix = ' <span class="caret"></span>';
|
||||
var dropdownVal = $(this).text();
|
||||
|
||||
// Function to render Discourse topics into cards
|
||||
function renderDiscourseTopics(topics) {
|
||||
const container = document.getElementById("community-news-container");
|
||||
if (!container) return;
|
||||
if (dropdownVal === 'Search') {
|
||||
$("#index-form").attr("action", '/search');
|
||||
$("#index-form").attr("method", 'GET');
|
||||
} else {
|
||||
$("#index-form").attr("action", currentPackage + "/pathway");
|
||||
}
|
||||
|
||||
// Clear container
|
||||
container.innerHTML = "";
|
||||
$('#action-button').html(dropdownVal + suffix);
|
||||
}
|
||||
|
||||
// Create cards for each topic
|
||||
topics.forEach((topic) => {
|
||||
const card = createDiscourseCard(topic);
|
||||
container.insertAdjacentHTML("beforeend", card);
|
||||
function ketcherToTextInput() {
|
||||
$('#index-form-text-input').val(this.ketcher.getSmiles());
|
||||
}
|
||||
|
||||
$(function () {
|
||||
|
||||
$('#index-form').on("keydown", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
goButtonClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to create HTML card for a topic
|
||||
function createDiscourseCard(topic) {
|
||||
const date = new Date(topic.created_at).toLocaleDateString();
|
||||
// Code that should be executed once DOM is ready goes here
|
||||
$('#dropdown-predict').on('click', actionDropdownClicked);
|
||||
$('#dropdown-search').on('click', actionDropdownClicked);
|
||||
|
||||
return `
|
||||
<div class="card bg-white shadow-sm hover:shadow-lg transition-shadow duration-300 h-52 w-75 flex-shrink-0">
|
||||
<div class="card-body flex flex-col h-full justify-between">
|
||||
<h3 class="card-title leading-tight font-normal tracking-tight mb-2 line-clamp-5 overflow-hidden">
|
||||
<a href="${topic.url}" target="_blank" class="hover:text-primary">
|
||||
${topic.title}
|
||||
</a>
|
||||
</h3>
|
||||
$('#run-button').on('click', goButtonClicked);
|
||||
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="avatar tooltip tooltip-right" data-tip="${topic.author}">
|
||||
<div class="w-8 rounded-full">
|
||||
<img src="${topic.author_avatar}" alt="${topic.author}" />
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">${date}</span>
|
||||
</div>
|
||||
<a href="${topic.url}" target="_blank" class="btn btn-ghost text-neutral-500 rounded-full p-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 7v14"/>
|
||||
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
// Update Ketcher Width
|
||||
var fullWidth = $('#index-form-bar').width();
|
||||
$('#index-form-ketcher').width(fullWidth);
|
||||
|
||||
// Make render function globally available
|
||||
window.renderDiscourseTopics = renderDiscourseTopics;
|
||||
|
||||
// Ketcher iframe load handler - set up change event to sync SMILES
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const indexKetcher = document.getElementById("index-ketcher");
|
||||
indexKetcher.addEventListener("load", function () {
|
||||
// add a listener that gets triggered whenever the structure in ketcher has changed
|
||||
$('#index-form-ketcher').on('load', function () {
|
||||
const checkKetcherReady = () => {
|
||||
const win = this.contentWindow;
|
||||
if (win.ketcher && "editor" in win.ketcher) {
|
||||
window.indexKetcher = win.ketcher;
|
||||
win = this.contentWindow
|
||||
if (win.ketcher && 'editor' in win.ketcher) {
|
||||
win.ketcher.editor.event.change.handlers.push({
|
||||
once: false,
|
||||
priority: 0,
|
||||
f: ketcherToTextInput,
|
||||
ketcher: win.ketcher
|
||||
});
|
||||
} else {
|
||||
setTimeout(checkKetcherReady, 100);
|
||||
}
|
||||
};
|
||||
|
||||
checkKetcherReady();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock main_content %}
|
||||
{% endblock content %}
|
||||
|
||||
@ -1,71 +1,44 @@
|
||||
{% extends "framework.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="panel-group" id="migration-detail">
|
||||
<div class="panel-group" id="migration-detail">
|
||||
<div class="panel panel-default">
|
||||
<div
|
||||
class="panel-heading"
|
||||
id="headingPanel"
|
||||
style="font-size:2rem;height: 46px"
|
||||
>
|
||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||
Migration Status BT Rules
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>Rules with Error: {{ error }}/{{ total }}</p>
|
||||
<p>Rules with Error: {{ error }}/{{ total }} </p>
|
||||
<p>Rules without Error: {{ success }}/{{ total }}</p>
|
||||
</div>
|
||||
|
||||
{% for obj in results %}
|
||||
<div
|
||||
class="panel panel-default panel-heading list-group-item"
|
||||
style="background-color:silver"
|
||||
>
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
{% if obj.status %}
|
||||
<span
|
||||
class="glyphicon glyphicon-ok"
|
||||
aria-hidden="true"
|
||||
style="float:right"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title=""
|
||||
data-original-title="Reviewed"
|
||||
>
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"
|
||||
style="float:right" data-toggle="tooltip"
|
||||
data-placement="top" title="" data-original-title="Reviewed">
|
||||
</span>
|
||||
{% else %}
|
||||
<span
|
||||
class="glyphicon glyphicon-remove"
|
||||
aria-hidden="true"
|
||||
style="float:right"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title=""
|
||||
data-original-title="Reviewed"
|
||||
>
|
||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"
|
||||
style="float:right" data-toggle="tooltip"
|
||||
data-placement="top" title="" data-original-title="Reviewed">
|
||||
</span>
|
||||
{% endif %}
|
||||
<h4 class="panel-title">
|
||||
<a
|
||||
id="{{ obj.id }}-link"
|
||||
data-toggle="collapse"
|
||||
data-parent="#migration-detail"
|
||||
href="#{{ obj.id }}"
|
||||
>{{ obj.name|safe }}</a
|
||||
>
|
||||
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
||||
href="#{{ obj.id }}">{{ obj.name }}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div
|
||||
id="{{ obj.id }}"
|
||||
class="panel-collapse {% if not obj.status %}in{% endif %} collapse"
|
||||
>
|
||||
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
||||
<div class="panel-body list-group-item">
|
||||
<a class="list-group-item" href="{{ obj.detail_url }}"
|
||||
>{{ obj.name|safe }} Migration Detail Page</a
|
||||
>
|
||||
<a class="list-group-item" href="{{ obj.detail_url }}">{{ obj.name }} Migration Detail Page</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script></script>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@ -3,68 +3,34 @@
|
||||
{% block content %}
|
||||
<div class="panel-group" id="migration-detail">
|
||||
<div class="panel panel-default">
|
||||
<div
|
||||
class="panel-heading"
|
||||
id="headingPanel"
|
||||
style="font-size:2rem;height: 46px"
|
||||
>
|
||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||
Migration Status for {{ bt_rule_name }}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
A package contains pathways, rules, etc. and can reflect specific
|
||||
experimental conditions.
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/packages"
|
||||
role="button"
|
||||
>Learn more >></a
|
||||
>
|
||||
</p>
|
||||
<p>A package contains pathways, rules, etc. and can reflect specific experimental
|
||||
conditions. <a target="_blank" href="https://wiki.envipath.org/index.php/packages" role="button">Learn
|
||||
more >></a></p>
|
||||
</div>
|
||||
|
||||
{% for obj in results %}
|
||||
<div
|
||||
class="panel panel-default panel-heading list-group-item"
|
||||
style="background-color:silver"
|
||||
>
|
||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||
{% if obj.status %}
|
||||
<span
|
||||
class="glyphicon glyphicon-ok"
|
||||
aria-hidden="true"
|
||||
style="float:right"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title=""
|
||||
data-original-title="Reviewed"
|
||||
>
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"
|
||||
style="float:right" data-toggle="tooltip"
|
||||
data-placement="top" title="" data-original-title="Reviewed">
|
||||
</span>
|
||||
{% else %}
|
||||
<span
|
||||
class="glyphicon glyphicon-remove"
|
||||
aria-hidden="true"
|
||||
style="float:right"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title=""
|
||||
data-original-title="Reviewed"
|
||||
>
|
||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"
|
||||
style="float:right" data-toggle="tooltip"
|
||||
data-placement="top" title="" data-original-title="Reviewed">
|
||||
</span>
|
||||
{% endif %}
|
||||
<h4 class="panel-title">
|
||||
<a
|
||||
id="{{ obj.id }}-link"
|
||||
data-toggle="collapse"
|
||||
data-parent="#migration-detail"
|
||||
href="#{{ obj.id }}"
|
||||
>{{ obj.name|safe }}</a
|
||||
>
|
||||
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
||||
href="#{{ obj.id }}">{{ obj.name }}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div
|
||||
id="{{ obj.id }}"
|
||||
class="panel-collapse {% if not obj.status %}in{% endif %} collapse"
|
||||
>
|
||||
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
||||
<div class="panel-body list-group-item">
|
||||
<pre>{{ obj.detail }}</pre>
|
||||
</div>
|
||||
@ -73,5 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script></script>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
31
templates/modals/cite_modal.html
Normal file
@ -0,0 +1,31 @@
|
||||
<div class="modal fade bs-modal-lg" id="citemodal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>How to cite enviPath</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ol class="list-group list-group-numbered">
|
||||
<li class="list-group-item">
|
||||
Hafner, J., Lorsbach, T., Schmidt, S. <em>et al.</em>
|
||||
<cite>Advancements in biotransformation pathway prediction: enhancements, datasets, and novel
|
||||
functionalities in enviPath.</cite>
|
||||
<a href="https://doi.org/10.1186/s13321-024-00881-6" target="_blank">J Cheminform 16, 93
|
||||
(2024)</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
Wicker, J., Lorsbach, T., Gütlein, M., Schmid, E., Latino, D., Kramer, S., Fenner, K.
|
||||
<cite>enviPath - The environmental contaminant biotransformation pathway resource</cite>
|
||||
<a href="https://doi.org/10.1093/nar/gkv1229" target="_blank">
|
||||
Nucleic Acids Research, Volume 44, Issue D1, 4 January 2016, Pages D502-D508
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,85 +1,42 @@
|
||||
<dialog
|
||||
id="import_legacy_package_modal"
|
||||
class="modal"
|
||||
x-data="modalForm()"
|
||||
@close="reset()"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<!-- Header -->
|
||||
<h3 class="text-lg font-bold">Import Package from Legacy System</h3>
|
||||
|
||||
<!-- Close button (X) -->
|
||||
<form method="dialog">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
✕
|
||||
<div class="modal fade" tabindex="-1" id="import_legacy_package_modal" role="dialog"
|
||||
aria-labelledby="import_legacy_package_modal" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="py-4">
|
||||
<p class="mb-4">
|
||||
Create a Package based on the JSON Export of the legacy system.
|
||||
</p>
|
||||
<form
|
||||
id="import-legacy-package-modal-form"
|
||||
accept-charset="UTF-8"
|
||||
method="post"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<h4 class="modal-title">Import Package from legacy System</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Create a Package based on the JSON Export of the legacy system.</p>
|
||||
<form id="import-legacy-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post"
|
||||
enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Legacy JSON File</span>
|
||||
<p>
|
||||
<label class="btn btn-primary" for="legacyJsonFile">
|
||||
<input id="legacyJsonFile" name="file" type="file" style="display:none;"
|
||||
onchange="$('#upload-legacy-file-info').html(this.files[0].name)">
|
||||
Choose JSON File
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="legacyJsonFile"
|
||||
name="file"
|
||||
class="file-input file-input-bordered w-full"
|
||||
accept=".json"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="hidden"
|
||||
value="import-legacy-package-json"
|
||||
name="hidden"
|
||||
readonly
|
||||
/>
|
||||
<span class="label label-info" id="upload-legacy-file-info"></span>
|
||||
<input type="hidden" value="import-legacy-package-json" name="hidden" readonly="">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-action">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@click="submit('import-legacy-package-modal-form')"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span x-show="!isSubmitting">Submit</span>
|
||||
<span
|
||||
x-show="isSubmitting"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
<span x-show="isSubmitting">Importing...</span>
|
||||
</button>
|
||||
<div class="modal-footer">
|
||||
<a id="import-legacy-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button :disabled="isSubmitting">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
$('#import-legacy-package-modal-form-submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#import-legacy-package-modal-form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,83 +1,42 @@
|
||||
<dialog
|
||||
id="import_package_modal"
|
||||
class="modal"
|
||||
x-data="modalForm()"
|
||||
@close="reset()"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<!-- Header -->
|
||||
<h3 class="text-lg font-bold">Import Package</h3>
|
||||
|
||||
<!-- Close button (X) -->
|
||||
<form method="dialog">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
✕
|
||||
<div class="modal fade" tabindex="-1" id="import_package_modal" role="dialog"
|
||||
aria-labelledby="import_package_modal" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="py-4">
|
||||
<p class="mb-4">Create a Package based on a JSON Export.</p>
|
||||
<form
|
||||
id="import-package-modal-form"
|
||||
accept-charset="UTF-8"
|
||||
method="post"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<h4 class="modal-title">Import Package</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Create a Package based on a JSON Export.</p>
|
||||
<form id="import-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post"
|
||||
enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">JSON File</span>
|
||||
<p>
|
||||
<label class="btn btn-primary" for="jsonFile">
|
||||
<input id="jsonFile" name="file" type="file" style="display:none;"
|
||||
onchange="$('#upload-file-info').html(this.files[0].name)">
|
||||
Choose JSON File
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="jsonFile"
|
||||
name="file"
|
||||
class="file-input file-input-bordered w-full"
|
||||
accept=".json"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="hidden"
|
||||
value="import-package-json"
|
||||
name="hidden"
|
||||
readonly
|
||||
/>
|
||||
<span class="label label-info" id="upload-file-info"></span>
|
||||
<input type="hidden" value="import-package-json" name="hidden" readonly="">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-action">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@click="submit('import-package-modal-form')"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span x-show="!isSubmitting">Submit</span>
|
||||
<span
|
||||
x-show="isSubmitting"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
<span x-show="isSubmitting">Importing...</span>
|
||||
</button>
|
||||
<div class="modal-footer">
|
||||
<a id="import-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button :disabled="isSubmitting">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
$('#import-package-modal-form-submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#import-package-modal-form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,137 +1,78 @@
|
||||
{% load static %}
|
||||
|
||||
<dialog
|
||||
id="new_compound_modal"
|
||||
class="modal"
|
||||
x-data="modalForm()"
|
||||
@close="reset()"
|
||||
>
|
||||
<div class="modal-box max-w-3xl">
|
||||
<!-- Header -->
|
||||
<h3 class="text-lg font-bold">Create a new Compound</h3>
|
||||
|
||||
<!-- Close button (X) -->
|
||||
<form method="dialog">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
✕
|
||||
<div class="modal fade bs-modal-lg" id="new_compound_modal" tabindex="-1" aria-labelledby="new_compound_modal" aria-modal="true"
|
||||
role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="py-4">
|
||||
<form
|
||||
id="new-compound-modal-form"
|
||||
accept-charset="UTF-8"
|
||||
action="{% url 'package compound list' meta.current_package.uuid %}"
|
||||
method="post"
|
||||
>
|
||||
<h4 class="modal-title">Create a new Compound</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="new_compound_modal_form" accept-charset="UTF-8" action="{% url 'package compound list' meta.current_package.uuid %}" data-remote="true" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="compound-name">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
id="compound-name"
|
||||
class="input input-bordered w-full"
|
||||
name="compound-name"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="compound-description">
|
||||
<span class="label-text">Description</span>
|
||||
</label>
|
||||
<input
|
||||
id="compound-description"
|
||||
class="input input-bordered w-full"
|
||||
name="compound-description"
|
||||
placeholder="Description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="compound-smiles">
|
||||
<span class="label-text">SMILES</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
name="compound-smiles"
|
||||
placeholder="SMILES"
|
||||
id="compound-smiles"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<iframe
|
||||
id="new_compound_ketcher"
|
||||
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||
width="100%"
|
||||
height="510"
|
||||
></iframe>
|
||||
<label for="compound-name">Name</label>
|
||||
<input id="compound-name" class="form-control" name="compound-name" placeholder="Name"/>
|
||||
<label for="compound-description">Description</label>
|
||||
<input id="compound-description" class="form-control" name="compound-description" placeholder="Description"/>
|
||||
<label for="compound-smiles">SMILES</label>
|
||||
<input type="text" class="form-control" name="compound-smiles" placeholder="SMILES" id="compound-smiles">
|
||||
<p></p>
|
||||
<div>
|
||||
<iframe id="new_compound_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
|
||||
height="510"></iframe>
|
||||
</div>
|
||||
<p></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-action">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@click="submit('new-compound-modal-form')"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span x-show="!isSubmitting">Submit</span>
|
||||
<span
|
||||
x-show="isSubmitting"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
<span x-show="isSubmitting">Creating...</span>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="new_compound_modal_form_submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button :disabled="isSubmitting">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
document
|
||||
.getElementById("new_compound_ketcher")
|
||||
.addEventListener("load", function () {
|
||||
const iframe = this;
|
||||
|
||||
function newCompoundModalketcherToNewCompoundModalTextInput() {
|
||||
$('#compound-smiles').val(this.ketcher.getSmiles());
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
||||
$('#new_compound_ketcher').on('load', function() {
|
||||
const checkKetcherReady = () => {
|
||||
const win = iframe.contentWindow;
|
||||
if (win.ketcher && "editor" in win.ketcher) {
|
||||
win = this.contentWindow
|
||||
if (win.ketcher && 'editor' in win.ketcher) {
|
||||
win.ketcher.editor.event.change.handlers.push({
|
||||
once: false,
|
||||
priority: 0,
|
||||
f: function () {
|
||||
document.getElementById("compound-smiles").value =
|
||||
this.ketcher.getSmiles();
|
||||
},
|
||||
ketcher: win.ketcher,
|
||||
f: newCompoundModalketcherToNewCompoundModalTextInput,
|
||||
ketcher: win.ketcher
|
||||
});
|
||||
} else {
|
||||
setTimeout(checkKetcherReady, 100);
|
||||
}
|
||||
};
|
||||
|
||||
checkKetcherReady();
|
||||
})
|
||||
|
||||
$(function() {
|
||||
$('#new_compound_modal_form_submit').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$(this).prop("disabled",true);
|
||||
|
||||
|
||||
|
||||
// submit form
|
||||
$('#new_compound_modal_form').submit();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@ -1,96 +1,45 @@
|
||||
{% load static %}
|
||||
|
||||
<dialog
|
||||
id="new_group_modal"
|
||||
class="modal"
|
||||
x-data="modalForm()"
|
||||
@close="reset()"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<!-- Header -->
|
||||
<h3 class="font-bold text-lg">New Group</h3>
|
||||
|
||||
<!-- Close button (X) -->
|
||||
<form method="dialog">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
✕
|
||||
<div class="modal fade" tabindex="-1" id="new_group_modal" role="dialog" aria-labelledby="new_group_modal"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="py-4">
|
||||
<p class="mb-4">
|
||||
Create new Group. You can assign users to the group once it is created.
|
||||
Description can be changed after creation.
|
||||
</p>
|
||||
|
||||
<form
|
||||
id="new-group-modal-form"
|
||||
accept-charset="UTF-8"
|
||||
action="{{ SERVER_BASE }}/group"
|
||||
method="post"
|
||||
>
|
||||
<h4 class="modal-title">New Group</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Create new Group. You can assign users to the group once
|
||||
it is created. Description can be changed after creation.</p>
|
||||
<form id="new_group_modal_form" accept-charset="UTF-8" action="{{ SERVER_BASE }}/group"
|
||||
data-remote="true"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="group-name">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
id="group-name"
|
||||
class="input input-bordered w-full"
|
||||
name="group-name"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="group-description">
|
||||
<span class="label-text">Description</span>
|
||||
</label>
|
||||
<input
|
||||
id="group-description"
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Description..."
|
||||
name="group-description"
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
<label for="name">Name</label>
|
||||
<input id="name" type="text" name="group-name" class="form-control" placeholder="Name"/>
|
||||
</p>
|
||||
<p>
|
||||
<label for="description">Description</label>
|
||||
<input id="description" type="text" class="form-control" placeholder="Description..."
|
||||
name="group-description"/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-action">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<div class="modal-footer">
|
||||
<a id="new_group_modal_form_submit" class="btn btn-primary" href="#">Submit</a>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@click="submit('new-group-modal-form')"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span x-show="!isSubmitting">Submit</span>
|
||||
<span
|
||||
x-show="isSubmitting"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
<span x-show="isSubmitting">Creating...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button :disabled="isSubmitting">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function() {
|
||||
$('#new_group_modal_form_submit').on('click', function() {
|
||||
$('#new_group_modal_form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,337 +1,187 @@
|
||||
<dialog
|
||||
id="new_model_modal"
|
||||
class="modal"
|
||||
x-data="{
|
||||
isSubmitting: false,
|
||||
modelType: '',
|
||||
buildAppDomain: false,
|
||||
|
||||
reset() {
|
||||
this.isSubmitting = false;
|
||||
this.modelType = '';
|
||||
this.buildAppDomain = false;
|
||||
},
|
||||
|
||||
get showMlrr() {
|
||||
return this.modelType === 'mlrr';
|
||||
},
|
||||
|
||||
get showRbrr() {
|
||||
return this.modelType === 'rbrr';
|
||||
},
|
||||
|
||||
get showEnviformer() {
|
||||
return this.modelType === 'enviformer';
|
||||
},
|
||||
|
||||
submit(formId) {
|
||||
const form = document.getElementById(formId);
|
||||
if (form && form.checkValidity()) {
|
||||
this.isSubmitting = true;
|
||||
form.submit();
|
||||
} else if (form) {
|
||||
form.reportValidity();
|
||||
}
|
||||
}
|
||||
}"
|
||||
@close="reset()"
|
||||
>
|
||||
<div class="modal-box max-w-3xl">
|
||||
<!-- Header -->
|
||||
<h3 class="text-lg font-bold">New Model</h3>
|
||||
|
||||
<!-- Close button (X) -->
|
||||
<form method="dialog">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
✕
|
||||
<div class="modal fade" tabindex="-1" id="new_model_modal" role="dialog" aria-labelledby="new_model_modal"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="py-4">
|
||||
<form
|
||||
id="new_model_form"
|
||||
accept-charset="UTF-8"
|
||||
action="{{ meta.current_package.url }}/model"
|
||||
method="post"
|
||||
>
|
||||
<h4 class="modal-title">New Model</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="new_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model"
|
||||
data-remote="true" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="alert alert-info mb-4">
|
||||
<span>
|
||||
Create a new Model to limit the number of degradation products in
|
||||
the prediction. You just need to set a name and the packages you
|
||||
want the object to be based on. There are multiple types of models
|
||||
available. For additional information have a look at our
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://wiki.envipath.org/index.php/relative-reasoning"
|
||||
class="link"
|
||||
>wiki >></a
|
||||
>
|
||||
</span>
|
||||
<div class="jumbotron">Create a new Model to
|
||||
limit the number of degradation products in the
|
||||
prediction. You just need to set a name and the packages
|
||||
you want the object to be based on. There are multiple types of models available.
|
||||
For additional information have a look at our
|
||||
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki
|
||||
>></a>
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="model-name">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
id="model-name"
|
||||
name="model-name"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<label for="model-name">Name</label>
|
||||
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="model-description">
|
||||
<span class="label-text">Description</span>
|
||||
</label>
|
||||
<input
|
||||
id="model-description"
|
||||
name="model-description"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Description"
|
||||
/>
|
||||
</div>
|
||||
<label for="model-description">Description</label>
|
||||
<input id="model-description" name="model-description" class="form-control"
|
||||
placeholder="Description"/>
|
||||
|
||||
<!-- Model Type -->
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="model-type">
|
||||
<span class="label-text">Model Type</span>
|
||||
</label>
|
||||
<select
|
||||
id="model-type"
|
||||
name="model-type"
|
||||
class="select select-bordered w-full"
|
||||
x-model="modelType"
|
||||
required
|
||||
>
|
||||
<option value="" disabled selected>Select Model Type</option>
|
||||
<label for="model-type">Model Type</label>
|
||||
<select id="model-type" name="model-type" class="form-control" data-width='100%'>
|
||||
<option disabled selected>Select Model Type</option>
|
||||
{% for k, v in model_types.items %}
|
||||
<option value="{{ v }}">{{ k }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Rule Packages (MLRR, RBRR) -->
|
||||
<div class="form-control mb-3" x-show="showMlrr || showRbrr" x-cloak>
|
||||
<label class="label" for="model-rule-packages">
|
||||
<span class="label-text">Rule Packages</span>
|
||||
</label>
|
||||
<select
|
||||
id="model-rule-packages"
|
||||
name="model-rule-packages"
|
||||
class="select select-bordered w-full h-32"
|
||||
multiple
|
||||
>
|
||||
<optgroup label="Reviewed Packages">
|
||||
<!-- Rule Packages -->
|
||||
<div id="rule-packages" class="ep-model-param mlrr rbrr">
|
||||
<label for="model-rule-packages">Rule Packages</label>
|
||||
<select id="model-rule-packages" name="model-rule-packages" data-actions-box='true'
|
||||
class="form-control" multiple data-width='100%'>
|
||||
<option disabled>Reviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
<optgroup label="Unreviewed Packages">
|
||||
|
||||
<option disabled>Unreviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if not obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
</select>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Data Packages (MLRR, RBRR, Enviformer) -->
|
||||
<div
|
||||
class="form-control mb-3"
|
||||
x-show="showMlrr || showRbrr || showEnviformer"
|
||||
x-cloak
|
||||
>
|
||||
<label class="label" for="model-data-packages">
|
||||
<span class="label-text">Data Packages</span>
|
||||
</label>
|
||||
<select
|
||||
id="model-data-packages"
|
||||
name="model-data-packages"
|
||||
class="select select-bordered w-full h-32"
|
||||
multiple
|
||||
>
|
||||
<optgroup label="Reviewed Packages">
|
||||
<!-- Data Packages -->
|
||||
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
|
||||
<label for="model-data-packages">Data Packages</label>
|
||||
<select id="model-data-packages" name="model-data-packages" data-actions-box='true'
|
||||
class="form-control" multiple data-width='100%'>
|
||||
<option disabled>Reviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
<optgroup label="Unreviewed Packages">
|
||||
|
||||
<option disabled>Unreviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if not obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
</select>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Fingerprinter (MLRR) -->
|
||||
<div class="form-control mb-3" x-show="showMlrr" x-cloak>
|
||||
<label class="label" for="model-fingerprinter">
|
||||
<span class="label-text">Fingerprinter</span>
|
||||
</label>
|
||||
<select
|
||||
id="model-fingerprinter"
|
||||
name="model-fingerprinter"
|
||||
class="select select-bordered w-full h-32"
|
||||
multiple
|
||||
>
|
||||
<!-- Fingerprinter -->
|
||||
<div id="fingerprinter" class="ep-model-param mlrr">
|
||||
<label for="model-fingerprinter">Fingerprinter</label>
|
||||
<select id="model-fingerprinter" name="model-fingerprinter" data-actions-box='true'
|
||||
class="form-control" multiple data-width='100%'>
|
||||
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
||||
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
||||
<optgroup label="Additional Fingerprinter / Descriptor">
|
||||
<option disabled selected>Select Additional Fingerprinter / Descriptor</option>
|
||||
{% for k, v in additional_descriptors.items %}
|
||||
<option value="{{ v }}">{{ k }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
</select>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Threshold (MLRR, Enviformer) -->
|
||||
<div
|
||||
class="form-control mb-3"
|
||||
x-show="showMlrr || showEnviformer"
|
||||
x-cloak
|
||||
>
|
||||
<label class="label" for="model-threshold">
|
||||
<span class="label-text">Threshold</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
value="0.5"
|
||||
id="model-threshold"
|
||||
name="model-threshold"
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
<!-- Threshold -->
|
||||
<div id="threshold" class="ep-model-param mlrr enviformer">
|
||||
<label for="model-threshold">Threshold</label>
|
||||
<input type="number" min="0" max="1" step="0.05" value="0.5" id="model-threshold"
|
||||
name="model-threshold" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Applicability Domain (MLRR) -->
|
||||
<div id="appdomain" class="ep-model-param mlrr">
|
||||
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
|
||||
<div x-show="showMlrr" x-cloak>
|
||||
<div class="form-control mb-3">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="build-app-domain"
|
||||
name="build-app-domain"
|
||||
class="checkbox"
|
||||
x-model="buildAppDomain"
|
||||
/>
|
||||
<span class="label-text"
|
||||
>Also build an Applicability Domain?</span
|
||||
>
|
||||
<!-- Build AD? -->
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="build-app-domain" name="build-app-domain">Also build an
|
||||
Applicability Domain?
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div x-show="buildAppDomain" x-cloak class="ml-4 space-y-3">
|
||||
<div class="form-control">
|
||||
<label class="label" for="num-neighbors">
|
||||
<span class="label-text">Number of Neighbors</span>
|
||||
</label>
|
||||
<input
|
||||
id="num-neighbors"
|
||||
name="num-neighbors"
|
||||
type="number"
|
||||
class="input input-bordered w-full"
|
||||
<div id="ad-params" style="display:none">
|
||||
<!-- Num Neighbors -->
|
||||
<label for="num-neighbors">Number of Neighbors</label>
|
||||
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control"
|
||||
value="5"
|
||||
step="1"
|
||||
min="0"
|
||||
max="10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label" for="local-compatibility-threshold">
|
||||
<span class="label-text">Local Compatibility Threshold</span>
|
||||
</label>
|
||||
<input
|
||||
id="local-compatibility-threshold"
|
||||
name="local-compatibility-threshold"
|
||||
step="1" min="0" max="10">
|
||||
<!-- Local Compatibility -->
|
||||
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
|
||||
<input id="local-compatibility-threshold" name="local-compatibility-threshold"
|
||||
type="number"
|
||||
class="input input-bordered w-full"
|
||||
value="0.5"
|
||||
step="0.01"
|
||||
min="0"
|
||||
max="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label" for="reliability-threshold">
|
||||
<span class="label-text">Reliability Threshold</span>
|
||||
</label>
|
||||
<input
|
||||
id="reliability-threshold"
|
||||
name="reliability-threshold"
|
||||
type="number"
|
||||
class="input input-bordered w-full"
|
||||
value="0.5"
|
||||
step="0.01"
|
||||
min="0"
|
||||
max="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
class="form-control" value="0.5" step="0.01" min="0" max="1">
|
||||
<!-- Reliability -->
|
||||
<label for="reliability-threshold">Reliability Threshold</label>
|
||||
<input id="reliability-threshold" name="reliability-threshold" type="number"
|
||||
class="form-control" value="0.5" step="0.01" min="0" max="1">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-action">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@click="submit('new_model_form')"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span x-show="!isSubmitting">Submit</span>
|
||||
<span
|
||||
x-show="isSubmitting"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
<span x-show="isSubmitting">Creating...</span>
|
||||
</button>
|
||||
<div class="modal-footer">
|
||||
<a id="new_model_modal_form_submit" class="btn btn-primary" href="#">Submit</a>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button :disabled="isSubmitting">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<script>
|
||||
$(function () {
|
||||
// Built in Model Types
|
||||
var nativeModelTypes = [
|
||||
"mlrr",
|
||||
"rbrr",
|
||||
"enviformer",
|
||||
]
|
||||
|
||||
// Initially hide all "specific" forms
|
||||
$(".ep-model-param").each(function () {
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
$('#model-type').selectpicker();
|
||||
$("#model-fingerprinter").selectpicker();
|
||||
$("#model-rule-packages").selectpicker();
|
||||
$("#model-data-packages").selectpicker();
|
||||
|
||||
$("#build-app-domain").change(function () {
|
||||
if ($(this).is(":checked")) {
|
||||
$('#ad-params').show();
|
||||
} else {
|
||||
$('#ad-params').hide();
|
||||
}
|
||||
});
|
||||
|
||||
// On change hide all and show only selected
|
||||
$("#model-type").change(function () {
|
||||
$('.ep-model-param').hide();
|
||||
var modelType = $('#model-type').val();
|
||||
if (nativeModelTypes.indexOf(modelType) !== -1) {
|
||||
$('.' + modelType).show();
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
|
||||
$('#new_model_modal_form_submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#new_model_form').submit();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,93 +1,62 @@
|
||||
{% load static %}
|
||||
|
||||
<dialog
|
||||
<div class="modal fade"
|
||||
tabindex="-1"
|
||||
id="new_package_modal"
|
||||
class="modal"
|
||||
x-data="modalForm()"
|
||||
@close="reset()"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<!-- Header -->
|
||||
<h3 class="font-bold text-lg">New Package</h3>
|
||||
|
||||
<!-- Close button (X) -->
|
||||
<form method="dialog">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
✕
|
||||
role="dialog"
|
||||
aria-labelledby="new_package_modal"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button"
|
||||
class="close"
|
||||
data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="py-4">
|
||||
<p class="mb-4">Create new package. Description can be changed later.</p>
|
||||
|
||||
<form
|
||||
id="new-package-modal-form"
|
||||
<h4 class="modal-title">New Package</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Create new package. Description can be changed later.</p>
|
||||
<form id="new_package_modal_form"
|
||||
accept-charset="UTF-8"
|
||||
action=""
|
||||
method="post"
|
||||
>
|
||||
data-remote="true"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="package-name">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
id="package-name"
|
||||
class="input input-bordered w-full"
|
||||
<p>
|
||||
<label for="name">Name</label>
|
||||
<input id="name" class="form-control"
|
||||
name="package-name"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="package-description">
|
||||
<span class="label-text">Description</span>
|
||||
</label>
|
||||
<input
|
||||
id="package-description"
|
||||
placeholder="Name"/>
|
||||
</p>
|
||||
<p>
|
||||
<label for="description">Description</label>
|
||||
<input id="description"
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
rows="3"
|
||||
class="form-control"
|
||||
placeholder="Description..."
|
||||
name="package-description"
|
||||
/>
|
||||
</div>
|
||||
name="package-description"/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-action">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
<div class="modal-footer">
|
||||
<a id="new_package_modal_form_submit"
|
||||
class="btn btn-primary"
|
||||
@click="submit('new-package-modal-form')"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span x-show="!isSubmitting">Submit</span>
|
||||
<span
|
||||
x-show="isSubmitting"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
<span x-show="isSubmitting">Creating...</span>
|
||||
href="#">Submit</a>
|
||||
<button type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="modal">Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button :disabled="isSubmitting">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function() {
|
||||
$('#new_package_modal_form_submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#new_package_modal_form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
301
templates/modals/collections/new_pathway_modal.html
Normal file
@ -0,0 +1,301 @@
|
||||
{% load static %}
|
||||
<div class="modal fade" tabindex="-1" id="new_pathway_modal" role="dialog" aria-labelledby="new_pathway_modal"
|
||||
aria-hidden="true" style="overflow-y: auto;">
|
||||
<!-- FIXME: make width dynamic-->
|
||||
<div class="modal-dialog" id="new_pathway_modal_dialog" style="width:900px">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span> <span class="sr-only">Close</span>
|
||||
</button>
|
||||
<h4 class="js-title-step"></h4>
|
||||
</div>
|
||||
<div class="modal-body hide" data-step="1" data-title="New Pathway">
|
||||
<div class="jumbotron">Create a new pathway by entering
|
||||
the root compound and a name. Then select if you want to
|
||||
use the prediction engine to generate a predicted pathway or
|
||||
create an empty pathway that you fill in by yourself. If
|
||||
you choose to predict a pathway, you can modify the
|
||||
settings for the prediction, or use the default settings
|
||||
and just click Submit.
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if current_user.name == 'anonymous' %}
|
||||
<div class="alert alert-warning">
|
||||
You are currently logged in as Anonymous. Please note:
|
||||
Pathways entered or predicted as anonymous user will be deleted after 30 days.
|
||||
Please log in to save your results.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="name">Name</label>
|
||||
<input id="name" class="form-control" name="name" placeholder="Name"/>
|
||||
<label for="description">Description</label>
|
||||
<input id="description" class="form-control" name="description" placeholder="no description"/>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="predict">Predict pathway or build yourself?</label>
|
||||
<div class="radio" id="predict">
|
||||
<p>
|
||||
<label>
|
||||
<input type="radio" name="predict" id="radioPredict" value="predict" checked/>Predict pathway
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="radio" name="predict" id="radioIncremental"value="incremental"/>Incremental prediction
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="radio" name="predict" id="radioBuild" value="build"/>Build pathway
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="smilesinput">SMILES</label>
|
||||
<table style="width: 100%">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 90%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
</colgroup>
|
||||
<tr>
|
||||
<td>
|
||||
<input id="smilesinput" class="form-control" name="smilesinput" placeholder="C1CCCCC1"
|
||||
autocapitalize="none"/>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-default" id="render-button">
|
||||
Render
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p id="ketcher_container"></p>
|
||||
<div>
|
||||
<iframe id="ifKetcher" src="{% static '/js/ketcher/ketcher.html' %}" width="850"
|
||||
height="510"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body hide" data-step="2" data-title="New Pathway - Advanced Settings">
|
||||
<div class="jumbotron">Choose if you want to use an existing
|
||||
setting, or create a new one for this pathway
|
||||
prediction. Then click Submit to use the specified setting,
|
||||
or click next to set the parameters.
|
||||
</div>
|
||||
<div id="settings">
|
||||
<div class="radio" id="settingRadio">
|
||||
<p>
|
||||
<label>
|
||||
<input type="radio" name="existing" id="radioDefault" value="exisiting" checked/>
|
||||
Use Default
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="radio" name="existing" id="radioExists" value="exisiting"/>
|
||||
Select Existing
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="radio" name="existing" id="radioNew" value="temporary"/>
|
||||
Create New
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<select id="settingSelect" name="settingSelect" class="form-control">
|
||||
{% for setting in available_settings %}
|
||||
<option value="{{ setting.id }}">{{ setting.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
{% with step_offset=1 %}
|
||||
{% include "templates/modals/collections/new_setting_modal_body.html" %}
|
||||
{% endwith %}
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default js-btn-step pull-left" data-orientation="cancel"
|
||||
onclick="reset()" data-dismiss="modal"></button>
|
||||
<button type="button" class="btn btn-default js-btn-step" data-orientation="previous"
|
||||
id="backbutton"></button>
|
||||
<button type="button" class="btn btn-default js-btn-step" data-orientation="next"
|
||||
id="nextbutton"></button>
|
||||
<a id="modal-form-submit" class="btn btn-primary" href="#">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
s = new Setting(
|
||||
'settingName',
|
||||
'package_multi_select',
|
||||
'modelSelect',
|
||||
'cutoff',
|
||||
'evalType',
|
||||
'availableTS',
|
||||
'forms',
|
||||
'truncatorTable',
|
||||
'summaryTable',
|
||||
);
|
||||
|
||||
$(function() {
|
||||
// hide all forms
|
||||
$('#forms').children().hide()
|
||||
|
||||
$("#render-button").on("click", function() {
|
||||
syncKetcherAndTextInput('text', "ifKetcher", "smilesinput");
|
||||
});
|
||||
|
||||
// If theres a change in the in '#smilesinput' sync the value to ketcher
|
||||
$('#smilesinput').on('input', function() {
|
||||
syncKetcherAndTextInput('text', 'ifKetcher', 'smilesinput');
|
||||
});
|
||||
|
||||
// If theres an update in ketcher sync it to textinput
|
||||
setInterval(function() {
|
||||
syncKetcherAndTextInput('ketcher', 'ifKetcher', 'smilesinput');
|
||||
}, 250);
|
||||
|
||||
$("#smilesinput").on("blur", function() {
|
||||
syncKetcherAndTextInput('text', 'ifKetcher', 'smilesinput');
|
||||
});
|
||||
|
||||
$("#smilesinput").on("keypress", function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
syncKetcherAndTextInput('text', 'ifKetcher', 'smilesinput');
|
||||
}
|
||||
});
|
||||
|
||||
// Show forms depending on the selected TS
|
||||
$('#availableTS').on('change', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var type = $(this).val();
|
||||
// hide current content
|
||||
$('#forms').children().hide()
|
||||
|
||||
if(type === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#' + type + '_form').show()
|
||||
});
|
||||
|
||||
$("#modelSelect").on("change", function() {
|
||||
setCutoff = function (thresh) {
|
||||
$("#cutoff").val(thresh);
|
||||
}
|
||||
|
||||
var modelUri = $("#modelSelect :selected").val();
|
||||
fillPRCurve(modelUri, setCutoff);
|
||||
});
|
||||
|
||||
// Add a TS to the setting
|
||||
$('#add-ts-button').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
s.addTruncator();
|
||||
});
|
||||
|
||||
$('input[type=radio][name=predict]').change(function() {
|
||||
if (this.id == 'radioBuild') {
|
||||
$("#nextbutton").prop("disabled", true);
|
||||
} else {
|
||||
$("#nextbutton").prop("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
$('input[type=radio][name=existing]').change(function() {
|
||||
if (this.id == 'radioDefault' || this.id == 'radioExists') {
|
||||
if(this.id == 'radioDefault') {
|
||||
$("#settingSelect").prop("disabled", true);
|
||||
} else {
|
||||
$("#settingSelect").prop("disabled", false);
|
||||
}
|
||||
$("#nextbutton").prop("disabled", true);
|
||||
} else {
|
||||
// build...
|
||||
$("#settingSelect").prop("disabled", true);
|
||||
$("#nextbutton").prop("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
var pwStep1 = function() {
|
||||
console.log("pw step 1");
|
||||
// Make "Next" to "Advanced"
|
||||
$('#nextbutton').val("Advanced");
|
||||
}
|
||||
|
||||
var pwStep2 = function() {
|
||||
console.log("pw step 2");
|
||||
// Make "Advanced" to "Next"
|
||||
$('#nextbutton').val("Next");
|
||||
// As "Use default is preselected" disable "Next" button
|
||||
$("#nextbutton").prop("disabled",true);
|
||||
// Disable setting dropdown as long as the correspndonding radio isnt checked
|
||||
$("#settingSelect").prop("disabled",true);
|
||||
// Show submit button
|
||||
$("#modal-form-submit").show();
|
||||
}
|
||||
|
||||
var settingStep1 = function (){
|
||||
// First step sets name and packages
|
||||
s.extractName();
|
||||
s.extractSelectedPackages();
|
||||
}
|
||||
|
||||
var settingStep2 = function (){
|
||||
// Seconds step gathers relative reasoning params
|
||||
s.extractRelativeReasoning();
|
||||
s.extractCutoff();
|
||||
s.extractEvaluationType();
|
||||
}
|
||||
|
||||
var settingStep3 = function() {
|
||||
s.updateTable();
|
||||
s.updateSummaryTable();
|
||||
// hide duplicate submit...
|
||||
$("#nextbutton").hide();
|
||||
}
|
||||
|
||||
var postPathway = function(){
|
||||
console.log("Complete!");
|
||||
console.log(s.tsParams);
|
||||
console.log("Getting SMILES");
|
||||
}
|
||||
|
||||
function dummy() {
|
||||
console.log("dummy");
|
||||
}
|
||||
|
||||
$('#new_pathway_modal').modalSteps({
|
||||
btnCancelHtml: 'Cancel',
|
||||
btnPreviousHtml: 'Back',
|
||||
btnNextHtml: 'Next',
|
||||
btnLastStepHtml: 'Submit',
|
||||
disableNextButton: false,
|
||||
completeCallback: postPathway,
|
||||
callbacks: {
|
||||
'1': pwStep1,
|
||||
'2' : pwStep2,
|
||||
'3' : dummy,
|
||||
'4' : settingStep1,
|
||||
'5' : settingStep2,
|
||||
'6' : settingStep3,
|
||||
}
|
||||
});
|
||||
|
||||
$('#modal-form-submit').on('click', function() {
|
||||
e.preventDefault();
|
||||
postPathway();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
@ -1,260 +1,108 @@
|
||||
{% load static %}
|
||||
|
||||
<dialog
|
||||
id="new_prediction_setting_modal"
|
||||
class="modal"
|
||||
x-data="{
|
||||
isSubmitting: false,
|
||||
tpMethod: '',
|
||||
|
||||
reset() {
|
||||
this.isSubmitting = false;
|
||||
this.tpMethod = '';
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const form = document.getElementById('new-prediction-setting-modal-form');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSubmitting = true;
|
||||
const formData = new FormData(form);
|
||||
|
||||
try {
|
||||
const response = await fetch('/setting', {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams(formData)
|
||||
});
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating setting:', error);
|
||||
} finally {
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}"
|
||||
@close="reset()"
|
||||
>
|
||||
<div class="modal-box max-w-2xl">
|
||||
<!-- Header -->
|
||||
<h3 class="text-lg font-bold">Create a Prediction Setting</h3>
|
||||
|
||||
<!-- Close button (X) -->
|
||||
<form method="dialog">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
✕
|
||||
<div id="new_prediction_setting_modal" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Create a Prediction Setting</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="py-4">
|
||||
<p class="mb-4">
|
||||
To create a Prediction Setting fill the form below and click "Create"
|
||||
</p>
|
||||
<form
|
||||
id="new-prediction-setting-modal-form"
|
||||
accept-charset="UTF-8"
|
||||
action=""
|
||||
method="post"
|
||||
>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>To create a Prediction Setting fill the form below and click "Create"</p>
|
||||
<form id="new-prediction-setting-modal-form" accept-charset="UTF-8" action="" data-remote="true"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="prediction-setting-name">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
id="prediction-setting-name"
|
||||
name="prediction-setting-name"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<label for="prediction-setting-name">Name</label>
|
||||
<input id="prediction-setting-name" name="prediction-setting-name" class="form-control" placeholder="Name"/>
|
||||
<label for="prediction-setting-description">Description</label>
|
||||
<input id="prediction-setting-description" name="prediction-setting-description" class="form-control"
|
||||
placeholder="Description"/>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="prediction-setting-description">
|
||||
<span class="label-text">Description</span>
|
||||
</label>
|
||||
<input
|
||||
id="prediction-setting-description"
|
||||
name="prediction-setting-description"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Description"
|
||||
/>
|
||||
</div>
|
||||
<label for="prediction-setting-max-nodes">Max #Nodes</label>
|
||||
<input id="prediction-setting-max-nodes" type="number" class="form-control" name="prediction-setting-max-nodes" value="30" min="1" max="50" step="1">
|
||||
<label for="prediction-setting-max-depth">Max Depth</label>
|
||||
<input id="prediction-setting-max-depth" type="number" class="form-control" name="prediction-setting-max-depth" value="5" min="1" max="8" step="1">
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="prediction-setting-max-nodes">
|
||||
<span class="label-text">Max #Nodes</span>
|
||||
</label>
|
||||
<input
|
||||
id="prediction-setting-max-nodes"
|
||||
type="number"
|
||||
class="input input-bordered w-full"
|
||||
name="prediction-setting-max-nodes"
|
||||
value="30"
|
||||
min="1"
|
||||
max="50"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="prediction-setting-max-depth">
|
||||
<span class="label-text">Max Depth</span>
|
||||
</label>
|
||||
<input
|
||||
id="prediction-setting-max-depth"
|
||||
type="number"
|
||||
class="input input-bordered w-full"
|
||||
name="prediction-setting-max-depth"
|
||||
value="5"
|
||||
min="1"
|
||||
max="8"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="tp-generation-method">
|
||||
<span class="label-text">TP Generation Method</span>
|
||||
</label>
|
||||
<select
|
||||
id="tp-generation-method"
|
||||
name="tp-generation-method"
|
||||
class="select select-bordered w-full"
|
||||
x-model="tpMethod"
|
||||
required
|
||||
>
|
||||
<option value="" disabled selected>
|
||||
Select how TPs are generated
|
||||
</option>
|
||||
<label for="tp-generation-method">TP Generation Method</label>
|
||||
<select id="tp-generation-method" name="tp-generation-method" class="form-control" data-width='100%'>
|
||||
<option disabled selected>Select how TPs are generated</option>
|
||||
<option value="rule-based-prediction-setting">Rule Based</option>
|
||||
<option value="model-based-prediction-setting">Model Based</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Rule Based Settings -->
|
||||
<div x-show="tpMethod === 'rule-based-prediction-setting'" x-cloak>
|
||||
<div class="form-control mb-3">
|
||||
<label class="label">
|
||||
<span class="label-text">Rule Packages</span>
|
||||
</label>
|
||||
<select
|
||||
id="rule-based-prediction-setting-packages"
|
||||
name="rule-based-prediction-setting-packages"
|
||||
class="select select-bordered w-full h-32"
|
||||
multiple
|
||||
>
|
||||
<optgroup label="Reviewed Packages">
|
||||
<div id="rule-based-prediction-setting-specific-form">
|
||||
<!-- Rule Packages -->
|
||||
<label>Rule Packages</label><br>
|
||||
<select id="rule-based-prediction-setting-packages" name="rule-based-prediction-setting-packages"
|
||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
||||
<option disabled>Reviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
<optgroup label="Unreviewed Packages">
|
||||
|
||||
<option disabled>Unreviewed Packages</option>
|
||||
{% for obj in meta.readable_packages %}
|
||||
{% if not obj.reviewed %}
|
||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
</select>
|
||||
<label class="label">
|
||||
<span class="label-text-alt"
|
||||
>Hold Ctrl/Cmd to select multiple</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model Based Settings -->
|
||||
<div x-show="tpMethod === 'model-based-prediction-setting'" x-cloak>
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="model-based-prediction-setting-model">
|
||||
<span class="label-text">Select Model</span>
|
||||
</label>
|
||||
<select
|
||||
id="model-based-prediction-setting-model"
|
||||
name="model-based-prediction-setting-model"
|
||||
class="select select-bordered w-full"
|
||||
>
|
||||
<option value="" disabled selected>Select the model</option>
|
||||
<div id="model-based-prediction-setting-specific-form">
|
||||
<label>Select Model</label><br>
|
||||
<select id="model-based-prediction-setting-model" name="model-based-prediction-setting-model" class="form-control" data-width='100%'>
|
||||
<option disabled selected>Select the model</option>
|
||||
{% for m in models %}
|
||||
<option value="{{ m.url }}">{{ m.name|safe }}</option>
|
||||
<option value="{{ m.url }}">{{ m.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="model-based-prediction-setting-threshold">Threshold</label>
|
||||
<input id="model-based-prediction-setting-threshold" name="model-based-prediction-setting-threshold" class="form-control" placeholder="0.25" type="number"/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="model-based-prediction-setting-threshold">
|
||||
<span class="label-text">Threshold</span>
|
||||
</label>
|
||||
<input
|
||||
id="model-based-prediction-setting-threshold"
|
||||
name="model-based-prediction-setting-threshold"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="0.25"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input class="form-check-input" type="checkbox" value="on" id="prediction-setting-new-default" name="prediction-setting-new-default">
|
||||
<label class="form-check-label" for="prediction-setting-new-default">Set this setting as new default</label>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
value="on"
|
||||
id="prediction-setting-new-default"
|
||||
name="prediction-setting-new-default"
|
||||
/>
|
||||
<span class="label-text">Set this setting as new default</span>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-action">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@click="submit()"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<span x-show="!isSubmitting">Create</span>
|
||||
<span
|
||||
x-show="isSubmitting"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
<span x-show="isSubmitting">Creating...</span>
|
||||
</button>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="new-prediction-setting-modal-submit">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function() {
|
||||
|
||||
<!-- Backdrop -->
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button :disabled="isSubmitting">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
// Initially hide all "specific" forms
|
||||
$("div[id$='-specific-form']").each( function() {
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
$("#rule-based-prediction-setting-packages").selectpicker();
|
||||
|
||||
// On change hide all and show only selected
|
||||
$("#tp-generation-method").change(function() {
|
||||
$("div[id$='-specific-form']").each( function() {
|
||||
$(this).hide();
|
||||
});
|
||||
val = $('option:selected', this).val();
|
||||
$("#" + val + "-specific-form").show();
|
||||
});
|
||||
|
||||
$('#new-prediction-setting-modal-submit').click(function(e){
|
||||
e.preventDefault();
|
||||
// $('#new-prediction-setting-modal-form').submit();
|
||||
|
||||
const formData = $('#new-prediction-setting-modal-form').serialize();
|
||||
$.post('/setting', formData, function(response) {
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||