18 Commits

Author SHA1 Message Date
19c37c2598 fix: remove search 2025-11-13 16:13:16 +13:00
39faab3d11 [Fix] Add extra styles to make show login form (#203)
FIx display on the login page

Reviewed-on: enviPath/enviPy#203
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-13 09:11:32 +13:00
4e80cd63cd [Fix] Added loading of envipytags (#201)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#201
2025-11-13 08:43:21 +13:00
6592f0a68e [Fix] Package Link + Adjusted License container (#197)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#197
2025-11-13 08:35:42 +13:00
21d30a923f [Refactor] Large scale formatting/linting (#193)
All html files now prettier formatted and fixes for incompatible blocks applied

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#193
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 22:47:10 +13:00
12a20756d6 [Fix] Add/Update missing nav links (#196)
Adds packages to the footer (instead of Browse link) and removes search from footer (search page not directly linked anymore).
Packages are also available in Browse now.

This is temporary until there is a proper data browsing page.

Co-authored-by: jebus <lorsbach@envipath.com>
Reviewed-on: enviPath/enviPy#196
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 22:24:33 +13:00
d20a705011 [Feature] Add per-package pathway prediction (#195)
## Major Changes

- Introduces a new view for per-package predictions

Co-authored-by: jebus <lorsbach@envipath.com>
Reviewed-on: enviPath/enviPy#195
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 22:01:34 +13:00
debbef8158 [Enhancement] Cleanup Landing Page Form (#194)
I changed the toggle style to be more self evident.
Do you think this is enough, or should I add an (ugly) label?

![image.png](/attachments/0e4ce043-7544-4852-9db9-460517b36d64)

Co-authored-by: jebus <lorsbach@envipath.com>
Reviewed-on: enviPath/enviPy#194
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 21:42:02 +13:00
2799718951 fix: open and close search modal (#192)
Modal now opens on badge click.
Modal now closes on random click around

Reviewed-on: enviPath/enviPy#192
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 20:53:52 +13:00
305fdc41fb [Fix] Replace datetime.now() with Djangos timezone.now() to get rid of NaiveTimestamp warning (#191)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#191
2025-11-12 11:04:00 +13:00
9deca8867e [Feature] Possibility to Retrain Models (#190)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#190
2025-11-12 10:28:35 +13:00
df6056fb86 [Enhancement] Refactor of license django model (#187)
Fixes #119

Licenses are now created in the bootstrap management command. To only create the licenses use the command line argument `-ol` or `--only-licenses`.
These licenses are then fetched by there `cc_string` when adding a license to a package.

Co-authored-by: Liam Brydon <62733830+MyCreativityOutlet@users.noreply.github.com>
Co-authored-by: jebus <lorsbach@envipath.com>
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#187
Co-authored-by: liambrydon <lbry121@aucklanduni.ac.nz>
Co-committed-by: liambrydon <lbry121@aucklanduni.ac.nz>
2025-11-12 08:20:43 +13:00
c1553d9cd4 [Feature] Add Predict Pathway Page in Modern UI (#188)
## Major Changes
- Predict Pathway is now a separate view as it is meant as adavanced prediction interface

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

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#188
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 02:38:57 +13:00
2b79adc2f7 [Feature] Implement Search modal in Modern UI (#185)
Implementing a search modal (stretching the level of dynamic that is possible without going to frameworks).

## Major Change
- Search needs packages and is available everywhere now; so had to add reviewed and user packages to global context.

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#185
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 01:52:41 +13:00
ddf1fd3515 Frontpage update (#179)
This PR introduces an overhaul for the front page and login features while keeping the rest of the application intact.

## Major Changes

- TailwindCSS + DaisyUI Integration: Add  modern CSS framework for component-based utility styling
- Build System: Added pnpm for CSS building; can be extended for updated frontend builds in the future
- Navbar + Footer: Redesigned and includable; old version retained for unstyled elements
- Optimized Assets: Added minified and CSS-stylable logos

## New Features

- Static Pages: Added comprehensive mockups of static pages (legal, privacy policy, terms of use, contact, etc.). **Note:** These have to be fixed before a public release, as their content is largely unreviewed and incorrect. Probably best to do in a separate PR that only contains updates to these.
- Discourse API: Implement minimal features based on RestAPI for controllable results.

## Current bugs
- [x] The static pages include the default navbar and footer on the login page. This will likely not work, as users need to access it before logging in; no good workaround so far (problem with Django templating system).
- [ ] The front page predict link is currently non-functional; the redesigned page is almost ready but better done in a separate PR as it also touches Django code.
- [x] Visual bug with the news cards. Still intend to fix for this PR

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#179
Reviewed-by: jebus <lorsbach@envipath.com>
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-12 01:09:39 +13:00
34589efbde [Fix] Mitigate XSS attack vector by cleaning input before it hits our Database (#171)
## Changes

- All text input fields are now cleaned with nh3 to remove html tags. We allow certain html tags under `settings.py/ALLOWED_HTML_TAGS` so we can easily update the tags we allow in the future.
- All names and descriptions now use the template tag `nh_safe` in all html files.
- Usernames and emails are a small exception and are not allowed any html tags

Co-authored-by: Liam Brydon <62733830+MyCreativityOutlet@users.noreply.github.com>
Co-authored-by: jebus <lorsbach@envipath.com>
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#171
Reviewed-by: jebus <lorsbach@envipath.com>
Co-authored-by: liambrydon <lbry121@aucklanduni.ac.nz>
Co-committed-by: liambrydon <lbry121@aucklanduni.ac.nz>
2025-11-11 22:49:55 +13:00
1cccefa991 [Feature] Basic Test Workflow (#186)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#186
Reviewed-by: liambrydon <lbry121@aucklanduni.ac.nz>
Reviewed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-11 21:07:25 +13:00
e26d5a21e3 [Enhancement] Refactor Dataset (#184)
# Summary
I have introduced a new base `class Dataset` in `ml.py` which all datasets should subclass. It stores the dataset as a polars DataFrame with the column names and number of columns determined by the subclass. It implements generic methods such as `add_row`, `at`, `limit` and dataset saving. It also details abstract methods required by the subclasses. These include `X`, `y` and `generate_dataset`.

There are two subclasses that currently exist. `RuleBasedDataset` for the MLRR models and `EnviFormerDataset` for the enviFormer models.

# Old Dataset to New RuleBasedDataset Functionality Translation

- [x] \_\_init\_\_
    - self.columns and self.num_labels moved to base Dataset class
    - self.data moved to base class with name self.df along with initialising from list or from another DataFrame
    - struct_features, triggered and observed remain the same
- [x] \_block\_indices
    - function moved to base Dataset class
- [x] structure_id
    - stays in RuleBasedDataset, now requires an index for the row of interest
- [x] add_row
    - moved to base Dataset class, now calls add_rows so one or more rows can be added at a time
- [x] times_triggered
    - stays in RuleBasedDataset, now does a look up using polars df.filter
- [x] struct_features (see init)
- [x] triggered (see init)
- [x] observed (see init)
- [x] at
    - removed in favour of indexing with getitem
- [x] limit
    - removed in favour of indexing with getitem
- [x] classification_dataset
    - stays in RuleBasedDataset, largely the same just with new dataset construction using add_rows
- [x] generate_dataset
    - stays in RuleBasedDataset, largely the same just with new dataset construction using add_rows
- [x] X
    - moved to base Dataset as @abstract_method, RuleBasedDataset implementation functionally the same but uses polars
- [x] trig
    - stays in RuleBasedDataset, functionally the same but uses polars
- [x] y
    - moved to base Dataset as @abstract_method, RuleBasedDataset implementation functionally the same but uses polars
- [x] \_\_get_item\_\_
    - moved to base dataset, now passes item to the dataframe for polars to handle
- [x] to_arff
    - stays in RuleBasedDataset, functionally the same but uses polars
- [x] \_\_repr\_\_
    - moved to base dataset
- [x] \_\_iter\_\_
    - moved to base Dataset, now uses polars iter_rows

# Base Dataset class Features
The following functions are available in the base Dataset class

- init - Create the dataset from a list of columns and data in format list of list. Or can create a dataset from a polars Dataframe, this is essential for recreating itself during indexing. Can create an empty dataset by just passing column names.
- add_rows - Add rows to the Dataset, we check that the new data length is the same but it is presumed that the column order matches the existing dataframe
- add_row - Add one row, see add_rows
- block_indices - Returns the column indices that start with the given prefix
- columns - Property, returns dataframe.columns
- shape - Property, returns dataframe.shape
- X - Abstract method to be implemented by the subclasses, it should represent the input to a ML model
- y - Abstract method to be implemented by the subclasses, it should represent the target for a ML model
- generate_dataset - Abstract and static method to be implemented by the subclasses, should return an initialised subclass of Dataset
- iter - returns the iterable from dataframe.iter_rows()
- getitem - passes the item argument to the dataframe. If the result of indexing the dataframe is another dataframe, the new dataframe is  packaged into a new Dataset of the same subclass. If the result of indexing is something else (int, float, polar Series) return the result.
- save - Pickle and save the dataframe to the given path
- load - Static method to load the dataset from the given path
- to_numpy - returns the dataframe as a numpy array. Required for compatibility with training of the ECC model
- repr - return a representation of the dataset
- len - return the length of the dataframe
- iter_rows - Return dataframe.iterrows with arguments passed through. Mainly used to get the named iterable which returns rows of the dataframe as dict of column names: column values instead of tuple of column values.
- filter - pass to dataframe.filter and recreates self with the result
- select - pass to dataframe.select and recreates self with the result
- with_columns - pass to dataframe.with_columns and recreates self with the result
- sort - pass to dataframe.sort and recreates self with the result
- item - pass to dataframe.item
- fill_nan - fill the dataframe nan's with value
- height - Property, returns the height (number of rows) of the dataframe

- [x] App domain
- [x] MACCS alternatives

Co-authored-by: Liam Brydon <62733830+MyCreativityOutlet@users.noreply.github.com>
Reviewed-on: enviPath/enviPy#184
Reviewed-by: jebus <lorsbach@envipath.com>
Co-authored-by: liambrydon <lbry121@aucklanduni.ac.nz>
Co-committed-by: liambrydon <lbry121@aucklanduni.ac.nz>
2025-11-07 08:46:17 +13:00
167 changed files with 16641 additions and 7940 deletions

116
.gitea/workflows/ci.yaml Normal file
View File

@ -0,0 +1,116 @@
name: CI
on:
pull_request:
branches:
- develop
workflow_dispatch:
jobs:
test:
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
- 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 Django tests
run: |
source .venv/bin/activate
python manage.py test tests --exclude-tag slow

5
.gitignore vendored
View File

@ -10,3 +10,8 @@ scratches/
data/ data/
.DS_Store .DS_Store
node_modules/
static/css/output.css
*.code-workspace

View File

@ -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
View File

@ -0,0 +1,11 @@
{
"plugins": ["prettier-plugin-jinja-template", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "templates/**/*.html",
"options": {
"parser": "jinja-template"
}
}
]
}

View File

@ -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)
``` ```

View File

@ -87,11 +87,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 +246,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 +347,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

View File

@ -21,6 +21,7 @@ from .models import (
ExternalDatabase, ExternalDatabase,
ExternalIdentifier, ExternalIdentifier,
JobLog, JobLog,
License,
) )
@ -62,6 +63,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 +123,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)

View File

@ -0,0 +1,32 @@
"""
Context processors for enviPy application.
Context processors automatically make variables available to all templates.
"""
from .logic import PackageManager
from .models import Package
def package_context(request):
"""
Provides package data for the search modal which is included globally
in framework_modern.html.
Returns:
dict: Context dictionary with reviewed and unreviewed packages
"""
current_user = request.user
reviewed_package_qs = PackageManager.get_reviewed_packages()
unreviewed_package_qs = Package.objects.none()
# Only get user-specific packages if user is authenticated
if current_user.is_authenticated:
unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user)
return {
"reviewed_packages": reviewed_package_qs,
"unreviewed_packages": unreviewed_package_qs,
}

View File

@ -4,6 +4,7 @@ import json
from typing import Union, List, Optional, Set, Dict, Any from typing import Union, List, Optional, Set, Dict, Any
from uuid import UUID from uuid import UUID
import nh3
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 django.conf import settings as s
@ -185,6 +186,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 +269,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 +526,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()
@ -1094,28 +1107,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):
@ -1544,7 +1558,7 @@ class SPathway(object):
if self.prediction_setting.model.app_domain: if self.prediction_setting.model.app_domain:
app_domain_assessment = self.prediction_setting.model.app_domain.assess( app_domain_assessment = self.prediction_setting.model.app_domain.assess(
sub.smiles sub.smiles
)[0] )
if self.persist is not None: if self.persist is not None:
n = self.snode_persist_lookup[sub] n = self.snode_persist_lookup[sub]
@ -1577,9 +1591,7 @@ class SPathway(object):
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 = ( app_domain_assessment = (
self.prediction_setting.model.app_domain.assess(c)[ self.prediction_setting.model.app_domain.assess(c)
0
]
) )
self.smiles_to_node[c] = SNode( self.smiles_to_node[c] = SNode(

View File

@ -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()

View 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,
),
]

View 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)]

View File

@ -11,6 +11,7 @@ from typing import Union, List, Optional, Dict, Tuple, Set, Any
from uuid import uuid4 from uuid import uuid4
import math 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
@ -28,7 +29,14 @@ from sklearn.metrics import precision_score, recall_score, jaccard_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, ProductSet, PredictionResult, IndigoUtils
from utilities.ml import Dataset, ApplicabilityDomainPCA, EnsembleClassifierChain, RelativeReasoning from utilities.ml import (
RuleBasedDataset,
ApplicabilityDomainPCA,
EnsembleClassifierChain,
RelativeReasoning,
EnviFormerDataset,
Dataset,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -647,6 +655,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")
@ -802,14 +811,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()
@ -981,11 +992,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
@ -1187,21 +1198,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
@ -1402,12 +1421,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
@ -1715,14 +1733,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:
@ -2017,11 +2036,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,
@ -2175,7 +2199,7 @@ class PackageBasedModel(EPModel):
applicable_rules = self.applicable_rules applicable_rules = self.applicable_rules
reactions = list(self._get_reactions()) reactions = list(self._get_reactions())
ds = Dataset.generate_dataset(reactions, applicable_rules, educts_only=True) ds = RuleBasedDataset.generate_dataset(reactions, applicable_rules, educts_only=True)
end = datetime.now() end = datetime.now()
logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds") logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds")
@ -2184,7 +2208,7 @@ class PackageBasedModel(EPModel):
ds.save(f) ds.save(f)
return ds return ds
def load_dataset(self) -> "Dataset": def load_dataset(self) -> "Dataset | RuleBasedDataset | EnviFormerDataset":
ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.pkl") ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.pkl")
return Dataset.load(ds_path) return Dataset.load(ds_path)
@ -2225,7 +2249,7 @@ class PackageBasedModel(EPModel):
self.model_status = self.BUILT_NOT_EVALUATED self.model_status = self.BUILT_NOT_EVALUATED
self.save() self.save()
def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None): def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None, **kwargs):
if self.model_status != self.BUILT_NOT_EVALUATED: if self.model_status != self.BUILT_NOT_EVALUATED:
raise ValueError(f"Can't evaluate a model in state {self.model_status}!") raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
@ -2343,37 +2367,39 @@ 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 = Dataset.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 = np.array(ds.X(exclude_id_col=False, na_replacement=None)) X = ds.X(exclude_id_col=False, na_replacement=None).to_numpy()
y = np.array(ds.y(na_replacement=np.nan)) y = ds.y(na_replacement=np.nan).to_numpy()
else: else:
X = np.array(ds.X(na_replacement=np.nan)) X = ds.X(na_replacement=np.nan).to_numpy()
y = np.array(ds.y(na_replacement=np.nan)) y = ds.y(na_replacement=np.nan).to_numpy()
single_gen_result = evaluate_sg(self.model, X, y, np.arange(len(X)), self.threshold) single_gen_result = evaluate_sg(self.model, X, y, np.arange(len(X)), self.threshold)
self.eval_results = self.compute_averages([single_gen_result]) self.eval_results = self.compute_averages([single_gen_result])
else: else:
ds = self.load_dataset() ds = self.load_dataset()
if isinstance(self, RuleBasedRelativeReasoning): if isinstance(self, RuleBasedRelativeReasoning):
X = np.array(ds.X(exclude_id_col=False, na_replacement=None)) X = ds.X(exclude_id_col=False, na_replacement=None).to_numpy()
y = np.array(ds.y(na_replacement=np.nan)) y = ds.y(na_replacement=np.nan).to_numpy()
else: else:
X = np.array(ds.X(na_replacement=np.nan)) X = ds.X(na_replacement=np.nan).to_numpy()
y = np.array(ds.y(na_replacement=np.nan)) y = ds.y(na_replacement=np.nan).to_numpy()
n_splits = 20 n_splits = kwargs.get("n_splits", 20)
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42) shuff = ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42)
splits = list(shuff.split(X)) splits = list(shuff.split(X))
from joblib import Parallel, delayed from joblib import Parallel, delayed
models = Parallel(n_jobs=10)( models = Parallel(n_jobs=min(10, len(splits)))(
delayed(train_func)(X, y, train_index, self._model_args()) delayed(train_func)(X, y, train_index, self._model_args())
for train_index, _ in splits for train_index, _ in splits
) )
evaluations = Parallel(n_jobs=10)( evaluations = Parallel(n_jobs=min(10, len(splits)))(
delayed(evaluate_sg)(model, X, y, test_index, self.threshold) delayed(evaluate_sg)(model, X, y, test_index, self.threshold)
for model, (_, test_index) in zip(models, splits) for model, (_, test_index) in zip(models, splits)
) )
@ -2541,14 +2567,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.")
@ -2585,11 +2612,11 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
return rbrr return rbrr
def _fit_model(self, ds: Dataset): def _fit_model(self, ds: RuleBasedDataset):
X, y = ds.X(exclude_id_col=False, na_replacement=None), ds.y(na_replacement=None) X, y = ds.X(exclude_id_col=False, na_replacement=None), ds.y(na_replacement=None)
model = RelativeReasoning( model = RelativeReasoning(
start_index=ds.triggered()[0], start_index=ds.triggered()[0],
end_index=ds.triggered()[1], end_index=ds.triggered()[-1],
) )
model.fit(X, y) model.fit(X, y)
return model return model
@ -2599,7 +2626,7 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
return { return {
"clz": "RuleBaseRelativeReasoning", "clz": "RuleBaseRelativeReasoning",
"start_index": ds.triggered()[0], "start_index": ds.triggered()[0],
"end_index": ds.triggered()[1], "end_index": ds.triggered()[-1],
} }
def _save_model(self, model): def _save_model(self, model):
@ -2645,14 +2672,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.")
@ -2687,11 +2715,11 @@ class MLRelativeReasoning(PackageBasedModel):
return mlrr return mlrr
def _fit_model(self, ds: Dataset): def _fit_model(self, ds: RuleBasedDataset):
X, y = ds.X(na_replacement=np.nan), ds.y(na_replacement=np.nan) X, y = ds.X(na_replacement=np.nan), ds.y(na_replacement=np.nan)
model = EnsembleClassifierChain(**s.DEFAULT_MODEL_PARAMS) model = EnsembleClassifierChain(**s.DEFAULT_MODEL_PARAMS)
model.fit(X, y) model.fit(X.to_numpy(), y.to_numpy())
return model return model
def _model_args(self): def _model_args(self):
@ -2714,7 +2742,7 @@ class MLRelativeReasoning(PackageBasedModel):
start = datetime.now() start = datetime.now()
ds = self.load_dataset() ds = self.load_dataset()
classify_ds, classify_prods = ds.classification_dataset([smiles], self.applicable_rules) classify_ds, classify_prods = ds.classification_dataset([smiles], self.applicable_rules)
pred = self.model.predict_proba(classify_ds.X()) pred = self.model.predict_proba(classify_ds.X().to_numpy())
res = MLRelativeReasoning.combine_products_and_probs( res = MLRelativeReasoning.combine_products_and_probs(
self.applicable_rules, pred[0], classify_prods[0] self.applicable_rules, pred[0], classify_prods[0]
@ -2759,7 +2787,9 @@ class ApplicabilityDomain(EnviPathModel):
@cached_property @cached_property
def training_set_probs(self): def training_set_probs(self):
return joblib.load(os.path.join(s.MODEL_DIR, f"{self.model.uuid}_train_probs.pkl")) ds = self.model.load_dataset()
col_ids = ds.block_indices("prob")
return ds[:, col_ids]
def build(self): def build(self):
ds = self.model.load_dataset() ds = self.model.load_dataset()
@ -2767,9 +2797,9 @@ class ApplicabilityDomain(EnviPathModel):
start = datetime.now() start = datetime.now()
# Get Trainingset probs and dump them as they're required when using the app domain # Get Trainingset probs and dump them as they're required when using the app domain
probs = self.model.model.predict_proba(ds.X()) probs = self.model.model.predict_proba(ds.X().to_numpy())
f = os.path.join(s.MODEL_DIR, f"{self.model.uuid}_train_probs.pkl") ds.add_probs(probs)
joblib.dump(probs, f) ds.save(os.path.join(s.MODEL_DIR, f"{self.model.uuid}_ds.pkl"))
ad = ApplicabilityDomainPCA(num_neighbours=self.num_neighbours) ad = ApplicabilityDomainPCA(num_neighbours=self.num_neighbours)
ad.build(ds) ad.build(ds)
@ -2792,15 +2822,20 @@ class ApplicabilityDomain(EnviPathModel):
joblib.dump(ad, f) joblib.dump(ad, f)
def assess(self, structure: Union[str, "CompoundStructure"]): def assess(self, structure: Union[str, "CompoundStructure"]):
return self.assess_batch([structure])[0]
def assess_batch(self, structures: List["CompoundStructure | str"]):
ds = self.model.load_dataset() ds = self.model.load_dataset()
if isinstance(structure, CompoundStructure): smiles = []
smiles = structure.smiles for struct in structures:
if isinstance(struct, CompoundStructure):
smiles.append(structures.smiles)
else: else:
smiles = structure smiles.append(structures)
assessment_ds, assessment_prods = ds.classification_dataset( assessment_ds, assessment_prods = ds.classification_dataset(
[structure], self.model.applicable_rules 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:
@ -2814,82 +2849,61 @@ class ApplicabilityDomain(EnviPathModel):
# it identifies all training structures that have the same trigger reaction activated (i.e., value 1). # it identifies all training structures that have the same trigger reaction activated (i.e., value 1).
# This is used to find "qualified neighbours" — training examples that share the same triggered feature # This is used to find "qualified neighbours" — training examples that share the same triggered feature
# with a given assessment structure under a particular rule. # with a given assessment structure under a particular rule.
qualified_neighbours_per_rule: Dict[int, Dict[int, List[int]]] = defaultdict( qualified_neighbours_per_rule: Dict = {}
lambda: defaultdict(list)
)
for rule_idx, feature_index in enumerate(range(*assessment_ds.triggered())): import polars as pl
feature = ds.columns[feature_index]
if feature.startswith("trig_"):
# TODO unroll loop
for i, cx in enumerate(assessment_ds.X(exclude_id_col=False)):
if int(cx[feature_index]) == 1:
for j, tx in enumerate(ds.X(exclude_id_col=False)):
if int(tx[feature_index]) == 1:
qualified_neighbours_per_rule[i][rule_idx].append(j)
probs = self.training_set_probs # Select only the triggered columns
# preds = self.model.model.predict_proba(assessment_ds.X()) 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
# trigger that rule.
train_trig = {
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
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(
self.model.applicable_rules, self.model.applicable_rules,
self.model.model.predict_proba(assessment_ds.X())[0], self.model.model.predict_proba(assessment_ds.X().to_numpy())[0],
assessment_prods[0], assessment_prods[0],
) )
assessments = list() assessments = list()
# loop through our assessment dataset # loop through our assessment dataset
for i, instance in enumerate(assessment_ds): for i, instance in enumerate(assessment_ds[:, assessment_ds.struct_features()]):
rule_reliabilities = dict() rule_reliabilities = dict()
local_compatibilities = dict() local_compatibilities = dict()
neighbours_per_rule = dict() neighbours_per_rule = dict()
neighbor_probs_per_rule = dict() neighbor_probs_per_rule = dict()
# 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_idx, vals in qualified_neighbours_per_rule[i].items(): for rule_uuid, train_instances in qualified_neighbours_per_rule[i].items():
# collect the train dataset instances and store it along with the index (a.k.a. row number) of the # compute tanimoto distance for all neighbours and add to dataset
# train dataset
train_instances = []
for v in vals:
train_instances.append((v, ds.at(v)))
# sf is a tuple with start/end index of the features
sf = ds.struct_features()
# compute tanimoto distance for all neighbours
# result ist a list of tuples with train index and computed distance
dists = self._compute_distances( dists = self._compute_distances(
instance.X()[0][sf[0] : sf[1]], assessment_ds[i, assessment_ds.struct_features()].to_numpy()[0],
[ti[1].X()[0][sf[0] : sf[1]] for ti in train_instances], train_instances[:, train_instances.struct_features()].to_numpy(),
) )
train_instances = train_instances.with_columns(dist=pl.Series(dists))
dists_with_index = list()
for ti, dist in zip(train_instances, dists):
dists_with_index.append((ti[0], dist[1]))
# 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`
dists_with_index = sorted(dists_with_index, key=lambda x: x[1], reverse=True) # TODO: Should this be descending? If we want the most similar then we want values close to zero (ascending)
dists_with_index = dists_with_index[: self.num_neighbours] train_instances = train_instances.sort("dist", descending=True)[
: self.num_neighbours
]
# compute average distance # compute average distance
rule_reliabilities[rule_idx] = ( rule_reliabilities[rule_uuid] = (
sum([d[1] for d in dists_with_index]) / len(dists_with_index) train_instances.select(pl.mean("dist")).fill_nan(0.0).item()
if len(dists_with_index) > 0
else 0.0
) )
# 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
neighbour_datasets = [(d[0], ds.at(d[0])) for d in dists_with_index] local_compatibilities[rule_uuid] = self._compute_compatibility(
local_compatibilities[rule_idx] = self._compute_compatibility( rule_uuid, train_instances
rule_idx, probs, neighbour_datasets
) )
neighbours_per_rule[rule_idx] = [ neighbours_per_rule[rule_uuid] = list(
CompoundStructure.objects.get(uuid=ds[1].structure_id()) CompoundStructure.objects.filter(uuid__in=train_instances["structure_id"])
for ds in neighbour_datasets )
] neighbor_probs_per_rule[rule_uuid] = train_instances[f"prob_{rule_uuid}"].to_list()
neighbor_probs_per_rule[rule_idx] = [
probs[d[0]][rule_idx] for d in dists_with_index
]
ad_res = { ad_res = {
"ad_params": { "ad_params": {
@ -2900,23 +2914,21 @@ class ApplicabilityDomain(EnviPathModel):
"local_compatibility_threshold": self.local_compatibilty_threshold, "local_compatibility_threshold": self.local_compatibilty_threshold,
}, },
"assessment": { "assessment": {
"smiles": smiles, "smiles": smiles[i],
"inside_app_domain": self.pca.is_applicable(instance)[0], "inside_app_domain": self.pca.is_applicable(assessment_ds[i])[0],
}, },
} }
transformations = list() transformations = list()
for rule_idx in rule_reliabilities.keys(): for rule_uuid in rule_reliabilities.keys():
rule = Rule.objects.get( rule = Rule.objects.get(uuid=rule_uuid)
uuid=instance.columns[instance.observed()[0] + rule_idx].replace("obs_", "")
)
rule_data = rule.simple_json() rule_data = rule.simple_json()
rule_data["image"] = f"{rule.url}?image=svg" rule_data["image"] = f"{rule.url}?image=svg"
neighbors = [] neighbors = []
for n, n_prob in zip( for n, n_prob in zip(
neighbours_per_rule[rule_idx], neighbor_probs_per_rule[rule_idx] neighbours_per_rule[rule_uuid], neighbor_probs_per_rule[rule_uuid]
): ):
neighbor = n.simple_json() neighbor = n.simple_json()
neighbor["image"] = f"{n.url}?image=svg" neighbor["image"] = f"{n.url}?image=svg"
@ -2933,14 +2945,14 @@ class ApplicabilityDomain(EnviPathModel):
transformation = { transformation = {
"rule": rule_data, "rule": rule_data,
"reliability": rule_reliabilities[rule_idx], "reliability": rule_reliabilities[rule_uuid],
# We're setting it here to False, as we don't know whether "assess" is called during pathway # We're setting it here to False, as we don't know whether "assess" is called during pathway
# prediction or from Model Page. For persisted Nodes this field will be overwritten at runtime # prediction or from Model Page. For persisted Nodes this field will be overwritten at runtime
"is_predicted": False, "is_predicted": False,
"local_compatibility": local_compatibilities[rule_idx], "local_compatibility": local_compatibilities[rule_uuid],
"probability": preds[rule_idx].probability, "probability": preds[rule_to_i[rule_uuid]].probability,
"transformation_products": [ "transformation_products": [
x.product_set for x in preds[rule_idx].product_sets x.product_set for x in preds[rule_to_i[rule_uuid]].product_sets
], ],
"times_triggered": ds.times_triggered(str(rule.uuid)), "times_triggered": ds.times_triggered(str(rule.uuid)),
"neighbors": neighbors, "neighbors": neighbors,
@ -2958,32 +2970,24 @@ class ApplicabilityDomain(EnviPathModel):
def _compute_distances(classify_instance: List[int], train_instances: List[List[int]]): def _compute_distances(classify_instance: List[int], train_instances: List[List[int]]):
from utilities.ml import tanimoto_distance from utilities.ml import tanimoto_distance
distances = [ distances = [tanimoto_distance(classify_instance, train) for train in train_instances]
(i, tanimoto_distance(classify_instance, train))
for i, train in enumerate(train_instances)
]
return distances return distances
@staticmethod def _compute_compatibility(self, rule_idx: int, neighbours: "RuleBasedDataset"):
def _compute_compatibility(rule_idx: int, preds, neighbours: List[Tuple[int, "Dataset"]]):
tp, tn, fp, fn = 0.0, 0.0, 0.0, 0.0
accuracy = 0.0 accuracy = 0.0
import polars as pl
for n in neighbours: obs_pred = neighbours.select(
obs = n[1].y()[0][rule_idx] obs=pl.col(f"obs_{rule_idx}").cast(pl.Boolean),
pred = preds[n[0]][rule_idx] pred=pl.col(f"prob_{rule_idx}") >= self.model.threshold,
if obs and pred: )
tp += 1 # Compute tp, tn, fp, fn using polars expressions
elif not obs and pred: tp = obs_pred.filter((pl.col("obs")) & (pl.col("pred"))).height
fp += 1 tn = obs_pred.filter((~pl.col("obs")) & (~pl.col("pred"))).height
elif obs and not pred: fp = obs_pred.filter((~pl.col("obs")) & (pl.col("pred"))).height
fn += 1 fn = obs_pred.filter((pl.col("obs")) & (~pl.col("pred"))).height
else:
tn += 1
# Jaccard Index
if tp + tn > 0.0: if tp + tn > 0.0:
accuracy = (tp + tn) / (tp + tn + fp + fn) accuracy = (tp + tn) / (tp + tn + fp + fn)
return accuracy return accuracy
@ -3003,14 +3007,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.")
@ -3084,44 +3089,24 @@ class EnviFormer(PackageBasedModel):
self.save() self.save()
start = datetime.now() start = datetime.now()
# Standardise reactions for the training data, EnviFormer ignores stereochemistry currently ds = EnviFormerDataset.generate_dataset(self._get_reactions())
co2 = {"C(=O)=O", "O=C=O"}
ds = []
for reaction in self._get_reactions():
educts = ".".join(
[
FormatConverter.standardize(smile.smiles, remove_stereo=True)
for smile in reaction.educts.all()
]
)
products = ".".join(
[
FormatConverter.standardize(smile.smiles, remove_stereo=True)
for smile in reaction.products.all()
]
)
if products not in co2:
ds.append(f"{educts}>>{products}")
end = datetime.now() end = datetime.now()
logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds") logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds")
f = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json") f = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json")
with open(f, "w") as d_file: ds.save(f)
json.dump(ds, d_file)
return ds return ds
def load_dataset(self) -> "Dataset": def load_dataset(self):
ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json") ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json")
with open(ds_path) as d_file: return EnviFormerDataset.load(ds_path)
ds = json.load(d_file)
return ds
def _fit_model(self, ds): def _fit_model(self, ds):
# Call to enviFormer's fine_tune function and return the model # Call to enviFormer's fine_tune function and return the model
from enviformer.finetune import fine_tune from enviformer.finetune import fine_tune
start = datetime.now() start = datetime.now()
model = fine_tune(ds, s.MODEL_DIR, str(self.uuid), device=s.ENVIFORMER_DEVICE) model = fine_tune(ds.X(), ds.y(), s.MODEL_DIR, str(self.uuid), device=s.ENVIFORMER_DEVICE)
end = datetime.now() end = datetime.now()
logger.debug(f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds") logger.debug(f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds")
return model return model
@ -3137,7 +3122,7 @@ class EnviFormer(PackageBasedModel):
args = {"clz": "EnviFormer"} args = {"clz": "EnviFormer"}
return args return args
def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None): def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None, **kwargs):
if self.model_status != self.BUILT_NOT_EVALUATED: if self.model_status != self.BUILT_NOT_EVALUATED:
raise ValueError(f"Can't evaluate a model in state {self.model_status}!") raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
@ -3152,21 +3137,20 @@ class EnviFormer(PackageBasedModel):
self.model_status = self.EVALUATING self.model_status = self.EVALUATING
self.save() self.save()
def evaluate_sg(test_reactions, predictions, model_thresh): def evaluate_sg(test_ds, predictions, model_thresh):
# Group the true products of reactions with the same reactant together # Group the true products of reactions with the same reactant together
assert len(test_ds) == len(predictions)
true_dict = {} true_dict = {}
for r in test_reactions: for r in test_ds:
reactant, true_product_set = r.split(">>") reactant, true_product_set = r
true_product_set = {p for p in true_product_set.split(".")} true_product_set = {p for p in true_product_set.split(".")}
true_dict[reactant] = true_dict.setdefault(reactant, []) + [true_product_set] true_dict[reactant] = true_dict.setdefault(reactant, []) + [true_product_set]
assert len(test_reactions) == len(predictions)
assert sum(len(v) for v in true_dict.values()) == len(test_reactions)
# Group the predicted products of reactions with the same reactant together # Group the predicted products of reactions with the same reactant together
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_reactions[k].split(">>") 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("."))
@ -3201,7 +3185,7 @@ class EnviFormer(PackageBasedModel):
break break
# Recall is TP (correct) / TP + FN (len(test_reactions)) # Recall is TP (correct) / TP + FN (len(test_reactions))
rec = {f"{k:.2f}": v / len(test_reactions) for k, v in correct.items()} rec = {f"{k:.2f}": v / len(test_ds) for k, v in correct.items()}
# Precision is TP (correct) / TP + FP (predicted) # Precision is TP (correct) / TP + FP (predicted)
prec = { prec = {
f"{k:.2f}": v / predicted[k] if predicted[k] > 0 else 0 for k, v in correct.items() f"{k:.2f}": v / predicted[k] if predicted[k] > 0 else 0 for k, v in correct.items()
@ -3280,47 +3264,35 @@ 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 = [] ds = EnviFormerDataset.generate_dataset(
for reaction in Reaction.objects.filter( Reaction.objects.filter(package__in=self.eval_packages.all()).distinct()
package__in=self.eval_packages.all()
).distinct():
educts = ".".join(
[
FormatConverter.standardize(smile.smiles, remove_stereo=True)
for smile in reaction.educts.all()
]
) )
products = ".".join( test_result = self.model.predict_batch(ds.X())
[
FormatConverter.standardize(smile.smiles, remove_stereo=True)
for smile in reaction.products.all()
]
)
ds.append(f"{educts}>>{products}")
test_result = self.model.predict_batch([smirk.split(">>")[0] for smirk in ds])
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])
else: else:
from enviformer.finetune import fine_tune from enviformer.finetune import fine_tune
ds = self.load_dataset() ds = self.load_dataset()
n_splits = 20 n_splits = kwargs.get("n_splits", 20)
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.1, random_state=42) shuff = ShuffleSplit(n_splits=n_splits, test_size=0.1, random_state=42)
# Single gen eval is done in one loop of train then evaluate rather than storing all n_splits trained models # Single gen eval is done in one loop of train then evaluate rather than storing all n_splits trained models
# this helps reduce the memory footprint. # this helps reduce the memory footprint.
single_gen_results = [] single_gen_results = []
for split_id, (train_index, test_index) in enumerate(shuff.split(ds)): for split_id, (train_index, test_index) in enumerate(shuff.split(ds)):
train = [ds[i] for i in train_index] train = ds[train_index]
test = [ds[i] for i in test_index] test = ds[test_index]
start = datetime.now() start = datetime.now()
model = fine_tune(train, 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"
) )
model.to(s.ENVIFORMER_DEVICE) model.to(s.ENVIFORMER_DEVICE)
test_result = model.predict_batch([smirk.split(">>")[0] for smirk in test]) test_result = model.predict_batch(test.X())
single_gen_results.append(evaluate_sg(test, test_result, self.threshold)) single_gen_results.append(evaluate_sg(test, test_result, self.threshold))
self.eval_results = self.compute_averages(single_gen_results) self.eval_results = self.compute_averages(single_gen_results)
@ -3399,23 +3371,12 @@ class EnviFormer(PackageBasedModel):
): ):
overlap += 1 overlap += 1
continue continue
educts = ".".join( train_reactions.append(reaction)
[ train_ds = EnviFormerDataset.generate_dataset(train_reactions)
FormatConverter.standardize(smile.smiles, remove_stereo=True)
for smile in reaction.educts.all()
]
)
products = ".".join(
[
FormatConverter.standardize(smile.smiles, remove_stereo=True)
for smile in reaction.products.all()
]
)
train_reactions.append(f"{educts}>>{products}")
logging.debug( logging.debug(
f"{overlap} compounds had to be removed from multigen split due to overlap within pathways" f"{overlap} compounds had to be removed from multigen split due to overlap within pathways"
) )
model = fine_tune(train_reactions, s.MODEL_DIR, f"mg_{split_id}") model = fine_tune(train_ds.X(), train_ds.y(), s.MODEL_DIR, f"mg_{split_id}")
multi_gen_results.append(evaluate_mg(model, test_pathways, self.threshold)) multi_gen_results.append(evaluate_mg(model, test_pathways, self.threshold))
self.eval_results.update( self.eval_results.update(
@ -3464,41 +3425,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:
@ -3533,7 +3497,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:

View File

@ -1,15 +1,15 @@
import csv import csv
import io import io
import logging import logging
from datetime import datetime
from typing import Any, Callable, List, Optional from typing import Any, Callable, List, Optional
from uuid import uuid4 from uuid import uuid4
from celery import shared_task from celery import shared_task
from celery.utils.functional import LRUCache from celery.utils.functional import LRUCache
from django.utils import timezone
from epdb.logic import SPathway from epdb.logic import SPathway
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge from epdb.models import Edge, EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times. ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
@ -29,7 +29,7 @@ def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
log.task_id = uuid4() log.task_id = uuid4()
log.job_name = job.__name__ log.job_name = job.__name__
log.status = "SUCCESS" log.status = "SUCCESS"
log.done_at = datetime.now() log.done_at = timezone.now()
log.task_result = str(x) if x else None log.task_result = str(x) if x else None
log.save() log.save()

View File

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

View File

@ -10,6 +10,7 @@ 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
from oauth2_provider.decorators import protected_resource from oauth2_provider.decorators import protected_resource
import nh3
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
@ -85,7 +86,11 @@ def login(request):
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth import login from django.contrib.auth import login
username = request.POST.get("username") username = request.POST.get("username").strip()
if username != request.POST.get("username"):
context["message"] = "Login failed!"
return render(request, "static/login.html", context)
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 +105,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 +143,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 +163,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"] = (
@ -351,6 +362,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 +709,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
@ -892,7 +931,7 @@ def package_model(request, package_uuid, model_uuid):
return JsonResponse(res, safe=False) return JsonResponse(res, safe=False)
else: else:
app_domain_assessment = current_model.app_domain.assess(stand_smiles)[0] app_domain_assessment = current_model.app_domain.assess(stand_smiles)
return JsonResponse(app_domain_assessment, safe=False) return JsonResponse(app_domain_assessment, safe=False)
context = get_base_context(request) context = get_base_context(request)
@ -932,12 +971,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:
@ -1039,17 +1090,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 +1134,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 +1250,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 +1395,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 +1611,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 +1708,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 +1768,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 +1854,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 +1865,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 +2023,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 +2118,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 +2284,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 +2371,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)
@ -2336,21 +2419,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 +2448,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 +2462,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 +2473,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 +2773,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 +2924,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)

