Compare commits
28 Commits
enhancemen
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a2c9bb543 | |||
| 7f6f209b4a | |||
| b6c35fea76 | |||
| fa8a191383 | |||
| 67b1baa5b0 | |||
| 89c194dcca | |||
| a8554c903c | |||
| d584791ee8 | |||
| e60052b05c | |||
| 3ff8d938d6 | |||
| a7f48c2cf9 | |||
| 39faab3d11 | |||
| 4e80cd63cd | |||
| 6592f0a68e | |||
| 21d30a923f | |||
| 12a20756d6 | |||
| d20a705011 | |||
| debbef8158 | |||
| 2799718951 | |||
| 305fdc41fb | |||
| 9deca8867e | |||
| df6056fb86 | |||
| c1553d9cd4 | |||
| 2b79adc2f7 | |||
| ddf1fd3515 | |||
| 34589efbde | |||
| 1cccefa991 | |||
| e26d5a21e3 |
134
.gitea/workflows/ci.yaml
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
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,7 +6,13 @@ static/django_extensions/
|
|||||||
.env
|
.env
|
||||||
debug.log
|
debug.log
|
||||||
scratches/
|
scratches/
|
||||||
|
test-results/
|
||||||
|
|
||||||
data/
|
data/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
static/css/output.css
|
||||||
|
|
||||||
|
*.code-workspace
|
||||||
|
|||||||
@ -8,6 +8,7 @@ repos:
|
|||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
|
exclude: ^static/images/
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.13.3
|
rev: v0.13.3
|
||||||
@ -20,6 +21,15 @@ repos:
|
|||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
types_or: [python, pyi]
|
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
|
# - repo: local
|
||||||
# hooks:
|
# hooks:
|
||||||
# - id: django-check
|
# - id: django-check
|
||||||
|
|||||||
11
.prettierrc.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-jinja-template", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "templates/**/*.html",
|
||||||
|
"options": {
|
||||||
|
"parser": "jinja-template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
22
README.md
@ -7,11 +7,13 @@ These instructions will guide you through setting up the project for local devel
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Python 3.11 or later
|
- Python 3.11 or later
|
||||||
- [uv](https://github.com/astral-sh/uv) - A fast Python package installer and resolver.
|
- [uv](https://github.com/astral-sh/uv) - Python package manager
|
||||||
- **Docker and Docker Compose** - Required for running the PostgreSQL database.
|
- **Docker and Docker Compose** - Required for running PostgreSQL database
|
||||||
- Git
|
- 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
|
### 1. Install Dependencies
|
||||||
|
|
||||||
@ -23,7 +25,12 @@ Then, sync the project dependencies. This will create a virtual environment in `
|
|||||||
uv sync --dev
|
uv sync --dev
|
||||||
```
|
```
|
||||||
|
|
||||||
> **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.
|
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).
|
||||||
|
|
||||||
### 2. Set Up Environment File
|
### 2. Set Up Environment File
|
||||||
|
|
||||||
@ -44,6 +51,7 @@ uv run poe setup
|
|||||||
```
|
```
|
||||||
|
|
||||||
This single command will:
|
This single command will:
|
||||||
|
|
||||||
1. Start the PostgreSQL database using Docker Compose.
|
1. Start the PostgreSQL database using Docker Compose.
|
||||||
2. Run database migrations.
|
2. Run database migrations.
|
||||||
3. Bootstrap initial data (anonymous user, default packages, models).
|
3. Bootstrap initial data (anonymous user, default packages, models).
|
||||||
@ -54,9 +62,12 @@ After setup, start the development server:
|
|||||||
uv run poe dev
|
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`.
|
The application will be available at `http://localhost:8000`.
|
||||||
|
|
||||||
#### Other useful Poe commands:
|
**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
|
||||||
|
|
||||||
You can list all available commands by running `uv run poe --help`.
|
You can list all available commands by running `uv run poe --help`.
|
||||||
|
|
||||||
@ -66,6 +77,7 @@ uv run poe db-down # Stop PostgreSQL
|
|||||||
uv run poe migrate # Run migrations only
|
uv run poe migrate # Run migrations only
|
||||||
uv run poe bootstrap # Bootstrap data only
|
uv run poe bootstrap # Bootstrap data only
|
||||||
uv run poe shell # Open the Django shell
|
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)
|
uv run poe clean # Remove database volumes (WARNING: destroys all data)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -49,9 +49,23 @@ INSTALLED_APPS = [
|
|||||||
"oauth2_provider",
|
"oauth2_provider",
|
||||||
# Custom
|
# Custom
|
||||||
"epdb",
|
"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 = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
"django.contrib.auth.backends.ModelBackend",
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
]
|
]
|
||||||
@ -87,11 +101,14 @@ TEMPLATES = [
|
|||||||
"django.template.context_processors.request",
|
"django.template.context_processors.request",
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
"epdb.context_processors.package_context",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ALLOWED_HTML_TAGS = {"b", "i", "u", "br", "em", "mark", "p", "s", "strong"}
|
||||||
|
|
||||||
WSGI_APPLICATION = "envipath.wsgi.application"
|
WSGI_APPLICATION = "envipath.wsgi.application"
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
@ -243,6 +260,7 @@ LOGGING = {
|
|||||||
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
||||||
ENVIFORMER_DEVICE = os.environ.get("ENVIFORMER_DEVICE", "cpu")
|
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
|
# 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"
|
FLAG_CELERY_PRESENT = os.environ.get("FLAG_CELERY_PRESENT", "False") == "True"
|
||||||
if not FLAG_CELERY_PRESENT:
|
if not FLAG_CELERY_PRESENT:
|
||||||
@ -343,6 +361,14 @@ LOGIN_EXEMPT_URLS = [
|
|||||||
"/password_reset/",
|
"/password_reset/",
|
||||||
"/reset/",
|
"/reset/",
|
||||||
"/microsoft/",
|
"/microsoft/",
|
||||||
|
"/terms",
|
||||||
|
"/privacy",
|
||||||
|
"/cookie-policy",
|
||||||
|
"/about",
|
||||||
|
"/contact",
|
||||||
|
"/jobs",
|
||||||
|
"/cite",
|
||||||
|
"/legal",
|
||||||
]
|
]
|
||||||
|
|
||||||
# MS AD/Entra
|
# MS AD/Entra
|
||||||
|
|||||||
@ -23,12 +23,20 @@ from .api import api_v1, api_legacy
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include("epdb.urls")),
|
path("", include("epdb.urls")),
|
||||||
path("", include("migration.urls")),
|
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/v1/", api_v1.urls),
|
path("api/v1/", api_v1.urls),
|
||||||
path("api/legacy/", api_legacy.urls),
|
path("api/legacy/", api_legacy.urls),
|
||||||
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
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:
|
if s.MS_ENTRA_ENABLED:
|
||||||
urlpatterns.append(path("", include("epauth.urls")))
|
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,28 +1,31 @@
|
|||||||
|
from django.conf import settings as s
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
User,
|
|
||||||
UserPackagePermission,
|
|
||||||
Group,
|
|
||||||
GroupPackagePermission,
|
|
||||||
Package,
|
|
||||||
MLRelativeReasoning,
|
|
||||||
EnviFormer,
|
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
SimpleAmbitRule,
|
|
||||||
ParallelRule,
|
|
||||||
Reaction,
|
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
Edge,
|
Edge,
|
||||||
Scenario,
|
EnviFormer,
|
||||||
Setting,
|
|
||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
ExternalIdentifier,
|
ExternalIdentifier,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
JobLog,
|
JobLog,
|
||||||
|
License,
|
||||||
|
MLRelativeReasoning,
|
||||||
|
Node,
|
||||||
|
ParallelRule,
|
||||||
|
Pathway,
|
||||||
|
Reaction,
|
||||||
|
Scenario,
|
||||||
|
Setting,
|
||||||
|
SimpleAmbitRule,
|
||||||
|
User,
|
||||||
|
UserPackagePermission,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(admin.ModelAdmin):
|
||||||
list_display = ["username", "email", "is_active"]
|
list_display = ["username", "email", "is_active"]
|
||||||
@ -62,6 +65,10 @@ class EnviFormerAdmin(EPAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["cc_string", "link", "image_link"]
|
||||||
|
|
||||||
|
|
||||||
class CompoundAdmin(EPAdmin):
|
class CompoundAdmin(EPAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -118,6 +125,7 @@ admin.site.register(JobLog, JobLogAdmin)
|
|||||||
admin.site.register(Package, PackageAdmin)
|
admin.site.register(Package, PackageAdmin)
|
||||||
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||||
admin.site.register(EnviFormer, EnviFormerAdmin)
|
admin.site.register(EnviFormer, EnviFormerAdmin)
|
||||||
|
admin.site.register(License, LicenseAdmin)
|
||||||
admin.site.register(Compound, CompoundAdmin)
|
admin.site.register(Compound, CompoundAdmin)
|
||||||
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||||
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EPDBConfig(AppConfig):
|
class EPDBConfig(AppConfig):
|
||||||
@ -7,3 +12,6 @@ class EPDBConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import epdb.signals # noqa: F401
|
import epdb.signals # noqa: F401
|
||||||
|
|
||||||
|
model_name = getattr(settings, "EPDB_PACKAGE_MODEL", "epdb.Package")
|
||||||
|
logger.info(f"Using Package model: {model_name}")
|
||||||
|
|||||||
32
epdb/context_processors.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
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,27 +1,35 @@
|
|||||||
from typing import List, Dict, Optional, Any
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
import nh3
|
||||||
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from ninja import Router, Schema, Field, Form
|
from ninja import Field, Form, Router, Schema, Query
|
||||||
|
from ninja.security import SessionAuth
|
||||||
|
|
||||||
from utilities.chem import FormatConverter
|
from utilities.chem import FormatConverter
|
||||||
from .logic import PackageManager, UserManager, SettingManager
|
from utilities.misc import PackageExporter
|
||||||
|
|
||||||
|
from .logic import GroupManager, PackageManager, SettingManager, UserManager, SearchManager
|
||||||
from .models import (
|
from .models import (
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
Package,
|
Edge,
|
||||||
|
EPModel,
|
||||||
|
Node,
|
||||||
|
Pathway,
|
||||||
|
Reaction,
|
||||||
|
Rule,
|
||||||
|
Scenario,
|
||||||
|
SimpleAmbitRule,
|
||||||
User,
|
User,
|
||||||
UserPackagePermission,
|
UserPackagePermission,
|
||||||
Rule,
|
ParallelRule,
|
||||||
Reaction,
|
|
||||||
Scenario,
|
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
Edge,
|
|
||||||
SimpleAmbitRule,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def _anonymous_or_real(request):
|
def _anonymous_or_real(request):
|
||||||
if request.user.is_authenticated and not request.user.is_anonymous:
|
if request.user.is_authenticated and not request.user.is_anonymous:
|
||||||
@ -29,8 +37,7 @@ def _anonymous_or_real(request):
|
|||||||
return get_user_model().objects.get(username="anonymous")
|
return get_user_model().objects.get(username="anonymous")
|
||||||
|
|
||||||
|
|
||||||
# router = Router(auth=SessionAuth())
|
router = Router(auth=SessionAuth(csrf=False))
|
||||||
router = Router()
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Schema):
|
class Error(Schema):
|
||||||
@ -118,13 +125,16 @@ class SimpleEdge(SimpleObject):
|
|||||||
identifier: str = "edge"
|
identifier: str = "edge"
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleModel(SimpleObject):
|
||||||
|
identifier: str = "relative-reasoning"
|
||||||
|
|
||||||
|
|
||||||
################
|
################
|
||||||
# Login/Logout #
|
# Login/Logout #
|
||||||
################
|
################
|
||||||
@router.post("/", response={200: SimpleUser, 403: Error})
|
@router.post("/", response={200: SimpleUser, 403: Error}, auth=None)
|
||||||
def login(request, loginusername: Form[str], loginpassword: Form[str]):
|
def login(request, loginusername: Form[str], loginpassword: Form[str]):
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth import login
|
|
||||||
|
|
||||||
email = User.objects.get(username=loginusername).email
|
email = User.objects.get(username=loginusername).email
|
||||||
user = authenticate(username=email, password=loginpassword)
|
user = authenticate(username=email, password=loginpassword)
|
||||||
@ -167,9 +177,13 @@ class UserSchema(Schema):
|
|||||||
return SettingManager.get_all_settings(obj)
|
return SettingManager.get_all_settings(obj)
|
||||||
|
|
||||||
|
|
||||||
|
class Me(Schema):
|
||||||
|
whoami: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@router.get("/user", response={200: UserWrapper, 403: Error})
|
@router.get("/user", response={200: UserWrapper, 403: Error})
|
||||||
def get_users(request, whoami: str = None):
|
def get_users(request, me: Query[Me]):
|
||||||
if whoami:
|
if me.whoami:
|
||||||
return {"user": [request.user]}
|
return {"user": [request.user]}
|
||||||
else:
|
else:
|
||||||
return {"user": User.objects.all()}
|
return {"user": User.objects.all()}
|
||||||
@ -186,6 +200,61 @@ 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 #
|
# Package #
|
||||||
###########
|
###########
|
||||||
@ -251,67 +320,110 @@ def get_packages(request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema, 403: Error})
|
class GetPackage(Schema):
|
||||||
def get_package(request, package_uuid):
|
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]):
|
||||||
try:
|
try:
|
||||||
return PackageManager.get_package_by_id(request.user, package_uuid)
|
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
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 403, {
|
return 403, {
|
||||||
"message": f"Getting Package with id {package_uuid} failed due to insufficient rights!"
|
"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")
|
@router.post("/package")
|
||||||
def create_packages(
|
def create_packages(
|
||||||
request, packageName: Form[str], packageDescription: Optional[str] = Form(None)
|
request,
|
||||||
|
p: Form[CreatePackage],
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if packageName.strip() == "":
|
if p.packageName.strip() == "":
|
||||||
raise ValueError("Package name cannot be empty!")
|
raise ValueError("Package name cannot be empty!")
|
||||||
|
|
||||||
new_pacakge = PackageManager.create_package(request.user, packageName, packageDescription)
|
new_pacakge = PackageManager.create_package(
|
||||||
|
request.user, p.packageName, p.packageDescription
|
||||||
|
)
|
||||||
return redirect(new_pacakge.url)
|
return redirect(new_pacakge.url)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return 400, {"message": str(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})
|
@router.post("/package/{uuid:package_uuid}", response={200: PackageSchema | Any, 400: Error})
|
||||||
def update_package(
|
def update_package(request, package_uuid, pack: Form[UpdatePackage]):
|
||||||
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:
|
try:
|
||||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
if hiddenMethod:
|
if pack.hiddenMethod:
|
||||||
if hiddenMethod == "DELETE":
|
if pack.hiddenMethod == "DELETE":
|
||||||
p.delete()
|
p.delete()
|
||||||
|
|
||||||
elif packageDescription and packageDescription.strip() != "":
|
elif pack.packageDescription is not None:
|
||||||
p.description = packageDescription
|
description = nh3.clean(pack.packageDescription, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
p.save()
|
|
||||||
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
|
|
||||||
|
|
||||||
|
if description:
|
||||||
|
p.description = description
|
||||||
|
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)
|
||||||
|
|
||||||
|
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:
|
except ValueError as e:
|
||||||
return 400, {"message": str(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 #
|
# Compound / CompoundStructure #
|
||||||
################################
|
################################
|
||||||
@ -509,6 +621,83 @@ 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 #
|
# Rules #
|
||||||
#########
|
#########
|
||||||
@ -672,6 +861,73 @@ def _get_package_rule(request, package_uuid, rule_uuid):
|
|||||||
|
|
||||||
|
|
||||||
# POST
|
# 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(
|
@router.post(
|
||||||
"/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}
|
"/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}
|
||||||
)
|
)
|
||||||
@ -721,6 +977,41 @@ 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 #
|
# Reaction #
|
||||||
############
|
############
|
||||||
@ -809,6 +1100,82 @@ 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 #
|
# Scenario #
|
||||||
############
|
############
|
||||||
@ -823,7 +1190,7 @@ class ScenarioSchema(Schema):
|
|||||||
description: str = Field(None, alias="description")
|
description: str = Field(None, alias="description")
|
||||||
id: str = Field(None, alias="url")
|
id: str = Field(None, alias="url")
|
||||||
identifier: str = "scenario"
|
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")
|
name: str = Field(None, alias="name")
|
||||||
pathways: List["SimplePathway"] = Field([], alias="related_pathways")
|
pathways: List["SimplePathway"] = Field([], alias="related_pathways")
|
||||||
relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios")
|
relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios")
|
||||||
@ -874,6 +1241,38 @@ 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 #
|
# Pathway #
|
||||||
###########
|
###########
|
||||||
@ -1013,46 +1412,67 @@ 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")
|
@router.post("/package/{uuid:package_uuid}/pathway")
|
||||||
def create_pathway(
|
def create_pathway(
|
||||||
request,
|
request,
|
||||||
package_uuid,
|
package_uuid,
|
||||||
smilesinput: Form[str],
|
pw: Form[CreatePathway],
|
||||||
name: Optional[str] = Form(None),
|
|
||||||
description: Optional[str] = Form(None),
|
|
||||||
rootOnly: Optional[str] = Form(None),
|
|
||||||
selectedSetting: Optional[str] = Form(None),
|
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
stand_smiles = FormatConverter.standardize(smilesinput.strip())
|
stand_smiles = FormatConverter.standardize(pw.smilesinput.strip())
|
||||||
|
|
||||||
pw = Pathway.create(p, stand_smiles, name=name, description=description)
|
new_pw = Pathway.create(p, stand_smiles, name=pw.name, description=pw.description)
|
||||||
|
|
||||||
pw_mode = "predict"
|
pw_mode = "predict"
|
||||||
if rootOnly and rootOnly == "true":
|
if pw.rootOnly and pw.rootOnly.strip() == "true":
|
||||||
pw_mode = "build"
|
pw_mode = "build"
|
||||||
|
|
||||||
pw.kv.update({"mode": pw_mode})
|
new_pw.kv.update({"mode": pw_mode})
|
||||||
pw.save()
|
new_pw.save()
|
||||||
|
|
||||||
if pw_mode == "predict":
|
if pw_mode == "predict":
|
||||||
setting = request.user.prediction_settings()
|
setting = request.user.prediction_settings()
|
||||||
|
|
||||||
if selectedSetting:
|
if pw.selectedSetting:
|
||||||
setting = SettingManager.get_setting_by_url(request.user, selectedSetting)
|
setting = SettingManager.get_setting_by_url(request.user, pw.selectedSetting)
|
||||||
|
|
||||||
pw.setting = setting
|
new_pw.setting = setting
|
||||||
pw.save()
|
new_pw.save()
|
||||||
|
|
||||||
from .tasks import predict
|
from .tasks import dispatch, predict
|
||||||
|
|
||||||
predict.delay(pw.pk, setting.pk, limit=-1)
|
dispatch(request.user, predict, new_pw.pk, setting.pk, limit=-1)
|
||||||
|
|
||||||
return redirect(pw.url)
|
return redirect(new_pw.url)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(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!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
########
|
########
|
||||||
@ -1143,6 +1563,52 @@ 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 #
|
# Edge #
|
||||||
########
|
########
|
||||||
@ -1206,6 +1672,200 @@ 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 #
|
# Setting #
|
||||||
###########
|
###########
|
||||||
|
|||||||
124
epdb/logic.py
@ -1,38 +1,40 @@
|
|||||||
import re
|
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
from typing import Union, List, Optional, Set, Dict, Any
|
import logging
|
||||||
|
import re
|
||||||
|
from typing import Any, Dict, List, Optional, Set, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import nh3
|
||||||
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.conf import settings as s
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from epdb.models import (
|
from epdb.models import (
|
||||||
User,
|
|
||||||
Package,
|
|
||||||
UserPackagePermission,
|
|
||||||
GroupPackagePermission,
|
|
||||||
Permission,
|
|
||||||
Group,
|
|
||||||
Setting,
|
|
||||||
EPModel,
|
|
||||||
UserSettingPermission,
|
|
||||||
Rule,
|
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
Edge,
|
|
||||||
Compound,
|
Compound,
|
||||||
Reaction,
|
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
|
Edge,
|
||||||
EnzymeLink,
|
EnzymeLink,
|
||||||
|
EPModel,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
|
Node,
|
||||||
|
Pathway,
|
||||||
|
Permission,
|
||||||
|
Reaction,
|
||||||
|
Rule,
|
||||||
|
Setting,
|
||||||
|
User,
|
||||||
|
UserPackagePermission,
|
||||||
|
UserSettingPermission,
|
||||||
)
|
)
|
||||||
from utilities.chem import FormatConverter
|
from utilities.chem import FormatConverter
|
||||||
from utilities.misc import PackageImporter, PackageExporter
|
from utilities.misc import PackageExporter, PackageImporter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class EPDBURLParser:
|
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}"
|
UUID_PATTERN = r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||||
@ -185,6 +187,12 @@ class UserManager(object):
|
|||||||
def create_user(
|
def create_user(
|
||||||
username, email, password, set_setting=True, add_to_group=True, *args, **kwargs
|
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
|
# avoid circular import :S
|
||||||
from .tasks import send_registration_mail
|
from .tasks import send_registration_mail
|
||||||
|
|
||||||
@ -262,8 +270,9 @@ class GroupManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def create_group(current_user, name, description):
|
def create_group(current_user, name, description):
|
||||||
g = Group()
|
g = Group()
|
||||||
g.name = name
|
# Clean for potential XSS
|
||||||
g.description = description
|
g.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
g.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
g.owner = current_user
|
g.owner = current_user
|
||||||
g.save()
|
g.save()
|
||||||
|
|
||||||
@ -518,8 +527,13 @@ class PackageManager(object):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_package(current_user, name: str, description: str = None):
|
def create_package(current_user, name: str, description: str = None):
|
||||||
p = Package()
|
p = Package()
|
||||||
p.name = name
|
|
||||||
p.description = description
|
# 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.save()
|
p.save()
|
||||||
|
|
||||||
up = UserPackagePermission()
|
up = UserPackagePermission()
|
||||||
@ -565,30 +579,39 @@ class PackageManager(object):
|
|||||||
else:
|
else:
|
||||||
_ = perm_cls.objects.update_or_create(defaults={"permission": new_perm}, **data)
|
_ = 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
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def import_legacy_package(
|
def import_legacy_package(
|
||||||
data: dict, owner: User, keep_ids=False, add_import_timestamp=True, trust_reviewed=False
|
data: dict, owner: User, keep_ids=False, add_import_timestamp=True, trust_reviewed=False
|
||||||
):
|
):
|
||||||
from uuid import UUID, uuid4
|
|
||||||
from datetime import datetime
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from datetime import datetime
|
||||||
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from envipy_additional_information import AdditionalInformationConverter
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Package,
|
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
SimpleRule,
|
Edge,
|
||||||
SimpleAmbitRule,
|
Node,
|
||||||
ParallelRule,
|
ParallelRule,
|
||||||
|
Pathway,
|
||||||
|
Reaction,
|
||||||
|
Scenario,
|
||||||
SequentialRule,
|
SequentialRule,
|
||||||
SequentialRuleOrdering,
|
SequentialRuleOrdering,
|
||||||
Reaction,
|
SimpleAmbitRule,
|
||||||
Pathway,
|
SimpleRule,
|
||||||
Node,
|
|
||||||
Edge,
|
|
||||||
Scenario,
|
|
||||||
)
|
)
|
||||||
from envipy_additional_information import AdditionalInformationConverter
|
|
||||||
|
|
||||||
pack = Package()
|
pack = Package()
|
||||||
pack.uuid = UUID(data["id"].split("/")[-1]) if keep_ids else uuid4()
|
pack.uuid = UUID(data["id"].split("/")[-1]) if keep_ids else uuid4()
|
||||||
@ -1094,28 +1117,29 @@ class SettingManager(object):
|
|||||||
model: EPModel = None,
|
model: EPModel = None,
|
||||||
model_threshold: float = None,
|
model_threshold: float = None,
|
||||||
):
|
):
|
||||||
s = Setting()
|
new_s = Setting()
|
||||||
s.name = name
|
# Clean for potential XSS
|
||||||
s.description = description
|
new_s.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
s.max_nodes = max_nodes
|
new_s.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
s.max_depth = max_depth
|
new_s.max_nodes = max_nodes
|
||||||
s.model = model
|
new_s.max_depth = max_depth
|
||||||
s.model_threshold = model_threshold
|
new_s.model = model
|
||||||
|
new_s.model_threshold = model_threshold
|
||||||
|
|
||||||
s.save()
|
new_s.save()
|
||||||
|
|
||||||
if rule_packages is not None:
|
if rule_packages is not None:
|
||||||
for r in rule_packages:
|
for r in rule_packages:
|
||||||
s.rule_packages.add(r)
|
new_s.rule_packages.add(r)
|
||||||
s.save()
|
new_s.save()
|
||||||
|
|
||||||
usp = UserSettingPermission()
|
usp = UserSettingPermission()
|
||||||
usp.user = user
|
usp.user = user
|
||||||
usp.setting = s
|
usp.setting = new_s
|
||||||
usp.permission = Permission.ALL[0]
|
usp.permission = Permission.ALL[0]
|
||||||
usp.save()
|
usp.save()
|
||||||
|
|
||||||
return s
|
return new_s
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_setting(user: User):
|
def get_default_setting(user: User):
|
||||||
@ -1542,7 +1566,9 @@ class SPathway(object):
|
|||||||
if sub.app_domain_assessment is None:
|
if sub.app_domain_assessment is None:
|
||||||
if self.prediction_setting.model:
|
if self.prediction_setting.model:
|
||||||
if self.prediction_setting.model.app_domain:
|
if self.prediction_setting.model.app_domain:
|
||||||
app_domain_assessment = self.prediction_setting.model.app_domain.assess(sub.smiles)
|
app_domain_assessment = self.prediction_setting.model.app_domain.assess(
|
||||||
|
sub.smiles
|
||||||
|
)
|
||||||
|
|
||||||
if self.persist is not None:
|
if self.persist is not None:
|
||||||
n = self.snode_persist_lookup[sub]
|
n = self.snode_persist_lookup[sub]
|
||||||
@ -1574,7 +1600,9 @@ class SPathway(object):
|
|||||||
app_domain_assessment = None
|
app_domain_assessment = None
|
||||||
if self.prediction_setting.model:
|
if self.prediction_setting.model:
|
||||||
if self.prediction_setting.model.app_domain:
|
if self.prediction_setting.model.app_domain:
|
||||||
app_domain_assessment = (self.prediction_setting.model.app_domain.assess(c))
|
app_domain_assessment = (
|
||||||
|
self.prediction_setting.model.app_domain.assess(c)
|
||||||
|
)
|
||||||
|
|
||||||
self.smiles_to_node[c] = SNode(
|
self.smiles_to_node[c] = SNode(
|
||||||
c, sub.depth + 1, app_domain_assessment
|
c, sub.depth + 1, app_domain_assessment
|
||||||
|
|||||||
@ -12,10 +12,16 @@ from epdb.models import (
|
|||||||
Permission,
|
Permission,
|
||||||
User,
|
User,
|
||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
|
License,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"-ol", "--only-licenses", action="store_true", help="Only create licenses."
|
||||||
|
)
|
||||||
|
|
||||||
def create_users(self):
|
def create_users(self):
|
||||||
# Anonymous User
|
# Anonymous User
|
||||||
if not User.objects.filter(email="anon@envipath.com").exists():
|
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||||
@ -83,6 +89,17 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
return anon, admin, g, user0
|
return anon, admin, g, user0
|
||||||
|
|
||||||
|
def create_licenses(self):
|
||||||
|
"""Create the six default licenses supported by enviPath"""
|
||||||
|
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
||||||
|
for cc_string in cc_strings:
|
||||||
|
if not License.objects.filter(cc_string=cc_string).exists():
|
||||||
|
new_license = License()
|
||||||
|
new_license.cc_string = cc_string
|
||||||
|
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
||||||
|
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
||||||
|
new_license.save()
|
||||||
|
|
||||||
def import_package(self, data, owner):
|
def import_package(self, data, owner):
|
||||||
return PackageManager.import_legacy_package(
|
return PackageManager.import_legacy_package(
|
||||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||||
@ -157,6 +174,10 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
# Create licenses
|
||||||
|
self.create_licenses()
|
||||||
|
if options.get("only_licenses", False):
|
||||||
|
return
|
||||||
# Create users
|
# Create users
|
||||||
anon, admin, g, user0 = self.create_users()
|
anon, admin, g, user0 = self.create_users()
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,9 @@ from django.conf import settings as s
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from epdb.models import MLRelativeReasoning, EnviFormer, Package
|
from epdb.models import EnviFormer, MLRelativeReasoning
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -75,11 +77,13 @@ class Command(BaseCommand):
|
|||||||
return packages
|
return packages
|
||||||
|
|
||||||
# Iteratively create models in options["model_names"]
|
# 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"Data packages: {options['data_packages']}\n"
|
||||||
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
||||||
f"Eval Packages: {options['eval_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"])
|
data_packages = decode_packages(options["data_packages"])
|
||||||
eval_packages = decode_packages(options["eval_packages"])
|
eval_packages = decode_packages(options["eval_packages"])
|
||||||
rule_packages = decode_packages(options["rule_packages"])
|
rule_packages = decode_packages(options["rule_packages"])
|
||||||
@ -90,7 +94,7 @@ class Command(BaseCommand):
|
|||||||
pack,
|
pack,
|
||||||
data_packages=data_packages,
|
data_packages=data_packages,
|
||||||
eval_packages=eval_packages,
|
eval_packages=eval_packages,
|
||||||
threshold=options['threshold'],
|
threshold=options["threshold"],
|
||||||
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
||||||
f"evaluated on {options['eval_packages']}.",
|
f"evaluated on {options['eval_packages']}.",
|
||||||
@ -101,7 +105,7 @@ class Command(BaseCommand):
|
|||||||
rule_packages=rule_packages,
|
rule_packages=rule_packages,
|
||||||
data_packages=data_packages,
|
data_packages=data_packages,
|
||||||
eval_packages=eval_packages,
|
eval_packages=eval_packages,
|
||||||
threshold=options['threshold'],
|
threshold=options["threshold"],
|
||||||
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
||||||
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
|
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
|
||||||
|
|||||||
@ -8,7 +8,9 @@ from django.conf import settings as s
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from epdb.models import EnviFormer, Package
|
from epdb.models import EnviFormer
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.conf import settings as s
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db.models import F, JSONField, TextField, Value
|
||||||
from django.db.models import F, Value, TextField, JSONField
|
from django.db.models.functions import Cast, Replace
|
||||||
from django.db.models.functions import Replace, Cast
|
|
||||||
|
|
||||||
from epdb.models import EnviPathModel
|
from epdb.models import EnviPathModel
|
||||||
|
|
||||||
@ -23,10 +23,13 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
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 = [
|
MODELS = [
|
||||||
"User",
|
"User",
|
||||||
"Group",
|
"Group",
|
||||||
"Package",
|
|
||||||
"Compound",
|
"Compound",
|
||||||
"CompoundStructure",
|
"CompoundStructure",
|
||||||
"Pathway",
|
"Pathway",
|
||||||
|
|||||||
18
epdb/migrations/0010_license_cc_string.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 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,
|
||||||
|
),
|
||||||
|
]
|
||||||
59
epdb/migrations/0011_auto_20251111_1413.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# 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)]
|
||||||
311
epdb/models.py
@ -2,34 +2,42 @@ import abc
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Union, List, Optional, Dict, Tuple, Set, Any
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import math
|
|
||||||
import joblib
|
import joblib
|
||||||
|
import nh3
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import JSONField, Count, Q, QuerySet
|
from django.db.models import Count, JSONField, Q, QuerySet
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from envipy_additional_information import EnviPyModel
|
from envipy_additional_information import EnviPyModel
|
||||||
from model_utils.models import TimeStampedModel
|
from model_utils.models import TimeStampedModel
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
from sklearn.metrics import precision_score, recall_score, jaccard_score
|
from sklearn.metrics import jaccard_score, precision_score, recall_score
|
||||||
from sklearn.model_selection import ShuffleSplit
|
from sklearn.model_selection import ShuffleSplit
|
||||||
|
|
||||||
from utilities.chem import FormatConverter, ProductSet, PredictionResult, IndigoUtils
|
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
|
||||||
from utilities.ml import RuleBasedDataset, ApplicabilityDomainPCA, EnsembleClassifierChain, RelativeReasoning, \
|
from utilities.ml import (
|
||||||
EnviFormerDataset, Dataset
|
ApplicabilityDomainPCA,
|
||||||
|
Dataset,
|
||||||
|
EnsembleClassifierChain,
|
||||||
|
EnviFormerDataset,
|
||||||
|
RelativeReasoning,
|
||||||
|
RuleBasedDataset,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -37,8 +45,6 @@ logger = logging.getLogger(__name__)
|
|||||||
##########################
|
##########################
|
||||||
# User/Groups/Permission #
|
# User/Groups/Permission #
|
||||||
##########################
|
##########################
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
email = models.EmailField(unique=True)
|
email = models.EmailField(unique=True)
|
||||||
uuid = models.UUIDField(
|
uuid = models.UUIDField(
|
||||||
@ -46,7 +52,10 @@ class User(AbstractUser):
|
|||||||
)
|
)
|
||||||
url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True)
|
url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True)
|
||||||
default_package = models.ForeignKey(
|
default_package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Default Package", null=True, on_delete=models.SET_NULL
|
s.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Default Package",
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
default_group = models.ForeignKey(
|
default_group = models.ForeignKey(
|
||||||
"Group",
|
"Group",
|
||||||
@ -236,7 +245,7 @@ class UserPackagePermission(Permission):
|
|||||||
)
|
)
|
||||||
user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE)
|
user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE)
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
|
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -252,7 +261,7 @@ class GroupPackagePermission(Permission):
|
|||||||
)
|
)
|
||||||
group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE)
|
group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE)
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
|
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -648,6 +657,7 @@ class ScenarioMixin(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class License(models.Model):
|
class License(models.Model):
|
||||||
|
cc_string = models.TextField(blank=False, null=False, verbose_name="CC string")
|
||||||
link = models.URLField(blank=False, null=False, verbose_name="link")
|
link = models.URLField(blank=False, null=False, verbose_name="link")
|
||||||
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
||||||
|
|
||||||
@ -720,10 +730,13 @@ class Package(EnviPathModel):
|
|||||||
rules = sorted(rules, key=lambda x: x.url)
|
rules = sorted(rules, key=lambda x: x.url)
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
swappable = "EPDB_PACKAGE_MODEL"
|
||||||
|
|
||||||
|
|
||||||
class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin):
|
class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
default_structure = models.ForeignKey(
|
default_structure = models.ForeignKey(
|
||||||
"CompoundStructure",
|
"CompoundStructure",
|
||||||
@ -773,7 +786,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs
|
package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs
|
||||||
) -> "Compound":
|
) -> "Compound":
|
||||||
if smiles is None or smiles.strip() == "":
|
if smiles is None or smiles.strip() == "":
|
||||||
raise ValueError("SMILES is required")
|
raise ValueError("SMILES is required")
|
||||||
@ -803,14 +816,16 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
|
|||||||
c = Compound()
|
c = Compound()
|
||||||
c.package = package
|
c.package = package
|
||||||
|
|
||||||
if name is None or name.strip() == "":
|
if name is not None:
|
||||||
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
|
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
c.name = name
|
c.name = name
|
||||||
|
|
||||||
# We have a default here only set the value if it carries some payload
|
# We have a default here only set the value if it carries some payload
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
c.description = description.strip()
|
c.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
@ -982,11 +997,11 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti
|
|||||||
raise ValueError("Unpersisted Compound! Persist compound first!")
|
raise ValueError("Unpersisted Compound! Persist compound first!")
|
||||||
|
|
||||||
cs = CompoundStructure()
|
cs = CompoundStructure()
|
||||||
|
# Clean for potential XSS
|
||||||
if name is not None:
|
if name is not None:
|
||||||
cs.name = name
|
cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if description is not None:
|
if description is not None:
|
||||||
cs.description = description
|
cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
cs.smiles = smiles
|
cs.smiles = smiles
|
||||||
cs.compound = compound
|
cs.compound = compound
|
||||||
@ -1051,7 +1066,7 @@ class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
|
|||||||
|
|
||||||
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# # https://github.com/django-polymorphic/django-polymorphic/issues/229
|
# # https://github.com/django-polymorphic/django-polymorphic/issues/229
|
||||||
@ -1064,6 +1079,10 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
def apply(self, *args, **kwargs):
|
def apply(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cls_for_type(rule_type: str):
|
def cls_for_type(rule_type: str):
|
||||||
if rule_type == "SimpleAmbitRule":
|
if rule_type == "SimpleAmbitRule":
|
||||||
@ -1157,7 +1176,7 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
package: Package,
|
package: "Package",
|
||||||
name: str = None,
|
name: str = None,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
smirks: str = None,
|
smirks: str = None,
|
||||||
@ -1188,21 +1207,29 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
r = SimpleAmbitRule()
|
r = SimpleAmbitRule()
|
||||||
r.package = package
|
r.package = package
|
||||||
|
|
||||||
if name is None or name.strip() == "":
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
|
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
r.name = name
|
r.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
r.description = description
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r.smirks = smirks
|
r.smirks = smirks
|
||||||
|
|
||||||
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
||||||
r.reactant_filter_smarts = reactant_filter_smarts
|
if not FormatConverter.is_valid_smarts(reactant_filter_smarts.strip()):
|
||||||
|
raise ValueError(f'Reactant Filter SMARTS "{reactant_filter_smarts}" is invalid!')
|
||||||
|
else:
|
||||||
|
r.reactant_filter_smarts = reactant_filter_smarts.strip()
|
||||||
|
|
||||||
if product_filter_smarts is not None and product_filter_smarts.strip() != "":
|
if product_filter_smarts is not None and product_filter_smarts.strip() != "":
|
||||||
r.product_filter_smarts = product_filter_smarts
|
if not FormatConverter.is_valid_smarts(product_filter_smarts.strip()):
|
||||||
|
raise ValueError(f'Product Filter SMARTS "{product_filter_smarts}" is invalid!')
|
||||||
|
else:
|
||||||
|
r.product_filter_smarts = product_filter_smarts.strip()
|
||||||
|
|
||||||
r.save()
|
r.save()
|
||||||
return r
|
return r
|
||||||
@ -1210,6 +1237,9 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
def _url(self):
|
def _url(self):
|
||||||
return "{}/simple-ambit-rule/{}".format(self.package.url, self.uuid)
|
return "{}/simple-ambit-rule/{}".format(self.package.url, self.uuid)
|
||||||
|
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
return "simple-rule"
|
||||||
|
|
||||||
def apply(self, smiles):
|
def apply(self, smiles):
|
||||||
return FormatConverter.apply(smiles, self.smirks)
|
return FormatConverter.apply(smiles, self.smirks)
|
||||||
|
|
||||||
@ -1223,7 +1253,7 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def related_reactions(self):
|
def related_reactions(self):
|
||||||
qs = Package.objects.filter(reviewed=True)
|
qs = s.GET_PACKAGE_MODEL().objects.filter(reviewed=True)
|
||||||
return self.reaction_rule.filter(package__in=qs).order_by("name")
|
return self.reaction_rule.filter(package__in=qs).order_by("name")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1255,6 +1285,9 @@ class ParallelRule(Rule):
|
|||||||
def _url(self):
|
def _url(self):
|
||||||
return "{}/parallel-rule/{}".format(self.package.url, self.uuid)
|
return "{}/parallel-rule/{}".format(self.package.url, self.uuid)
|
||||||
|
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
return "parallel-rule"
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def srs(self) -> QuerySet:
|
def srs(self) -> QuerySet:
|
||||||
return self.simple_rules.all()
|
return self.simple_rules.all()
|
||||||
@ -1286,6 +1319,57 @@ class ParallelRule(Rule):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@transaction.atomic
|
||||||
|
def create(
|
||||||
|
package: "Package",
|
||||||
|
simple_rules: List["SimpleRule"],
|
||||||
|
name: str = None,
|
||||||
|
description: str = None,
|
||||||
|
reactant_filter_smarts: str = None,
|
||||||
|
product_filter_smarts: str = None,
|
||||||
|
):
|
||||||
|
if len(simple_rules) == 0:
|
||||||
|
raise ValueError("At least one simple rule is required!")
|
||||||
|
|
||||||
|
for sr in simple_rules:
|
||||||
|
if sr.package != package:
|
||||||
|
raise ValueError(
|
||||||
|
f"Simple rule {sr.uuid} does not belong to package {package.uuid}!"
|
||||||
|
)
|
||||||
|
|
||||||
|
r = ParallelRule()
|
||||||
|
r.package = package
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
if name is None or name == "":
|
||||||
|
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
|
r.name = name
|
||||||
|
if description is not None and description.strip() != "":
|
||||||
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
||||||
|
if not FormatConverter.is_valid_smarts(reactant_filter_smarts.strip()):
|
||||||
|
raise ValueError(f'Reactant Filter SMARTS "{reactant_filter_smarts}" is invalid!')
|
||||||
|
else:
|
||||||
|
r.reactant_filter_smarts = reactant_filter_smarts.strip()
|
||||||
|
|
||||||
|
if product_filter_smarts is not None and product_filter_smarts.strip() != "":
|
||||||
|
if not FormatConverter.is_valid_smarts(product_filter_smarts.strip()):
|
||||||
|
raise ValueError(f'Product Filter SMARTS "{product_filter_smarts}" is invalid!')
|
||||||
|
else:
|
||||||
|
r.product_filter_smarts = product_filter_smarts.strip()
|
||||||
|
|
||||||
|
r.save()
|
||||||
|
|
||||||
|
for sr in simple_rules:
|
||||||
|
r.simple_rules.add(sr)
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
class SequentialRule(Rule):
|
class SequentialRule(Rule):
|
||||||
simple_rules = models.ManyToManyField(
|
simple_rules = models.ManyToManyField(
|
||||||
@ -1295,6 +1379,9 @@ class SequentialRule(Rule):
|
|||||||
def _url(self):
|
def _url(self):
|
||||||
return "{}/sequential-rule/{}".format(self.compound.url, self.uuid)
|
return "{}/sequential-rule/{}".format(self.compound.url, self.uuid)
|
||||||
|
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
return "sequential-rule"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def srs(self):
|
def srs(self):
|
||||||
return self.simple_rules.all()
|
return self.simple_rules.all()
|
||||||
@ -1315,7 +1402,7 @@ class SequentialRuleOrdering(models.Model):
|
|||||||
|
|
||||||
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
|
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
educts = models.ManyToManyField(
|
educts = models.ManyToManyField(
|
||||||
"epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts"
|
"epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts"
|
||||||
@ -1337,7 +1424,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
package: Package,
|
package: "Package",
|
||||||
name: str = None,
|
name: str = None,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
educts: Union[List[str], List[CompoundStructure]] = None,
|
educts: Union[List[str], List[CompoundStructure]] = None,
|
||||||
@ -1403,12 +1490,11 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
|||||||
|
|
||||||
r = Reaction()
|
r = Reaction()
|
||||||
r.package = package
|
r.package = package
|
||||||
|
# Clean for potential XSS
|
||||||
if name is not None and name.strip() != "":
|
if name is not None and name.strip() != "":
|
||||||
r.name = name
|
r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if description is not None and name.strip() != "":
|
if description is not None and name.strip() != "":
|
||||||
r.description = description
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r.multi_step = multi_step
|
r.multi_step = multi_step
|
||||||
|
|
||||||
@ -1497,7 +1583,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
|||||||
|
|
||||||
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
setting = models.ForeignKey(
|
setting = models.ForeignKey(
|
||||||
"epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True
|
"epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True
|
||||||
@ -1716,14 +1802,15 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
):
|
):
|
||||||
pw = Pathway()
|
pw = Pathway()
|
||||||
pw.package = package
|
pw.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Pathway {Pathway.objects.filter(package=package).count() + 1}"
|
name = f"Pathway {Pathway.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
pw.name = name
|
pw.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
pw.description = description
|
pw.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
pw.save()
|
pw.save()
|
||||||
try:
|
try:
|
||||||
@ -2018,11 +2105,16 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
for node in end_nodes:
|
for node in end_nodes:
|
||||||
e.end_nodes.add(node)
|
e.end_nodes.add(node)
|
||||||
|
|
||||||
if name is None:
|
# Clean for potential XSS
|
||||||
|
# Cleaning technically not needed as it is also done in Reaction.create, including it here for consistency
|
||||||
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Reaction {pathway.package.reactions.count() + 1}"
|
name = f"Reaction {pathway.package.reactions.count() + 1}"
|
||||||
|
|
||||||
if description is None:
|
if description is None:
|
||||||
description = s.DEFAULT_VALUES["description"]
|
description = s.DEFAULT_VALUES["description"]
|
||||||
|
description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r = Reaction.create(
|
r = Reaction.create(
|
||||||
pathway.package,
|
pathway.package,
|
||||||
@ -2053,7 +2145,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
|
|
||||||
class EPModel(PolymorphicModel, EnviPathModel):
|
class EPModel(PolymorphicModel, EnviPathModel):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def _url(self):
|
def _url(self):
|
||||||
@ -2062,17 +2154,17 @@ class EPModel(PolymorphicModel, EnviPathModel):
|
|||||||
|
|
||||||
class PackageBasedModel(EPModel):
|
class PackageBasedModel(EPModel):
|
||||||
rule_packages = models.ManyToManyField(
|
rule_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Rule Packages",
|
verbose_name="Rule Packages",
|
||||||
related_name="%(app_label)s_%(class)s_rule_packages",
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
)
|
)
|
||||||
data_packages = models.ManyToManyField(
|
data_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Data Packages",
|
verbose_name="Data Packages",
|
||||||
related_name="%(app_label)s_%(class)s_data_packages",
|
related_name="%(app_label)s_%(class)s_data_packages",
|
||||||
)
|
)
|
||||||
eval_packages = models.ManyToManyField(
|
eval_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Evaluation Packages",
|
verbose_name="Evaluation Packages",
|
||||||
related_name="%(app_label)s_%(class)s_eval_packages",
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
)
|
)
|
||||||
@ -2344,7 +2436,9 @@ class PackageBasedModel(EPModel):
|
|||||||
eval_reactions = list(
|
eval_reactions = list(
|
||||||
Reaction.objects.filter(package__in=self.eval_packages.all()).distinct()
|
Reaction.objects.filter(package__in=self.eval_packages.all()).distinct()
|
||||||
)
|
)
|
||||||
ds = RuleBasedDataset.generate_dataset(eval_reactions, self.applicable_rules, educts_only=True)
|
ds = RuleBasedDataset.generate_dataset(
|
||||||
|
eval_reactions, self.applicable_rules, educts_only=True
|
||||||
|
)
|
||||||
if isinstance(self, RuleBasedRelativeReasoning):
|
if isinstance(self, RuleBasedRelativeReasoning):
|
||||||
X = ds.X(exclude_id_col=False, na_replacement=None).to_numpy()
|
X = ds.X(exclude_id_col=False, na_replacement=None).to_numpy()
|
||||||
y = ds.y(na_replacement=np.nan).to_numpy()
|
y = ds.y(na_replacement=np.nan).to_numpy()
|
||||||
@ -2542,14 +2636,15 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
|
|||||||
):
|
):
|
||||||
rbrr = RuleBasedRelativeReasoning()
|
rbrr = RuleBasedRelativeReasoning()
|
||||||
rbrr.package = package
|
rbrr.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"RuleBasedRelativeReasoning {RuleBasedRelativeReasoning.objects.filter(package=package).count() + 1}"
|
name = f"RuleBasedRelativeReasoning {RuleBasedRelativeReasoning.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
rbrr.name = name
|
rbrr.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
rbrr.description = description
|
rbrr.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
||||||
raise ValueError("Threshold must be a float between 0 and 1.")
|
raise ValueError("Threshold must be a float between 0 and 1.")
|
||||||
@ -2646,14 +2741,15 @@ class MLRelativeReasoning(PackageBasedModel):
|
|||||||
):
|
):
|
||||||
mlrr = MLRelativeReasoning()
|
mlrr = MLRelativeReasoning()
|
||||||
mlrr.package = package
|
mlrr.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"MLRelativeReasoning {MLRelativeReasoning.objects.filter(package=package).count() + 1}"
|
name = f"MLRelativeReasoning {MLRelativeReasoning.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
mlrr.name = name
|
mlrr.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
mlrr.description = description
|
mlrr.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
||||||
raise ValueError("Threshold must be a float between 0 and 1.")
|
raise ValueError("Threshold must be a float between 0 and 1.")
|
||||||
@ -2807,7 +2903,9 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
else:
|
else:
|
||||||
smiles.append(structures)
|
smiles.append(structures)
|
||||||
|
|
||||||
assessment_ds, assessment_prods = ds.classification_dataset(structures, self.model.applicable_rules)
|
assessment_ds, assessment_prods = ds.classification_dataset(
|
||||||
|
structures, self.model.applicable_rules
|
||||||
|
)
|
||||||
|
|
||||||
# qualified_neighbours_per_rule is a nested dictionary structured as:
|
# qualified_neighbours_per_rule is a nested dictionary structured as:
|
||||||
# {
|
# {
|
||||||
@ -2823,12 +2921,16 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
qualified_neighbours_per_rule: Dict = {}
|
qualified_neighbours_per_rule: Dict = {}
|
||||||
|
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
# Select only the triggered columns
|
# Select only the triggered columns
|
||||||
for i, row in enumerate(assessment_ds[:, assessment_ds.triggered()].iter_rows(named=True)):
|
for i, row in enumerate(assessment_ds[:, assessment_ds.triggered()].iter_rows(named=True)):
|
||||||
# Find the rules the structure triggers. For each rule, filter the training dataset to rows that also
|
# Find the rules the structure triggers. For each rule, filter the training dataset to rows that also
|
||||||
# trigger that rule.
|
# trigger that rule.
|
||||||
train_trig = {trig_uuid.split("_")[-1]: ds.filter(pl.col(trig_uuid).eq(1))
|
train_trig = {
|
||||||
for trig_uuid, value in row.items() if value == 1}
|
trig_uuid.split("_")[-1]: ds.filter(pl.col(trig_uuid).eq(1))
|
||||||
|
for trig_uuid, value in row.items()
|
||||||
|
if value == 1
|
||||||
|
}
|
||||||
qualified_neighbours_per_rule[i] = train_trig
|
qualified_neighbours_per_rule[i] = train_trig
|
||||||
rule_to_i = {str(r.uuid): i for i, r in enumerate(self.model.applicable_rules)}
|
rule_to_i = {str(r.uuid): i for i, r in enumerate(self.model.applicable_rules)}
|
||||||
preds = self.model.combine_products_and_probs(
|
preds = self.model.combine_products_and_probs(
|
||||||
@ -2848,17 +2950,28 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
# loop through rule indices together with the collected neighbours indices from train dataset
|
# loop through rule indices together with the collected neighbours indices from train dataset
|
||||||
for rule_uuid, train_instances in qualified_neighbours_per_rule[i].items():
|
for rule_uuid, train_instances in qualified_neighbours_per_rule[i].items():
|
||||||
# compute tanimoto distance for all neighbours and add to dataset
|
# compute tanimoto distance for all neighbours and add to dataset
|
||||||
dists = self._compute_distances(assessment_ds[i, assessment_ds.struct_features()].to_numpy()[0],
|
dists = self._compute_distances(
|
||||||
train_instances[:, train_instances.struct_features()].to_numpy())
|
assessment_ds[i, assessment_ds.struct_features()].to_numpy()[0],
|
||||||
|
train_instances[:, train_instances.struct_features()].to_numpy(),
|
||||||
|
)
|
||||||
train_instances = train_instances.with_columns(dist=pl.Series(dists))
|
train_instances = train_instances.with_columns(dist=pl.Series(dists))
|
||||||
|
|
||||||
# sort them in a descending way and take at most `self.num_neighbours`
|
# sort them in a descending way and take at most `self.num_neighbours`
|
||||||
train_instances = train_instances.sort("dist", descending=True)[:self.num_neighbours]
|
# TODO: Should this be descending? If we want the most similar then we want values close to zero (ascending)
|
||||||
|
train_instances = train_instances.sort("dist", descending=True)[
|
||||||
|
: self.num_neighbours
|
||||||
|
]
|
||||||
# compute average distance
|
# compute average distance
|
||||||
rule_reliabilities[rule_uuid] = train_instances.select(pl.mean("dist")).fill_nan(0.0).item()
|
rule_reliabilities[rule_uuid] = (
|
||||||
|
train_instances.select(pl.mean("dist")).fill_nan(0.0).item()
|
||||||
|
)
|
||||||
# for local_compatibility we'll need the datasets for the indices having the highest similarity
|
# for local_compatibility we'll need the datasets for the indices having the highest similarity
|
||||||
local_compatibilities[rule_uuid] = self._compute_compatibility(rule_uuid, train_instances)
|
local_compatibilities[rule_uuid] = self._compute_compatibility(
|
||||||
neighbours_per_rule[rule_uuid] = list(CompoundStructure.objects.filter(uuid__in=train_instances["structure_id"]))
|
rule_uuid, train_instances
|
||||||
|
)
|
||||||
|
neighbours_per_rule[rule_uuid] = list(
|
||||||
|
CompoundStructure.objects.filter(uuid__in=train_instances["structure_id"])
|
||||||
|
)
|
||||||
neighbor_probs_per_rule[rule_uuid] = train_instances[f"prob_{rule_uuid}"].to_list()
|
neighbor_probs_per_rule[rule_uuid] = train_instances[f"prob_{rule_uuid}"].to_list()
|
||||||
|
|
||||||
ad_res = {
|
ad_res = {
|
||||||
@ -2932,8 +3045,11 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
def _compute_compatibility(self, rule_idx: int, neighbours: "RuleBasedDataset"):
|
def _compute_compatibility(self, rule_idx: int, neighbours: "RuleBasedDataset"):
|
||||||
accuracy = 0.0
|
accuracy = 0.0
|
||||||
import polars as pl
|
import polars as pl
|
||||||
obs_pred = neighbours.select(obs=pl.col(f"obs_{rule_idx}").cast(pl.Boolean),
|
|
||||||
pred=pl.col(f"prob_{rule_idx}") >= self.model.threshold)
|
obs_pred = neighbours.select(
|
||||||
|
obs=pl.col(f"obs_{rule_idx}").cast(pl.Boolean),
|
||||||
|
pred=pl.col(f"prob_{rule_idx}") >= self.model.threshold,
|
||||||
|
)
|
||||||
# Compute tp, tn, fp, fn using polars expressions
|
# Compute tp, tn, fp, fn using polars expressions
|
||||||
tp = obs_pred.filter((pl.col("obs")) & (pl.col("pred"))).height
|
tp = obs_pred.filter((pl.col("obs")) & (pl.col("pred"))).height
|
||||||
tn = obs_pred.filter((~pl.col("obs")) & (~pl.col("pred"))).height
|
tn = obs_pred.filter((~pl.col("obs")) & (~pl.col("pred"))).height
|
||||||
@ -2960,14 +3076,15 @@ class EnviFormer(PackageBasedModel):
|
|||||||
):
|
):
|
||||||
mod = EnviFormer()
|
mod = EnviFormer()
|
||||||
mod.package = package
|
mod.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"EnviFormer {EnviFormer.objects.filter(package=package).count() + 1}"
|
name = f"EnviFormer {EnviFormer.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
mod.name = name
|
mod.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
mod.description = description
|
mod.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
||||||
raise ValueError("Threshold must be a float between 0 and 1.")
|
raise ValueError("Threshold must be a float between 0 and 1.")
|
||||||
@ -3102,7 +3219,7 @@ class EnviFormer(PackageBasedModel):
|
|||||||
pred_dict = {}
|
pred_dict = {}
|
||||||
for k, pred in enumerate(predictions):
|
for k, pred in enumerate(predictions):
|
||||||
pred_smiles, pred_proba = zip(*pred.items())
|
pred_smiles, pred_proba = zip(*pred.items())
|
||||||
reactant, true_product = test_ds[k, "educts"], test_ds[k, "products"]
|
reactant, _ = test_ds[k, "educts"], test_ds[k, "products"]
|
||||||
pred_dict.setdefault(reactant, {"predict": [], "scores": []})
|
pred_dict.setdefault(reactant, {"predict": [], "scores": []})
|
||||||
for smiles, proba in zip(pred_smiles, pred_proba):
|
for smiles, proba in zip(pred_smiles, pred_proba):
|
||||||
smiles = set(smiles.split("."))
|
smiles = set(smiles.split("."))
|
||||||
@ -3216,8 +3333,9 @@ class EnviFormer(PackageBasedModel):
|
|||||||
|
|
||||||
# If there are eval packages perform single generation evaluation on them instead of random splits
|
# If there are eval packages perform single generation evaluation on them instead of random splits
|
||||||
if self.eval_packages.count() > 0:
|
if self.eval_packages.count() > 0:
|
||||||
ds = EnviFormerDataset.generate_dataset(Reaction.objects.filter(
|
ds = EnviFormerDataset.generate_dataset(
|
||||||
package__in=self.eval_packages.all()).distinct())
|
Reaction.objects.filter(package__in=self.eval_packages.all()).distinct()
|
||||||
|
)
|
||||||
test_result = self.model.predict_batch(ds.X())
|
test_result = self.model.predict_batch(ds.X())
|
||||||
single_gen_result = evaluate_sg(ds, test_result, self.threshold)
|
single_gen_result = evaluate_sg(ds, test_result, self.threshold)
|
||||||
self.eval_results = self.compute_averages([single_gen_result])
|
self.eval_results = self.compute_averages([single_gen_result])
|
||||||
@ -3235,7 +3353,9 @@ class EnviFormer(PackageBasedModel):
|
|||||||
train = ds[train_index]
|
train = ds[train_index]
|
||||||
test = ds[test_index]
|
test = ds[test_index]
|
||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
model = fine_tune(train.X(), train.y(), s.MODEL_DIR, str(split_id), device=s.ENVIFORMER_DEVICE)
|
model = fine_tune(
|
||||||
|
train.X(), train.y(), s.MODEL_DIR, str(split_id), device=s.ENVIFORMER_DEVICE
|
||||||
|
)
|
||||||
end = datetime.now()
|
end = datetime.now()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds"
|
f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds"
|
||||||
@ -3312,7 +3432,12 @@ class EnviFormer(PackageBasedModel):
|
|||||||
for pathway in train_pathways:
|
for pathway in train_pathways:
|
||||||
for reaction in pathway.edges:
|
for reaction in pathway.edges:
|
||||||
reaction = reaction.edge_label
|
reaction = reaction.edge_label
|
||||||
if any([educt in test_educts for educt in reaction_to_educts[str(reaction.uuid)]]):
|
if any(
|
||||||
|
[
|
||||||
|
educt in test_educts
|
||||||
|
for educt in reaction_to_educts[str(reaction.uuid)]
|
||||||
|
]
|
||||||
|
):
|
||||||
overlap += 1
|
overlap += 1
|
||||||
continue
|
continue
|
||||||
train_reactions.append(reaction)
|
train_reactions.append(reaction)
|
||||||
@ -3344,7 +3469,7 @@ class PluginModel(EPModel):
|
|||||||
|
|
||||||
class Scenario(EnviPathModel):
|
class Scenario(EnviPathModel):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
scenario_date = models.CharField(max_length=256, null=False, blank=False, default="No date")
|
scenario_date = models.CharField(max_length=256, null=False, blank=False, default="No date")
|
||||||
scenario_type = models.CharField(
|
scenario_type = models.CharField(
|
||||||
@ -3369,41 +3494,44 @@ class Scenario(EnviPathModel):
|
|||||||
scenario_type: str,
|
scenario_type: str,
|
||||||
additional_information: List["EnviPyModel"],
|
additional_information: List["EnviPyModel"],
|
||||||
):
|
):
|
||||||
s = Scenario()
|
new_s = Scenario()
|
||||||
s.package = package
|
new_s.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Scenario {Scenario.objects.filter(package=package).count() + 1}"
|
name = f"Scenario {Scenario.objects.filter(package=package).count() + 1}"
|
||||||
|
new_s.name = name
|
||||||
s.name = name
|
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
s.description = description
|
new_s.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if scenario_date is not None and scenario_date.strip() != "":
|
if scenario_date is not None and scenario_date.strip() != "":
|
||||||
s.scenario_date = scenario_date
|
new_s.scenario_date = nh3.clean(scenario_date).strip()
|
||||||
|
|
||||||
if scenario_type is not None and scenario_type.strip() != "":
|
if scenario_type is not None and scenario_type.strip() != "":
|
||||||
s.scenario_type = scenario_type
|
new_s.scenario_type = scenario_type
|
||||||
|
|
||||||
add_inf = defaultdict(list)
|
add_inf = defaultdict(list)
|
||||||
|
|
||||||
for info in additional_information:
|
for info in additional_information:
|
||||||
cls_name = info.__class__.__name__
|
cls_name = info.__class__.__name__
|
||||||
ai_data = json.loads(info.model_dump_json())
|
# Clean for potential XSS hidden in the additional information fields.
|
||||||
|
ai_data = json.loads(nh3.clean(info.model_dump_json()).strip())
|
||||||
ai_data["uuid"] = f"{uuid4()}"
|
ai_data["uuid"] = f"{uuid4()}"
|
||||||
add_inf[cls_name].append(ai_data)
|
add_inf[cls_name].append(ai_data)
|
||||||
|
|
||||||
s.additional_information = add_inf
|
new_s.additional_information = add_inf
|
||||||
|
|
||||||
s.save()
|
new_s.save()
|
||||||
|
|
||||||
return s
|
return new_s
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def add_additional_information(self, data: "EnviPyModel"):
|
def add_additional_information(self, data: "EnviPyModel"):
|
||||||
cls_name = data.__class__.__name__
|
cls_name = data.__class__.__name__
|
||||||
ai_data = json.loads(data.model_dump_json())
|
# Clean for potential XSS hidden in the additional information fields.
|
||||||
|
ai_data = json.loads(nh3.clean(data.model_dump_json()).strip())
|
||||||
ai_data["uuid"] = f"{uuid4()}"
|
ai_data["uuid"] = f"{uuid4()}"
|
||||||
|
|
||||||
if cls_name not in self.additional_information:
|
if cls_name not in self.additional_information:
|
||||||
@ -3438,7 +3566,8 @@ class Scenario(EnviPathModel):
|
|||||||
new_ais = defaultdict(list)
|
new_ais = defaultdict(list)
|
||||||
for k, vals in data.items():
|
for k, vals in data.items():
|
||||||
for v in vals:
|
for v in vals:
|
||||||
ai_data = json.loads(v.model_dump_json())
|
# Clean for potential XSS hidden in the additional information fields.
|
||||||
|
ai_data = json.loads(nh3.clean(v.model_dump_json()).strip())
|
||||||
if hasattr(v, "uuid"):
|
if hasattr(v, "uuid"):
|
||||||
ai_data["uuid"] = str(v.uuid)
|
ai_data["uuid"] = str(v.uuid)
|
||||||
else:
|
else:
|
||||||
@ -3495,7 +3624,7 @@ class Setting(EnviPathModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
rule_packages = models.ManyToManyField(
|
rule_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Setting Rule Packages",
|
verbose_name="Setting Rule Packages",
|
||||||
related_name="setting_rule_packages",
|
related_name="setting_rule_packages",
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any, Callable, List, Optional
|
from typing import Any, Callable, List, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from celery.utils.functional import LRUCache
|
from celery.utils.functional import LRUCache
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from epdb.logic import SPathway
|
from epdb.logic import SPathway
|
||||||
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
|
from epdb.models import Edge, EPModel, JobLog, Node, Pathway, Rule, Setting, User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def get_ml_model(model_pk: int):
|
def get_ml_model(model_pk: int):
|
||||||
if model_pk not in ML_CACHE:
|
if model_pk not in ML_CACHE:
|
||||||
@ -29,7 +32,7 @@ def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
|
|||||||
log.task_id = uuid4()
|
log.task_id = uuid4()
|
||||||
log.job_name = job.__name__
|
log.job_name = job.__name__
|
||||||
log.status = "SUCCESS"
|
log.status = "SUCCESS"
|
||||||
log.done_at = datetime.now()
|
log.done_at = timezone.now()
|
||||||
log.task_result = str(x) if x else None
|
log.task_result = str(x) if x else None
|
||||||
log.save()
|
log.save()
|
||||||
|
|
||||||
|
|||||||
15
epdb/urls.py
@ -48,6 +48,7 @@ urlpatterns = [
|
|||||||
re_path(r"^user$", v.users, name="users"),
|
re_path(r"^user$", v.users, name="users"),
|
||||||
re_path(r"^group$", v.groups, name="groups"),
|
re_path(r"^group$", v.groups, name="groups"),
|
||||||
re_path(r"^search$", v.search, name="search"),
|
re_path(r"^search$", v.search, name="search"),
|
||||||
|
re_path(r"^predict$", v.predict_pathway, name="predict_pathway"),
|
||||||
# User Detail
|
# User Detail
|
||||||
re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"),
|
re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"),
|
||||||
# Group Detail
|
# Group Detail
|
||||||
@ -141,6 +142,11 @@ urlpatterns = [
|
|||||||
v.package_pathway,
|
v.package_pathway,
|
||||||
name="package pathway detail",
|
name="package pathway detail",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^package/(?P<package_uuid>{UUID})/predict$",
|
||||||
|
v.package_predict_pathway,
|
||||||
|
name="package predict pathway",
|
||||||
|
),
|
||||||
# Pathway Nodes
|
# Pathway Nodes
|
||||||
re_path(
|
re_path(
|
||||||
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$",
|
rf"^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$",
|
||||||
@ -193,4 +199,13 @@ urlpatterns = [
|
|||||||
re_path(r"^jobs", v.jobs, name="jobs"),
|
re_path(r"^jobs", v.jobs, name="jobs"),
|
||||||
# OAuth Stuff
|
# OAuth Stuff
|
||||||
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
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"),
|
||||||
]
|
]
|
||||||
|
|||||||
368
epdb/views.py
@ -1,11 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Any
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
import nh3
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from envipy_additional_information import NAME_MAPPING
|
from envipy_additional_information import NAME_MAPPING
|
||||||
@ -14,44 +15,46 @@ from oauth2_provider.decorators import protected_resource
|
|||||||
from utilities.chem import FormatConverter, IndigoUtils
|
from utilities.chem import FormatConverter, IndigoUtils
|
||||||
from utilities.decorators import package_permission_required
|
from utilities.decorators import package_permission_required
|
||||||
from utilities.misc import HTMLGenerator
|
from utilities.misc import HTMLGenerator
|
||||||
|
|
||||||
from .logic import (
|
from .logic import (
|
||||||
|
EPDBURLParser,
|
||||||
GroupManager,
|
GroupManager,
|
||||||
PackageManager,
|
PackageManager,
|
||||||
UserManager,
|
|
||||||
SettingManager,
|
|
||||||
SearchManager,
|
SearchManager,
|
||||||
EPDBURLParser,
|
SettingManager,
|
||||||
|
UserManager,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
Package,
|
APIToken,
|
||||||
GroupPackagePermission,
|
|
||||||
Group,
|
|
||||||
CompoundStructure,
|
|
||||||
Compound,
|
Compound,
|
||||||
|
CompoundStructure,
|
||||||
|
Edge,
|
||||||
|
EnviFormer,
|
||||||
|
EnzymeLink,
|
||||||
|
EPModel,
|
||||||
|
ExternalDatabase,
|
||||||
|
ExternalIdentifier,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
|
JobLog,
|
||||||
|
License,
|
||||||
|
MLRelativeReasoning,
|
||||||
|
Node,
|
||||||
|
Pathway,
|
||||||
|
Permission,
|
||||||
Reaction,
|
Reaction,
|
||||||
Rule,
|
Rule,
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
EPModel,
|
|
||||||
EnviFormer,
|
|
||||||
MLRelativeReasoning,
|
|
||||||
RuleBasedRelativeReasoning,
|
RuleBasedRelativeReasoning,
|
||||||
Scenario,
|
Scenario,
|
||||||
SimpleAmbitRule,
|
SimpleAmbitRule,
|
||||||
APIToken,
|
|
||||||
UserPackagePermission,
|
|
||||||
Permission,
|
|
||||||
License,
|
|
||||||
User,
|
User,
|
||||||
Edge,
|
UserPackagePermission,
|
||||||
ExternalDatabase,
|
|
||||||
ExternalIdentifier,
|
|
||||||
EnzymeLink,
|
|
||||||
JobLog,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def log_post_params(request):
|
def log_post_params(request):
|
||||||
if s.DEBUG:
|
if s.DEBUG:
|
||||||
@ -59,6 +62,26 @@ def log_post_params(request):
|
|||||||
logger.debug(f"{k}\t{v}")
|
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):
|
def error(request, message: str, detail: str, code: int = 400):
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
error_context = {
|
error_context = {
|
||||||
@ -73,6 +96,48 @@ def error(request, message: str, detail: str, code: int = 400):
|
|||||||
return render(request, "errors/error.html", context, status=code)
|
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):
|
def login(request):
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
@ -82,10 +147,13 @@ def login(request):
|
|||||||
return render(request, "static/login.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth import login
|
|
||||||
|
username = request.POST.get("username").strip()
|
||||||
|
if username != request.POST.get("username"):
|
||||||
|
context["message"] = "Login failed!"
|
||||||
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
username = request.POST.get("username")
|
|
||||||
password = request.POST.get("password")
|
password = request.POST.get("password")
|
||||||
|
|
||||||
# Get email for username and check if the account is active
|
# Get email for username and check if the account is active
|
||||||
@ -100,6 +168,7 @@ def login(request):
|
|||||||
except get_user_model().DoesNotExist:
|
except get_user_model().DoesNotExist:
|
||||||
context["message"] = "Login failed!"
|
context["message"] = "Login failed!"
|
||||||
return render(request, "static/login.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = authenticate(username=email, password=password)
|
user = authenticate(username=email, password=password)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -137,9 +206,14 @@ def register(request):
|
|||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
context["title"] = "enviPath"
|
# Redirect to unified login page with signup tab
|
||||||
context["next"] = request.GET.get("next", "")
|
next_url = request.GET.get("next", "")
|
||||||
return render(request, "static/register.html", context)
|
redirect_url = reverse("login") + "#signup"
|
||||||
|
|
||||||
|
if next_url:
|
||||||
|
redirect_url += f"?next={next_url}"
|
||||||
|
|
||||||
|
return redirect(redirect_url)
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
context["title"] = "enviPath"
|
context["title"] = "enviPath"
|
||||||
if next := request.POST.get("next"):
|
if next := request.POST.get("next"):
|
||||||
@ -152,18 +226,18 @@ def register(request):
|
|||||||
|
|
||||||
if not (username and email and password):
|
if not (username and email and password):
|
||||||
context["message"] = "Invalid username/email/password"
|
context["message"] = "Invalid username/email/password"
|
||||||
return render(request, "static/register.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
if password != rpassword or password == "":
|
if password != rpassword or password == "":
|
||||||
context["message"] = "Registration failed, provided passwords differ!"
|
context["message"] = "Registration failed, provided passwords differ!"
|
||||||
return render(request, "static/register.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
u = UserManager.create_user(username, email, password)
|
u = UserManager.create_user(username, email, password)
|
||||||
logger.info(f"Created user {u.username} ({u.pk})")
|
logger.info(f"Created user {u.username} ({u.pk})")
|
||||||
except Exception:
|
except Exception:
|
||||||
context["message"] = "Registration failed! Couldn't create User Account."
|
context["message"] = "Registration failed! Couldn't create User Account."
|
||||||
return render(request, "static/register.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
if s.ADMIN_APPROVAL_REQUIRED:
|
if s.ADMIN_APPROVAL_REQUIRED:
|
||||||
context["success_message"] = (
|
context["success_message"] = (
|
||||||
@ -180,8 +254,8 @@ def register(request):
|
|||||||
|
|
||||||
|
|
||||||
def editable(request, user):
|
def editable(request, user):
|
||||||
if user.is_superuser:
|
# if user.is_superuser:
|
||||||
return True
|
# return True
|
||||||
|
|
||||||
url = request.build_absolute_uri(request.path)
|
url = request.build_absolute_uri(request.path)
|
||||||
if PackageManager.is_package_url(url):
|
if PackageManager.is_package_url(url):
|
||||||
@ -351,6 +425,34 @@ def index(request):
|
|||||||
return render(request, "index/index.html", context)
|
return render(request, "index/index.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def predict_pathway(request):
|
||||||
|
"""Top-level predict pathway view using user's default package."""
|
||||||
|
if request.method != "GET":
|
||||||
|
return HttpResponseNotAllowed(["GET"])
|
||||||
|
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Predict Pathway"
|
||||||
|
context["meta"]["current_package"] = context["meta"]["user"].default_package
|
||||||
|
|
||||||
|
return render(request, "predict_pathway.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@package_permission_required()
|
||||||
|
def package_predict_pathway(request, package_uuid):
|
||||||
|
"""Package-specific predict pathway view."""
|
||||||
|
if request.method != "GET":
|
||||||
|
return HttpResponseNotAllowed(["GET"])
|
||||||
|
|
||||||
|
current_user = _anonymous_or_real(request)
|
||||||
|
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
|
||||||
|
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = f"enviPath - {current_package.name} - Predict Pathway"
|
||||||
|
context["meta"]["current_package"] = current_package
|
||||||
|
|
||||||
|
return render(request, "predict_pathway.html", context)
|
||||||
|
|
||||||
|
|
||||||
def packages(request):
|
def packages(request):
|
||||||
current_user = _anonymous_or_real(request)
|
current_user = _anonymous_or_real(request)
|
||||||
|
|
||||||
@ -670,7 +772,7 @@ def search(request):
|
|||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
package_urls = request.GET.getlist("packages")
|
package_urls = request.GET.getlist("packages")
|
||||||
searchterm = request.GET.get("search")
|
searchterm = request.GET.get("search", "").strip()
|
||||||
mode = request.GET.get("mode")
|
mode = request.GET.get("mode")
|
||||||
|
|
||||||
# add HTTP_ACCEPT check to differentiate between index and ajax call
|
# add HTTP_ACCEPT check to differentiate between index and ajax call
|
||||||
@ -833,7 +935,7 @@ def package_models(request, package_uuid):
|
|||||||
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
|
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
|
||||||
)
|
)
|
||||||
|
|
||||||
from .tasks import dispatch, build_model
|
from .tasks import build_model, dispatch
|
||||||
|
|
||||||
dispatch(current_user, build_model, mod.pk)
|
dispatch(current_user, build_model, mod.pk)
|
||||||
|
|
||||||
@ -867,9 +969,10 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
if classify:
|
if classify:
|
||||||
from epdb.tasks import dispatch_eager, predict_simple
|
from epdb.tasks import dispatch_eager, predict_simple
|
||||||
|
|
||||||
res = dispatch_eager(current_user, predict_simple, current_model.pk, stand_smiles)
|
pred_res = dispatch_eager(
|
||||||
|
current_user, predict_simple, current_model.pk, stand_smiles
|
||||||
|
)
|
||||||
|
|
||||||
pred_res = current_model.predict(stand_smiles)
|
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
for pr in pred_res:
|
for pr in pred_res:
|
||||||
@ -932,12 +1035,24 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
]
|
]
|
||||||
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
||||||
|
|
||||||
|
return redirect(current_model.url)
|
||||||
|
elif hidden == "retrain":
|
||||||
|
from .tasks import dispatch, retrain
|
||||||
|
|
||||||
|
dispatch(current_user, retrain, current_model.pk)
|
||||||
|
|
||||||
return redirect(current_model.url)
|
return redirect(current_model.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
else:
|
else:
|
||||||
name = request.POST.get("model-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
description = request.POST.get("model-description", "").strip()
|
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()
|
||||||
|
|
||||||
if any([name, description]):
|
if any([name, description]):
|
||||||
if name:
|
if name:
|
||||||
@ -1017,9 +1132,7 @@ def package(request, package_uuid):
|
|||||||
return redirect(s.SERVER_URL + "/package")
|
return redirect(s.SERVER_URL + "/package")
|
||||||
elif hidden == "publish-package":
|
elif hidden == "publish-package":
|
||||||
for g in Group.objects.filter(public=True):
|
for g in Group.objects.filter(public=True):
|
||||||
PackageManager.update_permissions(
|
PackageManager.grant_read(current_user, current_package, g)
|
||||||
current_user, current_package, g, Permission.READ[0]
|
|
||||||
)
|
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
elif hidden == "copy":
|
elif hidden == "copy":
|
||||||
object_to_copy = request.POST.get("object_to_copy")
|
object_to_copy = request.POST.get("object_to_copy")
|
||||||
@ -1039,17 +1152,23 @@ def package(request, package_uuid):
|
|||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
# TODO: Move cleaning to property updater
|
||||||
new_package_name = request.POST.get("package-name")
|
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")
|
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")
|
grantee_url = request.POST.get("grantee")
|
||||||
read = request.POST.get("read") == "on"
|
read = request.POST.get("read") == "on"
|
||||||
write = request.POST.get("write") == "on"
|
write = request.POST.get("write") == "on"
|
||||||
owner = request.POST.get("owner") == "on"
|
owner = request.POST.get("owner") == "on"
|
||||||
|
|
||||||
license = request.POST.get("license")
|
cc_string = request.POST.get("license")
|
||||||
license_link = request.POST.get("license-link")
|
|
||||||
license_image_link = request.POST.get("license-image-link")
|
|
||||||
|
|
||||||
if new_package_name:
|
if new_package_name:
|
||||||
current_package.name = new_package_name
|
current_package.name = new_package_name
|
||||||
@ -1077,24 +1196,15 @@ def package(request, package_uuid):
|
|||||||
|
|
||||||
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
elif license is not None:
|
|
||||||
if license == "no-license":
|
|
||||||
if current_package.license is not None:
|
|
||||||
current_package.license.delete()
|
|
||||||
|
|
||||||
|
elif cc_string is not None:
|
||||||
|
cc_string = cc_string.strip()
|
||||||
|
if cc_string == "no-license": # Reset the package's license
|
||||||
current_package.license = None
|
current_package.license = None
|
||||||
current_package.save()
|
current_package.save()
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
else:
|
else: # Get the license and assign it to the package
|
||||||
if current_package.license is not None:
|
current_package.license = License.objects.get(cc_string=cc_string)
|
||||||
current_package.license.delete()
|
|
||||||
|
|
||||||
license = License()
|
|
||||||
license.link = license_link
|
|
||||||
license.image_link = license_image_link
|
|
||||||
license.save()
|
|
||||||
|
|
||||||
current_package.license = license
|
|
||||||
current_package.save()
|
current_package.save()
|
||||||
|
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
@ -1202,8 +1312,16 @@ def package_compound(request, package_uuid, compound_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_compound.url})
|
return JsonResponse({"success": current_compound.url})
|
||||||
|
|
||||||
new_compound_name = request.POST.get("compound-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
new_compound_description = request.POST.get("compound-description", "").strip()
|
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()
|
||||||
|
|
||||||
if new_compound_name:
|
if new_compound_name:
|
||||||
current_compound.name = new_compound_name
|
current_compound.name = new_compound_name
|
||||||
@ -1339,8 +1457,16 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u
|
|||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
new_structure_name = request.POST.get("compound-structure-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
new_structure_description = request.POST.get("compound-structure-description", "").strip()
|
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()
|
||||||
|
|
||||||
if new_structure_name:
|
if new_structure_name:
|
||||||
current_structure.name = new_structure_name
|
current_structure.name = new_structure_name
|
||||||
@ -1547,8 +1673,14 @@ def package_rule(request, package_uuid, rule_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_rule.url})
|
return JsonResponse({"success": current_rule.url})
|
||||||
|
|
||||||
rule_name = request.POST.get("rule-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
rule_description = request.POST.get("rule-description", "").strip()
|
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()
|
||||||
|
|
||||||
if rule_name:
|
if rule_name:
|
||||||
current_rule.name = rule_name
|
current_rule.name = rule_name
|
||||||
@ -1638,7 +1770,6 @@ def package_reactions(request, package_uuid):
|
|||||||
reaction_name = request.POST.get("reaction-name")
|
reaction_name = request.POST.get("reaction-name")
|
||||||
reaction_description = request.POST.get("reaction-description")
|
reaction_description = request.POST.get("reaction-description")
|
||||||
reactions_smirks = request.POST.get("reaction-smirks")
|
reactions_smirks = request.POST.get("reaction-smirks")
|
||||||
|
|
||||||
educts = reactions_smirks.split(">>")[0].split(".")
|
educts = reactions_smirks.split(">>")[0].split(".")
|
||||||
products = reactions_smirks.split(">>")[1].split(".")
|
products = reactions_smirks.split(">>")[1].split(".")
|
||||||
|
|
||||||
@ -1699,8 +1830,16 @@ def package_reaction(request, package_uuid, reaction_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_reaction.url})
|
return JsonResponse({"success": current_reaction.url})
|
||||||
|
|
||||||
new_reaction_name = request.POST.get("reaction-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
new_reaction_description = request.POST.get("reaction-description", "").strip()
|
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()
|
||||||
|
|
||||||
if new_reaction_name:
|
if new_reaction_name:
|
||||||
current_reaction.name = new_reaction_name
|
current_reaction.name = new_reaction_name
|
||||||
@ -1777,8 +1916,9 @@ def package_pathways(request, package_uuid):
|
|||||||
|
|
||||||
name = request.POST.get("name")
|
name = request.POST.get("name")
|
||||||
description = request.POST.get("description")
|
description = request.POST.get("description")
|
||||||
pw_mode = request.POST.get("predict", "predict").strip()
|
|
||||||
smiles = request.POST.get("smiles", "").strip()
|
smiles = request.POST.get("smiles", "").strip()
|
||||||
|
pw_mode = request.POST.get("predict", "predict").strip()
|
||||||
|
|
||||||
if "smiles" in request.POST and smiles == "":
|
if "smiles" in request.POST and smiles == "":
|
||||||
return error(
|
return error(
|
||||||
@ -1787,8 +1927,6 @@ def package_pathways(request, package_uuid):
|
|||||||
"Pathway prediction failed due to missing or empty SMILES",
|
"Pathway prediction failed due to missing or empty SMILES",
|
||||||
)
|
)
|
||||||
|
|
||||||
smiles = smiles.strip()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stand_smiles = FormatConverter.standardize(smiles)
|
stand_smiles = FormatConverter.standardize(smiles)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -1947,8 +2085,14 @@ def package_pathway(request, package_uuid, pathway_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_pathway.url})
|
return JsonResponse({"success": current_pathway.url})
|
||||||
|
|
||||||
|
# TODO: Move cleaning to property updater
|
||||||
pathway_name = request.POST.get("pathway-name")
|
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")
|
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 any([pathway_name, pathway_description]):
|
||||||
if pathway_name is not None and pathway_name.strip() != "":
|
if pathway_name is not None and pathway_name.strip() != "":
|
||||||
@ -2036,8 +2180,8 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
|
|||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
node_name = request.POST.get("node-name")
|
node_name = request.POST.get("node-name")
|
||||||
node_description = request.POST.get("node-description")
|
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)
|
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
||||||
|
|
||||||
return redirect(current_pathway.url)
|
return redirect(current_pathway.url)
|
||||||
@ -2202,6 +2346,7 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
|
|||||||
|
|
||||||
edge_name = request.POST.get("edge-name")
|
edge_name = request.POST.get("edge-name")
|
||||||
edge_description = request.POST.get("edge-description")
|
edge_description = request.POST.get("edge-description")
|
||||||
|
|
||||||
edge_substrates = request.POST.getlist("edge-substrates")
|
edge_substrates = request.POST.getlist("edge-substrates")
|
||||||
edge_products = request.POST.getlist("edge-products")
|
edge_products = request.POST.getlist("edge-products")
|
||||||
|
|
||||||
@ -2288,7 +2433,7 @@ def package_scenarios(request, package_uuid):
|
|||||||
"all", False
|
"all", False
|
||||||
):
|
):
|
||||||
scens = Scenario.objects.filter(package=current_package).order_by("name")
|
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)
|
return JsonResponse(res, safe=False)
|
||||||
|
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
@ -2326,9 +2471,9 @@ def package_scenarios(request, package_uuid):
|
|||||||
context["unreviewed_objects"] = unreviewed_scenario_qs
|
context["unreviewed_objects"] = unreviewed_scenario_qs
|
||||||
|
|
||||||
from envipy_additional_information import (
|
from envipy_additional_information import (
|
||||||
|
SEDIMENT_ADDITIONAL_INFORMATION,
|
||||||
SLUDGE_ADDITIONAL_INFORMATION,
|
SLUDGE_ADDITIONAL_INFORMATION,
|
||||||
SOIL_ADDITIONAL_INFORMATION,
|
SOIL_ADDITIONAL_INFORMATION,
|
||||||
SEDIMENT_ADDITIONAL_INFORMATION,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
context["scenario_types"] = {
|
context["scenario_types"] = {
|
||||||
@ -2336,21 +2481,21 @@ def package_scenarios(request, package_uuid):
|
|||||||
"name": "soil",
|
"name": "soil",
|
||||||
"widgets": [
|
"widgets": [
|
||||||
HTMLGenerator.generate_html(ai, prefix=f"soil_{0}")
|
HTMLGenerator.generate_html(ai, prefix=f"soil_{0}")
|
||||||
for ai in [x for s in SOIL_ADDITIONAL_INFORMATION.values() for x in s]
|
for ai in [x for sv in SOIL_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"Sludge Data": {
|
"Sludge Data": {
|
||||||
"name": "sludge",
|
"name": "sludge",
|
||||||
"widgets": [
|
"widgets": [
|
||||||
HTMLGenerator.generate_html(ai, prefix=f"sludge_{0}")
|
HTMLGenerator.generate_html(ai, prefix=f"sludge_{0}")
|
||||||
for ai in [x for s in SLUDGE_ADDITIONAL_INFORMATION.values() for x in s]
|
for ai in [x for sv in SLUDGE_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"Water-Sediment System Data": {
|
"Water-Sediment System Data": {
|
||||||
"name": "sediment",
|
"name": "sediment",
|
||||||
"widgets": [
|
"widgets": [
|
||||||
HTMLGenerator.generate_html(ai, prefix=f"sediment_{0}")
|
HTMLGenerator.generate_html(ai, prefix=f"sediment_{0}")
|
||||||
for ai in [x for s in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in s]
|
for ai in [x for sv in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -2365,6 +2510,7 @@ def package_scenarios(request, package_uuid):
|
|||||||
|
|
||||||
scenario_name = request.POST.get("scenario-name")
|
scenario_name = request.POST.get("scenario-name")
|
||||||
scenario_description = request.POST.get("scenario-description")
|
scenario_description = request.POST.get("scenario-description")
|
||||||
|
|
||||||
scenario_date_year = request.POST.get("scenario-date-year")
|
scenario_date_year = request.POST.get("scenario-date-year")
|
||||||
scenario_date_month = request.POST.get("scenario-date-month")
|
scenario_date_month = request.POST.get("scenario-date-month")
|
||||||
scenario_date_day = request.POST.get("scenario-date-day")
|
scenario_date_day = request.POST.get("scenario-date-day")
|
||||||
@ -2378,9 +2524,9 @@ def package_scenarios(request, package_uuid):
|
|||||||
scenario_type = request.POST.get("scenario-type")
|
scenario_type = request.POST.get("scenario-type")
|
||||||
|
|
||||||
additional_information = HTMLGenerator.build_models(request.POST.dict())
|
additional_information = HTMLGenerator.build_models(request.POST.dict())
|
||||||
additional_information = [x for s in additional_information.values() for x in s]
|
additional_information = [x for sv in additional_information.values() for x in sv]
|
||||||
|
|
||||||
s = Scenario.create(
|
new_scen = Scenario.create(
|
||||||
current_package,
|
current_package,
|
||||||
name=scenario_name,
|
name=scenario_name,
|
||||||
description=scenario_description,
|
description=scenario_description,
|
||||||
@ -2389,7 +2535,7 @@ def package_scenarios(request, package_uuid):
|
|||||||
additional_information=additional_information,
|
additional_information=additional_information,
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect(s.url)
|
return redirect(new_scen.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseNotAllowed(
|
return HttpResponseNotAllowed(
|
||||||
[
|
[
|
||||||
@ -2689,6 +2835,7 @@ def settings(request):
|
|||||||
|
|
||||||
name = request.POST.get("prediction-setting-name")
|
name = request.POST.get("prediction-setting-name")
|
||||||
description = request.POST.get("prediction-setting-description")
|
description = request.POST.get("prediction-setting-description")
|
||||||
|
|
||||||
new_default = request.POST.get("prediction-setting-new-default", "off") == "on"
|
new_default = request.POST.get("prediction-setting-new-default", "off") == "on"
|
||||||
|
|
||||||
max_nodes = min(
|
max_nodes = min(
|
||||||
@ -2839,3 +2986,60 @@ def userinfo(request):
|
|||||||
"email_verified": user.is_active,
|
"email_verified": user.is_active,
|
||||||
}
|
}
|
||||||
return JsonResponse(res)
|
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,24 +1,21 @@
|
|||||||
import gzip
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.http import HttpResponseNotAllowed
|
from django.http import HttpResponseNotAllowed
|
||||||
from django.shortcuts import render
|
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 import Chem
|
||||||
from rdkit.Chem.MolStandardize import rdMolStandardize
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def normalize_smiles(smiles):
|
def normalize_smiles(smiles):
|
||||||
m1 = Chem.MolFromSmiles(smiles)
|
m1 = Chem.MolFromSmiles(smiles)
|
||||||
@ -59,9 +56,7 @@ def run_both_engines(SMILES, SMIRKS):
|
|||||||
set(
|
set(
|
||||||
[
|
[
|
||||||
normalize_smiles(str(x))
|
normalize_smiles(str(x))
|
||||||
for x in FormatConverter.sanitize_smiles(
|
for x in FormatConverter.sanitize_smiles([str(s) for s in all_rdkit_prods])[0]
|
||||||
[str(s) for s in all_rdkit_prods]
|
|
||||||
)[0]
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -85,8 +80,7 @@ def migration(request):
|
|||||||
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
||||||
)
|
)
|
||||||
ALL_SMILES = [
|
ALL_SMILES = [
|
||||||
cs.smiles
|
cs.smiles for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
||||||
for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
|
||||||
]
|
]
|
||||||
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
||||||
|
|
||||||
@ -142,9 +136,7 @@ def migration(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for r in migration_status["results"]:
|
for r in migration_status["results"]:
|
||||||
r["detail_url"] = r["detail_url"].replace(
|
r["detail_url"] = r["detail_url"].replace("http://localhost:8000", s.SERVER_URL)
|
||||||
"http://localhost:8000", s.SERVER_URL
|
|
||||||
)
|
|
||||||
|
|
||||||
context.update(**migration_status)
|
context.update(**migration_status)
|
||||||
|
|
||||||
@ -152,8 +144,6 @@ def migration(request):
|
|||||||
|
|
||||||
|
|
||||||
def migration_detail(request, package_uuid, rule_uuid):
|
def migration_detail(request, package_uuid, rule_uuid):
|
||||||
current_user = _anonymous_or_real(request)
|
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
@ -235,9 +225,7 @@ def compare(request):
|
|||||||
context["smirks"] = (
|
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"
|
"[#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"] = (
|
context["smiles"] = "C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N"
|
||||||
"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)
|
return render(request, "compare.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
|
|||||||
25
package.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"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
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
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,6 +27,7 @@ dependencies = [
|
|||||||
"scikit-learn>=1.6.1",
|
"scikit-learn>=1.6.1",
|
||||||
"sentry-sdk[django]>=2.32.0",
|
"sentry-sdk[django]>=2.32.0",
|
||||||
"setuptools>=80.8.0",
|
"setuptools>=80.8.0",
|
||||||
|
"nh3==0.3.2",
|
||||||
"polars==1.35.1",
|
"polars==1.35.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -44,6 +45,8 @@ dev = [
|
|||||||
"poethepoet>=0.37.0",
|
"poethepoet>=0.37.0",
|
||||||
"pre-commit>=4.3.0",
|
"pre-commit>=4.3.0",
|
||||||
"ruff>=0.13.3",
|
"ruff>=0.13.3",
|
||||||
|
"pytest-playwright>=0.7.1",
|
||||||
|
"pytest-django>=4.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
@ -65,23 +68,39 @@ docstring-code-format = true
|
|||||||
|
|
||||||
[tool.poe.tasks]
|
[tool.poe.tasks]
|
||||||
# Main tasks
|
# Main tasks
|
||||||
setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
setup = { sequence = [
|
||||||
dev = { cmd = "python manage.py runserver", help = "Start the development server", deps = ["db-up"] }
|
"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" }
|
||||||
|
|
||||||
# Database tasks
|
# Database tasks
|
||||||
db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" }
|
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" }
|
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
|
# 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." }
|
clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." }
|
||||||
|
|
||||||
# Django tasks
|
# Django tasks
|
||||||
migrate = { cmd = "python manage.py migrate", help = "Run database migrations" }
|
migrate = { cmd = "uv run python manage.py migrate", help = "Run database migrations" }
|
||||||
bootstrap = { shell = """
|
bootstrap = { shell = """
|
||||||
echo "Bootstrapping initial data..."
|
echo "Bootstrapping initial data..."
|
||||||
echo "This will take a bit ⏱️. Get yourself some coffee..."
|
echo "This will take a bit ⏱️. Get yourself some coffee..."
|
||||||
python manage.py bootstrap
|
uv run python manage.py bootstrap
|
||||||
echo "✓ Bootstrap complete"
|
echo "✓ Bootstrap complete"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Default admin credentials:"
|
echo "Default admin credentials:"
|
||||||
@ -89,4 +108,16 @@ echo " Username: admin"
|
|||||||
echo " Email: admin@envipath.com"
|
echo " Email: admin@envipath.com"
|
||||||
echo " Password: SuperSafe"
|
echo " Password: SuperSafe"
|
||||||
""", help = "Bootstrap initial data (anonymous user, packages, models)" }
|
""", help = "Bootstrap initial data (anonymous user, packages, models)" }
|
||||||
shell = { cmd = "python manage.py shell", help = "Open Django shell" }
|
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" }
|
||||||
|
|||||||
201
scripts/dev_server.py
Executable file
@ -0,0 +1,201 @@
|
|||||||
|
#!/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()
|
||||||
59
scripts/pnpm_wrapper.py
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
#!/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()
|
||||||
84
static/css/daisyui-theme.css
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
63
static/css/input.css
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
static/css/theme.css
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
BIN
static/images/ep-rule-artwork.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
static/images/hero.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
static/images/linkedin.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
3
static/images/logo-eawag.svg
Normal file
|
After Width: | Height: | Size: 21 KiB |
@ -1,225 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 41 KiB |
9
static/images/logo-mission.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
3
static/images/logo-name.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
3
static/images/logo-square.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
static/images/uoa-logo-small.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 814 B |
265
static/js/alpine/index.js
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}));
|
||||||
|
});
|
||||||
133
static/js/alpine/pagination.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
145
static/js/alpine/search.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
173
static/js/discourse-api.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
1147
static/js/pps.js
229
static/js/pw.js
@ -1,14 +1,21 @@
|
|||||||
console.log("loaded pw.js")
|
console.log("loaded pw.js")
|
||||||
|
|
||||||
function predictFromNode(url) {
|
function predictFromNode(url) {
|
||||||
$.post("", {node: url})
|
fetch("", {
|
||||||
.done(function (data) {
|
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 => {
|
||||||
console.log("Success:", data);
|
console.log("Success:", data);
|
||||||
window.location.href = data.success;
|
window.location.href = data.success;
|
||||||
})
|
})
|
||||||
.fail(function (xhr, status, error) {
|
.catch(error => {
|
||||||
console.error("Error:", xhr.status, xhr.responseText);
|
console.error("Error:", error);
|
||||||
// show user-friendly message or log error
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +110,9 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dragstarted(event, d) {
|
function dragstarted(event, d) {
|
||||||
|
// Prevent zoom pan when dragging nodes
|
||||||
|
event.sourceEvent.stopPropagation();
|
||||||
|
|
||||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||||
d.fx = d.x;
|
d.fx = d.x;
|
||||||
d.fy = d.y;
|
d.fy = d.y;
|
||||||
@ -117,6 +127,9 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dragged(event, d) {
|
function dragged(event, d) {
|
||||||
|
// Prevent zoom pan when dragging nodes
|
||||||
|
event.sourceEvent.stopPropagation();
|
||||||
|
|
||||||
d.fx = event.x;
|
d.fx = event.x;
|
||||||
d.fy = event.y;
|
d.fy = event.y;
|
||||||
|
|
||||||
@ -127,6 +140,9 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dragended(event, d) {
|
function dragended(event, d) {
|
||||||
|
// Prevent zoom pan when dragging nodes
|
||||||
|
event.sourceEvent.stopPropagation();
|
||||||
|
|
||||||
if (!event.active) simulation.alphaTarget(0);
|
if (!event.active) simulation.alphaTarget(0);
|
||||||
|
|
||||||
// Mark that dragging has ended
|
// Mark that dragging has ended
|
||||||
@ -192,52 +208,153 @@ function draw(pathway, elem) {
|
|||||||
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
|
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait one second before showing popup
|
// Wait before showing popup (ms)
|
||||||
var popupWaitBeforeShow = 1000;
|
var popupWaitBeforeShow = 1000;
|
||||||
// Keep Popup at least for one second
|
|
||||||
var popushowAtLeast = 1000;
|
|
||||||
|
|
||||||
function pop_show_e(element) {
|
// Custom popover element
|
||||||
var e = element;
|
let popoverTimeout = null;
|
||||||
setTimeout(function () {
|
|
||||||
if ($(e).is(':hover')) { // if element is still hovered
|
|
||||||
$(e).popover("show");
|
|
||||||
|
|
||||||
// workaround to set fixed positions
|
function createPopover() {
|
||||||
pop = $(e).attr("aria-describedby")
|
const popover = document.createElement('div');
|
||||||
h = $('#' + pop).height();
|
popover.id = 'custom-popover';
|
||||||
$('#' + pop).attr("style", `position: fixed; top: ${clientY - (h / 2.0)}px; left: ${clientX + 10}px; margin: 0px; max-width: 1000px; display: block;`)
|
popover.className = 'fixed z-50';
|
||||||
setTimeout(function () {
|
popover.style.cssText = `
|
||||||
var close = setInterval(function () {
|
background: #ffffff;
|
||||||
if (!$(".popover:hover").length // mouse outside popover
|
border: 1px solid #d1d5db;
|
||||||
&& !$(e).is(':hover')) { // mouse outside element
|
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
$(e).popover('hide');
|
max-width: 320px;
|
||||||
clearInterval(close);
|
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;
|
||||||
}
|
}
|
||||||
}, 100);
|
#custom-popover a {
|
||||||
}, popushowAtLeast);
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}, popupWaitBeforeShow);
|
#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;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pop_add(objects, title, contentFunction) {
|
function pop_add(objects, title, contentFunction) {
|
||||||
objects.attr("id", "pop")
|
objects.each(function (d) {
|
||||||
.attr("data-container", "body")
|
const element = this;
|
||||||
.attr("data-toggle", "popover")
|
|
||||||
.attr("data-placement", "right")
|
|
||||||
.attr("title", title);
|
|
||||||
|
|
||||||
objects.each(function (d, i) {
|
element.addEventListener('mouseenter', () => {
|
||||||
options = {trigger: "manual", html: true, animation: false};
|
if (popoverTimeout) clearTimeout(popoverTimeout);
|
||||||
this_ = this;
|
|
||||||
var p = $(this).popover(options).on("mouseenter", function () {
|
popoverTimeout = setTimeout(() => {
|
||||||
pop_show_e(this);
|
if (element.matches(':hover')) {
|
||||||
|
const content = contentFunction(d);
|
||||||
|
showPopover(element, title, content);
|
||||||
|
}
|
||||||
|
}, popupWaitBeforeShow);
|
||||||
});
|
});
|
||||||
p.on("show.bs.popover", function (e) {
|
|
||||||
// this is to dynamically ajdust the content and bounds of the popup
|
element.addEventListener('mouseleave', () => {
|
||||||
p.attr('data-content', contentFunction(d));
|
if (popoverTimeout) {
|
||||||
p.data("bs.popover").setContent();
|
clearTimeout(popoverTimeout);
|
||||||
p.data("bs.popover").tip().css({"max-width": "1000px"});
|
popoverTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay hide to allow moving to popover
|
||||||
|
setTimeout(() => {
|
||||||
|
const popover = getPopover();
|
||||||
|
if (!popover.matches(':hover') && !element.matches(':hover')) {
|
||||||
|
hidePopover();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -255,7 +372,7 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
popupContent += "<img src='" + n.image + "' width='" + 20 * nodeRadius + "'><br>"
|
popupContent += "<img src='" + n.image + "'><br>"
|
||||||
if (n.scenarios.length > 0) {
|
if (n.scenarios.length > 0) {
|
||||||
popupContent += '<b>Half-lives and related scenarios:</b><br>'
|
popupContent += '<b>Half-lives and related scenarios:</b><br>'
|
||||||
for (var s of n.scenarios) {
|
for (var s of n.scenarios) {
|
||||||
@ -265,7 +382,7 @@ function draw(pathway, elem) {
|
|||||||
|
|
||||||
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
|
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
|
||||||
if (pathway.isIncremental && isLeaf) {
|
if (pathway.isIncremental && isLeaf) {
|
||||||
popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
|
popupContent += '<br><a class="btn btn-primary btn-sm mt-2" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return popupContent;
|
return popupContent;
|
||||||
@ -285,7 +402,7 @@ function draw(pathway, elem) {
|
|||||||
popupContent += adcontent;
|
popupContent += adcontent;
|
||||||
}
|
}
|
||||||
|
|
||||||
popupContent += "<img src='" + e.image + "' width='" + 20 * nodeRadius + "'><br>"
|
popupContent += "<img src='" + e.image + "'><br>"
|
||||||
if (e.reaction_probability) {
|
if (e.reaction_probability) {
|
||||||
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
|
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
|
||||||
}
|
}
|
||||||
@ -308,6 +425,23 @@ function draw(pathway, elem) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const zoomable = d3.select("#zoomable");
|
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
|
// Zoom Funktion aktivieren
|
||||||
const zoom = d3.zoom()
|
const zoom = d3.zoom()
|
||||||
@ -316,7 +450,12 @@ function draw(pathway, elem) {
|
|||||||
zoomable.attr("transform", event.transform);
|
zoomable.attr("transform", event.transform);
|
||||||
});
|
});
|
||||||
|
|
||||||
d3.select("svg").call(zoom);
|
// 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);
|
||||||
|
|
||||||
nodes = pathway['nodes'];
|
nodes = pathway['nodes'];
|
||||||
links = pathway['links'];
|
links = pathway['links'];
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_compound_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Compound</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_compound_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Compound</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_compound_structure_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_compound_structure_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_edge_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Edge</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_edge_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Edge</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,4 +1,8 @@
|
|||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_group_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Group</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_group_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Group</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_model_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Model</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Model</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_node_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Node</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Node</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,12 +1,25 @@
|
|||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_package_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Package</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Package</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#import_package_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('import_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#import_legacy_package_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-import"></span> Import Package from legacy JSON</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
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#predict_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
|
href="{% if meta.current_package %}{{ meta.current_package.url }}/predict{% else %}{{ meta.server_url }}/predict{% endif %}"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Pathway</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_reaction_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Reaction</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_reaction_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Reaction</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_rule_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Rule</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_rule_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Rule</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_scenario_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Scenario</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span> New Scenario</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_setting_modal">
|
<a
|
||||||
<span class="glyphicon glyphicon-plus"></span>New Setting</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_setting_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<span class="glyphicon glyphicon-plus"></span>New Setting</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,32 +1,60 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_compound_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_compound_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#add_structure_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add Structure</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('add_structure_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Add Structure</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,22 +1,42 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_compound_structure_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_compound_structure_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,14 +1,26 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,10 +1,18 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_group_member_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Group</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Group</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,18 +1,34 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_model_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Model</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Model</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#evaluate_model_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('evaluate_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#retrain_model_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('retrain_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Model</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Model</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,18 +1,34 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_node_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Node</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Node</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Node</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Node</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,26 +1,50 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_package_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Package</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Package</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_package_permissions_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_package_permissions_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#publish_package_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('publish_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#export_package_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('export_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_license_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> License</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_license_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-duplicate"></i> License</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Package</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Package</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,59 +1,104 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#add_pathway_node_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add Compound</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('add_pathway_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Add Compound</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('add_pathway_edge_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#download_pathway_csv_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</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>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#download_pathway_image_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</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>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#identify_missing_rules_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing Rules</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('identify_missing_rules_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing
|
||||||
|
Rules</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('edit_pathway_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
|
>
|
||||||
</li>
|
</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 role="separator" class="divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#delete_pathway_node_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#delete_pathway_edge_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,28 +1,52 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_reaction_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,24 +1,44 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_rule_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_rule_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,14 +1,26 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#add_additional_information_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('add_additional_information_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#update_scenario_additional_information_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('update_scenario_additional_information_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,22 +1,38 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_user_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-edit"></i> Update</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_user_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-edit"></i> Update</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_password_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-lock"></i> Update Password</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_password_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-lock"></i> Update Password</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_prediction_setting_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a>
|
role="button"
|
||||||
|
onclick="document.getElementById('new_prediction_setting_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{# <li>#}
|
{# <li>#}
|
||||||
{# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
|
{# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
|
||||||
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
|
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
|
||||||
{# </li>#}
|
{# </li>#}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Account</a>
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Delete Account</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1,12 +1,17 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div id="searchContent">
|
||||||
<div id=searchContent>
|
|
||||||
<form id="admin-form" action="{{ SERVER_BASE }}/admin" method="post">
|
<form id="admin-form" action="{{ SERVER_BASE }}/admin" method="post">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="textarea">Query</label>
|
<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#>
|
PREFIX pps: <http://localhost:8080/vocabulary#>
|
||||||
SELECT ?name (count(?objId) as ?xcnt)
|
SELECT ?name (count(?objId) as ?xcnt)
|
||||||
WHERE {
|
WHERE {
|
||||||
@ -15,32 +20,29 @@ WHERE {
|
|||||||
?packageId pps:reviewStatus 'reviewed' .
|
?packageId pps:reviewStatus 'reviewed' .
|
||||||
?packageId pps:pathway ?objId .
|
?packageId pps:pathway ?objId .
|
||||||
} GROUP BY ?name
|
} GROUP BY ?name
|
||||||
</textarea>
|
</textarea
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<button id="submit" type="button" class="btn btn-primary">Submit</button>
|
<button id="submit" type="button" class="btn btn-primary">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
<div id="results">
|
<div id="results"></div>
|
||||||
</div>
|
|
||||||
<div id="loading"></div>
|
<div id="loading"></div>
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
$('#submit').on('click', function() {
|
$("#submit").on("click", function () {
|
||||||
|
|
||||||
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
|
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"query": $("#textarea").val()
|
query: $("#textarea").val(),
|
||||||
}
|
};
|
||||||
|
|
||||||
$.post("{{ SERVER_BASE }}/expire", data, function (result) {
|
$.post("{{ SERVER_BASE }}/expire", data, function (result) {
|
||||||
$("#loading").empty();
|
$("#loading").empty();
|
||||||
queryResultToTable("results", result);
|
queryResultToTable("results", result);
|
||||||
})
|
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,49 +1,52 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load envipytags %}
|
{% load envipytags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="space-y-2 p-4">
|
||||||
<div class="panel-group" id="reviewListAccordion">
|
<!-- Header Section -->
|
||||||
<div class="panel panel-default">
|
<div class="card bg-base-100">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="card-body">
|
||||||
Jobs
|
<h2 class="card-title text-2xl">User Prediction Jobs</h2>
|
||||||
|
<p class="mt-2">Job Logs Desc</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
|
||||||
<p>
|
|
||||||
Job Logs Desc
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<!-- Jobs -->
|
||||||
<h4 class="panel-title">
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
<a id="job-accordion-link" data-toggle="collapse" data-parent="#job-accordion" href="#jobs">
|
<input type="checkbox" checked />
|
||||||
Jobs
|
<div class="collapse-title text-xl font-medium">Recent Jobs</div>
|
||||||
</a>
|
<div class="collapse-content" id="job-content">
|
||||||
</h4>
|
<div class="overflow-x-auto">
|
||||||
</div>
|
<table class="table-zebra table">
|
||||||
<div id="jobs"
|
<thead>
|
||||||
class="panel-collapse collapse in">
|
<tr>
|
||||||
<div class="panel-body list-group-item" id="job-content">
|
<th>ID</th>
|
||||||
<table class="table table-bordered table-hover">
|
<th>Name</th>
|
||||||
<tr style="background-color: rgba(0, 0, 0, 0.08);">
|
<th>Status</th>
|
||||||
<th scope="col">ID</th>
|
<th>Queued</th>
|
||||||
<th scope="col">Name</th>
|
<th>Done</th>
|
||||||
<th scope="col">Status</th>
|
<th>Result</th>
|
||||||
<th scope="col">Queued</th>
|
|
||||||
<th scope="col">Done</th>
|
|
||||||
<th scope="col">Result</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for job in jobs %}
|
{% for job in jobs %}
|
||||||
<tr>
|
<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.task_id }}</td>
|
||||||
<td>{{ job.job_name }}</td>
|
<td>{{ job.job_name }}</td>
|
||||||
<td>{{ job.status }}</td>
|
<td>{{ job.status }}</td>
|
||||||
<td>{{ job.created }}</td>
|
<td>{{ job.created }}</td>
|
||||||
<td>{{ job.done_at }}</td>
|
<td>{{ job.done_at }}</td>
|
||||||
{% if job.task_result and job.task_result|is_url == True %}
|
{% if job.task_result and job.task_result|is_url == True %}
|
||||||
<td><a href="{{ job.task_result }}">Result</a></td>
|
<td>
|
||||||
|
<a href="{{ job.task_result }}" class="link link-primary"
|
||||||
|
>Result</a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
{% elif job.task_result %}
|
{% elif job.task_result %}
|
||||||
<td>{{ job.task_result|slice:"40" }}...</td>
|
<td>{{ job.task_result|slice:"40" }}...</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -55,17 +58,31 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if objects %}
|
||||||
<!-- Unreviewable objects such as User / Group / Setting -->
|
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||||
<ul class='list-group'>
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="menu bg-base-200 rounded-box">
|
||||||
{% for obj in objects %}
|
{% for obj in objects %}
|
||||||
{% if object_type == 'user' %}
|
{% if object_type == 'user' %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
|
<li>
|
||||||
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
|
>{{ obj.username }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
<li>
|
||||||
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
|
>{{ obj.name }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,20 +1,32 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if object_type != 'package' %}
|
{# Serialize objects data for Alpine pagination #}
|
||||||
<div>
|
{# prettier-ignore-start #}
|
||||||
<div id="load-all-error" style="display: none;">
|
{# FIXME: This is a hack to get the objects data into the JavaScript code. #}
|
||||||
<div class="alert alert-danger" role="alert">
|
<script>
|
||||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
window.reviewedObjects = [
|
||||||
<span class="sr-only">Error:</span>
|
{% for obj in reviewed_objects %}
|
||||||
Getting objects failed!
|
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
||||||
</div>
|
{% endfor %}
|
||||||
</div>
|
];
|
||||||
|
window.unreviewedObjects = [
|
||||||
|
{% for obj in unreviewed_objects %}
|
||||||
|
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
{# prettier-ignore-end #}
|
||||||
|
|
||||||
<input type="text" id="object-search" class="form-control" placeholder="Search by name"
|
{% if object_type != 'package' %}
|
||||||
style="display: none;">
|
<div class="px-8 py-4">
|
||||||
<p></p>
|
<input
|
||||||
|
type="text"
|
||||||
|
id="object-search"
|
||||||
|
class="input input-bordered hidden w-full max-w-xs"
|
||||||
|
placeholder="Search by name"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -48,9 +60,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
|
|
||||||
<div class="panel-group" id="reviewListAccordion">
|
<div class="px-8 py-4">
|
||||||
<div class="panel panel-default">
|
<!-- Header Section -->
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="card bg-base-100">
|
||||||
|
<div class="card-body px-0 py-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="card-title text-2xl">
|
||||||
{% if object_type == 'package' %}
|
{% if object_type == 'package' %}
|
||||||
Packages
|
Packages
|
||||||
{% elif object_type == 'compound' %}
|
{% elif object_type == 'compound' %}
|
||||||
@ -78,13 +93,31 @@
|
|||||||
{% elif object_type == 'group' %}
|
{% elif object_type == 'group' %}
|
||||||
Groups
|
Groups
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="actionsButton"
|
</h2>
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
<div id="actionsButton" class="dropdown dropdown-end hidden">
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
|
||||||
aria-haspopup="true" aria-expanded="false"><span
|
<svg
|
||||||
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
style="padding-right:1em"></span></a>
|
width="16"
|
||||||
<ul id="actionsList" class="dropdown-menu">
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-wrench"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Actions
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
tabindex="-1"
|
||||||
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
||||||
|
>
|
||||||
{% block actions %}
|
{% block actions %}
|
||||||
{% if object_type == 'package' %}
|
{% if object_type == 'package' %}
|
||||||
{% include "actions/collections/package.html" %}
|
{% include "actions/collections/package.html" %}
|
||||||
@ -115,205 +148,386 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="mt-2">
|
||||||
<!-- Set Text above links -->
|
<!-- Set Text above links -->
|
||||||
{% if object_type == 'package' %}
|
{% if object_type == 'package' %}
|
||||||
<p>A package contains pathways, rules, etc. and can reflect specific experimental
|
<p>
|
||||||
conditions. <a target="_blank" href="https://wiki.envipath.org/index.php/packages" role="button">Learn
|
A package contains pathways, rules, etc. and can reflect specific
|
||||||
more >></a></p>
|
experimental conditions.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/packages"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'compound' %}
|
{% elif object_type == 'compound' %}
|
||||||
<p>A compound stores the structure of a molecule and can include meta-information.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/compounds" role="button">Learn more
|
A compound stores the structure of a molecule and can include
|
||||||
>></a></p>
|
meta-information.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/compounds"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'structure' %}
|
{% elif object_type == 'structure' %}
|
||||||
<p>The structures stored in this compound
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/compounds" role="button">Learn more
|
The structures stored in this compound
|
||||||
>></a></p>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/compounds"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'rule' %}
|
{% elif object_type == 'rule' %}
|
||||||
<p>A rule describes a biotransformation reaction template that is defined as SMIRKS.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/Rules" role="button">Learn more
|
A rule describes a biotransformation reaction template that is
|
||||||
>></a></p>
|
defined as SMIRKS.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/Rules"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'reaction' %}
|
{% elif object_type == 'reaction' %}
|
||||||
<p>A reaction is a specific biotransformation from educt compounds to product compounds.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/reactions" role="button">Learn more
|
A reaction is a specific biotransformation from educt compounds to
|
||||||
>></a></p>
|
product compounds.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/reactions"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'pathway' %}
|
{% elif object_type == 'pathway' %}
|
||||||
<p>A pathway displays the (predicted) biodegradation of a compound as graph.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/pathways" role="button">Learn more
|
A pathway displays the (predicted) biodegradation of a compound as
|
||||||
>></a></p>
|
graph.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/pathways"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'node' %}
|
{% elif object_type == 'node' %}
|
||||||
<p>Nodes represent the (predicted) compounds in a graph.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/nodes" role="button">Learn more
|
Nodes represent the (predicted) compounds in a graph.
|
||||||
>></a></p>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/nodes"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'edge' %}
|
{% elif object_type == 'edge' %}
|
||||||
<p>Edges represent the links between Nodes in a graph
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/edges" role="button">Learn more
|
Edges represent the links between Nodes in a graph
|
||||||
>></a></p>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/edges"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'scenario' %}
|
{% elif object_type == 'scenario' %}
|
||||||
<p>A scenario contains meta-information that can be attached to other data (compounds, rules, ..).
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/scenarios" role="button">Learn more
|
A scenario contains meta-information that can be attached to other
|
||||||
>></a></p>
|
data (compounds, rules, ..).
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/scenarios"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'model' %}
|
{% elif object_type == 'model' %}
|
||||||
<p>A model applies machine learning to limit the combinatorial explosion.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/relative_reasoning" role="button">Learn
|
A model applies machine learning to limit the combinatorial
|
||||||
more
|
explosion.
|
||||||
>></a></p>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/relative_reasoning"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'setting' %}
|
{% elif object_type == 'setting' %}
|
||||||
<p>A setting includes configuration parameters for pathway predictions.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/settings" role="button">Learn more
|
A setting includes configuration parameters for pathway
|
||||||
>></a></p>
|
predictions.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/settings"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'user' %}
|
{% elif object_type == 'user' %}
|
||||||
<p>Register now to create own packages and to submit and manage your data.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/users" role="button">Learn more
|
Register now to create own packages and to submit and manage your
|
||||||
>></a></p>
|
data.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/users"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% elif object_type == 'group' %}
|
{% elif object_type == 'group' %}
|
||||||
<p>Users can team up in groups to share packages.
|
<p>
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/groups" role="button">Learn more
|
Users can team up in groups to share packages.
|
||||||
>></a></p>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/groups"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- If theres nothing to show extend the text above -->
|
<!-- If theres nothing to show extend the text above -->
|
||||||
{% if reviewed_objects and unreviewed_objects %}
|
{% if reviewed_objects and unreviewed_objects %}
|
||||||
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
|
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
|
||||||
<p>Nothing found. There are two possible reasons: <br><br>1. There is no content yet.<br>2. You have no
|
<p class="mt-4">
|
||||||
reading permissions.<br><br>Please be sure you have at least reading permissions.</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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lists Container - Full Width with Reviewed on Right -->
|
||||||
|
<div class="w-full">
|
||||||
{% if reviewed_objects %}
|
{% if reviewed_objects %}
|
||||||
{% if reviewed_objects|length > 0 %}
|
{% if reviewed_objects|length > 0 %}
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<!-- Reviewed -->
|
||||||
<h4 class="panel-title">
|
<div
|
||||||
<a id="ReviewedLink" data-toggle="collapse" data-parent="#reviewListAccordion"
|
class="collapse-arrow bg-base-200 collapse order-2 w-full"
|
||||||
href="#Reviewed">Reviewed</a>
|
x-data="paginatedList(window.reviewedObjects || [], { isReviewed: true, instanceId: 'reviewed' })"
|
||||||
</h4>
|
>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Reviewed
|
||||||
|
<span
|
||||||
|
class="badge badge-sm badge-neutral ml-2"
|
||||||
|
x-text="totalItems"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="Reviewed" class="panel-collapse collapse in">
|
<div class="collapse-content w-full">
|
||||||
<div class="panel-body list-group-item" id="ReviewedContent">
|
<ul class="menu bg-base-100 rounded-box w-full">
|
||||||
{% if object_type == 'package' %}
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||||
{% for obj in reviewed_objects %}
|
<li>
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}
|
<a :href="obj.url" class="hover:bg-base-200">
|
||||||
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
<span x-text="obj.name"></span>
|
||||||
style="float:right" data-toggle="tooltip"
|
<span
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
class="tooltip tooltip-left ml-auto"
|
||||||
|
data-tip="Reviewed"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-star"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
</li>
|
||||||
{% else %}
|
</template>
|
||||||
{% for obj in reviewed_objects|slice:":50" %}
|
</ul>
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}{# <i>({{ obj.package.name }})</i> #}
|
<!-- Pagination Controls -->
|
||||||
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
<div
|
||||||
style="float:right" data-toggle="tooltip"
|
x-show="totalPages > 1"
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
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>
|
</span>
|
||||||
</a>
|
<div class="join">
|
||||||
{% endfor %}
|
<button
|
||||||
{% endif %}
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === 1"
|
||||||
|
@click="prevPage()"
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
<template x-for="item in pageNumbers" :key="item.key">
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:class="{ 'btn-active': item.page === currentPage }"
|
||||||
|
:disabled="item.isEllipsis"
|
||||||
|
@click="!item.isEllipsis && goToPage(item.page)"
|
||||||
|
x-text="item.page"
|
||||||
|
></button>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === totalPages"
|
||||||
|
@click="nextPage()"
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if unreviewed_objects %}
|
{% if unreviewed_objects %}
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"><h4
|
<!-- Unreviewed -->
|
||||||
class="panel-title"><a id="UnreviewedLink" data-toggle="collapse" data-parent="#unReviewListAccordion"
|
<div
|
||||||
href="#Unreviewed">Unreviewed</a></h4></div>
|
class="collapse-arrow bg-base-200 collapse order-1 w-full"
|
||||||
<div id="Unreviewed" class="panel-collapse collapse {% if reviewed_objects|length == 0 or object_type == 'package' %}in{% endif %}">
|
x-data="paginatedList(window.unreviewedObjects || [], { isReviewed: false, instanceId: 'unreviewed' })"
|
||||||
<div class="panel-body list-group-item" id="UnreviewedContent">
|
>
|
||||||
{% if object_type == 'package' %}
|
<input
|
||||||
{% for obj in unreviewed_objects %}
|
type="checkbox"
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
{% if reviewed_objects|length == 0 or object_type == 'package' %}checked{% endif %}
|
||||||
{% endfor %}
|
/>
|
||||||
{% else %}
|
<div class="collapse-title text-xl font-medium">
|
||||||
{% for obj in unreviewed_objects|slice:":50" %}
|
Unreviewed
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
<span
|
||||||
{% endfor %}
|
class="badge badge-sm badge-neutral ml-2"
|
||||||
{% endif %}
|
x-text="totalItems"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content w-full">
|
||||||
|
<ul class="menu bg-base-100 rounded-box w-full">
|
||||||
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
:href="obj.url"
|
||||||
|
class="hover:bg-base-200"
|
||||||
|
x-text="obj.name"
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div
|
||||||
|
x-show="totalPages > 1"
|
||||||
|
class="mt-4 flex items-center justify-between px-2"
|
||||||
|
>
|
||||||
|
<span class="text-base-content/70 text-sm">
|
||||||
|
Showing <span x-text="showingStart"></span>-<span
|
||||||
|
x-text="showingEnd"
|
||||||
|
></span>
|
||||||
|
of <span x-text="totalItems"></span>
|
||||||
|
</span>
|
||||||
|
<div class="join">
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === 1"
|
||||||
|
@click="prevPage()"
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
<template x-for="item in pageNumbers" :key="item.key">
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:class="{ 'btn-active': item.page === currentPage }"
|
||||||
|
:disabled="item.isEllipsis"
|
||||||
|
@click="!item.isEllipsis && goToPage(item.page)"
|
||||||
|
x-text="item.page"
|
||||||
|
></button>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === totalPages"
|
||||||
|
@click="nextPage()"
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if objects %}
|
{% if objects %}
|
||||||
<!-- Unreviewable objects such as User / Group / Setting -->
|
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||||
<ul class='list-group'>
|
<div class="card bg-base-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="menu bg-base-200 rounded-box">
|
||||||
{% for obj in objects %}
|
{% for obj in objects %}
|
||||||
{% if object_type == 'user' %}
|
{% if object_type == 'user' %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
|
<li>
|
||||||
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
|
>{{ obj.username }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
<li>
|
||||||
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
|
>{{ obj.name }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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>
|
<script>
|
||||||
$(function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
// Show actions button if there are actions
|
||||||
$('#object-search').show();
|
const actionsButton = document.getElementById("actionsButton");
|
||||||
|
const actionsList = actionsButton?.querySelector("ul");
|
||||||
{% if object_type != 'package' and object_type != 'user' and object_type != 'group' %}
|
if (actionsList && actionsList.children.length > 0) {
|
||||||
{% if reviewed_objects|length > 50 or unreviewed_objects|length > 50 %}
|
actionsButton?.classList.remove("hidden");
|
||||||
$('#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>');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#load-all-loading').hide();
|
// Show search input and connect to Alpine pagination
|
||||||
$('#load-remaining').hide();
|
const objectSearch = document.getElementById("object-search");
|
||||||
}).fail(function (resp) {
|
if (objectSearch) {
|
||||||
$('#load-all-loading').hide();
|
objectSearch.classList.remove("hidden");
|
||||||
$('#load-all-error').show();
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}, 2500);
|
// Delete form submit handler
|
||||||
{% endif %}
|
const deleteSubmit = document.getElementById("modal-form-delete-submit");
|
||||||
{% endif %}
|
const deleteForm = document.getElementById("modal-form-delete");
|
||||||
|
if (deleteSubmit && deleteForm) {
|
||||||
$('#modal-form-delete-submit').on('click', function (e) {
|
deleteSubmit.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#modal-form-delete').submit();
|
deleteForm.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>
|
</script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -4,16 +4,32 @@
|
|||||||
<div>
|
<div>
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="text" class="form-control" id="smiles" name="smiles" placeholder="SMILES"
|
<input
|
||||||
value="{{ smiles }}"/>
|
type="text"
|
||||||
<input type="text" class="form-control" id="smiles" name="smirks" placeholder="SMIRKS"
|
class="form-control"
|
||||||
value="{{ smirks }}"/>
|
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>
|
<button type="submit" class="btn btn-primary">Test</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if result %}
|
{% if result %}
|
||||||
{{ smiles }}<p></p>
|
{{ smiles }}
|
||||||
<img width='400' src='{% url 'depict' %}?smiles={{ smiles|urlencode }}'><br>
|
<p></p>
|
||||||
|
<img
|
||||||
|
width="400"
|
||||||
|
src="{% url 'depict' %}?smiles={{ smiles|urlencode }}"
|
||||||
|
/><br />
|
||||||
<p></p>
|
<p></p>
|
||||||
{% if rule %}
|
{% if rule %}
|
||||||
{{ smirks }}
|
{{ smirks }}
|
||||||
@ -22,9 +38,7 @@
|
|||||||
<p></p>
|
<p></p>
|
||||||
{{ rule.products_smarts }}
|
{{ rule.products_smarts }}
|
||||||
<p></p>
|
<p></p>
|
||||||
<div>
|
<div>{{ rule.as_svg|safe }}</div>
|
||||||
{{ rule.as_svg|safe }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>Diff</h2>
|
<h2>Diff</h2>
|
||||||
{% if diff %}
|
{% if diff %}
|
||||||
@ -38,16 +52,22 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2>Ambit</h2>
|
<h2>Ambit</h2>
|
||||||
{% for p in ambit_res %}
|
{% for p in ambit_res %}
|
||||||
{{ p }}<br>
|
{{ p }}<br />
|
||||||
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
<img
|
||||||
|
width="400"
|
||||||
|
src="{% url 'depict' %}?smiles={{ p|urlencode }}"
|
||||||
|
/><br />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2>RDKit</h2>
|
<h2>RDKit</h2>
|
||||||
{% for p in rdkit_res %}
|
{% for p in rdkit_res %}
|
||||||
{{ p }}<br>
|
{{ p }}<br />
|
||||||
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
|
<img
|
||||||
|
width="400"
|
||||||
|
src="{% url 'depict' %}?smiles={{ p|urlencode }}"
|
||||||
|
/><br />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,15 +1,77 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<div class="alert alert-error" role="alert">
|
<div class="w-full max-w-2xl">
|
||||||
<h4 class="alert-heading">Bad Request!</h4>
|
<div class="alert alert-error mb-6 shadow-lg">
|
||||||
<p>Lorem</p>
|
<svg
|
||||||
<hr>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<p class="mb-0">
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
You can find out more about permissions in our <a target="_blank"
|
fill="none"
|
||||||
href="https://wiki.envipath.org/index.php/packages"
|
viewBox="0 0 24 24"
|
||||||
role="button">Wiki >></a></p>
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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
|
||||||
|
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>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,15 +1,80 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<div class="alert alert-error" role="alert">
|
<div class="w-full max-w-2xl">
|
||||||
<h4 class="alert-heading">Access Denied!</h4>
|
<div class="alert alert-warning mb-6 shadow-lg">
|
||||||
<p>Access to X denied. </p>
|
<svg
|
||||||
<hr>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<p class="mb-0">
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
You can find out more about permissions in our <a target="_blank"
|
fill="none"
|
||||||
href="https://wiki.envipath.org/index.php/packages"
|
viewBox="0 0 24 24"
|
||||||
role="button">Wiki >></a></p>
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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
|
||||||
|
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>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,15 +1,77 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<div class="alert alert-error" role="alert">
|
<div class="w-full max-w-2xl">
|
||||||
<h4 class="alert-heading">Not Found!</h4>
|
<div class="alert alert-info mb-6 shadow-lg">
|
||||||
<p>Does not exist</p>
|
<svg
|
||||||
<hr>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<p class="mb-0">
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
You can find out more about permissions in our <a target="_blank"
|
fill="none"
|
||||||
href="https://wiki.envipath.org/index.php/packages"
|
viewBox="0 0 24 24"
|
||||||
role="button">Wiki >></a></p>
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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
|
||||||
|
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>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,13 +1,76 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="w-full max-w-2xl">
|
||||||
<h4 class="alert-heading">{{ error_message }}</h4>
|
<div class="alert alert-error mb-6 shadow-lg">
|
||||||
<hr>
|
<svg
|
||||||
<p class="mb-0">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
{{ error_detail }}
|
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." }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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 %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,12 +1,81 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="w-full max-w-2xl">
|
||||||
<h4 class="alert-heading">Your account has not been activated yet!</h4>
|
<div class="alert alert-warning mb-6 shadow-lg">
|
||||||
<p>Your account has not been activated yet. If you have questions <a href="mailto:admin@envipath.org">contact
|
<svg
|
||||||
us.</a>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</p>
|
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>
|
</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 %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,28 +1,52 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html data-theme="envipath">
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<head>
|
<head>
|
||||||
<title>{{ title }}</title>
|
<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>
|
<style>
|
||||||
html, body {
|
/* Ensure proper viewport behavior */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
height: 100%; /* ensure body fills viewport */
|
height: 100%; /* ensure body fills viewport */
|
||||||
overflow-x: hidden; /* prevent horizontal scroll */
|
overflow-x: hidden; /* prevent horizontal scroll */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{# 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>
|
<script>
|
||||||
const csrftoken = document.querySelector('[name=csrf-token]').content;
|
const csrftoken = document.querySelector("[name=csrf-token]").content;
|
||||||
|
|
||||||
// Setup CSRF header for all jQuery AJAX requests
|
// Setup CSRF header for all jQuery AJAX requests
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
@ -30,17 +54,15 @@
|
|||||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
|
||||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{# Favicon #}
|
|
||||||
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
|
|
||||||
<!-- {# C3 CSS #}-->
|
<!-- {# C3 CSS #}-->
|
||||||
<!-- <link id="css-c3" href="{% static 'css/c3.css' %}" rel="stylesheet" type="text/css"/>-->
|
<!-- <link id="css-c3" href="{% static 'css/c3.css' %}" rel="stylesheet" type="text/css"/>-->
|
||||||
<!-- {# EP CSS #}-->
|
<!-- {# EP CSS #}-->
|
||||||
<!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>-->
|
<!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>-->
|
||||||
|
|
||||||
|
|
||||||
{# General EP JS #}
|
{# General EP JS #}
|
||||||
<script src="{% static 'js/pps.js' %}"></script>
|
<script src="{% static 'js/pps.js' %}"></script>
|
||||||
{# Modal Steps for Stepwise Modal Wizards #}
|
{# Modal Steps for Stepwise Modal Wizards #}
|
||||||
@ -49,26 +71,33 @@
|
|||||||
{% if not debug %}
|
{% if not debug %}
|
||||||
<!-- Matomo -->
|
<!-- Matomo -->
|
||||||
<script>
|
<script>
|
||||||
var _paq = window._paq = window._paq || [];
|
var _paq = (window._paq = window._paq || []);
|
||||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
_paq.push(['trackPageView']);
|
_paq.push(["trackPageView"]);
|
||||||
_paq.push(['enableLinkTracking']);
|
_paq.push(["enableLinkTracking"]);
|
||||||
(function () {
|
(function () {
|
||||||
var u = "//matomo.envipath.com/";
|
var u = "//matomo.envipath.com/";
|
||||||
_paq.push(['setTrackerUrl', u + 'matomo.php']);
|
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||||
_paq.push(['setSiteId', '{{ meta.site_id }}']);
|
_paq.push(["setSiteId", "{{ meta.site_id }}"]);
|
||||||
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
var d = document,
|
||||||
|
g = d.createElement("script"),
|
||||||
|
s = d.getElementsByTagName("script")[0];
|
||||||
g.async = true;
|
g.async = true;
|
||||||
g.src = u + 'matomo.js';
|
g.src = u + "matomo.js";
|
||||||
s.parentNode.insertBefore(g, s);
|
s.parentNode.insertBefore(g, s);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<!-- End Matomo Code -->
|
<!-- End Matomo Code -->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-default navbar-inverse" style="border-radius:0px;" role="navigation">
|
<!-- Legacy Bootstrap navbar - isolated from Tailwind -->
|
||||||
|
<div class="legacy-bootstrap">
|
||||||
|
<nav
|
||||||
|
class="navbar navbar-default navbar-inverse"
|
||||||
|
style="border-radius:0px;"
|
||||||
|
role="navigation"
|
||||||
|
>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!-- Brand and toggle get grouped for better mobile display -->
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
<div class="navbar-header navbar-header-framework">
|
<div class="navbar-header navbar-header-framework">
|
||||||
@ -79,19 +108,28 @@
|
|||||||
<!-- <span class="icon-bar"></span>-->
|
<!-- <span class="icon-bar"></span>-->
|
||||||
<!-- <span class="icon-bar"></span>-->
|
<!-- <span class="icon-bar"></span>-->
|
||||||
<!-- </button>-->
|
<!-- </button>-->
|
||||||
<a id="pictureLink" href="{{ meta.server_url }}" class="navbar-brand">
|
<a
|
||||||
<img id="image-logo-short-white.svg" src='{% static "/images/logo-short-white.svg" %}' width="100"
|
id="pictureLink"
|
||||||
alt="enviPath">
|
href="{{ meta.server_url }}"
|
||||||
|
class="navbar-brand"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
id="image-logo-short-white.svg"
|
||||||
|
src="{% static "/images/logo-short-white.svg" %}"
|
||||||
|
width="100"
|
||||||
|
alt="enviPath"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
<div
|
||||||
|
class="navbar-collapse collapse-framework navbar-collapse-framework collapse"
|
||||||
|
id="navbarCollapse"
|
||||||
|
>
|
||||||
<ul class="nav navbar-nav navbar-nav-framework">
|
<ul class="nav navbar-nav navbar-nav-framework">
|
||||||
<li>
|
<li>
|
||||||
<a href="#" data-toggle="modal" data-target="#predict_modal">
|
<a href="{{ meta.server_url }}/predict"> Predict Pathway </a>
|
||||||
Predict Pathway
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{# <li class="dropdown">#}
|
{# <li class="dropdown">#}
|
||||||
{# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
|
{# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
|
||||||
@ -108,67 +146,135 @@
|
|||||||
{# </li>#}
|
{# </li>#}
|
||||||
{# </ul>#}
|
{# </ul>#}
|
||||||
{# </li>#}
|
{# </li>#}
|
||||||
<li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li>
|
<li>
|
||||||
<li><a href="{{ meta.server_url }}/search" id="searchLink">Search</a></li>
|
<a href="{{ meta.server_url }}/package" id="packageLink"
|
||||||
<li><a href="{{ meta.server_url }}/model" id="modelLink">Modelling</a></li>
|
>Package</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/model" id="modelLink"
|
||||||
|
>Modelling</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Browse Data<b class="caret"></b></a>
|
<a data-toggle="dropdown" class="dropdown-toggle" href="#"
|
||||||
|
>Browse Data<b class="caret"></b
|
||||||
|
></a>
|
||||||
<ul role="menu" class="dropdown-menu">
|
<ul role="menu" class="dropdown-menu">
|
||||||
<li><a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a></li>
|
<li>
|
||||||
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li>
|
<a href="{{ meta.server_url }}/pathway" id="pathwayLink"
|
||||||
<li><a href="{{ meta.server_url }}/compound" id="compoundLink">Compound</a></li>
|
>Pathway</a
|
||||||
<li><a href="{{ meta.server_url }}/reaction" id="reactionLink">Reaction</a></li>
|
>
|
||||||
<li><a href="{{ meta.server_url }}/model" id="relative-reasoningLink">Model</a></li>
|
</li>
|
||||||
<li><a href="{{ meta.server_url }}/scenario" id="scenarioLink">Scenario</a></li>
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/compound" id="compoundLink"
|
||||||
|
>Compound</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/reaction" id="reactionLink"
|
||||||
|
>Reaction</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="{{ meta.server_url }}/model"
|
||||||
|
id="relative-reasoningLink"
|
||||||
|
>Model</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/scenario" id="scenarioLink"
|
||||||
|
>Scenario</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
{# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#}
|
{# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#}
|
||||||
{# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#}
|
{# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#}
|
||||||
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
|
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework">
|
<ul
|
||||||
<li><a href="https://community.envipath.org/" id="communityLink">Community</a></li>
|
class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a href="https://community.envipath.org/" id="communityLink"
|
||||||
|
>Community</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
|
<a data-toggle="dropdown" class="dropdown-toggle" href="#"
|
||||||
|
>Info <b class="caret"></b
|
||||||
|
></a>
|
||||||
<ul role="menu" class="dropdown-menu">
|
<ul role="menu" class="dropdown-menu">
|
||||||
<!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>-->
|
<!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>-->
|
||||||
<li><a href="https://community.envipath.org/t/envipath-license/109" id="licenceLink">Licences</a></li>
|
<li>
|
||||||
<li class="divider"></li>
|
<a
|
||||||
<li><a target="_blank" href="https://wiki.envipath.org/" id="wikiLink">Documentation Wiki</a>
|
href="https://community.envipath.org/t/envipath-license/109"
|
||||||
|
id="licenceLink"
|
||||||
|
>Licences</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#" id="citeButton" data-toggle="modal" data-target="#citemodal">How to cite
|
<li class="divider"></li>
|
||||||
enviPath</a></li>
|
<li>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/"
|
||||||
|
id="wikiLink"
|
||||||
|
>Documentation Wiki</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li><a>Version: {{ meta.version }}</a></li>
|
<li><a>Version: {{ meta.version }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.user.username == 'anonymous' %}
|
{% if meta.user.username == 'anonymous' %}
|
||||||
<li>
|
<li>
|
||||||
<a href="#signup" id="loginButton" data-toggle="modal" data-target="#signupmodal"
|
<a
|
||||||
style="margin-right:10px">Login</a>
|
href="{% url 'login' %}"
|
||||||
|
id="loginButton"
|
||||||
|
style="margin-right:10px"
|
||||||
|
>Login</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown" id="loggedInButton" class="dropdown-toggle" id="logedInButton"
|
<a
|
||||||
href="#">
|
data-toggle="dropdown"
|
||||||
|
id="loggedInButton"
|
||||||
|
class="dropdown-toggle"
|
||||||
|
id="logedInButton"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
<div id="username">
|
<div id="username">
|
||||||
{{ user.username }}<b class="caret"></b>
|
{{ user.username }}<b class="caret"></b>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<ul role="menu" class="dropdown-menu">
|
<ul role="menu" class="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ meta.user.url }}" id="accountbutton">My Account</a>
|
<a href="{{ meta.user.url }}" id="accountbutton"
|
||||||
|
>My Account</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<form class="navbar-form navbar-left navbar-left-framework" role="logout"
|
<form
|
||||||
action="{% url 'logout' %}" method="post">
|
class="navbar-form navbar-left navbar-left-framework"
|
||||||
|
role="logout"
|
||||||
|
action="{% url 'logout' %}"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="hidden" name="logout" value="true">
|
<input type="hidden" name="logout" value="true" />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-default">Logout</button>
|
<button type="submit" class="btn btn-default">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -177,6 +283,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
</div>
|
||||||
|
<!-- End legacy Bootstrap navbar -->
|
||||||
|
|
||||||
<div id="docContent" class="content container">
|
<div id="docContent" class="content container">
|
||||||
{% if breadcrumbs %}
|
{% if breadcrumbs %}
|
||||||
<div id="bread">
|
<div id="bread">
|
||||||
@ -196,24 +305,30 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if message %}
|
{% if message %}
|
||||||
<div id="message">
|
<div id="message">{{ message }}</div>
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% if meta.url_contains_package and meta.current_package.license %}
|
{% if meta.url_contains_package and meta.current_package.license %}
|
||||||
<p></p>
|
<p></p>
|
||||||
<div class="panel-group" id="license_accordion">
|
<div class="panel-group" id="license_accordion">
|
||||||
<div class="panel panel-default list-group-item" style="background-color:#f5f5f5">
|
<div
|
||||||
|
class="panel panel-default list-group-item"
|
||||||
|
style="background-color:#f5f5f5"
|
||||||
|
>
|
||||||
<div class="panel-title">
|
<div class="panel-title">
|
||||||
<a data-toggle="collapse" data-parent="#licence_accordion" href="#license">License</a>
|
<a
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#licence_accordion"
|
||||||
|
href="#license"
|
||||||
|
>License</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="license" class="panel-collapse collapse in">
|
<div id="license" class="panel-collapse in collapse">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<a target="_blank" href="{{ meta.current_package.license.link }}">
|
<a target="_blank" href="{{ meta.current_package.license.link }}">
|
||||||
<img src="{{ meta.current_package.license.image_link }}">
|
<img src="{{ meta.current_package.license.image_link }}" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -221,7 +336,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FOOTER -->
|
<!-- FOOTER - Legacy Bootstrap -->
|
||||||
|
<div class="legacy-bootstrap">
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<hr />
|
<hr />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -229,19 +345,32 @@
|
|||||||
<ul class="nav nav-pills nav-justified">
|
<ul class="nav nav-pills nav-justified">
|
||||||
<li>
|
<li>
|
||||||
<a href="http://ml.auckland.ac.nz" target="_blank">
|
<a href="http://ml.auckland.ac.nz" target="_blank">
|
||||||
<img id="image-uoalogo" height="60" src='{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}'
|
<img
|
||||||
alt="The Univserity of Auckland"/>
|
id="image-uoalogo"
|
||||||
|
height="60"
|
||||||
|
src="{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}"
|
||||||
|
alt="The Univserity of Auckland"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://eawag.ch" target="_blank">
|
<a href="https://eawag.ch" target="_blank">
|
||||||
<img id="image-ealogo" height="60" src='{% static "/images/ealogo.gif" %}' alt="Eawag"/>
|
<img
|
||||||
|
id="image-ealogo"
|
||||||
|
height="60"
|
||||||
|
src="{% static "/images/ealogo.gif" %}"
|
||||||
|
alt="Eawag"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://www.uzh.ch/" target="_blank">
|
<a href="https://www.uzh.ch/" target="_blank">
|
||||||
<img id="image-ufzlogo" height="60" src='{% static "/images/uzh-logo.svg" %}'
|
<img
|
||||||
alt="University of Zurich"/>
|
id="image-ufzlogo"
|
||||||
|
height="60"
|
||||||
|
src="{% static "/images/uzh-logo.svg" %}"
|
||||||
|
alt="University of Zurich"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -252,25 +381,24 @@
|
|||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<ul class="nav nav-pills nav-justified">
|
<ul class="nav nav-pills nav-justified">
|
||||||
<!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>-->
|
<!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>-->
|
||||||
<li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li>
|
<li>
|
||||||
|
<a href="mailto:admin@envipath.org" target="_blank">Contact</a>
|
||||||
|
</li>
|
||||||
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschränkt) & Co. KG ©-->
|
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschränkt) & Co. KG ©-->
|
||||||
<!-- {{ YEAR }}</a></li>-->
|
<!-- {{ YEAR }}</a></li>-->
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End legacy Bootstrap footer -->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
// Hide actionsbutton if theres no action defined
|
// Hide actionsbutton if theres no action defined
|
||||||
if ($('#actionsButton ul').children().length > 0) {
|
if ($("#actionsButton ul").children().length > 0) {
|
||||||
$('#actionsButton').show();
|
$("#actionsButton").show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% block modals %}
|
|
||||||
{% include "modals/cite_modal.html" %}
|
|
||||||
{% include "modals/signup_modal.html" %}
|
|
||||||
{% include "modals/predict_modal.html" %}
|
|
||||||
{% include "modals/batch_predict_modal.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
219
templates/framework_modern.html
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
<!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>
|
||||||
94
templates/includes/footer.html
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{% 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>
|
||||||
277
templates/includes/navbar.html
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
{% 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,186 +1,424 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block main_content %}
|
||||||
<!-- TODO rename ids as well as remove pathways if modal is closed!-->
|
<!-- Hero Section with Logo and Search -->
|
||||||
<div class="modal fade" tabindex="-1" id="foundMatching" role="dialog" aria-labelledby="foundModal"
|
<section class="hero relative mx-auto h-fit w-full max-w-5xl shadow-none">
|
||||||
aria-hidden="true">
|
<div
|
||||||
<div class="modal-dialog">
|
class="hero from-primary-800 to-primary-600 min-h-[calc(100vh*0.4)] bg-gradient-to-br"
|
||||||
<div class="modal-content">
|
style="background-image: url('{% static "/images/hero.png" %}'); background-size: cover; background-position: center;"
|
||||||
<div class="modal-header">
|
>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
<div class="hero-overlay"></div>
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Predict Pathway text over the image -->
|
||||||
<span class="sr-only">Close</span>
|
<div class="absolute bottom-40 left-1/8 -translate-x-8">
|
||||||
</button>
|
<h2 class="text-base-100 text-left text-3xl text-shadow-lg">
|
||||||
<h4 class="modal-title" id="newPackMod">Found Pathway in Database</h4>
|
Predict Your Pathway
|
||||||
</div>
|
</h2>
|
||||||
<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>
|
|
||||||
<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>
|
</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>
|
</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>
|
||||||
<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. The pathway prediction system
|
|
||||||
provides different relative reasoning models to predict likely biotransformation
|
|
||||||
pathways and products. You can try it out below.
|
|
||||||
</p>
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
</div>
|
<fieldset
|
||||||
<div id="loading"></div>
|
class="fieldset overflow-hidden transition-all duration-300 ease-in-out"
|
||||||
<div class="col-xs-4">
|
:class="drawMode ? 'p-4' : 'p-8'"
|
||||||
<d-topics-list discourse-url="https://community.envipath.org" per-page="10" category="10"
|
>
|
||||||
template="complete"></d-topics-list>
|
<form
|
||||||
</div>
|
id="index-form"
|
||||||
</div>
|
action="{{ meta.current_package.url }}/pathway"
|
||||||
<div class="row">
|
method="POST"
|
||||||
<form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST">
|
@submit.prevent="submitForm()"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="input-group" id="index-form-bar">
|
<div
|
||||||
<div class="input-group-btn">
|
id="text-input-container"
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
x-show="!drawMode"
|
||||||
aria-expanded="false">
|
x-transition:enter="transition ease-out duration-300"
|
||||||
<span class="caret"></span>
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
</button>
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
<ul class="dropdown-menu" role="menu">
|
x-transition:leave="transition ease-in duration-300"
|
||||||
<li>
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
<iframe id="index-form-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
height="510"></iframe>
|
>
|
||||||
</li>
|
<div class="join mx-auto w-full">
|
||||||
</ul>
|
<input
|
||||||
</div>
|
type="text"
|
||||||
<input type="text" class="form-control" id='index-form-text-input'
|
id="index-form-text-input"
|
||||||
placeholder="Enter a SMILES to predict a Pathway or type something to search">
|
placeholder="canonical SMILES string"
|
||||||
<div class="input-group-btn">
|
class="input input-md join-item grow"
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
x-model="smiles"
|
||||||
aria-haspopup="true"
|
/>
|
||||||
aria-expanded="false" id="action-button">Predict <span class="caret"></span></button>
|
<button class="btn btn-neutral join-item">Predict!</button>
|
||||||
<ul class="dropdown-menu">
|
</div>
|
||||||
<li><a id="dropdown-predict">Predict</a></li>
|
<div class="label relative mt-1 w-full">
|
||||||
<li><a id="dropdown-search">Search</a></li>
|
<div class="flex gap-2">
|
||||||
</ul>
|
<a
|
||||||
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go!
|
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!
|
||||||
</button>
|
</button>
|
||||||
|
<div class="mt-1 flex w-full justify-end">
|
||||||
|
<a class="label justify-end" href="/predict">Advanced</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="index-form-smiles" name="smiles" value="smiles">
|
<input
|
||||||
<input type="hidden" id="index-form-predict" name="predict" value="predict">
|
type="hidden"
|
||||||
|
id="index-form-smiles"
|
||||||
|
name="smiles"
|
||||||
|
value="smiles"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
id="index-form-predict"
|
||||||
|
name="predict"
|
||||||
|
value="predict"
|
||||||
|
/>
|
||||||
|
<input type="hidden" id="current-action" value="predict" />
|
||||||
</form>
|
</form>
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
</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
|
||||||
|
biotransformation of organic environmental contaminants. The
|
||||||
|
database provides the possibility to store and view experimentally
|
||||||
|
observed biotransformation pathways.
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
|
||||||
<script language="javascript">
|
<script language="javascript">
|
||||||
var currentPackage = "{{ meta.current_package.url }}";
|
var currentPackage = "{{ meta.current_package.url }}";
|
||||||
|
|
||||||
function goButtonClicked() {
|
// Helper function to safely get Ketcher instance from iframe
|
||||||
$(this).prop("disabled", true);
|
function getKetcherInstance(iframeId) {
|
||||||
|
const ketcherFrame = document.getElementById(iframeId);
|
||||||
var action = $('#action-button').text().trim();
|
if (!ketcherFrame) {
|
||||||
|
console.error("Ketcher iframe not found:", iframeId);
|
||||||
var textSmiles = $('#index-form-text-input').val().trim();
|
return null;
|
||||||
|
|
||||||
if (textSmiles === '') {
|
|
||||||
$(this).prop("disabled", false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ketcherSmiles = getKetcher('index-form-ketcher').getSmiles().trim();
|
try {
|
||||||
|
if (
|
||||||
if (action !== 'Search' && ketcherSmiles !== '' && textSmiles !== ketcherSmiles) {
|
"contentWindow" in ketcherFrame &&
|
||||||
console.log("Ketcher and TextInput differ!");
|
ketcherFrame.contentWindow.ketcher
|
||||||
|
) {
|
||||||
|
return ketcherFrame.contentWindow.ketcher;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
"Cannot access Ketcher iframe - possible CORS issue:",
|
||||||
|
err,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === 'Search') {
|
return null;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function actionDropdownClicked() {
|
// Discourse API integration is now handled by discourse-api.js
|
||||||
var suffix = ' <span class="caret"></span>';
|
|
||||||
var dropdownVal = $(this).text();
|
|
||||||
|
|
||||||
if (dropdownVal === 'Search') {
|
// Function to render Discourse topics into cards
|
||||||
$("#index-form").attr("action", '/search');
|
function renderDiscourseTopics(topics) {
|
||||||
$("#index-form").attr("method", 'GET');
|
const container = document.getElementById("community-news-container");
|
||||||
} else {
|
if (!container) return;
|
||||||
$("#index-form").attr("action", currentPackage + "/pathway");
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#action-button').html(dropdownVal + suffix);
|
// Clear container
|
||||||
}
|
container.innerHTML = "";
|
||||||
|
|
||||||
function ketcherToTextInput() {
|
// Create cards for each topic
|
||||||
$('#index-form-text-input').val(this.ketcher.getSmiles());
|
topics.forEach((topic) => {
|
||||||
}
|
const card = createDiscourseCard(topic);
|
||||||
|
container.insertAdjacentHTML("beforeend", card);
|
||||||
$(function () {
|
|
||||||
|
|
||||||
$('#index-form').on("keydown", function (e) {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
goButtonClicked();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Code that should be executed once DOM is ready goes here
|
// Function to create HTML card for a topic
|
||||||
$('#dropdown-predict').on('click', actionDropdownClicked);
|
function createDiscourseCard(topic) {
|
||||||
$('#dropdown-search').on('click', actionDropdownClicked);
|
const date = new Date(topic.created_at).toLocaleDateString();
|
||||||
|
|
||||||
$('#run-button').on('click', goButtonClicked);
|
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>
|
||||||
|
|
||||||
// Update Ketcher Width
|
<div class="flex flex-row items-center justify-between">
|
||||||
var fullWidth = $('#index-form-bar').width();
|
<div class="flex items-center gap-2">
|
||||||
$('#index-form-ketcher').width(fullWidth);
|
<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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// add a listener that gets triggered whenever the structure in ketcher has changed
|
// Make render function globally available
|
||||||
$('#index-form-ketcher').on('load', function () {
|
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 () {
|
||||||
const checkKetcherReady = () => {
|
const checkKetcherReady = () => {
|
||||||
win = this.contentWindow
|
const win = this.contentWindow;
|
||||||
if (win.ketcher && 'editor' in win.ketcher) {
|
if (win.ketcher && "editor" in win.ketcher) {
|
||||||
win.ketcher.editor.event.change.handlers.push({
|
window.indexKetcher = win.ketcher;
|
||||||
once: false,
|
|
||||||
priority: 0,
|
|
||||||
f: ketcherToTextInput,
|
|
||||||
ketcher: win.ketcher
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkKetcherReady, 100);
|
setTimeout(checkKetcherReady, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkKetcherReady();
|
checkKetcherReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock content %}
|
{% endblock main_content %}
|
||||||
|
|||||||
@ -3,7 +3,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="panel-group" id="migration-detail">
|
<div class="panel-group" id="migration-detail">
|
||||||
<div class="panel panel-default">
|
<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
|
Migration Status BT Rules
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
@ -12,33 +16,56 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for obj in results %}
|
{% 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 %}
|
{% if obj.status %}
|
||||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"
|
<span
|
||||||
style="float:right" data-toggle="tooltip"
|
class="glyphicon glyphicon-ok"
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
aria-hidden="true"
|
||||||
|
style="float:right"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-placement="top"
|
||||||
|
title=""
|
||||||
|
data-original-title="Reviewed"
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"
|
<span
|
||||||
style="float:right" data-toggle="tooltip"
|
class="glyphicon glyphicon-remove"
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
aria-hidden="true"
|
||||||
|
style="float:right"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-placement="top"
|
||||||
|
title=""
|
||||||
|
data-original-title="Reviewed"
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
<a
|
||||||
href="#{{ obj.id }}">{{ obj.name }}</a>
|
id="{{ obj.id }}-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#migration-detail"
|
||||||
|
href="#{{ obj.id }}"
|
||||||
|
>{{ obj.name|safe }}</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
<div
|
||||||
|
id="{{ obj.id }}"
|
||||||
|
class="panel-collapse {% if not obj.status %}in{% endif %} collapse"
|
||||||
|
>
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<a class="list-group-item" href="{{ obj.detail_url }}">{{ obj.name }} Migration Detail Page</a>
|
<a class="list-group-item" href="{{ obj.detail_url }}"
|
||||||
|
>{{ obj.name|safe }} Migration Detail Page</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script></script>
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -3,34 +3,68 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="panel-group" id="migration-detail">
|
<div class="panel-group" id="migration-detail">
|
||||||
<div class="panel panel-default">
|
<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 }}
|
Migration Status for {{ bt_rule_name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p>A package contains pathways, rules, etc. and can reflect specific experimental
|
<p>
|
||||||
conditions. <a target="_blank" href="https://wiki.envipath.org/index.php/packages" role="button">Learn
|
A package contains pathways, rules, etc. and can reflect specific
|
||||||
more >></a></p>
|
experimental conditions.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/packages"
|
||||||
|
role="button"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for obj in results %}
|
{% 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 %}
|
{% if obj.status %}
|
||||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"
|
<span
|
||||||
style="float:right" data-toggle="tooltip"
|
class="glyphicon glyphicon-ok"
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
aria-hidden="true"
|
||||||
|
style="float:right"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-placement="top"
|
||||||
|
title=""
|
||||||
|
data-original-title="Reviewed"
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"
|
<span
|
||||||
style="float:right" data-toggle="tooltip"
|
class="glyphicon glyphicon-remove"
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
aria-hidden="true"
|
||||||
|
style="float:right"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-placement="top"
|
||||||
|
title=""
|
||||||
|
data-original-title="Reviewed"
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
<a
|
||||||
href="#{{ obj.id }}">{{ obj.name }}</a>
|
id="{{ obj.id }}-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#migration-detail"
|
||||||
|
href="#{{ obj.id }}"
|
||||||
|
>{{ obj.name|safe }}</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
<div
|
||||||
|
id="{{ obj.id }}"
|
||||||
|
class="panel-collapse {% if not obj.status %}in{% endif %} collapse"
|
||||||
|
>
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<pre>{{ obj.detail }}</pre>
|
<pre>{{ obj.detail }}</pre>
|
||||||
</div>
|
</div>
|
||||||
@ -39,7 +73,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script></script>
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
<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,42 +1,85 @@
|
|||||||
<div class="modal fade" tabindex="-1" id="import_legacy_package_modal" role="dialog"
|
<dialog
|
||||||
aria-labelledby="import_legacy_package_modal" aria-hidden="true">
|
id="import_legacy_package_modal"
|
||||||
<div class="modal-dialog">
|
class="modal"
|
||||||
<div class="modal-content">
|
x-data="modalForm()"
|
||||||
<div class="modal-header">
|
@close="reset()"
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
>
|
||||||
<span aria-hidden="true">×</span>
|
<div class="modal-box">
|
||||||
<span class="sr-only">Close</span>
|
<!-- 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"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">Import Package from legacy System</h4>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<!-- Body -->
|
||||||
<p>Create a Package based on the JSON Export of the legacy system.</p>
|
<div class="py-4">
|
||||||
<form id="import-legacy-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post"
|
<p class="mb-4">
|
||||||
enctype="multipart/form-data">
|
Create a Package based on the JSON Export of the legacy system.
|
||||||
{% csrf_token %}
|
|
||||||
<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>
|
|
||||||
<span class="label label-info" id="upload-legacy-file-info"></span>
|
|
||||||
<input type="hidden" value="import-legacy-package-json" name="hidden" readonly="">
|
|
||||||
</p>
|
</p>
|
||||||
|
<form
|
||||||
|
id="import-legacy-package-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Legacy JSON File</span>
|
||||||
|
</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
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<a id="import-legacy-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a>
|
<!-- Footer -->
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<!-- Backdrop -->
|
||||||
<script>
|
<form method="dialog" class="modal-backdrop">
|
||||||
$(function () {
|
<button :disabled="isSubmitting">close</button>
|
||||||
$('#import-legacy-package-modal-form-submit').on('click', function (e) {
|
</form>
|
||||||
e.preventDefault();
|
</dialog>
|
||||||
$('#import-legacy-package-modal-form').submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,42 +1,83 @@
|
|||||||
<div class="modal fade" tabindex="-1" id="import_package_modal" role="dialog"
|
<dialog
|
||||||
aria-labelledby="import_package_modal" aria-hidden="true">
|
id="import_package_modal"
|
||||||
<div class="modal-dialog">
|
class="modal"
|
||||||
<div class="modal-content">
|
x-data="modalForm()"
|
||||||
<div class="modal-header">
|
@close="reset()"
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
>
|
||||||
<span aria-hidden="true">×</span>
|
<div class="modal-box">
|
||||||
<span class="sr-only">Close</span>
|
<!-- 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"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">Import Package</h4>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<!-- Body -->
|
||||||
<p>Create a Package based on a JSON Export.</p>
|
<div class="py-4">
|
||||||
<form id="import-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post"
|
<p class="mb-4">Create a Package based on a JSON Export.</p>
|
||||||
enctype="multipart/form-data">
|
<form
|
||||||
|
id="import-package-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<div class="form-control">
|
||||||
<label class="btn btn-primary" for="jsonFile">
|
<label class="label">
|
||||||
<input id="jsonFile" name="file" type="file" style="display:none;"
|
<span class="label-text">JSON File</span>
|
||||||
onchange="$('#upload-file-info').html(this.files[0].name)">
|
|
||||||
Choose JSON File
|
|
||||||
</label>
|
</label>
|
||||||
<span class="label label-info" id="upload-file-info"></span>
|
<input
|
||||||
<input type="hidden" value="import-package-json" name="hidden" readonly="">
|
type="file"
|
||||||
</p>
|
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
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<a id="import-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a>
|
<!-- Footer -->
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<!-- Backdrop -->
|
||||||
<script>
|
<form method="dialog" class="modal-backdrop">
|
||||||
$(function () {
|
<button :disabled="isSubmitting">close</button>
|
||||||
$('#import-package-modal-form-submit').on('click', function (e) {
|
</form>
|
||||||
e.preventDefault();
|
</dialog>
|
||||||
$('#import-package-modal-form').submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,78 +1,137 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div class="modal fade bs-modal-lg" id="new_compound_modal" tabindex="-1" aria-labelledby="new_compound_modal" aria-modal="true"
|
|
||||||
role="dialog">
|
<dialog
|
||||||
<div class="modal-dialog modal-lg">
|
id="new_compound_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
@close="reset()"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">Create a new Compound</h4>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<!-- 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">
|
<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"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<label for="compound-name">Name</label>
|
|
||||||
<input id="compound-name" class="form-control" name="compound-name" placeholder="Name"/>
|
<div class="form-control mb-3">
|
||||||
<label for="compound-description">Description</label>
|
<label class="label" for="compound-name">
|
||||||
<input id="compound-description" class="form-control" name="compound-description" placeholder="Description"/>
|
<span class="label-text">Name</span>
|
||||||
<label for="compound-smiles">SMILES</label>
|
</label>
|
||||||
<input type="text" class="form-control" name="compound-smiles" placeholder="SMILES" id="compound-smiles">
|
<input
|
||||||
<p></p>
|
id="compound-name"
|
||||||
<div>
|
class="input input-bordered w-full"
|
||||||
<iframe id="new_compound_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
|
name="compound-name"
|
||||||
height="510"></iframe>
|
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>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
|
<!-- 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>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-primary" id="new_compound_modal_form_submit">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<!-- Backdrop -->
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document
|
||||||
function newCompoundModalketcherToNewCompoundModalTextInput() {
|
.getElementById("new_compound_ketcher")
|
||||||
$('#compound-smiles').val(this.ketcher.getSmiles());
|
.addEventListener("load", function () {
|
||||||
}
|
const iframe = this;
|
||||||
|
|
||||||
$(function() {
|
|
||||||
|
|
||||||
$('#new_compound_ketcher').on('load', function() {
|
|
||||||
const checkKetcherReady = () => {
|
const checkKetcherReady = () => {
|
||||||
win = this.contentWindow
|
const win = iframe.contentWindow;
|
||||||
if (win.ketcher && 'editor' in win.ketcher) {
|
if (win.ketcher && "editor" in win.ketcher) {
|
||||||
win.ketcher.editor.event.change.handlers.push({
|
win.ketcher.editor.event.change.handlers.push({
|
||||||
once: false,
|
once: false,
|
||||||
priority: 0,
|
priority: 0,
|
||||||
f: newCompoundModalketcherToNewCompoundModalTextInput,
|
f: function () {
|
||||||
ketcher: win.ketcher
|
document.getElementById("compound-smiles").value =
|
||||||
|
this.ketcher.getSmiles();
|
||||||
|
},
|
||||||
|
ketcher: win.ketcher,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkKetcherReady, 100);
|
setTimeout(checkKetcherReady, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkKetcherReady();
|
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>
|
</script>
|
||||||
|
|||||||
@ -1,45 +1,96 @@
|
|||||||
<div class="modal fade" tabindex="-1" id="new_group_modal" role="dialog" aria-labelledby="new_group_modal"
|
{% load static %}
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
<dialog
|
||||||
<div class="modal-content">
|
id="new_group_modal"
|
||||||
<div class="modal-header">
|
class="modal"
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
x-data="modalForm()"
|
||||||
<span aria-hidden="true">×</span>
|
@close="reset()"
|
||||||
<span class="sr-only">Close</span>
|
>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">New Group</h4>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<!-- Body -->
|
||||||
<p>Create new Group. You can assign users to the group once
|
<div class="py-4">
|
||||||
it is created. Description can be changed after creation.</p>
|
<p class="mb-4">
|
||||||
<form id="new_group_modal_form" accept-charset="UTF-8" action="{{ SERVER_BASE }}/group"
|
Create new Group. You can assign users to the group once it is created.
|
||||||
data-remote="true"
|
Description can be changed after creation.
|
||||||
method="post">
|
</p>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="new-group-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{{ SERVER_BASE }}/group"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
|
||||||
<label for="name">Name</label>
|
<div class="form-control mb-3">
|
||||||
<input id="name" type="text" name="group-name" class="form-control" placeholder="Name"/>
|
<label class="label" for="group-name">
|
||||||
</p>
|
<span class="label-text">Name</span>
|
||||||
<p>
|
</label>
|
||||||
<label for="description">Description</label>
|
<input
|
||||||
<input id="description" type="text" class="form-control" placeholder="Description..."
|
id="group-name"
|
||||||
name="group-description"/>
|
class="input input-bordered w-full"
|
||||||
</p>
|
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>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<a id="new_group_modal_form_submit" class="btn btn-primary" href="#">Submit</a>
|
<!-- Footer -->
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<!-- Backdrop -->
|
||||||
<script>
|
<form method="dialog" class="modal-backdrop">
|
||||||
$(function() {
|
<button :disabled="isSubmitting">close</button>
|
||||||
$('#new_group_modal_form_submit').on('click', function() {
|
</form>
|
||||||
$('#new_group_modal_form').submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,187 +1,337 @@
|
|||||||
<div class="modal fade" tabindex="-1" id="new_model_modal" role="dialog" aria-labelledby="new_model_modal"
|
<dialog
|
||||||
aria-hidden="true">
|
id="new_model_modal"
|
||||||
<div class="modal-dialog modal-lg">
|
class="modal"
|
||||||
<div class="modal-content">
|
x-data="{
|
||||||
<div class="modal-header">
|
isSubmitting: false,
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
modelType: '',
|
||||||
<span aria-hidden="true">×</span>
|
buildAppDomain: false,
|
||||||
<span class="sr-only">Close</span>
|
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">New Model</h4>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<!-- Body -->
|
||||||
<form id="new_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model"
|
<div class="py-4">
|
||||||
data-remote="true" method="post">
|
<form
|
||||||
|
id="new_model_form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{{ meta.current_package.url }}/model"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="jumbotron">Create a new Model to
|
<div class="alert alert-info mb-4">
|
||||||
limit the number of degradation products in the
|
<span>
|
||||||
prediction. You just need to set a name and the packages
|
Create a new Model to limit the number of degradation products in
|
||||||
you want the object to be based on. There are multiple types of models available.
|
the prediction. You just need to set a name and the packages you
|
||||||
For additional information have a look at our
|
want the object to be based on. There are multiple types of models
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki
|
available. For additional information have a look at our
|
||||||
>></a>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/relative-reasoning"
|
||||||
|
class="link"
|
||||||
|
>wiki >></a
|
||||||
|
>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Name -->
|
<!-- Name -->
|
||||||
<label for="model-name">Name</label>
|
<div class="form-control mb-3">
|
||||||
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
|
<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>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<label for="model-description">Description</label>
|
<div class="form-control mb-3">
|
||||||
<input id="model-description" name="model-description" class="form-control"
|
<label class="label" for="model-description">
|
||||||
placeholder="Description"/>
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="model-description"
|
||||||
|
name="model-description"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
placeholder="Description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Model Type -->
|
<!-- Model Type -->
|
||||||
<label for="model-type">Model Type</label>
|
<div class="form-control mb-3">
|
||||||
<select id="model-type" name="model-type" class="form-control" data-width='100%'>
|
<label class="label" for="model-type">
|
||||||
<option disabled selected>Select Model Type</option>
|
<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>
|
||||||
{% for k, v in model_types.items %}
|
{% for k, v in model_types.items %}
|
||||||
<option value="{{ v }}">{{ k }}</option>
|
<option value="{{ v }}">{{ k }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 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 }}</option>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<option disabled>Unreviewed Packages</option>
|
|
||||||
{% for obj in meta.readable_packages %}
|
|
||||||
{% if not obj.reviewed %}
|
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Data Packages -->
|
<!-- Rule Packages (MLRR, RBRR) -->
|
||||||
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
|
<div class="form-control mb-3" x-show="showMlrr || showRbrr" x-cloak>
|
||||||
<label for="model-data-packages">Data Packages</label>
|
<label class="label" for="model-rule-packages">
|
||||||
<select id="model-data-packages" name="model-data-packages" data-actions-box='true'
|
<span class="label-text">Rule Packages</span>
|
||||||
class="form-control" multiple data-width='100%'>
|
</label>
|
||||||
<option disabled>Reviewed Packages</option>
|
<select
|
||||||
|
id="model-rule-packages"
|
||||||
|
name="model-rule-packages"
|
||||||
|
class="select select-bordered w-full h-32"
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<optgroup label="Reviewed Packages">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
<option disabled>Unreviewed Packages</option>
|
<optgroup label="Unreviewed Packages">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if not obj.reviewed %}
|
{% if not obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fingerprinter -->
|
<!-- Data Packages (MLRR, RBRR, Enviformer) -->
|
||||||
<div id="fingerprinter" class="ep-model-param mlrr">
|
<div
|
||||||
<label for="model-fingerprinter">Fingerprinter</label>
|
class="form-control mb-3"
|
||||||
<select id="model-fingerprinter" name="model-fingerprinter" data-actions-box='true'
|
x-show="showMlrr || showRbrr || showEnviformer"
|
||||||
class="form-control" multiple data-width='100%'>
|
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">
|
||||||
|
{% for obj in meta.readable_packages %}
|
||||||
|
{% if obj.reviewed %}
|
||||||
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Unreviewed Packages">
|
||||||
|
{% for obj in meta.readable_packages %}
|
||||||
|
{% if not obj.reviewed %}
|
||||||
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</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
|
||||||
|
>
|
||||||
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
||||||
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
||||||
<option disabled selected>Select Additional Fingerprinter / Descriptor</option>
|
<optgroup label="Additional Fingerprinter / Descriptor">
|
||||||
{% for k, v in additional_descriptors.items %}
|
{% for k, v in additional_descriptors.items %}
|
||||||
<option value="{{ v }}">{{ k }}</option>
|
<option value="{{ v }}">{{ k }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||||
<!-- 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>
|
|
||||||
|
|
||||||
<div id="appdomain" class="ep-model-param mlrr">
|
|
||||||
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
|
|
||||||
<!-- Build AD? -->
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="build-app-domain" name="build-app-domain">Also build an
|
|
||||||
Applicability Domain?
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="ad-params" style="display:none">
|
|
||||||
<!-- Num Neighbors -->
|
<!-- Threshold (MLRR, Enviformer) -->
|
||||||
<label for="num-neighbors">Number of Neighbors</label>
|
<div
|
||||||
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control"
|
class="form-control mb-3"
|
||||||
value="5"
|
x-show="showMlrr || showEnviformer"
|
||||||
step="1" min="0" max="10">
|
x-cloak
|
||||||
<!-- Local Compatibility -->
|
>
|
||||||
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
|
<label class="label" for="model-threshold">
|
||||||
<input id="local-compatibility-threshold" name="local-compatibility-threshold"
|
<span class="label-text">Threshold</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
class="form-control" value="0.5" step="0.01" min="0" max="1">
|
min="0"
|
||||||
<!-- Reliability -->
|
max="1"
|
||||||
<label for="reliability-threshold">Reliability Threshold</label>
|
step="0.05"
|
||||||
<input id="reliability-threshold" name="reliability-threshold" type="number"
|
value="0.5"
|
||||||
class="form-control" value="0.5" step="0.01" min="0" max="1">
|
id="model-threshold"
|
||||||
|
name="model-threshold"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Applicability Domain (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
|
||||||
|
>
|
||||||
|
</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"
|
||||||
|
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"
|
||||||
|
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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<a id="new_model_modal_form_submit" class="btn btn-primary" href="#">Submit</a>
|
<!-- Footer -->
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<div class="modal-action">
|
||||||
</div>
|
<button
|
||||||
</div>
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
// Built in Model Types
|
<button :disabled="isSubmitting">close</button>
|
||||||
var nativeModelTypes = [
|
</form>
|
||||||
"mlrr",
|
</dialog>
|
||||||
"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,62 +1,93 @@
|
|||||||
<div class="modal fade"
|
{% load static %}
|
||||||
tabindex="-1"
|
|
||||||
|
<dialog
|
||||||
id="new_package_modal"
|
id="new_package_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="new_package_modal"
|
x-data="modalForm()"
|
||||||
aria-hidden="true">
|
@close="reset()"
|
||||||
<div class="modal-dialog">
|
>
|
||||||
<div class="modal-content">
|
<div class="modal-box">
|
||||||
<div class="modal-header">
|
<!-- Header -->
|
||||||
<button type="button"
|
<h3 class="font-bold text-lg">New Package</h3>
|
||||||
class="close"
|
|
||||||
data-dismiss="modal">
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
<span class="sr-only">Close</span>
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">New Package</h4>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<!-- Body -->
|
||||||
<p>Create new package. Description can be changed later.</p>
|
<div class="py-4">
|
||||||
<form id="new_package_modal_form"
|
<p class="mb-4">Create new package. Description can be changed later.</p>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="new-package-modal-form"
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
action=""
|
action=""
|
||||||
data-remote="true"
|
method="post"
|
||||||
method="post">
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
|
||||||
<label for="name">Name</label>
|
<div class="form-control mb-3">
|
||||||
<input id="name" class="form-control"
|
<label class="label" for="package-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="package-name"
|
||||||
|
class="input input-bordered w-full"
|
||||||
name="package-name"
|
name="package-name"
|
||||||
placeholder="Name"/>
|
placeholder="Name"
|
||||||
</p>
|
required
|
||||||
<p>
|
/>
|
||||||
<label for="description">Description</label>
|
</div>
|
||||||
<input id="description"
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="package-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="package-description"
|
||||||
type="text"
|
type="text"
|
||||||
rows="3"
|
class="input input-bordered w-full"
|
||||||
class="form-control"
|
|
||||||
placeholder="Description..."
|
placeholder="Description..."
|
||||||
name="package-description"/>
|
name="package-description"
|
||||||
</p>
|
/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<a id="new_package_modal_form_submit"
|
<!-- 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"
|
class="btn btn-primary"
|
||||||
href="#">Submit</a>
|
@click="submit('new-package-modal-form')"
|
||||||
<button type="button"
|
:disabled="isSubmitting"
|
||||||
class="btn btn-default"
|
>
|
||||||
data-dismiss="modal">Cancel
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<!-- Backdrop -->
|
||||||
<script>
|
<form method="dialog" class="modal-backdrop">
|
||||||
$(function() {
|
<button :disabled="isSubmitting">close</button>
|
||||||
$('#new_package_modal_form_submit').on('click', function (e) {
|
</form>
|
||||||
e.preventDefault();
|
</dialog>
|
||||||
$('#new_package_modal_form').submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,301 +0,0 @@
|
|||||||
{% 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,108 +1,260 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<div id="new_prediction_setting_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="new_prediction_setting_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="{
|
||||||
<h5 class="modal-title">Create a Prediction Setting</h5>
|
isSubmitting: false,
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
tpMethod: '',
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-body">
|
|
||||||
<p>To create a Prediction Setting fill the form below and click "Create"</p>
|
<!-- Body -->
|
||||||
<form id="new-prediction-setting-modal-form" accept-charset="UTF-8" action="" data-remote="true"
|
<div class="py-4">
|
||||||
method="post">
|
<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"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<label for="prediction-setting-name">Name</label>
|
<div class="form-control mb-3">
|
||||||
<input id="prediction-setting-name" name="prediction-setting-name" class="form-control" placeholder="Name"/>
|
<label class="label" for="prediction-setting-name">
|
||||||
<label for="prediction-setting-description">Description</label>
|
<span class="label-text">Name</span>
|
||||||
<input id="prediction-setting-description" name="prediction-setting-description" class="form-control"
|
</label>
|
||||||
placeholder="Description"/>
|
<input
|
||||||
|
id="prediction-setting-name"
|
||||||
|
name="prediction-setting-name"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
placeholder="Name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="prediction-setting-max-nodes">Max #Nodes</label>
|
<div class="form-control mb-3">
|
||||||
<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 class="label" for="prediction-setting-description">
|
||||||
<label for="prediction-setting-max-depth">Max Depth</label>
|
<span class="label-text">Description</span>
|
||||||
<input id="prediction-setting-max-depth" type="number" class="form-control" name="prediction-setting-max-depth" value="5" min="1" max="8" step="1">
|
</label>
|
||||||
|
<input
|
||||||
|
id="prediction-setting-description"
|
||||||
|
name="prediction-setting-description"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
placeholder="Description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="tp-generation-method">TP Generation Method</label>
|
<div class="form-control mb-3">
|
||||||
<select id="tp-generation-method" name="tp-generation-method" class="form-control" data-width='100%'>
|
<label class="label" for="prediction-setting-max-nodes">
|
||||||
<option disabled selected>Select how TPs are generated</option>
|
<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>
|
||||||
<option value="rule-based-prediction-setting">Rule Based</option>
|
<option value="rule-based-prediction-setting">Rule Based</option>
|
||||||
<option value="model-based-prediction-setting">Model Based</option>
|
<option value="model-based-prediction-setting">Model Based</option>
|
||||||
</select>
|
</select>
|
||||||
<div id="rule-based-prediction-setting-specific-form">
|
</div>
|
||||||
<!-- Rule Packages -->
|
|
||||||
<label>Rule Packages</label><br>
|
<!-- Rule Based Settings -->
|
||||||
<select id="rule-based-prediction-setting-packages" name="rule-based-prediction-setting-packages"
|
<div x-show="tpMethod === 'rule-based-prediction-setting'" x-cloak>
|
||||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
<div class="form-control mb-3">
|
||||||
<option disabled>Reviewed Packages</option>
|
<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">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
<option disabled>Unreviewed Packages</option>
|
<optgroup label="Unreviewed Packages">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if not obj.reviewed %}
|
{% if not obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt"
|
||||||
|
>Hold Ctrl/Cmd to select multiple</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="model-based-prediction-setting-specific-form">
|
</div>
|
||||||
<label>Select Model</label><br>
|
|
||||||
<select id="model-based-prediction-setting-model" name="model-based-prediction-setting-model" class="form-control" data-width='100%'>
|
<!-- Model Based Settings -->
|
||||||
<option disabled selected>Select the model</option>
|
<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>
|
||||||
{% for m in models %}
|
{% for m in models %}
|
||||||
<option value="{{ m.url }}">{{ m.name }}</option>
|
<option value="{{ m.url }}">{{ m.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</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>
|
||||||
|
|
||||||
<input class="form-check-input" type="checkbox" value="on" id="prediction-setting-new-default" name="prediction-setting-new-default">
|
<div class="form-control mb-3">
|
||||||
<label class="form-check-label" for="prediction-setting-new-default">Set this setting as new default</label>
|
<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>
|
||||||
|
|
||||||
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
<!-- Footer -->
|
||||||
<button type="button" class="btn btn-primary" id="new-prediction-setting-modal-submit">Create</button>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function() {
|
|
||||||
|
|
||||||
// Initially hide all "specific" forms
|
<!-- Backdrop -->
|
||||||
$("div[id$='-specific-form']").each( function() {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$(this).hide();
|
<button :disabled="isSubmitting">close</button>
|
||||||
});
|
</form>
|
||||||
|
</dialog>
|
||||||
$("#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>
|
|
||||||
|
|||||||
@ -1,50 +1,105 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div class="modal fade bs-modal-lg" id="new_reaction_modal" tabindex="-1" aria-labelledby="new_reaction_modal" aria-modal="true"
|
|
||||||
role="dialog">
|
<dialog
|
||||||
<div class="modal-dialog modal-lg">
|
id="new_reaction_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
@close="reset()"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
|
<div class="modal-box max-w-3xl">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="font-bold text-lg">Create a new Reaction</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">Create a new Reaction</h4>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<!-- Body -->
|
||||||
<form id="new_reaction_modal_form" accept-charset="UTF-8" action="{% url 'package reaction list' meta.current_package.uuid %}" data-remote="true" method="post">
|
<div class="py-4">
|
||||||
|
<form
|
||||||
|
id="new-reaction-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{% url 'package reaction list' meta.current_package.uuid %}"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<label for="reaction-name">Name</label>
|
|
||||||
<input id="reaction-name" class="form-control" name="reaction-name" placeholder="Name"/>
|
<div class="form-control mb-3">
|
||||||
<label for="reaction-description">Description</label>
|
<label class="label" for="reaction-name">
|
||||||
<input id="reaction-description" class="form-control" name="reaction-description" placeholder="Description"/>
|
<span class="label-text">Name</span>
|
||||||
<p></p>
|
</label>
|
||||||
<div>
|
<input
|
||||||
<iframe id="new_reaction_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
|
id="reaction-name"
|
||||||
height="510"></iframe>
|
class="input input-bordered w-full"
|
||||||
|
name="reaction-name"
|
||||||
|
placeholder="Name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="reaction-smirks" id="reaction-smirks">
|
|
||||||
<p></p>
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="reaction-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="reaction-description"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
name="reaction-description"
|
||||||
|
placeholder="Description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<iframe
|
||||||
|
id="new_reaction_ketcher"
|
||||||
|
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
|
width="100%"
|
||||||
|
height="510"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="reaction-smirks" id="reaction-smirks" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
|
<!-- 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="
|
||||||
|
const k = getKetcher('new_reaction_ketcher');
|
||||||
|
document.getElementById('reaction-smirks').value = k.getSmiles();
|
||||||
|
submit('new-reaction-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>
|
</button>
|
||||||
<button type="button" class="btn btn-primary" id="new_reaction_modal_form_submit">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<!-- Backdrop -->
|
||||||
<script>
|
<form method="dialog" class="modal-backdrop">
|
||||||
$(function() {
|
<button :disabled="isSubmitting">close</button>
|
||||||
$('#new_reaction_modal_form_submit').on('click', function(e) {
|
</form>
|
||||||
e.preventDefault();
|
</dialog>
|
||||||
$(this).prop("disabled",true);
|
|
||||||
|
|
||||||
k = getKetcher('new_reaction_ketcher');
|
|
||||||
$('#reaction-smirks').val(k.getSmiles());
|
|
||||||
|
|
||||||
// submit form
|
|
||||||
$('#new_reaction_modal_form').submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||