25
package.json Normal file
View 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
View 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

View File

@ -27,10 +27,12 @@ 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",
] ]
[tool.uv.sources] [tool.uv.sources]
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.2" } enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.4" }
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" } envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"} envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"}
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" } envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
@ -65,22 +67,54 @@ 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 = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
dev = { cmd = "python manage.py runserver", help = "Start the development server", deps = ["db-up"] } dev = { shell = """
# Start pnpm CSS watcher in background
pnpm run dev &
PNPM_PID=$!
echo "Started CSS watcher (PID: $PNPM_PID)"
# Cleanup function
cleanup() {
echo "\nShutting down..."
if kill -0 $PNPM_PID 2>/dev/null; then
kill $PNPM_PID
echo " CSS watcher stopped"
fi
if [ ! -z "${DJ_PID:-}" ] && kill -0 $DJ_PID 2>/dev/null; then
kill $DJ_PID
echo " Django server stopped"
fi
}
# Set trap for cleanup
trap cleanup EXIT INT TERM
# Start Django dev server in background
uv run python manage.py runserver &
DJ_PID=$!
# Wait for Django to finish
wait $DJ_PID
""", 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 = "pnpm 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:"
@ -88,4 +122,8 @@ 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 tasks
build-frontend = { cmd = "pnpm run build", help = "Build frontend assets using pnpm", deps = ["js-deps"] }
collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = ["build-frontend"] }

View 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;
}

36
static/css/input.css Normal file
View File

@ -0,0 +1,36 @@
@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";

111
static/css/theme.css Normal file
View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

BIN
static/images/hero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
static/images/linkedin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

170
static/js/discourse-api.js Normal file
View File

@ -0,0 +1,170 @@
/**
* 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 'Click to read more';
// Remove HTML tags and clean up; collapse whitespace; do not add manual ellipsis
return excerpt
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/&nbsp;/g, ' ') // Replace &nbsp; with spaces
.replace(/&amp;/g, '&') // Replace &amp; with &
.replace(/&lt;/g, '<') // Replace &lt; with <
.replace(/&gt;/g, '>') // Replace &gt; with >
.replace(/\s+/g, ' ') // Collapse all whitespace/newlines
.trim()
}
/**
* 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);
}
});
}
});

View File

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

View File

@ -1,6 +1,11 @@
{% 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"
data-toggle="modal"
data-target="#new_compound_structure_modal"
>
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a
>
</li> </li>
{% endif %} {% endif %}

View File

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

View File

@ -1,4 +1,5 @@
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_group_modal"> <a role="button" data-toggle="modal" data-target="#new_group_modal">
<span class="glyphicon glyphicon-plus"></span> New Group</a> <span class="glyphicon glyphicon-plus"></span> New Group</a
>
</li> </li>

View File

@ -1,6 +1,7 @@
{% 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 role="button" data-toggle="modal" data-target="#new_model_modal">
<span class="glyphicon glyphicon-plus"></span> New Model</a> <span class="glyphicon glyphicon-plus"></span> New Model</a
>
</li> </li>
{% endif %} {% endif %}

View File

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

View File

@ -1,12 +1,20 @@
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_package_modal"> <a role="button" data-toggle="modal" data-target="#new_package_modal">
<span class="glyphicon glyphicon-plus"></span> New Package</a> <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 role="button" data-toggle="modal" data-target="#import_package_modal">
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a> <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"
data-toggle="modal"
data-target="#import_legacy_package_modal"
>
<span class="glyphicon glyphicon-import"></span> Import Package from legacy
JSON</a
>
</li> </li>

View File

@ -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 %}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,32 +1,43 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_compound_modal"> <a role="button" data-toggle="modal" data-target="#edit_compound_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a> <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 role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a> <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 role="button" data-toggle="modal" data-target="#add_structure_modal">
<i class="glyphicon glyphicon-plus"></i> Add Structure</a> <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 role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a> <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"
data-toggle="modal"
data-target="#generic_set_external_reference_modal"
>
<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 role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
<i class="glyphicon glyphicon-duplicate"></i> Copy</a> <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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a> <i class="glyphicon glyphicon-trash"></i> Delete Compound</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,22 +1,35 @@
{% 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"
data-toggle="modal"
data-target="#edit_compound_structure_modal"
>
<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 role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a> <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 role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a> <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"
data-toggle="modal"
data-target="#generic_set_external_reference_modal"
>
<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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a> <i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,14 +1,17 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#set_aliases_modal"> <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a> <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 role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a> <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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a> <i class="glyphicon glyphicon-trash"></i> Delete Edge</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,10 +1,12 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_group_member_modal"> <a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a> <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 role="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Group</a> <i class="glyphicon glyphicon-trash"></i> Delete Group</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,18 +1,22 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_model_modal"> <a role="button" data-toggle="modal" data-target="#edit_model_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Model</a> <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 role="button" data-toggle="modal" data-target="#evaluate_model_modal">
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a> <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 role="button" data-toggle="modal" data-target="#retrain_model_modal">
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a> <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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Model</a> <i class="glyphicon glyphicon-trash"></i> Delete Model</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,18 +1,22 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_node_modal"> <a role="button" data-toggle="modal" data-target="#edit_node_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Node</a> <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 role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a> <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 role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a> <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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Node</a> <i class="glyphicon glyphicon-trash"></i> Delete Node</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,26 +1,36 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_package_modal"> <a role="button" data-toggle="modal" data-target="#edit_package_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Package</a> <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"
data-toggle="modal"
data-target="#edit_package_permissions_modal"
>
<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 role="button" data-toggle="modal" data-target="#publish_package_modal">
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a> <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 role="button" data-toggle="modal" data-target="#export_package_modal">
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a> <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 role="button" data-toggle="modal" data-target="#set_license_modal">
<i class="glyphicon glyphicon-duplicate"></i> License</a> <i class="glyphicon glyphicon-duplicate"></i> License</a
>
</li> </li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#generic_delete_modal"> <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Package</a> <i class="glyphicon glyphicon-trash"></i> Delete Package</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,43 +1,65 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a class="button" data-toggle="modal" data-target="#add_pathway_node_modal"> <a class="button" data-toggle="modal" data-target="#add_pathway_node_modal">
<i class="glyphicon glyphicon-plus"></i> Add Compound</a> <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 class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a> <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 role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
<i class="glyphicon glyphicon-duplicate"></i> Copy</a> <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"
data-toggle="modal"
data-target="#download_pathway_csv_modal"
>
<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"
data-toggle="modal"
data-target="#download_pathway_image_modal"
>
<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"
data-toggle="modal"
data-target="#identify_missing_rules_modal"
>
<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 class="button" data-toggle="modal" data-target="#edit_pathway_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a> <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 role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a> <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 role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a> <i class="glyphicon glyphicon-plus"></i> Set Aliases</a
>
</li> </li>
{# <li>#} {# <li>#}
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#} {# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
@ -45,15 +67,26 @@
{# </li>#} {# </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"
data-toggle="modal"
data-target="#delete_pathway_node_modal"
>
<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"
data-toggle="modal"
data-target="#delete_pathway_edge_modal"
>
<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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a> <i class="glyphicon glyphicon-trash"></i> Delete Pathway</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,28 +1,38 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal"> <a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a> <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 role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a> <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 role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a> <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"
data-toggle="modal"
data-target="#generic_set_external_reference_modal"
>
<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 role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
<i class="glyphicon glyphicon-duplicate"></i> Copy</a> <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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a> <i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,24 +1,29 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_rule_modal"> <a role="button" data-toggle="modal" data-target="#edit_rule_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a> <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 role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a> <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 role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a> <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 role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
<i class="glyphicon glyphicon-duplicate"></i> Copy</a> <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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a> <i class="glyphicon glyphicon-trash"></i> Delete Rule</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,14 +1,25 @@
{% 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"
data-toggle="modal"
data-target="#add_additional_information_modal"
>
<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"
data-toggle="modal"
data-target="#update_scenario_additional_information_modal"
>
<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 class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a> <i class="glyphicon glyphicon-trash"></i> Delete Scenario</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -1,15 +1,22 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_user_modal"> <a role="button" data-toggle="modal" data-target="#edit_user_modal">
<i class="glyphicon glyphicon-edit"></i> Update</a> <i class="glyphicon glyphicon-edit"></i> Update</a
>
</li> </li>
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_password_modal"> <a role="button" data-toggle="modal" data-target="#edit_password_modal">
<i class="glyphicon glyphicon-lock"></i> Update Password</a> <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"
data-toggle="modal"
data-target="#new_prediction_setting_modal"
>
<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">#}
@ -17,6 +24,7 @@
{# </li>#} {# </li>#}
<li> <li>
<a role="button" data-toggle="modal" data-target="#generic_delete_modal"> <a role="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Account</a> <i class="glyphicon glyphicon-trash"></i> Delete Account</a
>
</li> </li>
{% endif %} {% endif %}

View File

@ -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 %}

View File

@ -2,30 +2,37 @@
{% load static %} {% load static %}
{% load envipytags %} {% load envipytags %}
{% block content %} {% block content %}
<div class="panel-group" id="reviewListAccordion"> <div class="panel-group" id="reviewListAccordion">
<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"
>
Jobs Jobs
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p> <p>Job Logs Desc</p>
Job Logs Desc
</p>
</div> </div>
<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"
>
<h4 class="panel-title"> <h4 class="panel-title">
<a id="job-accordion-link" data-toggle="collapse" data-parent="#job-accordion" href="#jobs"> <a
id="job-accordion-link"
data-toggle="collapse"
data-parent="#job-accordion"
href="#jobs"
>
Jobs Jobs
</a> </a>
</h4> </h4>
</div> </div>
<div id="jobs" <div id="jobs" class="panel-collapse in collapse">
class="panel-collapse collapse in">
<div class="panel-body list-group-item" id="job-content"> <div class="panel-body list-group-item" id="job-content">
<table class="table table-bordered table-hover"> <table class="table-bordered table-hover table">
<tr style="background-color: rgba(0, 0, 0, 0.08);"> <tr style="background-color: rgba(0, 0, 0, 0.08);">
<th scope="col">ID</th> <th scope="col">ID</th>
<th scope="col">Name</th> <th scope="col">Name</th>
@ -57,10 +64,12 @@
</div> </div>
<!-- Unreviewable objects such as User / Group / Setting --> <!-- Unreviewable objects such as User / Group / Setting -->
<ul class='list-group'> <ul class="list-group">
{% 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> <a class="list-group-item" href="{{ obj.url }}"
>{{ obj.username }}</a
>
{% else %} {% else %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a> <a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
{% endif %} {% endif %}

View File

@ -6,14 +6,22 @@
<div> <div>
<div id="load-all-error" style="display: none;"> <div id="load-all-error" style="display: none;">
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <span
class="glyphicon glyphicon-exclamation-sign"
aria-hidden="true"
></span>
<span class="sr-only">Error:</span> <span class="sr-only">Error:</span>
Getting objects failed! Getting objects failed!
</div> </div>
</div> </div>
<input type="text" id="object-search" class="form-control" placeholder="Search by name" <input
style="display: none;"> type="text"
id="object-search"
class="form-control"
placeholder="Search by name"
style="display: none;"
/>
<p></p> <p></p>
</div> </div>
{% endif %} {% endif %}
@ -50,7 +58,11 @@
<div class="panel-group" id="reviewListAccordion"> <div class="panel-group" id="reviewListAccordion">
<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"
>
{% if object_type == 'package' %} {% if object_type == 'package' %}
Packages Packages
{% elif object_type == 'compound' %} {% elif object_type == 'compound' %}
@ -78,12 +90,21 @@
{% elif object_type == 'group' %} {% elif object_type == 'group' %}
Groups Groups
{% endif %} {% endif %}
<div id="actionsButton" <div
id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;" style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" class="dropdown"
aria-haspopup="true" aria-expanded="false"><span >
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span <a
style="padding-right:1em"></span></a> href="#"
class="dropdown-toggle"
data-toggle="dropdown"
role="button"
aria-haspopup="true"
aria-expanded="false"
><span class="glyphicon glyphicon-wrench"></span> Actions
<span class="caret"></span><span style="padding-right:1em"></span
></a>
<ul id="actionsList" class="dropdown-menu"> <ul id="actionsList" class="dropdown-menu">
{% block actions %} {% block actions %}
{% if object_type == 'package' %} {% if object_type == 'package' %}
@ -118,93 +139,204 @@
<div class="panel-body"> <div class="panel-body">
<!-- 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 &gt;&gt;</a></p> experimental conditions.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/packages"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> meta-information.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/compounds"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> <a
target="_blank"
href="https://wiki.envipath.org/index.php/compounds"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> defined as SMIRKS.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/Rules"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> product compounds.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/reactions"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> graph.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/pathways"
role="button"
>Learn more &gt;&gt;</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.
&gt;&gt;</a></p> <a
target="_blank"
href="https://wiki.envipath.org/index.php/nodes"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> <a
target="_blank"
href="https://wiki.envipath.org/index.php/edges"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> data (compounds, rules, ..).
<a
target="_blank"
href="https://wiki.envipath.org/index.php/scenarios"
role="button"
>Learn more &gt;&gt;</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.
&gt;&gt;</a></p> <a
target="_blank"
href="https://wiki.envipath.org/index.php/relative_reasoning"
role="button"
>Learn more &gt;&gt;</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 predictions.
&gt;&gt;</a></p> <a
target="_blank"
href="https://wiki.envipath.org/index.php/settings"
role="button"
>Learn more &gt;&gt;</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
&gt;&gt;</a></p> data.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/users"
role="button"
>Learn more &gt;&gt;</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.
&gt;&gt;</a></p> <a
target="_blank"
href="https://wiki.envipath.org/index.php/groups"
role="button"
>Learn more &gt;&gt;</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>
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>
{% 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"> <div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title"> <h4 class="panel-title">
<a id="ReviewedLink" data-toggle="collapse" data-parent="#reviewListAccordion" <a
href="#Reviewed">Reviewed</a> id="ReviewedLink"
data-toggle="collapse"
data-parent="#reviewListAccordion"
href="#Reviewed"
>Reviewed</a
>
</h4> </h4>
</div> </div>
<div id="Reviewed" class="panel-collapse collapse in"> <div id="Reviewed" class="panel-collapse in collapse">
<div class="panel-body list-group-item" id="ReviewedContent"> <div class="panel-body list-group-item" id="ReviewedContent">
{% if object_type == 'package' %} {% if object_type == 'package' %}
{% for obj in reviewed_objects %} {% for obj in reviewed_objects %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }} <a class="list-group-item" href="{{ obj.url }}"
<span class="glyphicon glyphicon-star" aria-hidden="true" >{{ obj.name|safe }}
style="float:right" data-toggle="tooltip" <span
data-placement="top" title="" data-original-title="Reviewed"> class="glyphicon glyphicon-star"
aria-hidden="true"
style="float:right"
data-toggle="tooltip"
data-placement="top"
title=""
data-original-title="Reviewed"
>
</span> </span>
</a> </a>
{% endfor %} {% endfor %}
{% else %} {% else %}
{% for obj in reviewed_objects|slice:":50" %} {% for obj in reviewed_objects|slice:":50" %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}{# <i>({{ obj.package.name }})</i> #} <a class="list-group-item" href="{{ obj.url }}"
<span class="glyphicon glyphicon-star" aria-hidden="true" >{{ obj.name|safe }}{# <i>({{ obj.package.name }})</i> #}
style="float:right" data-toggle="tooltip" <span
data-placement="top" title="" data-original-title="Reviewed"> class="glyphicon glyphicon-star"
aria-hidden="true"
style="float:right"
data-toggle="tooltip"
data-placement="top"
title=""
data-original-title="Reviewed"
>
</span> </span>
</a> </a>
{% endfor %} {% endfor %}
@ -214,18 +346,36 @@
{% 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 <div
class="panel-title"><a id="UnreviewedLink" data-toggle="collapse" data-parent="#unReviewListAccordion" class="panel panel-default panel-heading list-group-item"
href="#Unreviewed">Unreviewed</a></h4></div> style="background-color:silver"
<div id="Unreviewed" class="panel-collapse collapse {% if reviewed_objects|length == 0 or object_type == 'package' %}in{% endif %}"> >
<h4 class="panel-title">
<a
id="UnreviewedLink"
data-toggle="collapse"
data-parent="#unReviewListAccordion"
href="#Unreviewed"
>Unreviewed</a
>
</h4>
</div>
<div
id="Unreviewed"
class="panel-collapse {% if reviewed_objects|length == 0 or object_type == 'package' %}in{% endif %} collapse"
>
<div class="panel-body list-group-item" id="UnreviewedContent"> <div class="panel-body list-group-item" id="UnreviewedContent">
{% if object_type == 'package' %} {% if object_type == 'package' %}
{% for obj in unreviewed_objects %} {% for obj in unreviewed_objects %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a> <a class="list-group-item" href="{{ obj.url }}"
>{{ obj.name|safe }}</a
>
{% endfor %} {% endfor %}
{% else %} {% else %}
{% for obj in unreviewed_objects|slice:":50" %} {% for obj in unreviewed_objects|slice:":50" %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a> <a class="list-group-item" href="{{ obj.url }}"
>{{ obj.name|safe }}</a
>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>
@ -233,12 +383,16 @@
{% endif %} {% endif %}
{% if objects %} {% if objects %}
<!-- Unreviewable objects such as User / Group / Setting --> <!-- Unreviewable objects such as User / Group / Setting -->
<ul class='list-group'> <ul class="list-group">
{% 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> <a class="list-group-item" href="{{ obj.url }}"
>{{ obj.username|safe }}</a
>
{% else %} {% else %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a> <a class="list-group-item" href="{{ obj.url }}"
>{{ obj.name|safe }}</a
>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
@ -261,10 +415,14 @@
</style> </style>
<div id="load-all-loading" class="spinner-widget" style="display: none"> <div id="load-all-loading" class="spinner-widget" style="display: none">
<img id="loading-gif" src="{% static '/images/wait.gif' %}" alt="Loading..."> <img
id="loading-gif"
src="{% static '/images/wait.gif' %}"
alt="Loading..."
/>
</div> </div>
</div> </div>
{# prettier-ignore-start #}
<script> <script>
$(function () { $(function () {
@ -316,4 +474,5 @@
}); });
</script> </script>
{# prettier-ignore-end #}
{% endblock content %} {% endblock content %}

View File

@ -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>

View File

@ -1,15 +1,18 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-error" role="alert"> <div class="alert alert-error" role="alert">
<h4 class="alert-heading">Bad Request!</h4> <h4 class="alert-heading">Bad Request!</h4>
<p>Lorem</p> <p>Lorem</p>
<hr> <hr />
<p class="mb-0"> <p class="mb-0">
You can find out more about permissions in our <a target="_blank" You can find out more about permissions in our
<a
target="_blank"
href="https://wiki.envipath.org/index.php/packages" href="https://wiki.envipath.org/index.php/packages"
role="button">Wiki &gt;&gt;</a></p> role="button"
>Wiki &gt;&gt;</a
>
</p>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,15 +1,18 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-error" role="alert"> <div class="alert alert-error" role="alert">
<h4 class="alert-heading">Access Denied!</h4> <h4 class="alert-heading">Access Denied!</h4>
<p>Access to X denied.</p> <p>Access to X denied.</p>
<hr> <hr />
<p class="mb-0"> <p class="mb-0">
You can find out more about permissions in our <a target="_blank" You can find out more about permissions in our
<a
target="_blank"
href="https://wiki.envipath.org/index.php/packages" href="https://wiki.envipath.org/index.php/packages"
role="button">Wiki &gt;&gt;</a></p> role="button"
>Wiki &gt;&gt;</a
>
</p>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,15 +1,18 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-error" role="alert"> <div class="alert alert-error" role="alert">
<h4 class="alert-heading">Not Found!</h4> <h4 class="alert-heading">Not Found!</h4>
<p>Does not exist</p> <p>Does not exist</p>
<hr> <hr />
<p class="mb-0"> <p class="mb-0">
You can find out more about permissions in our <a target="_blank" You can find out more about permissions in our
<a
target="_blank"
href="https://wiki.envipath.org/index.php/packages" href="https://wiki.envipath.org/index.php/packages"
role="button">Wiki &gt;&gt;</a></p> role="button"
>Wiki &gt;&gt;</a
>
</p>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,13 +1,9 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
<h4 class="alert-heading">{{ error_message }}</h4> <h4 class="alert-heading">{{ error_message }}</h4>
<hr> <hr />
<p class="mb-0"> <p class="mb-0">{{ error_detail }}</p>
{{ error_detail }}
</p>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,12 +1,11 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Your account has not been activated yet!</h4> <h4 class="alert-heading">Your account has not been activated yet!</h4>
<p>Your account has not been activated yet. If you have questions <a href="mailto:admin@envipath.org">contact <p>
us.</a> Your account has not been activated yet. If you have questions
<a href="mailto:admin@envipath.org">contact us.</a>
</p> </p>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -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,143 @@
{# </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 class="divider"></li>
<li>
<a
target="_blank"
href="https://wiki.envipath.org/"
id="wikiLink"
>Documentation Wiki</a
>
</li>
<li>
<a
href="#"
id="citeButton"
data-toggle="modal"
data-target="#citemodal"
>How to cite enviPath</a
>
</li> </li>
<li><a href="#" id="citeButton" data-toggle="modal" data-target="#citemodal">How to cite
enviPath</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a>Version: {{ meta.version }}</a></li> <li><a>Version: {{ meta.version }}</a></li>
</ul> </ul>
</li> </li>
{% if meta.user.username == 'anonymous' %} {% if meta.user.username == 'anonymous' %}
<li> <li>
<a href="#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 +291,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 +313,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 +344,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 +353,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,23 +389,27 @@
<div class="col-lg-12"> <div class="col-lg-12">
<ul class="nav nav-pills nav-justified"> <ul class="nav nav-pills nav-justified">
<!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>--> <!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>-->
<li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li> <li>
<a href="mailto:admin@envipath.org" target="_blank">Contact</a>
</li>
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschr&auml;nkt) &amp; Co. KG &copy;--> <!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschr&auml;nkt) &amp; Co. KG &copy;-->
<!-- {{ YEAR }}</a></li>--> <!-- {{ YEAR }}</a></li>-->
</ul> </ul>
</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 %} {% block modals %}
{% include "modals/cite_modal.html" %} {% include "modals/cite_modal.html" %}
{% include "modals/signup_modal.html" %}
{% include "modals/predict_modal.html" %} {% include "modals/predict_modal.html" %}
{% include "modals/batch_predict_modal.html" %} {% include "modals/batch_predict_modal.html" %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,223 @@
<!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"
/>
{# jQuery - Keep for compatibility with existing JS #}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.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;
// Setup CSRF header for all jQuery AJAX requests
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
});
</script>
{# General EP JS #}
<script src="{% static 'js/pps.js' %}"></script>
{# Modal Steps for Stepwise Modal Wizards #}
<script src="{% static 'js/jquery-bootstrap-modal-steps.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>
$(function () {
// Hide actionsbutton if there's no action defined
if ($("#actionsButton ul").children().length > 0) {
$("#actionsButton").show();
}
});
// 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>

View 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>

View File

@ -0,0 +1,150 @@
{% load static %}
{# Modern DaisyUI Navbar #}
<div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg">
<div class="navbar-start">
<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 %}
<div class="navbar-center hidden lg:flex">
<a
href="{{ meta.server_url }}/predict"
role="button"
class="btn btn-ghost"
id="predictLink"
>Predict</a
>
<!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> -->
<!--<li><a href="{{ meta.server_url }}/browse" id="browseLink">Browse</a></li>-->
<div class="dropdown dropdown-center">
<div tabindex="0" role="button" class="btn btn-ghost">Browse</div>
<ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"
>
<li>
<a href="{{ meta.server_url }}/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>
<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>

View File

@ -1,186 +1,453 @@
{% 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">&times;</span> <!-- Predict Pathway text over the image -->
<span class="sr-only">Close</span> <div class="absolute bottom-40 left-1/8 z-10 -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 z-20 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"
>
<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" />
<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 &gt;&gt;</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"> >
<d-topics-list discourse-url="https://community.envipath.org" per-page="10" category="10" <form
template="complete"></d-topics-list> id="index-form"
</div> action="{{ meta.current_package.url }}/pathway"
</div> method="POST"
<div class="row"> >
<form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST">
{% 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" class="scale-100 transform opacity-100 transition-all duration-300 ease-in-out"
aria-expanded="false"> >
<span class="caret"></span> <div class="join mx-auto w-full">
</button> <input
<ul class="dropdown-menu" role="menu"> type="text"
<li> id="index-form-text-input"
<iframe id="index-form-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%" placeholder="canonical SMILES string"
height="510"></iframe> class="input input-md join-item grow"
</li> />
</ul> <button class="btn btn-neutral join-item">Predict!</button>
</div> </div>
<input type="text" class="form-control" id='index-form-text-input' <div class="label relative mt-1 w-full">
placeholder="Enter a SMILES to predict a Pathway or type something to search"> <div class="flex gap-2">
<div class="input-group-btn"> <a
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="#"
aria-haspopup="true" class="example-link hover:text-primary cursor-pointer"
aria-expanded="false" id="action-button">Predict <span class="caret"></span></button> data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
<ul class="dropdown-menu"> title="load example"
<li><a id="dropdown-predict">Predict</a></li> >Caffeine</a
<li><a id="dropdown-search">Search</a></li> >
</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"
data-smiles="CC(C)CC1=CC=C(C=C1)C(C)C(=O)O"
title="load example"
>Ibuprofen</a
>
</div>
<a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#"
>Advanced</a
>
</div>
</div>
<div
id="ketcher-container"
class="hidden w-full scale-95 transform opacity-0 transition-all duration-300 ease-in-out"
>
<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() { // Discourse API integration is now handled by discourse-api.js
$(this).prop("disabled", true);
var action = $('#action-button').text().trim(); // Function to render Discourse topics into cards
function renderDiscourseTopics(topics) {
const container = document.getElementById("community-news-container");
if (!container) return;
var textSmiles = $('#index-form-text-input').val().trim(); // Clear container
container.innerHTML = "";
if (textSmiles === '') { // Create cards for each topic
$(this).prop("disabled", false); topics.forEach((topic) => {
return; const card = createDiscourseCard(topic);
container.insertAdjacentHTML("beforeend", card);
});
} }
var ketcherSmiles = getKetcher('index-form-ketcher').getSmiles().trim(); // Function to create HTML card for a topic
function createDiscourseCard(topic) {
const date = new Date(topic.created_at).toLocaleDateString();
if (action !== 'Search' && ketcherSmiles !== '' && textSmiles !== ketcherSmiles) { return `
console.log("Ketcher and TextInput differ!"); <div class="card bg-white shadow-xs hover:shadow-lg transition-shadow duration-300 h-64 w-75 flex-shrink-0">
<div class="card-body flex flex-col h-full">
<h3 class="card-title leading-tight font-normal tracking-tight h-12 mb-2 line-clamp-2 text-ellipsis wrap-break-word overflow-hidden">
<a href="${topic.url}" target="_blank" class="hover:text-primary">
${topic.title}
</a>
</h3>
<div class="text-sm line-clamp-4 break-words" >
${topic.excerpt}
</div>
<div class="flex flex-row items-center justify-between">
<div class="flex items-center gap-2">
<div class="avatar tooltip tooltip-right" data-tip="${topic.author}">
<div class="w-8 rounded-full">
<img src="${topic.author_avatar}" alt="${topic.author}" />
</div>
</div>
<span class="text-xs text-gray-500">${date}</span>
</div>
<a href="${topic.url}" target="_blank" class="btn btn-ghost text-neutral-500 rounded-full p-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 7v14"/>
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>
</svg>
</a>
</div>
</div>
</div>
`;
} }
if (action === 'Search') { // Make render function globally available
var par = {}; window.renderDiscourseTopics = renderDiscourseTopics;
par['search'] = textSmiles;
par['mode'] = 'text'; // Toggle functionality with smooth animations
var queryString = $.param(par, true); function toggleInputMode() {
window.location.href = "/search?" + queryString; const toggle = $('input[type="checkbox"]');
const textContainer = $("#text-input-container");
const ketcherContainer = $("#ketcher-container");
const formCard = $(".card");
const fieldset = $(".fieldset");
if (toggle.is(":checked")) {
// Draw mode - show Ketcher, hide text input
textContainer.addClass("opacity-0 transform scale-95");
textContainer.removeClass("opacity-100 transform scale-100");
// Adjust fieldset padding for Ketcher mode - reduce padding and make more compact
fieldset.removeClass("p-8");
fieldset.addClass("p-4");
// Wait for fade out to complete, then hide and show new content
setTimeout(() => {
textContainer.addClass("hidden");
ketcherContainer.removeClass("hidden opacity-0 transform scale-95");
ketcherContainer.addClass("opacity-100 transform scale-100");
// Force re-evaluation of iframe size
const iframe = document.getElementById("index-ketcher");
if (iframe) {
iframe.style.height = "400px";
}
}, 300);
} else { } else {
$('#index-form-smiles').val(textSmiles); // SMILES mode - show text input, hide Ketcher
$('#index-form').submit(); ketcherContainer.addClass("opacity-0 transform scale-95");
ketcherContainer.removeClass("opacity-100 transform scale-100");
// Restore fieldset padding for text input mode
fieldset.removeClass("p-4");
fieldset.addClass("p-8");
// Wait for fade out to complete, then hide and show new content
setTimeout(() => {
ketcherContainer.addClass("hidden");
textContainer.removeClass("hidden opacity-0 transform scale-95");
textContainer.addClass("opacity-100 transform scale-100");
}, 300);
// Transfer SMILES from Ketcher to text input if available
if (window.indexKetcher && window.indexKetcher.getSmiles) {
const smiles = window.indexKetcher.getSmiles();
if (smiles && smiles.trim() !== "") {
$("#index-form-text-input").val(smiles);
}
}
} }
} }
function actionDropdownClicked() { // Ketcher integration
var suffix = ' <span class="caret"></span>'; function indexKetcherToTextInput() {
var dropdownVal = $(this).text(); $("#index-form-smiles").val(this.ketcher.getSmiles());
if (dropdownVal === 'Search') {
$("#index-form").attr("action", '/search');
$("#index-form").attr("method", 'GET');
} else {
$("#index-form").attr("action", currentPackage + "/pathway");
}
$('#action-button').html(dropdownVal + suffix);
}
function ketcherToTextInput() {
$('#index-form-text-input').val(this.ketcher.getSmiles());
} }
$(function () { $(function () {
// Initialize fieldset with proper padding
$(".fieldset").addClass("p-8");
$('#index-form').on("keydown", function (e) { // Toggle event listener
if (e.key === "Enter") { $('input[type="checkbox"]').on("change", toggleInputMode);
e.preventDefault();
goButtonClicked();
}
});
// Code that should be executed once DOM is ready goes here // Ketcher iframe load handler
$('#dropdown-predict').on('click', actionDropdownClicked); $("#index-ketcher").on("load", function () {
$('#dropdown-search').on('click', actionDropdownClicked);
$('#run-button').on('click', goButtonClicked);
// Update Ketcher Width
var fullWidth = $('#index-form-bar').width();
$('#index-form-ketcher').width(fullWidth);
// add a listener that gets triggered whenever the structure in ketcher has changed
$('#index-form-ketcher').on('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) {
window.indexKetcher = win.ketcher;
win.ketcher.editor.event.change.handlers.push({ win.ketcher.editor.event.change.handlers.push({
once: false, once: false,
priority: 0, priority: 0,
f: ketcherToTextInput, f: indexKetcherToTextInput,
ketcher: win.ketcher ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
} }
}; };
checkKetcherReady(); checkKetcherReady();
}); });
// Handle example link clicks
$(".example-link").on("click", function (e) {
e.preventDefault();
const smiles = $(this).data("smiles");
const title = $(this).attr("title");
// Check if we're in Ketcher mode or text input mode
if ($('input[type="checkbox"]').is(":checked")) {
// In Ketcher mode - set the SMILES in Ketcher
if (window.indexKetcher && window.indexKetcher.setMolecule) {
window.indexKetcher.setMolecule(smiles);
}
} else {
// In text input mode - set the SMILES in the text input
$("#index-form-text-input").val(smiles);
}
// Show a brief feedback
const originalText = $(this).text();
$(this).text(`loaded!`);
setTimeout(() => {
$(this).text(originalText);
}, 1000);
});
// Handle form submission on Enter
$("#index-form").on("submit", function (e) {
e.preventDefault();
var textSmiles = "";
// Check if we're in Ketcher mode and extract SMILES
if ($('input[type="checkbox"]').is(":checked") && window.indexKetcher) {
textSmiles = window.indexKetcher.getSmiles().trim();
} else {
textSmiles = $("#index-form-text-input").val().trim();
}
if (textSmiles === "") {
return;
}
$("#index-form-smiles").val(textSmiles);
$("#index-form").attr("action", currentPackage + "/pathway");
$("#index-form").attr("method", "POST");
this.submit();
});
// Discourse topics are now loaded automatically by discourse-api.js
}); });
</script> </script>
{% endblock content %} {% endblock main_content %}

View File

@ -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 %}

View File

@ -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 &gt;&gt;</a></p> experimental conditions.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/packages"
role="button"
>Learn more &gt;&gt;</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 %}

View File

@ -1,5 +1,11 @@
<div class="modal fade bs-modal-lg" id="citemodal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" <div
aria-hidden="true"> 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-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -9,22 +15,33 @@
<ol class="list-group list-group-numbered"> <ol class="list-group list-group-numbered">
<li class="list-group-item"> <li class="list-group-item">
Hafner, J., Lorsbach, T., Schmidt, S. <em>et al.</em> Hafner, J., Lorsbach, T., Schmidt, S. <em>et al.</em>
<cite>Advancements in biotransformation pathway prediction: enhancements, datasets, and novel <cite
functionalities in enviPath.</cite> >Advancements in biotransformation pathway prediction:
<a href="https://doi.org/10.1186/s13321-024-00881-6" target="_blank">J Cheminform 16, 93 enhancements, datasets, and novel functionalities in
(2024)</a> enviPath.</cite
>
<a href="https://doi.org/10.1186/s13321-024-00881-6" target="_blank"
>J Cheminform 16, 93 (2024)</a
>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
Wicker, J., Lorsbach, T., Gütlein, M., Schmid, E., Latino, D., Kramer, S., Fenner, K. Wicker, J., Lorsbach, T., Gütlein, M., Schmid, E., Latino, D.,
<cite>enviPath - The environmental contaminant biotransformation pathway resource</cite> Kramer, S., Fenner, K.
<cite
>enviPath - The environmental contaminant biotransformation
pathway resource</cite
>
<a href="https://doi.org/10.1093/nar/gkv1229" target="_blank"> <a href="https://doi.org/10.1093/nar/gkv1229" target="_blank">
Nucleic Acids Research, Volume 44, Issue D1, 4 January 2016, Pages D502-D508 Nucleic Acids Research, Volume 44, Issue D1, 4 January 2016, Pages
D502-D508
</a> </a>
</li> </li>
</ol> </ol>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,11 @@
<div class="modal fade" tabindex="-1" id="import_legacy_package_modal" role="dialog" <div
aria-labelledby="import_legacy_package_modal" aria-hidden="true"> class="modal fade"
tabindex="-1"
id="import_legacy_package_modal"
role="dialog"
aria-labelledby="import_legacy_package_modal"
aria-hidden="true"
>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -11,32 +17,54 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Create a Package based on the JSON Export of the legacy system.</p> <p>Create a Package based on the JSON Export of the legacy system.</p>
<form id="import-legacy-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post" <form
enctype="multipart/form-data"> id="import-legacy-package-modal-form"
accept-charset="UTF-8"
data-remote="true"
method="post"
enctype="multipart/form-data"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label class="btn btn-primary" for="legacyJsonFile"> <label class="btn btn-primary" for="legacyJsonFile">
<input id="legacyJsonFile" name="file" type="file" style="display:none;" <input
onchange="$('#upload-legacy-file-info').html(this.files[0].name)"> id="legacyJsonFile"
name="file"
type="file"
style="display:none;"
onchange="$('#upload-legacy-file-info').html(this.files[0].name)"
/>
Choose JSON File Choose JSON File
</label> </label>
<span class="label label-info" id="upload-legacy-file-info"></span> <span class="label label-info" id="upload-legacy-file-info"></span>
<input type="hidden" value="import-legacy-package-json" name="hidden" readonly=""> <input
type="hidden"
value="import-legacy-package-json"
name="hidden"
readonly=""
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="import-legacy-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a> <a
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> id="import-legacy-package-modal-form-submit"
class="btn btn-primary"
href="#"
>Submit</a
>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$('#import-legacy-package-modal-form-submit').on('click', function (e) { $("#import-legacy-package-modal-form-submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$('#import-legacy-package-modal-form').submit(); $("#import-legacy-package-modal-form").submit();
}); });
}); });
</script> </script>

View File

@ -1,5 +1,11 @@
<div class="modal fade" tabindex="-1" id="import_package_modal" role="dialog" <div
aria-labelledby="import_package_modal" aria-hidden="true"> class="modal fade"
tabindex="-1"
id="import_package_modal"
role="dialog"
aria-labelledby="import_package_modal"
aria-hidden="true"
>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -11,32 +17,54 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Create a Package based on a JSON Export.</p> <p>Create a Package based on a JSON Export.</p>
<form id="import-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post" <form
enctype="multipart/form-data"> id="import-package-modal-form"
accept-charset="UTF-8"
data-remote="true"
method="post"
enctype="multipart/form-data"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label class="btn btn-primary" for="jsonFile"> <label class="btn btn-primary" for="jsonFile">
<input id="jsonFile" name="file" type="file" style="display:none;" <input
onchange="$('#upload-file-info').html(this.files[0].name)"> id="jsonFile"
name="file"
type="file"
style="display:none;"
onchange="$('#upload-file-info').html(this.files[0].name)"
/>
Choose JSON File Choose JSON File
</label> </label>
<span class="label label-info" id="upload-file-info"></span> <span class="label label-info" id="upload-file-info"></span>
<input type="hidden" value="import-package-json" name="hidden" readonly=""> <input
type="hidden"
value="import-package-json"
name="hidden"
readonly=""
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="import-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a> <a
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> id="import-package-modal-form-submit"
class="btn btn-primary"
href="#"
>Submit</a
>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$('#import-package-modal-form-submit').on('click', function (e) { $("#import-package-modal-form-submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$('#import-package-modal-form').submit(); $("#import-package-modal-form").submit();
}); });
}); });
</script> </script>

View File

@ -1,57 +1,102 @@
{% 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" <div
role="dialog"> class="modal fade bs-modal-lg"
id="new_compound_modal"
tabindex="-1"
aria-labelledby="new_compound_modal"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
<h4 class="modal-title">Create a new Compound</h4> <h4 class="modal-title">Create a new Compound</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="new_compound_modal_form" accept-charset="UTF-8" action="{% url 'package compound list' meta.current_package.uuid %}" data-remote="true" method="post"> <form
id="new_compound_modal_form"
accept-charset="UTF-8"
action="{% url 'package compound list' meta.current_package.uuid %}"
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<label for="compound-name">Name</label> <label for="compound-name">Name</label>
<input id="compound-name" class="form-control" name="compound-name" placeholder="Name"/> <input
id="compound-name"
class="form-control"
name="compound-name"
placeholder="Name"
/>
<label for="compound-description">Description</label> <label for="compound-description">Description</label>
<input id="compound-description" class="form-control" name="compound-description" placeholder="Description"/> <input
id="compound-description"
class="form-control"
name="compound-description"
placeholder="Description"
/>
<label for="compound-smiles">SMILES</label> <label for="compound-smiles">SMILES</label>
<input type="text" class="form-control" name="compound-smiles" placeholder="SMILES" id="compound-smiles"> <input
type="text"
class="form-control"
name="compound-smiles"
placeholder="SMILES"
id="compound-smiles"
/>
<p></p> <p></p>
<div> <div>
<iframe id="new_compound_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%" <iframe
height="510"></iframe> id="new_compound_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div> </div>
<p></p> <p></p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close <button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
id="new_compound_modal_form_submit"
>
Submit
</button> </button>
<button type="button" class="btn btn-primary" id="new_compound_modal_form_submit">Submit</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
function newCompoundModalketcherToNewCompoundModalTextInput() { function newCompoundModalketcherToNewCompoundModalTextInput() {
$('#compound-smiles').val(this.ketcher.getSmiles()); $("#compound-smiles").val(this.ketcher.getSmiles());
} }
$(function () { $(function () {
$("#new_compound_ketcher").on("load", function () {
$('#new_compound_ketcher').on('load', function() {
const checkKetcherReady = () => { const checkKetcherReady = () => {
win = this.contentWindow win = this.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: newCompoundModalketcherToNewCompoundModalTextInput,
ketcher: win.ketcher ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
@ -59,20 +104,16 @@ $(function() {
}; };
checkKetcherReady(); checkKetcherReady();
}) });
$(function () { $(function () {
$('#new_compound_modal_form_submit').on('click', function(e) { $("#new_compound_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$(this).prop("disabled", true); $(this).prop("disabled", true);
// submit form // submit form
$('#new_compound_modal_form').submit(); $("#new_compound_modal_form").submit();
}); });
}); });
}); });
</script> </script>

View File

@ -1,5 +1,11 @@
<div class="modal fade" tabindex="-1" id="new_group_modal" role="dialog" aria-labelledby="new_group_modal" <div
aria-hidden="true"> class="modal fade"
tabindex="-1"
id="new_group_modal"
role="dialog"
aria-labelledby="new_group_modal"
aria-hidden="true"
>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -10,25 +16,44 @@
<h4 class="modal-title">New Group</h4> <h4 class="modal-title">New Group</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Create new Group. You can assign users to the group once <p>
it is created. Description can be changed after creation.</p> Create new Group. You can assign users to the group once it is
<form id="new_group_modal_form" accept-charset="UTF-8" action="{{ SERVER_BASE }}/group" created. Description can be changed after creation.
</p>
<form
id="new_group_modal_form"
accept-charset="UTF-8"
action="{{ SERVER_BASE }}/group"
data-remote="true" data-remote="true"
method="post"> method="post"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label for="name">Name</label> <label for="name">Name</label>
<input id="name" type="text" name="group-name" class="form-control" placeholder="Name"/> <input
id="name"
type="text"
name="group-name"
class="form-control"
placeholder="Name"
/>
</p> </p>
<p> <p>
<label for="description">Description</label> <label for="description">Description</label>
<input id="description" type="text" class="form-control" placeholder="Description..." <input
name="group-description"/> id="description"
type="text"
class="form-control"
placeholder="Description..."
name="group-description"
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="new_group_modal_form_submit" class="btn btn-primary" href="#">Submit</a> <a id="new_group_modal_form_submit" class="btn btn-primary" href="#"
>Submit</a
>
<button type="button" class="btn btn-default" data-dismiss="modal"> <button type="button" class="btn btn-default" data-dismiss="modal">
Cancel Cancel
</button> </button>
@ -38,8 +63,8 @@
</div> </div>
<script> <script>
$(function () { $(function () {
$('#new_group_modal_form_submit').on('click', function() { $("#new_group_modal_form_submit").on("click", function () {
$('#new_group_modal_form').submit(); $("#new_group_modal_form").submit();
}); });
}); });
</script> </script>

View File

@ -1,5 +1,11 @@
<div class="modal fade" tabindex="-1" id="new_model_modal" role="dialog" aria-labelledby="new_model_modal" <div
aria-hidden="true"> class="modal fade"
tabindex="-1"
id="new_model_modal"
role="dialog"
aria-labelledby="new_model_modal"
aria-hidden="true"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -10,29 +16,52 @@
<h4 class="modal-title">New Model</h4> <h4 class="modal-title">New Model</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="new_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model" <form
data-remote="true" method="post"> id="new_model_form"
accept-charset="UTF-8"
action="{{ meta.current_package.url }}/model"
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<div class="jumbotron">Create a new Model to <div class="jumbotron">
limit the number of degradation products in the Create a new Model to limit the number of degradation products in
prediction. You just need to set a name and the packages the prediction. You just need to set a name and the packages you
you want the object to be based on. There are multiple types of models available. want the object to be based on. There are multiple types of models
For additional information have a look at our available. For additional information have a look at our
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki <a
&gt;&gt;</a> target="_blank"
href="https://wiki.envipath.org/index.php/relative-reasoning"
role="button"
>wiki &gt;&gt;</a
>
</div> </div>
<!-- Name --> <!-- Name -->
<label for="model-name">Name</label> <label for="model-name">Name</label>
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/> <input
id="model-name"
name="model-name"
class="form-control"
placeholder="Name"
/>
<!-- Description --> <!-- Description -->
<label for="model-description">Description</label> <label for="model-description">Description</label>
<input id="model-description" name="model-description" class="form-control" <input
placeholder="Description"/> id="model-description"
name="model-description"
class="form-control"
placeholder="Description"
/>
<!-- Model Type --> <!-- Model Type -->
<label for="model-type">Model Type</label> <label for="model-type">Model Type</label>
<select id="model-type" name="model-type" class="form-control" data-width='100%'> <select
id="model-type"
name="model-type"
class="form-control"
data-width="100%"
>
<option disabled selected>Select Model Type</option> <option 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>
@ -42,19 +71,25 @@
<!-- Rule Packages --> <!-- Rule Packages -->
<div id="rule-packages" class="ep-model-param mlrr rbrr"> <div id="rule-packages" class="ep-model-param mlrr rbrr">
<label for="model-rule-packages">Rule Packages</label> <label for="model-rule-packages">Rule Packages</label>
<select id="model-rule-packages" name="model-rule-packages" data-actions-box='true' <select
class="form-control" multiple data-width='100%'> id="model-rule-packages"
name="model-rule-packages"
data-actions-box="true"
class="form-control"
multiple
data-width="100%"
>
<option disabled>Reviewed Packages</option> <option disabled>Reviewed Packages</option>
{% 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 %}
<option disabled>Unreviewed Packages</option> <option disabled>Unreviewed Packages</option>
{% 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 %}
</select> </select>
@ -63,19 +98,25 @@
<!-- Data Packages --> <!-- Data Packages -->
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer"> <div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
<label for="model-data-packages">Data Packages</label> <label for="model-data-packages">Data Packages</label>
<select id="model-data-packages" name="model-data-packages" data-actions-box='true' <select
class="form-control" multiple data-width='100%'> id="model-data-packages"
name="model-data-packages"
data-actions-box="true"
class="form-control"
multiple
data-width="100%"
>
<option disabled>Reviewed Packages</option> <option disabled>Reviewed Packages</option>
{% 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 %}
<option disabled>Unreviewed Packages</option> <option disabled>Unreviewed Packages</option>
{% 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 %}
</select> </select>
@ -84,11 +125,19 @@
<!-- Fingerprinter --> <!-- Fingerprinter -->
<div id="fingerprinter" class="ep-model-param mlrr"> <div id="fingerprinter" class="ep-model-param mlrr">
<label for="model-fingerprinter">Fingerprinter</label> <label for="model-fingerprinter">Fingerprinter</label>
<select id="model-fingerprinter" name="model-fingerprinter" data-actions-box='true' <select
class="form-control" multiple data-width='100%'> id="model-fingerprinter"
name="model-fingerprinter"
data-actions-box="true"
class="form-control"
multiple
data-width="100%"
>
<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> <option disabled selected>
Select Additional Fingerprinter / Descriptor
</option>
{% 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 %}
@ -99,8 +148,16 @@
<!-- Threshold --> <!-- Threshold -->
<div id="threshold" class="ep-model-param mlrr enviformer"> <div id="threshold" class="ep-model-param mlrr enviformer">
<label for="model-threshold">Threshold</label> <label for="model-threshold">Threshold</label>
<input type="number" min="0" max="1" step="0.05" value="0.5" id="model-threshold" <input
name="model-threshold" class="form-control"> type="number"
min="0"
max="1"
step="0.05"
value="0.5"
id="model-threshold"
name="model-threshold"
class="form-control"
/>
</div> </div>
<div id="appdomain" class="ep-model-param mlrr"> <div id="appdomain" class="ep-model-param mlrr">
@ -108,33 +165,64 @@
<!-- Build AD? --> <!-- Build AD? -->
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" id="build-app-domain" name="build-app-domain">Also build an <input
Applicability Domain? 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"> <div id="ad-params" style="display:none">
<!-- Num Neighbors --> <!-- Num Neighbors -->
<label for="num-neighbors">Number of Neighbors</label> <label for="num-neighbors">Number of Neighbors</label>
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control" <input
value="5" id="num-neighbors"
step="1" min="0" max="10"> name="num-neighbors"
<!-- Local Compatibility -->
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
<input id="local-compatibility-threshold" name="local-compatibility-threshold"
type="number" type="number"
class="form-control" value="0.5" step="0.01" min="0" max="1"> class="form-control"
value="5"
step="1"
min="0"
max="10"
/>
<!-- Local Compatibility -->
<label for="local-compatibility-threshold"
>Local Compatibility Threshold</label
>
<input
id="local-compatibility-threshold"
name="local-compatibility-threshold"
type="number"
class="form-control"
value="0.5"
step="0.01"
min="0"
max="1"
/>
<!-- Reliability --> <!-- Reliability -->
<label for="reliability-threshold">Reliability Threshold</label> <label for="reliability-threshold">Reliability Threshold</label>
<input id="reliability-threshold" name="reliability-threshold" type="number" <input
class="form-control" value="0.5" step="0.01" min="0" max="1"> id="reliability-threshold"
name="reliability-threshold"
type="number"
class="form-control"
value="0.5"
step="0.01"
min="0"
max="1"
/>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="new_model_modal_form_submit" class="btn btn-primary" href="#">Submit</a> <a id="new_model_modal_form_submit" class="btn btn-primary" href="#"
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> >Submit</a
>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
</div> </div>
</div> </div>
</div> </div>
@ -143,45 +231,40 @@
<script> <script>
$(function () { $(function () {
// Built in Model Types // Built in Model Types
var nativeModelTypes = [ var nativeModelTypes = ["mlrr", "rbrr", "enviformer"];
"mlrr",
"rbrr",
"enviformer",
]
// Initially hide all "specific" forms // Initially hide all "specific" forms
$(".ep-model-param").each(function () { $(".ep-model-param").each(function () {
$(this).hide(); $(this).hide();
}); });
$('#model-type').selectpicker(); $("#model-type").selectpicker();
$("#model-fingerprinter").selectpicker(); $("#model-fingerprinter").selectpicker();
$("#model-rule-packages").selectpicker(); $("#model-rule-packages").selectpicker();
$("#model-data-packages").selectpicker(); $("#model-data-packages").selectpicker();
$("#build-app-domain").change(function () { $("#build-app-domain").change(function () {
if ($(this).is(":checked")) { if ($(this).is(":checked")) {
$('#ad-params').show(); $("#ad-params").show();
} else { } else {
$('#ad-params').hide(); $("#ad-params").hide();
} }
}); });
// On change hide all and show only selected // On change hide all and show only selected
$("#model-type").change(function () { $("#model-type").change(function () {
$('.ep-model-param').hide(); $(".ep-model-param").hide();
var modelType = $('#model-type').val(); var modelType = $("#model-type").val();
if (nativeModelTypes.indexOf(modelType) !== -1) { if (nativeModelTypes.indexOf(modelType) !== -1) {
$('.' + modelType).show(); $("." + modelType).show();
} else { } else {
// do nothing // do nothing
} }
}); });
$('#new_model_modal_form_submit').on('click', function (e) { $("#new_model_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$('#new_model_form').submit(); $("#new_model_form").submit();
}); });
}); });
</script> </script>

View File

@ -1,15 +1,15 @@
<div class="modal fade" <div
class="modal fade"
tabindex="-1" tabindex="-1"
id="new_package_modal" id="new_package_modal"
role="dialog" role="dialog"
aria-labelledby="new_package_modal" aria-labelledby="new_package_modal"
aria-hidden="true"> aria-hidden="true"
>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" <button type="button" class="close" data-dismiss="modal">
class="close"
data-dismiss="modal">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</button> </button>
@ -17,36 +17,42 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Create new package. Description can be changed later.</p> <p>Create new package. Description can be changed later.</p>
<form id="new_package_modal_form" <form
id="new_package_modal_form"
accept-charset="UTF-8" accept-charset="UTF-8"
action="" action=""
data-remote="true" data-remote="true"
method="post"> method="post"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label for="name">Name</label> <label for="name">Name</label>
<input id="name" class="form-control" <input
id="name"
class="form-control"
name="package-name" name="package-name"
placeholder="Name"/> placeholder="Name"
/>
</p> </p>
<p> <p>
<label for="description">Description</label> <label for="description">Description</label>
<input id="description" <input
id="description"
type="text" type="text"
rows="3" rows="3"
class="form-control" class="form-control"
placeholder="Description..." placeholder="Description..."
name="package-description"/> name="package-description"
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="new_package_modal_form_submit" <a id="new_package_modal_form_submit" class="btn btn-primary" href="#"
class="btn btn-primary" >Submit</a
href="#">Submit</a> >
<button type="button" <button type="button" class="btn btn-default" data-dismiss="modal">
class="btn btn-default" Cancel
data-dismiss="modal">Cancel
</button> </button>
</div> </div>
</div> </div>
@ -54,9 +60,9 @@
</div> </div>
<script> <script>
$(function () { $(function () {
$('#new_package_modal_form_submit').on('click', function (e) { $("#new_package_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$('#new_package_modal_form').submit(); $("#new_package_modal_form").submit();
}); });
}); });
</script> </script>

View File

@ -1,39 +1,57 @@
{% load static %} {% load static %}
<div class="modal fade" tabindex="-1" id="new_pathway_modal" role="dialog" aria-labelledby="new_pathway_modal" <div
aria-hidden="true" style="overflow-y: auto;"> 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--> <!-- FIXME: make width dynamic-->
<div class="modal-dialog" id="new_pathway_modal_dialog" style="width:900px"> <div class="modal-dialog" id="new_pathway_modal_dialog" style="width:900px">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal"> <button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <span class="sr-only">Close</span> <span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button> </button>
<h4 class="js-title-step"></h4> <h4 class="js-title-step"></h4>
</div> </div>
<div class="modal-body hide" data-step="1" data-title="New Pathway"> <div class="modal-body hide" data-step="1" data-title="New Pathway">
<div class="jumbotron">Create a new pathway by entering <div class="jumbotron">
the root compound and a name. Then select if you want to Create a new pathway by entering the root compound and a name. Then
use the prediction engine to generate a predicted pathway or select if you want to use the prediction engine to generate a
create an empty pathway that you fill in by yourself. If predicted pathway or create an empty pathway that you fill in by
you choose to predict a pathway, you can modify the yourself. If you choose to predict a pathway, you can modify the
settings for the prediction, or use the default settings settings for the prediction, or use the default settings and just
and just click Submit. click Submit.
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% if current_user.name == 'anonymous' %} {% if current_user.name == 'anonymous' %}
<div class="alert alert-warning"> <div class="alert alert-warning">
You are currently logged in as Anonymous. Please note: You are currently logged in as Anonymous. Please note: Pathways
Pathways entered or predicted as anonymous user will be deleted after 30 days. entered or predicted as anonymous user will be deleted after 30
Please log in to save your results. days. Please log in to save your results.
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<label for="name">Name</label> <label for="name">Name</label>
<input id="name" class="form-control" name="name" placeholder="Name"/> <input
id="name"
class="form-control"
name="name"
placeholder="Name"
/>
<label for="description">Description</label> <label for="description">Description</label>
<input id="description" class="form-control" name="description" placeholder="no description"/> <input
id="description"
class="form-control"
name="description"
placeholder="no description"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -41,17 +59,33 @@
<div class="radio" id="predict"> <div class="radio" id="predict">
<p> <p>
<label> <label>
<input type="radio" name="predict" id="radioPredict" value="predict" checked/>Predict pathway <input
type="radio"
name="predict"
id="radioPredict"
value="predict"
checked
/>Predict pathway
</label> </label>
</p> </p>
<p> <p>
<label> <label>
<input type="radio" name="predict" id="radioIncremental"value="incremental"/>Incremental prediction <input
type="radio"
name="predict"
id="radioIncremental"
value="incremental"
/>Incremental prediction
</label> </label>
</p> </p>
<p> <p>
<label> <label>
<input type="radio" name="predict" id="radioBuild" value="build"/>Build pathway <input
type="radio"
name="predict"
id="radioBuild"
value="build"
/>Build pathway
</label> </label>
</p> </p>
</div> </div>
@ -60,13 +94,18 @@
<label for="smilesinput">SMILES</label> <label for="smilesinput">SMILES</label>
<table style="width: 100%"> <table style="width: 100%">
<colgroup> <colgroup>
<col span="1" style="width: 90%;"> <col span="1" style="width: 90%;" />
<col span="1" style="width: 10%;"> <col span="1" style="width: 10%;" />
</colgroup> </colgroup>
<tr> <tr>
<td> <td>
<input id="smilesinput" class="form-control" name="smilesinput" placeholder="C1CCCCC1" <input
autocapitalize="none"/> id="smilesinput"
class="form-control"
name="smilesinput"
placeholder="C1CCCCC1"
autocapitalize="none"
/>
</td> </td>
<td> <td>
<button type="button" class="btn btn-default" id="render-button"> <button type="button" class="btn btn-default" id="render-button">
@ -77,33 +116,57 @@
</table> </table>
<p id="ketcher_container"></p> <p id="ketcher_container"></p>
<div> <div>
<iframe id="ifKetcher" src="{% static '/js/ketcher/ketcher.html' %}" width="850" <iframe
height="510"></iframe> id="ifKetcher"
src="{% static '/js/ketcher/ketcher.html' %}"
width="850"
height="510"
></iframe>
</div> </div>
</div> </div>
<div class="modal-body hide" data-step="2" data-title="New Pathway - Advanced Settings"> <div
<div class="jumbotron">Choose if you want to use an existing class="modal-body hide"
setting, or create a new one for this pathway data-step="2"
prediction. Then click Submit to use the specified setting, data-title="New Pathway - Advanced Settings"
or click next to set the parameters. >
<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>
<div id="settings"> <div id="settings">
<div class="radio" id="settingRadio"> <div class="radio" id="settingRadio">
<p> <p>
<label> <label>
<input type="radio" name="existing" id="radioDefault" value="exisiting" checked/> <input
type="radio"
name="existing"
id="radioDefault"
value="exisiting"
checked
/>
Use Default Use Default
</label> </label>
</p> </p>
<p> <p>
<label> <label>
<input type="radio" name="existing" id="radioExists" value="exisiting"/> <input
type="radio"
name="existing"
id="radioExists"
value="exisiting"
/>
Select Existing Select Existing
</label> </label>
</p> </p>
<p> <p>
<label> <label>
<input type="radio" name="existing" id="radioNew" value="temporary"/> <input
type="radio"
name="existing"
id="radioNew"
value="temporary"
/>
Create New Create New
</label> </label>
</p> </p>
@ -111,7 +174,7 @@
<select id="settingSelect" name="settingSelect" class="form-control"> <select id="settingSelect" name="settingSelect" class="form-control">
{% for setting in available_settings %} {% for setting in available_settings %}
<option value="{{ setting.id }}">{{ setting.name }}</option> <option value="{{ setting.id }}">{{ setting.name|safe }}</option>
{% endfor %} {% endfor %}
</select> </select>
<p></p> <p></p>
@ -121,12 +184,25 @@
{% include "templates/modals/collections/new_setting_modal_body.html" %} {% include "templates/modals/collections/new_setting_modal_body.html" %}
{% endwith %} {% endwith %}
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default js-btn-step pull-left" data-orientation="cancel" <button
onclick="reset()" data-dismiss="modal"></button> type="button"
<button type="button" class="btn btn-default js-btn-step" data-orientation="previous" class="btn btn-default js-btn-step pull-left"
id="backbutton"></button> data-orientation="cancel"
<button type="button" class="btn btn-default js-btn-step" data-orientation="next" onclick="reset()"
id="nextbutton"></button> 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> <a id="modal-form-submit" class="btn btn-primary" href="#">Submit</a>
</div> </div>
</div> </div>
@ -135,86 +211,86 @@
<script> <script>
s = new Setting( s = new Setting(
'settingName', "settingName",
'package_multi_select', "package_multi_select",
'modelSelect', "modelSelect",
'cutoff', "cutoff",
'evalType', "evalType",
'availableTS', "availableTS",
'forms', "forms",
'truncatorTable', "truncatorTable",
'summaryTable', "summaryTable",
); );
$(function () { $(function () {
// hide all forms // hide all forms
$('#forms').children().hide() $("#forms").children().hide();
$("#render-button").on("click", function () { $("#render-button").on("click", function () {
syncKetcherAndTextInput('text', "ifKetcher", "smilesinput"); syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
}); });
// If theres a change in the in '#smilesinput' sync the value to ketcher // If theres a change in the in '#smilesinput' sync the value to ketcher
$('#smilesinput').on('input', function() { $("#smilesinput").on("input", function () {
syncKetcherAndTextInput('text', 'ifKetcher', 'smilesinput'); syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
}); });
// If theres an update in ketcher sync it to textinput // If theres an update in ketcher sync it to textinput
setInterval(function () { setInterval(function () {
syncKetcherAndTextInput('ketcher', 'ifKetcher', 'smilesinput'); syncKetcherAndTextInput("ketcher", "ifKetcher", "smilesinput");
}, 250); }, 250);
$("#smilesinput").on("blur", function () { $("#smilesinput").on("blur", function () {
syncKetcherAndTextInput('text', 'ifKetcher', 'smilesinput'); syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
}); });
$("#smilesinput").on("keypress", function (event) { $("#smilesinput").on("keypress", function (event) {
if (event.keyCode == 13) { if (event.keyCode == 13) {
syncKetcherAndTextInput('text', 'ifKetcher', 'smilesinput'); syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
} }
}); });
// Show forms depending on the selected TS // Show forms depending on the selected TS
$('#availableTS').on('change', function(e) { $("#availableTS").on("change", function (e) {
e.preventDefault(); e.preventDefault();
var type = $(this).val(); var type = $(this).val();
// hide current content // hide current content
$('#forms').children().hide() $("#forms").children().hide();
if(type === '') { if (type === "") {
return; return;
} }
$('#' + type + '_form').show() $("#" + type + "_form").show();
}); });
$("#modelSelect").on("change", function () { $("#modelSelect").on("change", function () {
setCutoff = function (thresh) { setCutoff = function (thresh) {
$("#cutoff").val(thresh); $("#cutoff").val(thresh);
} };
var modelUri = $("#modelSelect :selected").val(); var modelUri = $("#modelSelect :selected").val();
fillPRCurve(modelUri, setCutoff); fillPRCurve(modelUri, setCutoff);
}); });
// Add a TS to the setting // Add a TS to the setting
$('#add-ts-button').on('click', function(e) { $("#add-ts-button").on("click", function (e) {
e.preventDefault(); e.preventDefault();
s.addTruncator(); s.addTruncator();
}); });
$('input[type=radio][name=predict]').change(function() { $("input[type=radio][name=predict]").change(function () {
if (this.id == 'radioBuild') { if (this.id == "radioBuild") {
$("#nextbutton").prop("disabled", true); $("#nextbutton").prop("disabled", true);
} else { } else {
$("#nextbutton").prop("disabled", false); $("#nextbutton").prop("disabled", false);
} }
}); });
$('input[type=radio][name=existing]').change(function() { $("input[type=radio][name=existing]").change(function () {
if (this.id == 'radioDefault' || this.id == 'radioExists') { if (this.id == "radioDefault" || this.id == "radioExists") {
if(this.id == 'radioDefault') { if (this.id == "radioDefault") {
$("#settingSelect").prop("disabled", true); $("#settingSelect").prop("disabled", true);
} else { } else {
$("#settingSelect").prop("disabled", false); $("#settingSelect").prop("disabled", false);
@ -230,72 +306,71 @@ $(function() {
var pwStep1 = function () { var pwStep1 = function () {
console.log("pw step 1"); console.log("pw step 1");
// Make "Next" to "Advanced" // Make "Next" to "Advanced"
$('#nextbutton').val("Advanced"); $("#nextbutton").val("Advanced");
} };
var pwStep2 = function () { var pwStep2 = function () {
console.log("pw step 2"); console.log("pw step 2");
// Make "Advanced" to "Next" // Make "Advanced" to "Next"
$('#nextbutton').val("Next"); $("#nextbutton").val("Next");
// As "Use default is preselected" disable "Next" button // As "Use default is preselected" disable "Next" button
$("#nextbutton").prop("disabled", true); $("#nextbutton").prop("disabled", true);
// Disable setting dropdown as long as the correspndonding radio isnt checked // Disable setting dropdown as long as the correspndonding radio isnt checked
$("#settingSelect").prop("disabled", true); $("#settingSelect").prop("disabled", true);
// Show submit button // Show submit button
$("#modal-form-submit").show(); $("#modal-form-submit").show();
} };
var settingStep1 = function () { var settingStep1 = function () {
// First step sets name and packages // First step sets name and packages
s.extractName(); s.extractName();
s.extractSelectedPackages(); s.extractSelectedPackages();
} };
var settingStep2 = function () { var settingStep2 = function () {
// Seconds step gathers relative reasoning params // Seconds step gathers relative reasoning params
s.extractRelativeReasoning(); s.extractRelativeReasoning();
s.extractCutoff(); s.extractCutoff();
s.extractEvaluationType(); s.extractEvaluationType();
} };
var settingStep3 = function () { var settingStep3 = function () {
s.updateTable(); s.updateTable();
s.updateSummaryTable(); s.updateSummaryTable();
// hide duplicate submit... // hide duplicate submit...
$("#nextbutton").hide(); $("#nextbutton").hide();
} };
var postPathway = function () { var postPathway = function () {
console.log("Complete!"); console.log("Complete!");
console.log(s.tsParams); console.log(s.tsParams);
console.log("Getting SMILES"); console.log("Getting SMILES");
} };
function dummy() { function dummy() {
console.log("dummy"); console.log("dummy");
} }
$('#new_pathway_modal').modalSteps({ $("#new_pathway_modal").modalSteps({
btnCancelHtml: 'Cancel', btnCancelHtml: "Cancel",
btnPreviousHtml: 'Back', btnPreviousHtml: "Back",
btnNextHtml: 'Next', btnNextHtml: "Next",
btnLastStepHtml: 'Submit', btnLastStepHtml: "Submit",
disableNextButton: false, disableNextButton: false,
completeCallback: postPathway, completeCallback: postPathway,
callbacks: { callbacks: {
'1': pwStep1, 1: pwStep1,
'2' : pwStep2, 2: pwStep2,
'3' : dummy, 3: dummy,
'4' : settingStep1, 4: settingStep1,
'5' : settingStep2, 5: settingStep2,
'6' : settingStep3, 6: settingStep3,
} },
}); });
$('#modal-form-submit').on('click', function() { $("#modal-form-submit").on("click", function () {
e.preventDefault(); e.preventDefault();
postPathway(); postPathway();
}); });
}); });
</script> </script>

View File

@ -5,80 +5,157 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Create a Prediction Setting</h5> <h5 class="modal-title">Create a Prediction Setting</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>To create a Prediction Setting fill the form below and click "Create"</p> <p>
<form id="new-prediction-setting-modal-form" accept-charset="UTF-8" action="" data-remote="true" To create a Prediction Setting fill the form below and click "Create"
method="post"> </p>
<form
id="new-prediction-setting-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<label for="prediction-setting-name">Name</label> <label for="prediction-setting-name">Name</label>
<input id="prediction-setting-name" name="prediction-setting-name" class="form-control" placeholder="Name"/> <input
id="prediction-setting-name"
name="prediction-setting-name"
class="form-control"
placeholder="Name"
/>
<label for="prediction-setting-description">Description</label> <label for="prediction-setting-description">Description</label>
<input id="prediction-setting-description" name="prediction-setting-description" class="form-control" <input
placeholder="Description"/> id="prediction-setting-description"
name="prediction-setting-description"
class="form-control"
placeholder="Description"
/>
<label for="prediction-setting-max-nodes">Max #Nodes</label> <label for="prediction-setting-max-nodes">Max #Nodes</label>
<input id="prediction-setting-max-nodes" type="number" class="form-control" name="prediction-setting-max-nodes" value="30" min="1" max="50" step="1"> <input
id="prediction-setting-max-nodes"
type="number"
class="form-control"
name="prediction-setting-max-nodes"
value="30"
min="1"
max="50"
step="1"
/>
<label for="prediction-setting-max-depth">Max Depth</label> <label for="prediction-setting-max-depth">Max Depth</label>
<input id="prediction-setting-max-depth" type="number" class="form-control" name="prediction-setting-max-depth" value="5" min="1" max="8" step="1"> <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 for="tp-generation-method">TP Generation Method</label> <label for="tp-generation-method">TP Generation Method</label>
<select id="tp-generation-method" name="tp-generation-method" class="form-control" data-width='100%'> <select
id="tp-generation-method"
name="tp-generation-method"
class="form-control"
data-width="100%"
>
<option disabled selected>Select how TPs are generated</option> <option 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 id="rule-based-prediction-setting-specific-form">
<!-- Rule Packages --> <!-- Rule Packages -->
<label>Rule Packages</label><br> <label>Rule Packages</label><br />
<select id="rule-based-prediction-setting-packages" name="rule-based-prediction-setting-packages" <select
data-actions-box='true' class="form-control" multiple data-width='100%'> id="rule-based-prediction-setting-packages"
name="rule-based-prediction-setting-packages"
data-actions-box="true"
class="form-control"
multiple
data-width="100%"
>
<option disabled>Reviewed Packages</option> <option disabled>Reviewed Packages</option>
{% 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 %}
<option disabled>Unreviewed Packages</option> <option disabled>Unreviewed Packages</option>
{% 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 %}
</select> </select>
</div> </div>
<div id="model-based-prediction-setting-specific-form"> <div id="model-based-prediction-setting-specific-form">
<label>Select Model</label><br> <label>Select Model</label><br />
<select id="model-based-prediction-setting-model" name="model-based-prediction-setting-model" class="form-control" data-width='100%'> <select
id="model-based-prediction-setting-model"
name="model-based-prediction-setting-model"
class="form-control"
data-width="100%"
>
<option disabled selected>Select the model</option> <option 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> <label for="model-based-prediction-setting-threshold"
<input id="model-based-prediction-setting-threshold" name="model-based-prediction-setting-threshold" class="form-control" placeholder="0.25" type="number"/> >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"> <input
<label class="form-check-label" for="prediction-setting-new-default">Set this setting as new default</label> class="form-check-input"
type="checkbox"
value="on"
id="prediction-setting-new-default"
name="prediction-setting-new-default"
/>
<label class="form-check-label" for="prediction-setting-new-default"
>Set this setting as new default</label
>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="new-prediction-setting-modal-submit">Create</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="new-prediction-setting-modal-submit"
>
Create
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
// Initially hide all "specific" forms // Initially hide all "specific" forms
$("div[id$='-specific-form']").each(function () { $("div[id$='-specific-form']").each(function () {
$(this).hide(); $(this).hide();
@ -91,18 +168,18 @@
$("div[id$='-specific-form']").each(function () { $("div[id$='-specific-form']").each(function () {
$(this).hide(); $(this).hide();
}); });
val = $('option:selected', this).val(); val = $("option:selected", this).val();
$("#" + val + "-specific-form").show(); $("#" + val + "-specific-form").show();
}); });
$('#new-prediction-setting-modal-submit').click(function(e){ $("#new-prediction-setting-modal-submit").click(function (e) {
e.preventDefault(); e.preventDefault();
// $('#new-prediction-setting-modal-form').submit(); // $('#new-prediction-setting-modal-form').submit();
const formData = $('#new-prediction-setting-modal-form').serialize(); const formData = $("#new-prediction-setting-modal-form").serialize();
$.post('/setting', formData, function(response) { $.post("/setting", formData, function (response) {
location.reload(); location.reload();
}); });
}); });
}) });
</script> </script>

View File

@ -1,50 +1,91 @@
{% 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" <div
role="dialog"> class="modal fade bs-modal-lg"
id="new_reaction_modal"
tabindex="-1"
aria-labelledby="new_reaction_modal"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
<h4 class="modal-title">Create a new Reaction</h4> <h4 class="modal-title">Create a new Reaction</h4>
</div> </div>
<div class="modal-body"> <div class="modal-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"> <form
id="new_reaction_modal_form"
accept-charset="UTF-8"
action="{% url 'package reaction list' meta.current_package.uuid %}"
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<label for="reaction-name">Name</label> <label for="reaction-name">Name</label>
<input id="reaction-name" class="form-control" name="reaction-name" placeholder="Name"/> <input
id="reaction-name"
class="form-control"
name="reaction-name"
placeholder="Name"
/>
<label for="reaction-description">Description</label> <label for="reaction-description">Description</label>
<input id="reaction-description" class="form-control" name="reaction-description" placeholder="Description"/> <input
id="reaction-description"
class="form-control"
name="reaction-description"
placeholder="Description"
/>
<p></p> <p></p>
<div> <div>
<iframe id="new_reaction_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%" <iframe
height="510"></iframe> id="new_reaction_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div> </div>
<input type="hidden" name="reaction-smirks" id="reaction-smirks"> <input type="hidden" name="reaction-smirks" id="reaction-smirks" />
<p></p> <p></p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close <button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
id="new_reaction_modal_form_submit"
>
Submit
</button> </button>
<button type="button" class="btn btn-primary" id="new_reaction_modal_form_submit">Submit</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$('#new_reaction_modal_form_submit').on('click', function(e) { $("#new_reaction_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$(this).prop("disabled", true); $(this).prop("disabled", true);
k = getKetcher('new_reaction_ketcher'); k = getKetcher("new_reaction_ketcher");
$('#reaction-smirks').val(k.getSmiles()); $("#reaction-smirks").val(k.getSmiles());
// submit form // submit form
$('#new_reaction_modal_form').submit(); $("#new_reaction_modal_form").submit();
}); });
}); });
</script> </script>

View File

@ -1,54 +1,102 @@
{% load static %} {% load static %}
<div class="modal fade bs-modal-lg" id="new_rule_modal" tabindex="-1" aria-labelledby="new_rule_modal" aria-modal="true" <div
role="dialog"> class="modal fade bs-modal-lg"
id="new_rule_modal"
tabindex="-1"
aria-labelledby="new_rule_modal"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
<h4 class="modal-title">Create a new Rule</h4> <h4 class="modal-title">Create a new Rule</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="new_rule_modal_form" accept-charset="UTF-8" action="{% url 'package rule list' meta.current_package.uuid %}" data-remote="true" method="post"> <form
id="new_rule_modal_form"
accept-charset="UTF-8"
action="{% url 'package rule list' meta.current_package.uuid %}"
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<label for="rule-name">Name</label> <label for="rule-name">Name</label>
<input id="rule-name" class="form-control" name="rule-name" placeholder="Name"/> <input
id="rule-name"
class="form-control"
name="rule-name"
placeholder="Name"
/>
<label for="rule-description">Description</label> <label for="rule-description">Description</label>
<input id="rule-description" class="form-control" name="rule-description" placeholder="Description"/> <input
id="rule-description"
class="form-control"
name="rule-description"
placeholder="Description"
/>
<label for="rule-smirks">SMIRKS</label> <label for="rule-smirks">SMIRKS</label>
<input id="rule-smirks" class="form-control" name="rule-smirks" placeholder="SMIRKS"/> <input
id="rule-smirks"
class="form-control"
name="rule-smirks"
placeholder="SMIRKS"
/>
<p></p> <p></p>
<div id="rule-smirks-viz"></div> <div id="rule-smirks-viz"></div>
<input type="hidden" name="rule-type" id="rule-type" value="SimpleAmbitRule"> <input
type="hidden"
name="rule-type"
id="rule-type"
value="SimpleAmbitRule"
/>
<p></p> <p></p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close <button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
id="new_rule_modal_form_submit"
>
Submit
</button> </button>
<button type="button" class="btn btn-primary" id="new_rule_modal_form_submit">Submit</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#rule-smirks").on("input", function (e) {
$("#rule-smirks-viz").empty();
$('#rule-smirks').on('input', function(e) { smirks = $("#rule-smirks").val();
$('#rule-smirks-viz').empty()
smirks = $('#rule-smirks').val()
const img = new Image(); const img = new Image();
img.src = "{% url 'depict' %}?is_query_smirks=true&smirks=" + encodeURIComponent(smirks); img.src =
img.style.width = '100%'; "{% url 'depict' %}?is_query_smirks=true&smirks=" +
img.style.height = '100%'; encodeURIComponent(smirks);
img.style.objectFit = 'cover'; img.style.width = "100%";
img.style.height = "100%";
img.style.objectFit = "cover";
img.onload = function () { img.onload = function () {
$('#rule-smirks-viz').append(img); $("#rule-smirks-viz").append(img);
}; };
img.onerror = function () { img.onerror = function () {
@ -57,16 +105,16 @@ $(function() {
<h4 class="alert-heading">Could not render SMIRKS!</h4> <h4 class="alert-heading">Could not render SMIRKS!</h4>
<p>Could not render SMIRKS - Have you entered a valid SMIRKS?</a> <p>Could not render SMIRKS - Have you entered a valid SMIRKS?</a>
</p> </p>
</div>` </div>`;
$('#rule-smirks-viz').append(error_tpl); $("#rule-smirks-viz").append(error_tpl);
}; };
}); });
$('#new_rule_modal_form_submit').on('click', function(e) { $("#new_rule_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$(this).prop("disabled", true); $(this).prop("disabled", true);
// submit form // submit form
$('#new_rule_modal_form').submit(); $("#new_rule_modal_form").submit();
}); });
}); });
</script> </script>

View File

@ -1,5 +1,11 @@
<div class="modal fade" tabindex="-1" id="new_scenario_modal" role="dialog" aria-labelledby="new_scenario_modal" <div
aria-hidden="true"> class="modal fade"
tabindex="-1"
id="new_scenario_modal"
role="dialog"
aria-labelledby="new_scenario_modal"
aria-hidden="true"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -10,39 +16,84 @@
<h4 class="modal-title">New Scenario</h4> <h4 class="modal-title">New Scenario</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="new_scenario_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/scenario" <form
data-remote="true" method="post"> id="new_scenario_form"
accept-charset="UTF-8"
action="{{ meta.current_package.url }}/scenario"
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<div class="jumbotron">Please enter name, description, and date of scenario. Date should be <div class="jumbotron">
associated to the data, not the current date. For example, this could reflect the publishing Please enter name, description, and date of scenario. Date should be
date of a study. You can leave all fields but the name empty and fill them in later. associated to the data, not the current date. For example, this
<a target="_blank" href="https://wiki.envipath.org/index.php/scenario" role="button">wiki could reflect the publishing date of a study. You can leave all
&gt;&gt;</a> fields but the name empty and fill them in later.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/scenario"
role="button"
>wiki &gt;&gt;</a
>
</div> </div>
<label for="scenario-name">Name</label> <label for="scenario-name">Name</label>
<input id="scenario-name" name="scenario-name" class="form-control" placeholder="Name"/> <input
id="scenario-name"
name="scenario-name"
class="form-control"
placeholder="Name"
/>
<label for="scenario-description">Description</label> <label for="scenario-description">Description</label>
<input id="scenario-description" name="scenario-description" class="form-control" <input
placeholder="Description"/> id="scenario-description"
name="scenario-description"
class="form-control"
placeholder="Description"
/>
<label id="dateField" for="dateYear">Date</label> <label id="dateField" for="dateYear">Date</label>
<table> <table>
<tr> <tr>
<th> <th>
<input type="number" id="dateYear" name="scenario-date-year" class="form-control" <input
placeholder="YYYY" max="{% now "Y" %}"> type="number"
id="dateYear"
name="scenario-date-year"
class="form-control"
placeholder="YYYY"
max="{% now "Y" %}"
/>
</th> </th>
<th> <th>
<input type="number" id="dateMonth" name="scenario-date-month" min="1" max="12" <input
class="form-control" placeholder="MM" > type="number"
id="dateMonth"
name="scenario-date-month"
min="1"
max="12"
class="form-control"
placeholder="MM"
/>
</th> </th>
<th> <th>
<input type="number" id="dateDay" name="scenario-date-day" min="1" max="31" class="form-control" <input
placeholder="DD"> type="number"
id="dateDay"
name="scenario-date-day"
min="1"
max="31"
class="form-control"
placeholder="DD"
/>
</th> </th>
</tr> </tr>
</table> </table>
<label for="scenario-type">Scenario Type</label> <label for="scenario-type">Scenario Type</label>
<select id="scenario-type" name="scenario-type" class="form-control" data-width='100%'> <select
id="scenario-type"
name="scenario-type"
class="form-control"
data-width="100%"
>
<option value="empty" selected>Empty Scenario</option> <option value="empty" selected>Empty Scenario</option>
{% for k, v in scenario_types.items %} {% for k, v in scenario_types.items %}
<option value="{{ v.name }}">{{ k }}</option> <option value="{{ v.name }}">{{ k }}</option>
@ -56,12 +107,15 @@
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="new_scenario_modal_form_submit" class="btn btn-primary" href="#">Submit</a> <a id="new_scenario_modal_form_submit" class="btn btn-primary" href="#"
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> >Submit</a
>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
</div> </div>
</div> </div>
</div> </div>
@ -79,24 +133,21 @@
$("div[id$='-specific-inputs']").each(function () { $("div[id$='-specific-inputs']").each(function () {
$(this).hide(); $(this).hide();
}); });
val = $('option:selected', this).val(); val = $("option:selected", this).val();
$("#" + val + "-specific-inputs").show(); $("#" + val + "-specific-inputs").show();
}); });
$('#new_scenario_modal_form_submit').on('click', function (e) { $("#new_scenario_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$('#new_scenario_form').submit(); $("#new_scenario_form").submit();
}); });
var dateYear = document.getElementById("dateYear"); var dateYear = document.getElementById("dateYear");
dateYear.addEventListener("change", () => { dateYear.addEventListener("change", () => {
console.log("Final value after editing:", dateYear.value); console.log("Final value after editing:", dateYear.value);
if (dateYear.value.length < 4) { if (dateYear.value.length < 4) {
dateYear.value = {% now "Y" %}; dateYear.value = new Date().getFullYear();
} }
}); });
}); });
</script> </script>

View File

@ -4,32 +4,60 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h3 class="modal-title">Add Additional Information</h3> <h3 class="modal-title">Add Additional Information</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<select id="select-additional-information-type" data-actions-box='true' class="form-control" data-width='100%'> <select
id="select-additional-information-type"
data-actions-box="true"
class="form-control"
data-width="100%"
>
<option selected disabled>Select the type to add</option> <option selected disabled>Select the type to add</option>
{% for add_inf in available_additional_information %} {% for add_inf in available_additional_information %}
<option value="{{ add_inf.name }}">{{ add_inf.display_name }}</option> <option value="{{ add_inf.name }}">
{{ add_inf.display_name }}
</option>
{% endfor %} {% endfor %}
</select> </select>
{% for add_inf in available_additional_information %} {% for add_inf in available_additional_information %}
<div class="aiform {{ add_inf.name }}" style="display: none;"> <div class="aiform {{ add_inf.name }}" style="display: none;">
<form id="add_{{ add_inf.name }}_add-additional-information-modal-form" accept-charset="UTF-8" <form
action="" data-remote="true" method="post"> id="add_{{ add_inf.name }}_add-additional-information-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
{{ add_inf.widget|safe }} {{ add_inf.widget|safe }}
<input type="hidden" name="hidden" value="add-additional-information"> <input
type="hidden"
name="hidden"
value="add-additional-information"
/>
</form> </form>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="add-additional-information-modal-submit">Add Close
</button>
<button
type="button"
class="btn btn-primary"
id="add-additional-information-modal-submit"
>
Add
</button> </button>
</div> </div>
</div> </div>
@ -37,25 +65,28 @@
</div> </div>
<script> <script>
$(function () { $(function () {
$("#select-additional-information-type").change(function (e) {
var selectedType = $(
"#select-additional-information-type :selected",
).val();
$(".aiform").hide();
$("." + selectedType).show();
});
$('#select-additional-information-type').change(function(e){ $("#add-additional-information-modal-submit").click(function (e) {
var selectedType = $("#select-additional-information-type :selected").val();
$('.aiform').hide();
$('.' + selectedType).show();
})
$('#add-additional-information-modal-submit').click(function(e){
e.preventDefault(); e.preventDefault();
var selectedType = $("#select-additional-information-type :selected").val(); var selectedType = $(
"#select-additional-information-type :selected",
).val();
console.log(selectedType); console.log(selectedType);
if (selectedType !== null && selectedType !== undefined && selectedType !== '') { if (
$('.' + selectedType + ' >form').submit(); selectedType !== null &&
selectedType !== undefined &&
selectedType !== ""
) {
$("." + selectedType + " >form").submit();
} }
}); });
}); });
</script> </script>

View File

@ -1,94 +1,150 @@
{% load static %} {% load static %}
<div class="modal fade bs-modal-lg" id="add_pathway_edge_modal" tabindex="-1" aria-labelledby="add_pathway_edge_modal" <div
class="modal fade bs-modal-lg"
id="add_pathway_edge_modal"
tabindex="-1"
aria-labelledby="add_pathway_edge_modal"
aria-modal="true" aria-modal="true"
role="dialog"> role="dialog"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h4 class="modal-title">Add a Reaction</h4> <h4 class="modal-title">Add a Reaction</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="add_pathway_edge_modal_form" accept-charset="UTF-8" <form
id="add_pathway_edge_modal_form"
accept-charset="UTF-8"
action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}" action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}"
data-remote="true" method="post"> data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<label for="edge-name">Name</label> <label for="edge-name">Name</label>
<input id="edge-name" class="form-control" name="edge-name" placeholder="Name"/> <input
id="edge-name"
class="form-control"
name="edge-name"
placeholder="Name"
/>
<label for="edge-description">Description</label> <label for="edge-description">Description</label>
<input id="edge-description" class="form-control" name="edge-description" placeholder="Description"/> <input
id="edge-description"
class="form-control"
name="edge-description"
placeholder="Description"
/>
<p></p> <p></p>
<div class="row"> <div class="row">
<div class="col-xs-5"> <div class="col-xs-5">
<legend>Substrate(s)</legend> <legend>Substrate(s)</legend>
</div> </div>
<div class="col-xs-2"> <div class="col-xs-2"></div>
</div>
<div class="col-xs-5"> <div class="col-xs-5">
<legend>Product(s)</legend> <legend>Product(s)</legend>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-5"> <div class="col-xs-5">
<select id="add_pathway_edge_substrates" name="edge-substrates" <select
data-actions-box='true' class="form-control" multiple data-width='100%'> id="add_pathway_edge_substrates"
name="edge-substrates"
data-actions-box="true"
class="form-control"
multiple
data-width="100%"
>
{% for n in pathway.nodes %} {% for n in pathway.nodes %}
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name }}</option> <option
data-smiles="{{ n.default_node_label.smiles }}"
value="{{ n.url }}"
>
{{ n.default_node_label.name|safe }}
</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="col-xs-2" style="display: flex; justify-content: center; align-items: center;"> <div
class="col-xs-2"
style="display: flex; justify-content: center; align-items: center;"
>
<i class="glyphicon glyphicon-arrow-right"></i> <i class="glyphicon glyphicon-arrow-right"></i>
</div> </div>
<div class="col-xs-5"> <div class="col-xs-5">
<select id="add_pathway_edge_products" name="edge-products" <select
data-actions-box='true' class="form-control" multiple data-width='100%'> id="add_pathway_edge_products"
name="edge-products"
data-actions-box="true"
class="form-control"
multiple
data-width="100%"
>
{% for n in pathway.nodes %} {% for n in pathway.nodes %}
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name }}</option> <option
data-smiles="{{ n.default_node_label.smiles }}"
value="{{ n.url }}"
>
{{ n.default_node_label.name|safe }}
</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<p></p> <p></p>
<div class="col-xs-12" id="reaction_image"> <div class="col-xs-12" id="reaction_image"></div>
</div>
</div> </div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close <button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
id="add_pathway_edge_modal_form_submit"
>
Submit
</button> </button>
<button type="button" class="btn btn-primary" id="add_pathway_edge_modal_form_submit">Submit</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
function reactionImage() { function reactionImage() {
var substrates = []; var substrates = [];
$('#add_pathway_edge_substrates option:selected').each(function () { $("#add_pathway_edge_substrates option:selected").each(function () {
var smiles = $(this).data('smiles'); // read data-smiles attribute var smiles = $(this).data("smiles"); // read data-smiles attribute
substrates.push(smiles); substrates.push(smiles);
}); });
var products = [] var products = [];
$('#add_pathway_edge_products option:selected').each(function () { $("#add_pathway_edge_products option:selected").each(function () {
var smiles = $(this).data('smiles'); // read data-smiles attribute var smiles = $(this).data("smiles"); // read data-smiles attribute
products.push(smiles); products.push(smiles);
}); });
if (substrates.length > 0 && products.length > 0) { if (substrates.length > 0 && products.length > 0) {
reaction = substrates.join('.') + ">>" + products.join('.'); reaction = substrates.join(".") + ">>" + products.join(".");
$('#reaction_image').empty(); $("#reaction_image").empty();
$('#reaction_image').append( $("#reaction_image").append(
"<img width='100%' src='{% url 'depict' %}?smirks=" + encodeURIComponent(reaction) +"'>" "<img width='100%' src='{% url 'depict' %}?smirks=" +
encodeURIComponent(reaction) +
"'>",
); );
} }
} }
@ -97,30 +153,22 @@
$("#add_pathway_edge_substrates").selectpicker(); $("#add_pathway_edge_substrates").selectpicker();
$("#add_pathway_edge_products").selectpicker(); $("#add_pathway_edge_products").selectpicker();
$("#add_pathway_edge_substrates").on('change', function (e) { $("#add_pathway_edge_substrates").on("change", function (e) {
reactionImage(); reactionImage();
}) });
$("#add_pathway_edge_products").on('change', function (e) { $("#add_pathway_edge_products").on("change", function (e) {
reactionImage(); reactionImage();
}) });
$(function () { $(function () {
$('#add_pathway_edge_modal_form_submit').on('click', function (e) { $("#add_pathway_edge_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$(this).prop("disabled", true); $(this).prop("disabled", true);
// submit form // submit form
$('#add_pathway_edge_modal_form').submit(); $("#add_pathway_edge_modal_form").submit();
}); });
}); });
}); });
</script> </script>

View File

@ -1,57 +1,102 @@
{% load static %} {% load static %}
<div class="modal fade bs-modal-lg" id="add_pathway_node_modal" tabindex="-1" aria-labelledby="add_pathway_node_modal" aria-modal="true" <div
role="dialog"> class="modal fade bs-modal-lg"
id="add_pathway_node_modal"
tabindex="-1"
aria-labelledby="add_pathway_node_modal"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h4 class="modal-title">Add a Node</h4> <h4 class="modal-title">Add a Node</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="add_pathway_node_modal_form" accept-charset="UTF-8" action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}" data-remote="true" method="post"> <form
id="add_pathway_node_modal_form"
accept-charset="UTF-8"
action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}"
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<label for="node-name">Name</label> <label for="node-name">Name</label>
<input id="node-name" class="form-control" name="node-name" placeholder="Name"/> <input
id="node-name"
class="form-control"
name="node-name"
placeholder="Name"
/>
<label for="node-description">Description</label> <label for="node-description">Description</label>
<input id="node-description" class="form-control" name="node-description" placeholder="Description"/> <input
id="node-description"
class="form-control"
name="node-description"
placeholder="Description"
/>
<label for="node-smiles">SMILES</label> <label for="node-smiles">SMILES</label>
<input type="text" class="form-control" name="node-smiles" placeholder="SMILES" id="node-smiles"> <input
type="text"
class="form-control"
name="node-smiles"
placeholder="SMILES"
id="node-smiles"
/>
<p></p> <p></p>
<div> <div>
<iframe id="add_node_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%" <iframe
height="510"></iframe> id="add_node_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div> </div>
<p></p> <p></p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close <button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
id="add_pathway_node_modal_form_submit"
>
Submit
</button> </button>
<button type="button" class="btn btn-primary" id="add_pathway_node_modal_form_submit">Submit</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
function newStructureModalketcherToNewStructureModalTextInput() { function newStructureModalketcherToNewStructureModalTextInput() {
$('#node-smiles').val(this.ketcher.getSmiles()); $("#node-smiles").val(this.ketcher.getSmiles());
} }
$(function () { $(function () {
$("#add_node_ketcher").on("load", function () {
$('#add_node_ketcher').on('load', function() {
const checkKetcherReady = () => { const checkKetcherReady = () => {
win = this.contentWindow win = this.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: newStructureModalketcherToNewStructureModalTextInput, f: newStructureModalketcherToNewStructureModalTextInput,
ketcher: win.ketcher ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
@ -59,20 +104,16 @@ $(function() {
}; };
checkKetcherReady(); checkKetcherReady();
}) });
$(function () { $(function () {
$('#add_pathway_node_modal_form_submit').on('click', function(e) { $("#add_pathway_node_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$(this).prop("disabled", true); $(this).prop("disabled", true);
// submit form // submit form
$('#add_pathway_node_modal_form').submit(); $("#add_pathway_node_modal_form").submit();
}); });
}); });
}); });
</script> </script>

View File

@ -1,57 +1,102 @@
{% load static %} {% load static %}
<div class="modal fade bs-modal-lg" id="add_structure_modal" tabindex="-1" aria-labelledby="add_structure_modal" aria-modal="true" <div
role="dialog"> class="modal fade bs-modal-lg"
id="add_structure_modal"
tabindex="-1"
aria-labelledby="add_structure_modal"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
<h4 class="modal-title">Create a new Structure</h4> <h4 class="modal-title">Create a new Structure</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="add_structure_modal_form" accept-charset="UTF-8" action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}" data-remote="true" method="post"> <form
id="add_structure_modal_form"
accept-charset="UTF-8"
action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}"
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<label for="structure-name">Name</label> <label for="structure-name">Name</label>
<input id="structure-name" class="form-control" name="structure-name" placeholder="Name"/> <input
id="structure-name"
class="form-control"
name="structure-name"
placeholder="Name"
/>
<label for="structure-description">Description</label> <label for="structure-description">Description</label>
<input id="structure-description" class="form-control" name="structure-description" placeholder="Description"/> <input
id="structure-description"
class="form-control"
name="structure-description"
placeholder="Description"
/>
<label for="structure-smiles">SMILES</label> <label for="structure-smiles">SMILES</label>
<input type="text" class="form-control" name="structure-smiles" placeholder="SMILES" id="structure-smiles"> <input
type="text"
class="form-control"
name="structure-smiles"
placeholder="SMILES"
id="structure-smiles"
/>
<p></p> <p></p>
<div> <div>
<iframe id="add_structure_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%" <iframe
height="510"></iframe> id="add_structure_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div> </div>
<p></p> <p></p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close <button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
id="add_structure_modal_form_submit"
>
Submit
</button> </button>
<button type="button" class="btn btn-primary" id="add_structure_modal_form_submit">Submit</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
function newStructureModalketcherToNewStructureModalTextInput() { function newStructureModalketcherToNewStructureModalTextInput() {
$('#structure-smiles').val(this.ketcher.getSmiles()); $("#structure-smiles").val(this.ketcher.getSmiles());
} }
$(function () { $(function () {
$("#add_structure_ketcher").on("load", function () {
$('#add_structure_ketcher').on('load', function() {
const checkKetcherReady = () => { const checkKetcherReady = () => {
win = this.contentWindow win = this.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: newStructureModalketcherToNewStructureModalTextInput, f: newStructureModalketcherToNewStructureModalTextInput,
ketcher: win.ketcher ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
@ -59,20 +104,16 @@ $(function() {
}; };
checkKetcherReady(); checkKetcherReady();
}) });
$(function () { $(function () {
$('#add_structure_modal_form_submit').on('click', function(e) { $("#add_structure_modal_form_submit").on("click", function (e) {
e.preventDefault(); e.preventDefault();
$(this).prop("disabled", true); $(this).prop("disabled", true);
// submit form // submit form
$('#add_structure_modal_form').submit(); $("#add_structure_modal_form").submit();
}); });
}); });
}); });
</script> </script>

View File

@ -5,21 +5,38 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">Delete Edge</h3> <h3 class="modal-title">Delete Edge</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
Deletes the Edge. Nodes referenced by this edge will remain. Deletes the Edge. Nodes referenced by this edge will remain.
<p></p> <p></p>
<form id="delete-pathway-edge-modal-form" accept-charset="UTF-8" action="" data-remote="true" <form
method="post"> id="delete-pathway-edge-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<select id="delete_pathway_edge_edges" name="edge-url" <select
data-actions-box='true' class="form-control" data-width='100%'> id="delete_pathway_edge_edges"
<option value="" disabled selected>Select Reaction to delete</option> name="edge-url"
data-actions-box="true"
class="form-control"
data-width="100%"
>
<option value="" disabled selected>
Select Reaction to delete
</option>
{% for e in pathway.edges %} {% for e in pathway.edges %}
<option value="{{ e.url }}">{{ e.edge_label.name }}</option> <option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" id="hidden" name="hidden" value="delete" /> <input type="hidden" id="hidden" name="hidden" value="delete" />
@ -28,8 +45,16 @@
<div id="delete_pathway_edge_image"></div> <div id="delete_pathway_edge_image"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="delete-pathway-edge-modal-submit">Delete</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="delete-pathway-edge-modal-submit"
>
Delete
</button>
</div> </div>
</div> </div>
</div> </div>
@ -38,28 +63,27 @@
$(function () { $(function () {
$("#delete_pathway_edge_edges").selectpicker(); $("#delete_pathway_edge_edges").selectpicker();
$("#delete_pathway_edge_edges").on('change', function (e) { $("#delete_pathway_edge_edges").on("change", function (e) {
edge_url = $('#delete_pathway_edge_edges option:selected').val() edge_url = $("#delete_pathway_edge_edges option:selected").val();
if (edge_url !== "") { if (edge_url !== "") {
$('#delete_pathway_edge_image').empty(); $("#delete_pathway_edge_image").empty();
$('#delete_pathway_edge_image').append( $("#delete_pathway_edge_image").append(
"<img width='100%' src='" + edge_url + "?image=svg'>" "<img width='100%' src='" + edge_url + "?image=svg'>",
); );
} }
}) });
$('#delete-pathway-edge-modal-submit').click(function (e) { $("#delete-pathway-edge-modal-submit").click(function (e) {
e.preventDefault(); e.preventDefault();
edge_url = $('#delete_pathway_edge_edges option:selected').val() edge_url = $("#delete_pathway_edge_edges option:selected").val();
if (edge_url === "") { if (edge_url === "") {
return; return;
} }
$('#delete-pathway-edge-modal-form').attr('action', edge_url) $("#delete-pathway-edge-modal-form").attr("action", edge_url);
$('#delete-pathway-edge-modal-form').submit(); $("#delete-pathway-edge-modal-form").submit();
});
}); });
})
</script> </script>

View File

@ -1,25 +1,46 @@
{% load static %} {% load static %}
<!-- Delete Node --> <!-- Delete Node -->
<div id="delete_pathway_node_modal" class="modal" tabindex="-1"> <div id="delete_pathway_node_modal" class="modal" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">Delete Node</h3> <h3 class="modal-title">Delete Node</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
Deletes the Node. Edges having this Node as Substrate or Product will be removed as well. Deletes the Node. Edges having this Node as Substrate or Product will be
removed as well.
<p></p> <p></p>
<form id="delete-pathway-node-modal-form" accept-charset="UTF-8" action="" data-remote="true" <form
method="post"> id="delete-pathway-node-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<select id="delete_pathway_node_nodes" name="node-url" <select
data-actions-box='true' class="form-control" data-width='100%'> id="delete_pathway_node_nodes"
<option value="" disabled selected>Select Compound to delete</option> name="node-url"
data-actions-box="true"
class="form-control"
data-width="100%"
>
<option value="" disabled selected>
Select Compound to delete
</option>
{% for n in pathway.nodes %} {% for n in pathway.nodes %}
<option value="{{ n.url }}">{{ n.default_node_label.name }}</option> <option value="{{ n.url }}">
{{ n.default_node_label.name|safe }}
</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" id="hidden" name="hidden" value="delete" /> <input type="hidden" id="hidden" name="hidden" value="delete" />
@ -28,8 +49,16 @@
<div id="delete_pathway_node_image"></div> <div id="delete_pathway_node_image"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="delete-pathway-node-modal-submit">Delete</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="delete-pathway-node-modal-submit"
>
Delete
</button>
</div> </div>
</div> </div>
</div> </div>
@ -38,28 +67,27 @@
$(function () { $(function () {
$("#delete_pathway_node_nodes").selectpicker(); $("#delete_pathway_node_nodes").selectpicker();
$("#delete_pathway_node_nodes").on('change', function (e) { $("#delete_pathway_node_nodes").on("change", function (e) {
node_url = $('#delete_pathway_node_nodes option:selected').val() node_url = $("#delete_pathway_node_nodes option:selected").val();
if (node_url !== "") { if (node_url !== "") {
$('#delete_pathway_node_image').empty(); $("#delete_pathway_node_image").empty();
$('#delete_pathway_node_image').append( $("#delete_pathway_node_image").append(
"<img width='100%' src='" + node_url + "?image=svg'>" "<img width='100%' src='" + node_url + "?image=svg'>",
); );
} }
}) });
$('#delete-pathway-node-modal-submit').click(function (e) { $("#delete-pathway-node-modal-submit").click(function (e) {
e.preventDefault(); e.preventDefault();
node_url = $('#delete_pathway_node_nodes option:selected').val() node_url = $("#delete_pathway_node_nodes option:selected").val();
if (node_url === "") { if (node_url === "") {
return; return;
} }
$('#delete-pathway-node-modal-form').attr('action', node_url) $("#delete-pathway-node-modal-form").attr("action", node_url);
$('#delete-pathway-node-modal-form').submit(); $("#delete-pathway-node-modal-form").submit();
});
}); });
})
</script> </script>

View File

@ -5,32 +5,49 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">Download Pathway as CSV</h3> <h3 class="modal-title">Download Pathway as CSV</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
By clicking on Download the Pathway will be converted into a CSV and directly downloaded. By clicking on Download the Pathway will be converted into a CSV and
<form id="download-pathway-csv-modal-form" accept-charset="UTF-8" action="{{ pathway.url }}" directly downloaded.
data-remote="true" method="GET"> <form
id="download-pathway-csv-modal-form"
accept-charset="UTF-8"
action="{{ pathway.url }}"
data-remote="true"
method="GET"
>
<input type="hidden" name="download" value="true" /> <input type="hidden" name="download" value="true" />
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="download-pathway-csv-modal-submit">Download</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="download-pathway-csv-modal-submit"
>
Download
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#download-pathway-csv-modal-submit").click(function (e) {
$('#download-pathway-csv-modal-submit').click(function (e) {
e.preventDefault(); e.preventDefault();
$('#download-pathway-csv-modal-form').submit(); $("#download-pathway-csv-modal-form").submit();
$('#download_pathway_csv_modal').modal('hide'); $("#download_pathway_csv_modal").modal("hide");
});
}); });
})
</script> </script>

View File

@ -5,7 +5,12 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">Download Pathway as Image</h3> <h3 class="modal-title">Download Pathway as Image</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
@ -13,20 +18,26 @@
By clicking on Download the Pathway will be saved as SVG. By clicking on Download the Pathway will be saved as SVG.
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="download-pathway-image-modal-submit">Download</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="download-pathway-image-modal-submit"
>
Download
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#download-pathway-image-modal-submit").click(function (e) {
$('#download-pathway-image-modal-submit').click(function (e) {
e.preventDefault(); e.preventDefault();
downloadSVG($('#pwsvg')[0], '{{ pathway.name.split|join:"_" }}.svg') downloadSVG($("#pwsvg")[0], '{{ pathway.name.split|join:"_" }}.svg');
$('#download_pathway_image_modal').modal('hide'); $("#download_pathway_image_modal").modal("hide");
});
}); });
})
</script> </script>

View File

@ -5,42 +5,66 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Edit Compound</h5> <h5 class="modal-title">Edit Compound</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Edit Compound.</p> <p>Edit Compound.</p>
<form id="edit-compound-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post"> <form
id="edit-compound-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label for="compound-name">Name</label> <label for="compound-name">Name</label>
<input id="compound-name" class="form-control" name="compound-name" value="{{ compound.name}}"> <input
id="compound-name"
class="form-control"
name="compound-name"
value="{{ compound.name|safe }}"
/>
</p> </p>
<p> <p>
<label for="compound-description">Description</label> <label for="compound-description">Description</label>
<input id="compound-description" type="text" class="form-control" <input
value="{{ compound.description }}" id="compound-description"
name="compound-description"> type="text"
class="form-control"
value="{{ compound.description|safe }}"
name="compound-description"
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="edit-compound-modal-submit">Update</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="edit-compound-modal-submit"
>
Update
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#edit-compound-modal-submit").click(function (e) {
$('#edit-compound-modal-submit').click(function(e){
e.preventDefault(); e.preventDefault();
$('#edit-compound-modal-form').submit(); $("#edit-compound-modal-form").submit();
}); });
}); });
</script> </script>

View File

@ -5,41 +5,66 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Create a Compound</h5> <h5 class="modal-title">Create a Compound</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Edit a Compound Structure.</p> <p>Edit a Compound Structure.</p>
<form id="edit-compound-structure-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post"> <form
id="edit-compound-structure-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label for="compound-structure-name">Name</label> <label for="compound-structure-name">Name</label>
<input id="compound-structure-name" class="form-control" name="compound-structure-name" value="{{ compound_structure.name }}"> <input
id="compound-structure-name"
class="form-control"
name="compound-structure-name"
value="{{ compound_structure.name|safe }}"
/>
</p> </p>
<p> <p>
<label for="compound-structure-description">Description</label> <label for="compound-structure-description">Description</label>
<input id="compound-structure-description" type="text" class="form-control" <input
value="{{ compound_structure.description }}" name="compound-structure-description"> id="compound-structure-description"
type="text"
class="form-control"
value="{{ compound_structure.description|safe }}"
name="compound-structure-description"
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="edit-compound-structure-modal-submit">Create</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="edit-compound-structure-modal-submit"
>
Create
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#edit-compound-structure-modal-submit").click(function (e) {
$('#edit-compound-structure-modal-submit').click(function(e){
e.preventDefault(); e.preventDefault();
$('#edit-compound-structure-modal-form').submit(); $("#edit-compound-structure-modal-form").submit();
}); });
}); });
</script> </script>

View File

@ -5,15 +5,20 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Add or Remove Group Member</h5> <h5 class="modal-title">Add or Remove Group Member</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p> <p>
To add member (either User or entire Groups) to this group select the entity you want to add below To add member (either User or entire Groups) to this group select the
and click the check mark. entity you want to add below and click the check mark.
<br> <br />
To remove member simply click the <code>X</code> next to the member. To remove member simply click the <code>X</code> next to the member.
</p> </p>
@ -27,25 +32,36 @@
</div> </div>
<div class="row"> <div class="row">
<form id="modal-form-group-member" class="form-inline" role="form" accept-charset="UTF-8" action="" <form
data-remote="true" method="post"> id="modal-form-group-member"
class="form-inline"
role="form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<div class="col-xs-8"> <div class="col-xs-8">
<select id="select_member" name="member" data-actions-box='true' <select
class="selPackages" data-width='100%'> id="select_member"
name="member"
data-actions-box="true"
class="selPackages"
data-width="100%"
>
<option disabled selected>User</option> <option disabled selected>User</option>
{% for u in users %} {% for u in users %}
<option value="{{ u.url }}">{{ u.username }}</option> <option value="{{ u.url }}">{{ u.username }}</option>
{% endfor %} {% endfor %}
<option disabled>Groups</option> <option disabled>Groups</option>
{% for g in groups %} {% for g in groups %}
<option value="{{ g.url }}">{{ g.name }}</option> <option value="{{ g.url }}">{{ g.name|safe }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" name="action" value="add"> <input type="hidden" name="action" value="add" />
</div>
<div class="col-xs-2">
</div> </div>
<div class="col-xs-2"></div>
<div class="col-xs-2"> <div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2"> <button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-ok"></span> <span class="glyphicon glyphicon-ok"></span>
@ -56,16 +72,22 @@
<p></p> <p></p>
{% for u in group.user_member.all %} {% for u in group.user_member.all %}
<div class="row"> <div class="row">
<form id="modal-form-group-member_{{ u.uuid }}" class="form-inline" role="form" <form
accept-charset="UTF-8" action="" data-remote="true" method="post"> id="modal-form-group-member_{{ u.uuid }}"
class="form-inline"
role="form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<div class="col-xs-8"> <div class="col-xs-8">
{{ u.username }} {{ u.username }}
<input type="hidden" name="member" value="{{ u.url }}" /> <input type="hidden" name="member" value="{{ u.url }}" />
<input type="hidden" name="action" value="remove"> <input type="hidden" name="action" value="remove" />
</div>
<div class="col-xs-2">
</div> </div>
<div class="col-xs-2"></div>
<div class="col-xs-2"> <div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2"> <button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-trash"></span> <span class="glyphicon glyphicon-trash"></span>
@ -77,16 +99,22 @@
<p></p> <p></p>
{% for g in group.group_member.all %} {% for g in group.group_member.all %}
<div class="row"> <div class="row">
<form id="modal-form-group-member_{{ g.uuid }}" class="form-inline" role="form" <form
accept-charset="UTF-8" action="" data-remote="true" method="post"> id="modal-form-group-member_{{ g.uuid }}"
class="form-inline"
role="form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<div class="col-xs-8"> <div class="col-xs-8">
{{ g.name }} {{ g.name|safe }}
<input type="hidden" name="member" value="{{ g.url }}" /> <input type="hidden" name="member" value="{{ g.url }}" />
<input type="hidden" name="action" value="remove"> <input type="hidden" name="action" value="remove" />
</div>
<div class="col-xs-2">
</div> </div>
<div class="col-xs-2"></div>
<div class="col-xs-2"> <div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2"> <button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-trash"></span> <span class="glyphicon glyphicon-trash"></span>
@ -95,26 +123,29 @@
</form> </form>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="edit-package-modal-submit">Update</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="edit-package-modal-submit"
>
Update
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#edit-package-modal-submit").click(function (e) {
$('#edit-package-modal-submit').click(function(e){
e.preventDefault(); e.preventDefault();
$('#edit-package-modal-form').submit(); $("#edit-package-modal-form").submit();
}); });
$("#select_member").selectpicker(); $("#select_member").selectpicker();
});
})
</script> </script>

View File

@ -4,41 +4,68 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h3 class="modal-title">Update Model</h3> <h3 class="modal-title">Update Model</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Alter Name and Description of the Model.</p> <p>Alter Name and Description of the Model.</p>
<form id="edit-model-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post"> <form
id="edit-model-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label for="model-name">Name</label> <label for="model-name">Name</label>
<input id="model-name" type="text" class="form-control" name="model-name" <input
value="{{ model.name }}"> id="model-name"
type="text"
class="form-control"
name="model-name"
value="{{ model.name|safe }}"
/>
</p> </p>
<p> <p>
<label for="model-description">Description</label> <label for="model-description">Description</label>
<input id="model-description" type="text" class="form-control" name="model-description" <input
value="{{ model.description }}"> id="model-description"
type="text"
class="form-control"
name="model-description"
value="{{ model.description|safe }}"
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="edit-model-modal-submit">Update</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="edit-model-modal-submit"
>
Update
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#edit-model-modal-submit").click(function (e) {
$('#edit-model-modal-submit').click(function (e) {
e.preventDefault(); e.preventDefault();
$('#edit-model-modal-form').submit(); $("#edit-model-modal-form").submit();
});
}); });
})
</script> </script>

View File

@ -5,42 +5,66 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Edit Node</h5> <h5 class="modal-title">Edit Node</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Edit Node.</p> <p>Edit Node.</p>
<form id="edit-node-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post"> <form
id="edit-node-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %} {% csrf_token %}
<p> <p>
<label for="node-name">Name</label> <label for="node-name">Name</label>
<input id="node-name" class="form-control" name="node-name" value="{{ node.name}}"> <input
id="node-name"
class="form-control"
name="node-name"
value="{{ node.name|safe }}"
/>
</p> </p>
<p> <p>
<label for="node-description">Description</label> <label for="node-description">Description</label>
<input id="node-description" type="text" class="form-control" <input
value="{{ node.description }}" id="node-description"
name="node-description"> type="text"
class="form-control"
value="{{ node.description|safe }}"
name="node-description"
/>
</p> </p>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">
<button type="button" class="btn btn-primary" id="edit-node-modal-submit">Create</button> Close
</button>
<button
type="button"
class="btn btn-primary"
id="edit-node-modal-submit"
>
Create
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { $(function () {
$("#edit-node-modal-submit").click(function (e) {
$('#edit-node-modal-submit').click(function(e){
e.preventDefault(); e.preventDefault();
$('#edit-node-modal-form').submit(); $("#edit-node-modal-form").submit();
}); });
}); });
</script> </script>

Some files were not shown because too many files have changed in this diff Show More