[Feature] Modern UI roll out (#236)

This PR moves all the collection pages into the new UI in a rough push.
I did not put the same amount of care into these as into search, index, and predict.

## Major changes

- All modals are now migrated to a state based alpine.js implementation.
- jQuery is no longer present in the base layout; ajax is replace by native fetch api
- most of the pps.js is now obsolte (as I understand it; the code is not referenced any more @jebus  please double check)
- in-memory pagination for large result lists (set to 50; we can make that configurable later; performance degrades at around 1k) stukk a bit rough tracked in #235

## Minor things

- Sarch and index also use alpine now
- The loading spinner is now CSS animated (not sure if it currently gets correctly called)

## Not done

- Ihave not even cheked the admin pages. Not sure If these need migrations
- The temporary migration pages still use the old template. Not sure what is supposed to happen with those? @jebus

## What I did to test

- opend all pages in browse, and user ; plus all pages reachable from there.
- Interacted and tested the functionality of each modal superfically with exception of the API key modal (no functional test).

---
This PR is massive sorry for that; just did not want to push half-brokenn state.
@jebus @liambrydon I would be glad if you could click around and try to break it :)

Finally closes #133

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#236
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2025-11-26 23:16:44 +13:00
committed by jebus
parent 7f6f209b4a
commit 1a2c9bb543
110 changed files with 10784 additions and 9465 deletions

View File

@ -103,6 +103,16 @@ jobs:
source .venv/bin/activate source .venv/bin/activate
playwright install --with-deps playwright install --with-deps
- name: Run PNPM Commands
run: |
uv run python scripts/pnpm_wrapper.py install
cat << 'EOF' > pnpm-workspace.yaml
onlyBuiltDependencies:
- '@parcel/watcher'
- '@tailwindcss/oxide'
EOF
uv run python scripts/pnpm_wrapper.py run build
- name: Wait for services - name: Wait for services
run: | run: |
until pg_isready -h postgres -U postgres; do sleep 2; done until pg_isready -h postgres -U postgres; do sleep 2; done

View File

@ -34,3 +34,9 @@ if "migration" in s.INSTALLED_APPS:
if s.MS_ENTRA_ENABLED: if s.MS_ENTRA_ENABLED:
urlpatterns.append(path("", include("epauth.urls"))) urlpatterns.append(path("", include("epauth.urls")))
# Custom error handlers
handler400 = "epdb.views.handler400"
handler403 = "epdb.views.handler403"
handler404 = "epdb.views.handler404"
handler500 = "epdb.views.handler500"

View File

@ -62,6 +62,26 @@ def log_post_params(request):
logger.debug(f"{k}\t{v}") logger.debug(f"{k}\t{v}")
def get_error_handler_context(request, for_user=None) -> Dict[str, Any]:
current_user = _anonymous_or_real(request)
if for_user:
current_user = for_user
ctx = {
"title": "enviPath",
"meta": {
"site_id": s.MATOMO_SITE_ID,
"version": "0.0.1",
"server_url": s.SERVER_URL,
"user": current_user,
"enabled_features": s.FLAGS,
"debug": s.DEBUG,
},
}
return ctx
def error(request, message: str, detail: str, code: int = 400): def error(request, message: str, detail: str, code: int = 400):
context = get_base_context(request) context = get_base_context(request)
error_context = { error_context = {
@ -76,6 +96,48 @@ def error(request, message: str, detail: str, code: int = 400):
return render(request, "errors/error.html", context, status=code) return render(request, "errors/error.html", context, status=code)
def handler400(request, exception):
"""Custom 400 Bad Request error handler"""
context = get_error_handler_context(request)
context["public_mode"] = True
return render(request, "errors/400_bad_request.html", context, status=400)
def handler403(request, exception):
"""Custom 403 Forbidden error handler"""
context = get_error_handler_context(request)
context["public_mode"] = True
return render(request, "errors/403_access_denied.html", context, status=403)
def handler404(request, exception):
"""Custom 404 Not Found error handler"""
context = get_error_handler_context(request)
context["public_mode"] = True
return render(request, "errors/404_not_found.html", context, status=404)
def handler500(request):
"""Custom 500 Internal Server Error handler"""
context = get_error_handler_context(request)
error_context = {}
error_context["error_message"] = "Internal Server Error"
error_context["error_detail"] = "An unexpected error occurred. Please try again later."
if request.headers.get("Accept") == "application/json":
return JsonResponse(error_context, status=500)
context["public_mode"] = True
context["error_code"] = 500
context["error_description"] = (
"We encountered an unexpected error while processing your request. Our team has been notified and is working to resolve the issue."
)
context.update(**error_context)
return render(request, "errors/error.html", context, status=500)
def login(request): def login(request):
context = get_base_context(request) context = get_base_context(request)
@ -192,8 +254,8 @@ def register(request):
def editable(request, user): def editable(request, user):
if user.is_superuser: # if user.is_superuser:
return True # return True
url = request.build_absolute_uri(request.path) url = request.build_absolute_uri(request.path)
if PackageManager.is_package_url(url): if PackageManager.is_package_url(url):

View File

@ -34,7 +34,7 @@ dependencies = [
[tool.uv.sources] [tool.uv.sources]
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.4" } enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.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" }
[project.optional-dependencies] [project.optional-dependencies]
@ -46,7 +46,7 @@ dev = [
"pre-commit>=4.3.0", "pre-commit>=4.3.0",
"ruff>=0.13.3", "ruff>=0.13.3",
"pytest-playwright>=0.7.1", "pytest-playwright>=0.7.1",
"pytest-django>=4.11.1" "pytest-django>=4.11.1",
] ]
[tool.ruff] [tool.ruff]
@ -68,47 +68,31 @@ docstring-code-format = true
[tool.poe.tasks] [tool.poe.tasks]
# Main tasks # Main tasks
setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" } setup = { sequence = [
dev = { shell = """ "db-up",
# Start pnpm CSS watcher in background "migrate",
pnpm run dev & "bootstrap",
PNPM_PID=$! ], help = "Complete setup: start database, run migrations, and bootstrap data" }
echo "Started CSS watcher (PID: $PNPM_PID)" dev = { cmd = "uv run python scripts/dev_server.py", help = "Start the development server with CSS watcher", deps = [
"db-up",
# Cleanup function "js-deps",
cleanup() { ] }
echo "\nShutting down..." build = { sequence = [
if kill -0 $PNPM_PID 2>/dev/null; then "build-frontend",
kill $PNPM_PID "collectstatic",
echo " CSS watcher stopped" ], help = "Build frontend assets and collect static files" }
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", "frontend-test-setup"], 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 # Frontend tasks
js-deps = { cmd = "pnpm install", help = "Install frontend dependencies" } js-deps = { cmd = "uv run python scripts/pnpm_wrapper.py install", help = "Install frontend dependencies" }
# Full cleanup tasks # Full cleanup tasks
clean = { sequence = ["clean-db"], help = "Remove model files and database volumes (WARNING: destroys all data!)" } clean = { sequence = [
"clean-db",
], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." } clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." }
# Django tasks # Django tasks
@ -123,10 +107,17 @@ echo "Default admin credentials:"
echo " Username: admin" 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 = "uv run 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"] } build-frontend = { cmd = "uv run python scripts/pnpm_wrapper.py run build", help = "Build frontend assets using pnpm", deps = [
collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = ["build-frontend"] } "js-deps",
frontend-test-setup = {cmd = "playwright install", help = "Install the browsers required for frontend testing"} ] } # Build tasks
collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = [
"build-frontend",
] }
frontend-test-setup = { cmd = "playwright install", help = "Install the browsers required for frontend testing" }

201
scripts/dev_server.py Executable file
View File

@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""
Cross-platform development server script.
Starts pnpm CSS watcher and Django dev server, handling cleanup on exit.
Works on both Windows and Unix systems.
"""
import atexit
import shutil
import signal
import subprocess
import sys
import time
def find_pnpm():
"""
Find pnpm executable on the system.
Returns the path to pnpm or None if not found.
"""
# Try to find pnpm using shutil.which
# On Windows, this will find pnpm.cmd if it's in PATH
pnpm_path = shutil.which("pnpm")
if pnpm_path:
return pnpm_path
# On Windows, also try pnpm.cmd explicitly
if sys.platform == "win32":
pnpm_cmd = shutil.which("pnpm.cmd")
if pnpm_cmd:
return pnpm_cmd
return None
class DevServerManager:
"""Manages background processes for development server."""
def __init__(self):
self.processes = []
self._cleanup_registered = False
def start_process(self, command, description, shell=False):
"""Start a background process and return the process object."""
print(f"Starting {description}...")
try:
if shell:
# Use shell=True for commands that need shell interpretation
process = subprocess.Popen(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
else:
# Split command into list for subprocess
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
self.processes.append((process, description))
print(f"✓ Started {description} (PID: {process.pid})")
return process
except Exception as e:
print(f"✗ Failed to start {description}: {e}", file=sys.stderr)
self.cleanup()
sys.exit(1)
def cleanup(self):
"""Terminate all running processes."""
if not self.processes:
return
print("\nShutting down...")
for process, description in self.processes:
if process.poll() is None: # Process is still running
try:
# Try graceful termination first
if sys.platform == "win32":
process.terminate()
else:
process.send_signal(signal.SIGTERM)
# Wait up to 5 seconds for graceful shutdown
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
# Force kill if graceful shutdown failed
if sys.platform == "win32":
process.kill()
else:
process.send_signal(signal.SIGKILL)
process.wait()
print(f"{description} stopped")
except Exception as e:
print(f"✗ Error stopping {description}: {e}", file=sys.stderr)
self.processes.clear()
def register_cleanup(self):
"""Register cleanup handlers for various exit scenarios."""
if self._cleanup_registered:
return
self._cleanup_registered = True
# Register atexit handler (works on all platforms)
atexit.register(self.cleanup)
# Register signal handlers (Unix only)
if sys.platform != "win32":
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
def _signal_handler(self, signum, frame):
"""Handle Unix signals."""
self.cleanup()
sys.exit(0)
def wait_for_process(self, process, description):
"""Wait for a process to finish and handle its output."""
try:
# Stream output from the process
for line in iter(process.stdout.readline, ""):
if line:
print(f"[{description}] {line.rstrip()}")
process.wait()
return process.returncode
except KeyboardInterrupt:
# Handle Ctrl+C
self.cleanup()
sys.exit(0)
except Exception as e:
print(f"Error waiting for {description}: {e}", file=sys.stderr)
self.cleanup()
sys.exit(1)
def main():
"""Main entry point."""
manager = DevServerManager()
manager.register_cleanup()
# Find pnpm executable
pnpm_path = find_pnpm()
if not pnpm_path:
print("Error: pnpm not found in PATH.", file=sys.stderr)
print("\nPlease install pnpm:", file=sys.stderr)
print(" Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
print(" Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
sys.exit(1)
# Determine shell usage based on platform
use_shell = sys.platform == "win32"
# Start pnpm CSS watcher
# Use the found pnpm path to ensure it works on Windows
pnpm_command = f'"{pnpm_path}" run dev' if use_shell else [pnpm_path, "run", "dev"]
manager.start_process(
pnpm_command,
"CSS watcher",
shell=use_shell,
)
# Give pnpm a moment to start
time.sleep(1)
# Start Django dev server
django_process = manager.start_process(
["uv", "run", "python", "manage.py", "runserver"],
"Django server",
shell=False,
)
print("\nDevelopment servers are running. Press Ctrl+C to stop.\n")
try:
# Wait for Django server (main process)
# If Django exits, we should clean up everything
return_code = manager.wait_for_process(django_process, "Django")
# If Django exited unexpectedly, clean up and exit
if return_code != 0:
manager.cleanup()
sys.exit(return_code)
except KeyboardInterrupt:
# Ctrl+C was pressed
manager.cleanup()
sys.exit(0)
if __name__ == "__main__":
main()

59
scripts/pnpm_wrapper.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""
Cross-platform pnpm command wrapper.
Finds pnpm correctly on Windows (handles pnpm.cmd) and Unix systems.
"""
import shutil
import subprocess
import sys
def find_pnpm():
"""
Find pnpm executable on the system.
Returns the path to pnpm or None if not found.
"""
# Try to find pnpm using shutil.which
# On Windows, this will find pnpm.cmd if it's in PATH
pnpm_path = shutil.which("pnpm")
if pnpm_path:
return pnpm_path
# On Windows, also try pnpm.cmd explicitly
if sys.platform == "win32":
pnpm_cmd = shutil.which("pnpm.cmd")
if pnpm_cmd:
return pnpm_cmd
return None
def main():
"""Main entry point - execute pnpm with provided arguments."""
pnpm_path = find_pnpm()
if not pnpm_path:
print("Error: pnpm not found in PATH.", file=sys.stderr)
print("\nPlease install pnpm:", file=sys.stderr)
print(" Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
print(" Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
sys.exit(1)
# Get all arguments passed to this script
args = sys.argv[1:]
# Execute pnpm with the provided arguments
try:
sys.exit(subprocess.call([pnpm_path] + args))
except KeyboardInterrupt:
# Handle Ctrl+C gracefully
sys.exit(130)
except Exception as e:
print(f"Error executing pnpm: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -34,3 +34,30 @@
} }
@import "./daisyui-theme.css"; @import "./daisyui-theme.css";
/* Loading Spinner - Benzene Ring */
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
}
.loading-spinner svg {
width: 48px;
height: 48px;
animation: spin 2s linear infinite;
}
.loading-spinner .hexagon,
.loading-spinner .double-bonds {
fill: none;
stroke: currentColor;
stroke-width: 2;
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 B

265
static/js/alpine/index.js Normal file
View File

@ -0,0 +1,265 @@
/**
* Alpine.js Components for enviPath
*
* This module provides reusable Alpine.js data components for modals,
* form validation, and form submission.
*/
document.addEventListener('alpine:init', () => {
/**
* Modal Form Component
*
* Provides form validation using HTML5 Constraint Validation API,
* loading states for submission, and error message management.
*
* Basic Usage:
* <dialog x-data="modalForm()" @close="reset()">
* <form id="my-form">
* <input name="field" required>
* </form>
* <button @click="submit('my-form')" :disabled="isSubmitting">Submit</button>
* </dialog>
*
* With Custom State:
* <dialog x-data="modalForm({ state: { selectedItem: '', imageUrl: '' } })" @close="reset()">
* <select x-model="selectedItem" @change="updateImagePreview(selectedItem + '?image=svg')">
* <img :src="imageUrl" x-show="imageUrl">
* </dialog>
*
* With AJAX:
* <button @click="submitAsync('my-form', { onSuccess: (data) => console.log(data) })">
*/
Alpine.data('modalForm', (options = {}) => ({
isSubmitting: false,
errors: {},
// Spread custom initial state from options
...(options.state || {}),
/**
* Validate a single field using HTML5 Constraint Validation API
* @param {HTMLElement} field - The input/select/textarea element
*/
validateField(field) {
const name = field.name || field.id;
if (!name) return;
if (!field.validity.valid) {
this.errors[name] = field.validationMessage;
} else {
delete this.errors[name];
}
},
/**
* Clear error for a field (call on input)
* @param {HTMLElement} field - The input element
*/
clearError(field) {
const name = field.name || field.id;
if (name && this.errors[name]) {
delete this.errors[name];
}
},
/**
* Get error message for a field
* @param {string} name - Field name
* @returns {string|undefined} Error message or undefined
*/
getError(name) {
return this.errors[name];
},
/**
* Check if form has any errors
* @returns {boolean} True if there are errors
*/
hasErrors() {
return Object.keys(this.errors).length > 0;
},
/**
* Validate all fields in a form
* @param {string} formId - The form element ID
* @returns {boolean} True if form is valid
*/
validateAll(formId) {
const form = document.getElementById(formId);
if (!form) return false;
this.errors = {};
const fields = form.querySelectorAll('input, select, textarea');
fields.forEach(field => {
if (field.name && !field.validity.valid) {
this.errors[field.name] = field.validationMessage;
}
});
return !this.hasErrors();
},
/**
* Validate that two password fields match
* @param {string} password1Id - ID of first password field
* @param {string} password2Id - ID of second password field
* @returns {boolean} True if passwords match
*/
validatePasswordMatch(password1Id, password2Id) {
const pw1 = document.getElementById(password1Id);
const pw2 = document.getElementById(password2Id);
if (!pw1 || !pw2) return false;
if (pw1.value !== pw2.value) {
this.errors[pw2.name || password2Id] = 'Passwords do not match';
pw2.setCustomValidity('Passwords do not match');
return false;
}
delete this.errors[pw2.name || password2Id];
pw2.setCustomValidity('');
return true;
},
/**
* Submit a form with loading state
* @param {string} formId - The form element ID
*/
submit(formId) {
const form = document.getElementById(formId);
if (!form) return;
// Validate before submit
if (!form.checkValidity()) {
form.reportValidity();
return;
}
// Set action to current URL if empty
if (!form.action || form.action === window.location.href + '#') {
form.action = window.location.href;
}
// Set loading state and submit
this.isSubmitting = true;
form.submit();
},
/**
* Submit form via AJAX (fetch)
* @param {string} formId - The form element ID
* @param {Object} options - Options { onSuccess, onError, closeOnSuccess }
*/
async submitAsync(formId, options = {}) {
const form = document.getElementById(formId);
if (!form) return;
// Validate before submit
if (!form.checkValidity()) {
form.reportValidity();
return;
}
this.isSubmitting = true;
try {
const formData = new FormData(form);
const response = await fetch(form.action || window.location.href, {
method: form.method || 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
});
const data = await response.json().catch(() => ({}));
if (response.ok) {
if (options.onSuccess) {
options.onSuccess(data);
}
if (data.redirect || data.success) {
window.location.href = data.redirect || data.success;
} else if (options.closeOnSuccess) {
this.$el.closest('dialog')?.close();
}
} else {
const errorMsg = data.error || data.message || `Error: ${response.status}`;
this.errors['_form'] = errorMsg;
if (options.onError) {
options.onError(errorMsg, data);
}
}
} catch (error) {
this.errors['_form'] = error.message;
if (options.onError) {
options.onError(error.message);
}
} finally {
this.isSubmitting = false;
}
},
/**
* Set form action URL dynamically
* @param {string} formId - The form element ID
* @param {string} url - The URL to set as action
*/
setFormAction(formId, url) {
const form = document.getElementById(formId);
if (form) {
form.action = url;
}
},
/**
* Update image preview
* @param {string} url - Image URL (with query params)
* @param {string} targetId - Target element ID for the image
*/
updateImagePreview(url) {
// Store URL for reactive binding with :src
this.imageUrl = url;
},
/**
* Reset form state (call on modal close)
* Resets to initial state from options
*/
reset() {
this.isSubmitting = false;
this.errors = {};
this.imageUrl = '';
// Reset custom state to initial values
if (options.state) {
Object.keys(options.state).forEach(key => {
this[key] = options.state[key];
});
}
// Call custom reset handler if provided
if (options.onReset) {
options.onReset.call(this);
}
}
}));
/**
* Simple Modal Component (no form)
*
* For modals that don't need form validation.
*
* Usage:
* <dialog x-data="modal()">
* <button @click="$el.closest('dialog').close()">Close</button>
* </dialog>
*/
Alpine.data('modal', () => ({
// Placeholder for simple modals that may need state later
}));
});

View File

@ -0,0 +1,133 @@
/**
* Alpine.js Pagination Component
*
* Provides client-side pagination for large lists.
*/
document.addEventListener('alpine:init', () => {
Alpine.data('paginatedList', (initialItems = [], options = {}) => ({
allItems: initialItems,
filteredItems: [],
currentPage: 1,
perPage: options.perPage || 50,
searchQuery: '',
isReviewed: options.isReviewed || false,
instanceId: options.instanceId || Math.random().toString(36).substring(2, 9),
init() {
this.filteredItems = this.allItems;
},
get totalPages() {
return Math.ceil(this.filteredItems.length / this.perPage);
},
get paginatedItems() {
const start = (this.currentPage - 1) * this.perPage;
const end = start + this.perPage;
return this.filteredItems.slice(start, end);
},
get totalItems() {
return this.filteredItems.length;
},
get showingStart() {
if (this.totalItems === 0) return 0;
return (this.currentPage - 1) * this.perPage + 1;
},
get showingEnd() {
return Math.min(this.currentPage * this.perPage, this.totalItems);
},
search(query) {
this.searchQuery = query.toLowerCase();
if (this.searchQuery === '') {
this.filteredItems = this.allItems;
} else {
this.filteredItems = this.allItems.filter(item =>
item.name.toLowerCase().includes(this.searchQuery)
);
}
this.currentPage = 1;
},
nextPage() {
if (this.currentPage < this.totalPages) {
this.currentPage++;
}
},
prevPage() {
if (this.currentPage > 1) {
this.currentPage--;
}
},
goToPage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
}
},
get pageNumbers() {
const pages = [];
const total = this.totalPages;
const current = this.currentPage;
// Handle empty case
if (total === 0) {
return pages;
}
if (total <= 7) {
// Show all pages if 7 or fewer
for (let i = 1; i <= total; i++) {
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
}
} else {
// More than 7 pages - show first, last, and sliding window around current
// Always show first page
pages.push({ page: 1, isEllipsis: false, key: `${this.instanceId}-page-1` });
// Determine the start and end of the middle range
let rangeStart, rangeEnd;
if (current <= 4) {
// Near the beginning: show pages 2-5
rangeStart = 2;
rangeEnd = 5;
} else if (current >= total - 3) {
// Near the end: show last 4 pages before the last page
rangeStart = total - 4;
rangeEnd = total - 1;
} else {
// In the middle: show current page and one on each side
rangeStart = current - 1;
rangeEnd = current + 1;
}
// Add ellipsis before range if there's a gap
if (rangeStart > 2) {
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-start` });
}
// Add pages in the range
for (let i = rangeStart; i <= rangeEnd; i++) {
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
}
// Add ellipsis after range if there's a gap
if (rangeEnd < total - 1) {
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-end` });
}
// Always show last page
pages.push({ page: total, isEllipsis: false, key: `${this.instanceId}-page-${total}` });
}
return pages;
}
}));
});

145
static/js/alpine/search.js Normal file
View File

@ -0,0 +1,145 @@
/**
* Search Modal Alpine.js Component
*
* Provides package selection, search mode switching, and results display
* for the search modal.
*/
document.addEventListener('alpine:init', () => {
/**
* Search Modal Component
*
* Usage:
* <dialog x-data="searchModal()" @close="reset()">
* ...
* </dialog>
*/
Alpine.data('searchModal', () => ({
// Package selector state
selectedPackages: [],
// Search state
searchMode: 'text',
searchModeLabel: 'Text',
query: '',
// Results state
results: null,
isSearching: false,
error: null,
// Initialize on modal open
init() {
// Load reviewed packages by default
this.loadInitialSelection();
// Watch for modal open to focus searchbar
this.$watch('$el.open', (open) => {
if (open) {
setTimeout(() => {
this.$refs.searchbar.focus();
}, 320);
}
});
},
loadInitialSelection() {
// Select all reviewed packages by default
const menuItems = this.$refs.packageDropdown.querySelectorAll('li');
for (const item of menuItems) {
// Stop at 'Unreviewed Packages' section
if (item.classList.contains('menu-title') &&
item.textContent.trim() === 'Unreviewed Packages') {
break;
}
const packageOption = item.querySelector('.package-option');
if (packageOption) {
this.selectedPackages.push({
url: packageOption.dataset.packageUrl,
name: packageOption.dataset.packageName
});
}
}
},
togglePackage(url, name) {
const index = this.selectedPackages.findIndex(pkg => pkg.url === url);
if (index !== -1) {
this.selectedPackages.splice(index, 1);
} else {
this.selectedPackages.push({ url, name });
}
},
removePackage(url) {
const index = this.selectedPackages.findIndex(pkg => pkg.url === url);
if (index !== -1) {
this.selectedPackages.splice(index, 1);
}
},
isPackageSelected(url) {
return this.selectedPackages.some(pkg => pkg.url === url);
},
setSearchMode(mode, label) {
this.searchMode = mode;
this.searchModeLabel = label;
this.$refs.modeDropdown.hidePopover();
},
async performSearch(serverBase) {
if (!this.query.trim()) {
return;
}
if (this.selectedPackages.length < 1) {
this.results = { error: 'no_packages' };
return;
}
const params = new URLSearchParams();
this.selectedPackages.forEach(pkg => params.append('packages', pkg.url));
params.append('search', this.query.trim());
params.append('mode', this.searchModeLabel.toLowerCase());
this.isSearching = true;
this.results = null;
this.error = null;
try {
const response = await fetch(`${serverBase}/search?${params.toString()}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error('Search request failed');
}
this.results = await response.json();
} catch (err) {
console.error('Search error:', err);
this.error = 'Search failed. Please try again.';
} finally {
this.isSearching = false;
}
},
hasResults() {
if (!this.results || this.results.error) return false;
const categories = ['Compounds', 'Compound Structures', 'Rules', 'Reactions', 'Pathways'];
return categories.some(cat => this.results[cat] && this.results[cat].length > 0);
},
reset() {
this.query = '';
this.results = null;
this.error = null;
this.isSearching = false;
}
}));
});

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,22 @@
console.log("loaded pw.js") console.log("loaded pw.js")
function predictFromNode(url) { function predictFromNode(url) {
$.post("", {node: url}) fetch("", {
.done(function (data) { method: "POST",
console.log("Success:", data); headers: {
window.location.href = data.success; "Content-Type": "application/x-www-form-urlencoded",
}) "X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
.fail(function (xhr, status, error) { },
console.error("Error:", xhr.status, xhr.responseText); body: new URLSearchParams({node: url})
// show user-friendly message or log error })
}); .then(response => response.json())
.then(data => {
console.log("Success:", data);
window.location.href = data.success;
})
.catch(error => {
console.error("Error:", error);
});
} }
// data = {{ pathway.d3_json | safe }}; // data = {{ pathway.d3_json | safe }};
@ -103,6 +110,9 @@ function draw(pathway, elem) {
} }
function dragstarted(event, d) { function dragstarted(event, d) {
// Prevent zoom pan when dragging nodes
event.sourceEvent.stopPropagation();
if (!event.active) simulation.alphaTarget(0.3).restart(); if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x; d.fx = d.x;
d.fy = d.y; d.fy = d.y;
@ -117,6 +127,9 @@ function draw(pathway, elem) {
} }
function dragged(event, d) { function dragged(event, d) {
// Prevent zoom pan when dragging nodes
event.sourceEvent.stopPropagation();
d.fx = event.x; d.fx = event.x;
d.fy = event.y; d.fy = event.y;
@ -127,6 +140,9 @@ function draw(pathway, elem) {
} }
function dragended(event, d) { function dragended(event, d) {
// Prevent zoom pan when dragging nodes
event.sourceEvent.stopPropagation();
if (!event.active) simulation.alphaTarget(0); if (!event.active) simulation.alphaTarget(0);
// Mark that dragging has ended // Mark that dragging has ended
@ -192,52 +208,153 @@ function draw(pathway, elem) {
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted")); d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
} }
// Wait one second before showing popup // Wait before showing popup (ms)
var popupWaitBeforeShow = 1000; var popupWaitBeforeShow = 1000;
// Keep Popup at least for one second
var popushowAtLeast = 1000;
function pop_show_e(element) { // Custom popover element
var e = element; let popoverTimeout = null;
setTimeout(function () {
if ($(e).is(':hover')) { // if element is still hovered
$(e).popover("show");
// workaround to set fixed positions function createPopover() {
pop = $(e).attr("aria-describedby") const popover = document.createElement('div');
h = $('#' + pop).height(); popover.id = 'custom-popover';
$('#' + pop).attr("style", `position: fixed; top: ${clientY - (h / 2.0)}px; left: ${clientX + 10}px; margin: 0px; max-width: 1000px; display: block;`) popover.className = 'fixed z-50';
setTimeout(function () { popover.style.cssText = `
var close = setInterval(function () { background: #ffffff;
if (!$(".popover:hover").length // mouse outside popover border: 1px solid #d1d5db;
&& !$(e).is(':hover')) { // mouse outside element box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
$(e).popover('hide'); max-width: 320px;
clearInterval(close); padding: 0.75rem;
} border-radius: 0.5rem;
}, 100); opacity: 0;
}, popushowAtLeast); visibility: hidden;
transition: opacity 150ms ease-in-out, visibility 150ms ease-in-out;
pointer-events: auto;
`;
popover.setAttribute('role', 'tooltip');
popover.innerHTML = `
<div class="font-semibold mb-2 popover-title" style="font-weight: 600; margin-bottom: 0.5rem;"></div>
<div class="text-sm popover-content" style="font-size: 0.875rem;"></div>
`;
// Add styles for content images
const style = document.createElement('style');
style.textContent = `
#custom-popover img {
max-width: 100%;
height: auto;
display: block;
margin: 0.5rem 0;
} }
}, popupWaitBeforeShow); #custom-popover a {
color: #2563eb;
text-decoration: none;
}
#custom-popover a:hover {
text-decoration: underline;
}
`;
if (!document.getElementById('popover-styles')) {
style.id = 'popover-styles';
document.head.appendChild(style);
}
// Keep popover open when hovering over it
popover.addEventListener('mouseenter', () => {
if (popoverTimeout) {
clearTimeout(popoverTimeout);
popoverTimeout = null;
}
});
popover.addEventListener('mouseleave', () => {
hidePopover();
});
document.body.appendChild(popover);
return popover;
}
function getPopover() {
return document.getElementById('custom-popover') || createPopover();
}
function showPopover(element, title, content) {
const popover = getPopover();
popover.querySelector('.popover-title').textContent = title;
popover.querySelector('.popover-content').innerHTML = content;
// Make visible to measure
popover.style.visibility = 'hidden';
popover.style.opacity = '0';
popover.style.display = 'block';
// Smart positioning - avoid viewport overflow
const padding = 10;
const popoverRect = popover.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let left = clientX + 15;
let top = clientY - (popoverRect.height / 2);
// Prevent right overflow
if (left + popoverRect.width > viewportWidth - padding) {
left = clientX - popoverRect.width - 15;
}
// Prevent bottom overflow
if (top + popoverRect.height > viewportHeight - padding) {
top = viewportHeight - popoverRect.height - padding;
}
// Prevent top overflow
if (top < padding) {
top = padding;
}
popover.style.top = `${top}px`;
popover.style.left = `${left}px`;
popover.style.visibility = 'visible';
popover.style.opacity = '1';
currentElement = element;
}
function hidePopover() {
const popover = getPopover();
popover.style.opacity = '0';
popover.style.visibility = 'hidden';
currentElement = null;
} }
function pop_add(objects, title, contentFunction) { function pop_add(objects, title, contentFunction) {
objects.attr("id", "pop") objects.each(function (d) {
.attr("data-container", "body") const element = this;
.attr("data-toggle", "popover")
.attr("data-placement", "right")
.attr("title", title);
objects.each(function (d, i) { element.addEventListener('mouseenter', () => {
options = {trigger: "manual", html: true, animation: false}; if (popoverTimeout) clearTimeout(popoverTimeout);
this_ = this;
var p = $(this).popover(options).on("mouseenter", function () { popoverTimeout = setTimeout(() => {
pop_show_e(this); if (element.matches(':hover')) {
const content = contentFunction(d);
showPopover(element, title, content);
}
}, popupWaitBeforeShow);
}); });
p.on("show.bs.popover", function (e) {
// this is to dynamically ajdust the content and bounds of the popup element.addEventListener('mouseleave', () => {
p.attr('data-content', contentFunction(d)); if (popoverTimeout) {
p.data("bs.popover").setContent(); clearTimeout(popoverTimeout);
p.data("bs.popover").tip().css({"max-width": "1000px"}); popoverTimeout = null;
}
// Delay hide to allow moving to popover
setTimeout(() => {
const popover = getPopover();
if (!popover.matches(':hover') && !element.matches(':hover')) {
hidePopover();
}
}, 100);
}); });
}); });
} }
@ -255,7 +372,7 @@ function draw(pathway, elem) {
} }
} }
popupContent += "<img src='" + n.image + "' width='" + 20 * nodeRadius + "'><br>" popupContent += "<img src='" + n.image + "'><br>"
if (n.scenarios.length > 0) { if (n.scenarios.length > 0) {
popupContent += '<b>Half-lives and related scenarios:</b><br>' popupContent += '<b>Half-lives and related scenarios:</b><br>'
for (var s of n.scenarios) { for (var s of n.scenarios) {
@ -265,7 +382,7 @@ function draw(pathway, elem) {
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0; var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
if (pathway.isIncremental && isLeaf) { if (pathway.isIncremental && isLeaf) {
popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>'; popupContent += '<br><a class="btn btn-primary btn-sm mt-2" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
} }
return popupContent; return popupContent;
@ -285,7 +402,7 @@ function draw(pathway, elem) {
popupContent += adcontent; popupContent += adcontent;
} }
popupContent += "<img src='" + e.image + "' width='" + 20 * nodeRadius + "'><br>" popupContent += "<img src='" + e.image + "'><br>"
if (e.reaction_probability) { if (e.reaction_probability) {
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>'; popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
} }
@ -308,6 +425,23 @@ function draw(pathway, elem) {
}); });
const zoomable = d3.select("#zoomable"); const zoomable = d3.select("#zoomable");
const svg = d3.select("#pwsvg");
const container = d3.select("#vizdiv");
// Set explicit SVG dimensions for proper zoom behavior
svg.attr("width", width)
.attr("height", height);
// Add background rectangle FIRST to enable pan/zoom on empty space
// This must be inserted before zoomable group so it's behind everything
svg.insert("rect", "#zoomable")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "transparent")
.attr("pointer-events", "all")
.style("cursor", "grab");
// Zoom Funktion aktivieren // Zoom Funktion aktivieren
const zoom = d3.zoom() const zoom = d3.zoom()
@ -316,7 +450,12 @@ function draw(pathway, elem) {
zoomable.attr("transform", event.transform); zoomable.attr("transform", event.transform);
}); });
d3.select("svg").call(zoom); // Apply zoom to the SVG element - this enables wheel zoom
svg.call(zoom);
// Also apply zoom to container to catch events that might not reach SVG
// This ensures drag-to-pan works even when clicking on empty space
container.call(zoom);
nodes = pathway['nodes']; nodes = pathway['nodes'];
links = pathway['links']; links = pathway['links'];

View File

@ -1,6 +1,9 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_compound_modal"> <a
role="button"
onclick="document.getElementById('new_compound_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Compound</a <span class="glyphicon glyphicon-plus"></span> New Compound</a
> >
</li> </li>

View File

@ -2,8 +2,7 @@
<li> <li>
<a <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('new_compound_structure_modal').showModal(); return false;"
data-target="#new_compound_structure_modal"
> >
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a <span class="glyphicon glyphicon-plus"></span> New Compound Structure</a
> >

View File

@ -1,6 +1,9 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_edge_modal"> <a
role="button"
onclick="document.getElementById('new_edge_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Edge</a <span class="glyphicon glyphicon-plus"></span> New Edge</a
> >
</li> </li>

View File

@ -1,5 +1,8 @@
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_group_modal"> <a
role="button"
onclick="document.getElementById('new_group_modal').showModal(); return false;"
>
<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,9 @@
{% 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"
onclick="document.getElementById('new_model_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Model</a <span class="glyphicon glyphicon-plus"></span> New Model</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="#new_node_modal"> <a
role="button"
onclick="document.getElementById('new_node_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Node</a <span class="glyphicon glyphicon-plus"></span> New Node</a
> >
</li> </li>

View File

@ -1,18 +1,23 @@
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_package_modal"> <a
role="button"
onclick="document.getElementById('new_package_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('import_package_modal').showModal(); return false;"
>
<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 <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;"
data-target="#import_legacy_package_modal"
> >
<span class="glyphicon glyphicon-import"></span> Import Package from legacy <span class="glyphicon glyphicon-import"></span> Import Package from legacy
JSON</a JSON</a

View File

@ -1,6 +1,9 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_reaction_modal"> <a
role="button"
onclick="document.getElementById('new_reaction_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Reaction</a <span class="glyphicon glyphicon-plus"></span> New Reaction</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="#new_rule_modal"> <a
role="button"
onclick="document.getElementById('new_rule_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Rule</a <span class="glyphicon glyphicon-plus"></span> New Rule</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="#new_scenario_modal"> <a
role="button"
onclick="document.getElementById('new_scenario_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Scenario</a <span class="glyphicon glyphicon-plus"></span> New Scenario</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="#new_setting_modal"> <a
role="button"
onclick="document.getElementById('new_setting_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span>New Setting</a <span class="glyphicon glyphicon-plus"></span>New Setting</a
> >
</li> </li>

View File

@ -1,42 +1,59 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_compound_modal"> <a
role="button"
onclick="document.getElementById('edit_compound_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('add_structure_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
> >
</li> </li>
<li> <li>
<a <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
data-target="#generic_set_external_reference_modal"
> >
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a <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"
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a <i class="glyphicon glyphicon-trash"></i> Delete Compound</a
> >
</li> </li>

View File

@ -2,33 +2,40 @@
<li> <li>
<a <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('edit_compound_structure_modal').showModal(); return false;"
data-target="#edit_compound_structure_modal"
> >
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a <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"
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
> >
</li> </li>
<li> <li>
<a <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
data-target="#generic_set_external_reference_modal"
> >
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a <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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a <i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a
> >
</li> </li>

View File

@ -1,16 +1,25 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#set_aliases_modal"> <a
role="button"
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a <i class="glyphicon glyphicon-trash"></i> Delete Edge</a
> >
</li> </li>

View File

@ -1,11 +1,17 @@
{% 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"
onclick="document.getElementById('edit_group_member_modal').showModal(); return false;"
>
<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
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Group</a <i class="glyphicon glyphicon-trash"></i> Delete Group</a
> >
</li> </li>

View File

@ -1,21 +1,33 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_model_modal"> <a
role="button"
onclick="document.getElementById('edit_model_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('evaluate_model_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('retrain_model_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Model</a <i class="glyphicon glyphicon-trash"></i> Delete Model</a
> >
</li> </li>

View File

@ -1,21 +1,33 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_node_modal"> <a
role="button"
onclick="document.getElementById('edit_node_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Node</a <i class="glyphicon glyphicon-trash"></i> Delete Node</a
> >
</li> </li>

View File

@ -1,35 +1,49 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_package_modal"> <a
role="button"
onclick="document.getElementById('edit_package_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-edit"></i> Edit Package</a <i class="glyphicon glyphicon-edit"></i> Edit Package</a
> >
</li> </li>
<li> <li>
<a <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('edit_package_permissions_modal').showModal(); return false;"
data-target="#edit_package_permissions_modal"
> >
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a <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"
onclick="document.getElementById('publish_package_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('export_package_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_license_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Package</a <i class="glyphicon glyphicon-trash"></i> Delete Package</a
> >
</li> </li>

View File

@ -1,26 +1,34 @@
{% 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"
onclick="document.getElementById('add_pathway_node_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('add_pathway_edge_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-duplicate"></i> Copy</a <i class="glyphicon glyphicon-duplicate"></i> Copy</a
> >
</li> </li>
<li> <li>
<a <a
class="button" class="button"
data-toggle="modal" onclick="document.getElementById('download_pathway_csv_modal').showModal(); return false;"
data-target="#download_pathway_csv_modal"
> >
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a <i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a
> >
@ -28,8 +36,7 @@
<li> <li>
<a <a
class="button" class="button"
data-toggle="modal" onclick="document.getElementById('download_pathway_image_modal').showModal(); return false;"
data-target="#download_pathway_image_modal"
> >
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a <i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a
> >
@ -38,8 +45,7 @@
<li> <li>
<a <a
class="button" class="button"
data-toggle="modal" onclick="document.getElementById('identify_missing_rules_modal').showModal(); return false;"
data-target="#identify_missing_rules_modal"
> >
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing <i class="glyphicon glyphicon-question-sign"></i> Identify Missing
Rules</a Rules</a
@ -47,30 +53,34 @@
</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"
onclick="document.getElementById('edit_pathway_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a <i class="glyphicon glyphicon-plus"></i> Set Aliases</a
> >
</li> </li>
{# <li>#}
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
{# </li>#}
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li> <li>
<a <a
class="button" class="button"
data-toggle="modal" onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
data-target="#delete_pathway_node_modal"
> >
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a <i class="glyphicon glyphicon-trash"></i> Delete Compound</a
> >
@ -78,14 +88,16 @@
<li> <li>
<a <a
class="button" class="button"
data-toggle="modal" onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
data-target="#delete_pathway_edge_modal"
> >
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a <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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a <i class="glyphicon glyphicon-trash"></i> Delete Pathway</a
> >
</li> </li>

View File

@ -1,37 +1,51 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal"> <a
role="button"
onclick="document.getElementById('edit_reaction_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
> >
</li> </li>
<li> <li>
<a <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
data-target="#generic_set_external_reference_modal"
> >
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a <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"
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a <i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
> >
</li> </li>

View File

@ -1,28 +1,43 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_rule_modal"> <a
role="button"
onclick="document.getElementById('edit_rule_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a <i class="glyphicon glyphicon-trash"></i> Delete Rule</a
> >
</li> </li>

View File

@ -2,8 +2,7 @@
<li> <li>
<a <a
class="button" class="button"
data-toggle="modal" onclick="document.getElementById('add_additional_information_modal').showModal(); return false;"
data-target="#add_additional_information_modal"
> >
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a <i class="glyphicon glyphicon-trash"></i> Add Additional Information</a
> >
@ -11,14 +10,16 @@
<li> <li>
<a <a
class="button" class="button"
data-toggle="modal" onclick="document.getElementById('update_scenario_additional_information_modal').showModal(); return false;"
data-target="#update_scenario_additional_information_modal"
> >
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a <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"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a <i class="glyphicon glyphicon-trash"></i> Delete Scenario</a
> >
</li> </li>

View File

@ -1,19 +1,24 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_user_modal"> <a
role="button"
onclick="document.getElementById('edit_user_modal').showModal(); return false;"
>
<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"
onclick="document.getElementById('edit_password_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-lock"></i> Update Password</a <i class="glyphicon glyphicon-lock"></i> Update Password</a
> >
</li> </li>
<li> <li>
<a <a
role="button" role="button"
data-toggle="modal" onclick="document.getElementById('new_prediction_setting_modal').showModal(); return false;"
data-target="#new_prediction_setting_modal"
> >
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a <i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a
> >
@ -23,7 +28,10 @@
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#} {# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
{# </li>#} {# </li>#}
<li> <li>
<a role="button" data-toggle="modal" data-target="#generic_delete_modal"> <a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Account</a <i class="glyphicon glyphicon-trash"></i> Delete Account</a
> >
</li> </li>

View File

@ -1,49 +1,33 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% load envipytags %} {% load envipytags %}
{% block content %} {% block content %}
<div class="panel-group" id="reviewListAccordion"> <div class="space-y-2 p-4">
<div class="panel panel-default"> <!-- Header Section -->
<div <div class="card bg-base-100">
class="panel-heading" <div class="card-body">
id="headingPanel" <h2 class="card-title text-2xl">User Prediction Jobs</h2>
style="font-size:2rem;height: 46px" <p class="mt-2">Job Logs Desc</p>
>
Jobs
</div>
<div class="panel-body">
<p>Job Logs Desc</p>
</div> </div>
</div>
<div <!-- Jobs -->
class="panel panel-default panel-heading list-group-item" <div class="collapse-arrow bg-base-200 collapse">
style="background-color:silver" <input type="checkbox" checked />
> <div class="collapse-title text-xl font-medium">Recent Jobs</div>
<h4 class="panel-title"> <div class="collapse-content" id="job-content">
<a <div class="overflow-x-auto">
id="job-accordion-link" <table class="table-zebra table">
data-toggle="collapse" <thead>
data-parent="#job-accordion" <tr>
href="#jobs" <th>ID</th>
> <th>Name</th>
Jobs <th>Status</th>
</a> <th>Queued</th>
</h4> <th>Done</th>
</div> <th>Result</th>
<div id="jobs" class="panel-collapse in collapse"> </tr>
<div class="panel-body list-group-item" id="job-content"> </thead>
<table class="table-bordered table-hover table">
<tr style="background-color: rgba(0, 0, 0, 0.08);">
{% if meta.user.is_superuser %}
<th scope="col">User</th>
{% endif %}
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Status</th>
<th scope="col">Queued</th>
<th scope="col">Done</th>
<th scope="col">Result</th>
</tr>
<tbody> <tbody>
{% for job in jobs %} {% for job in jobs %}
<tr> <tr>
@ -58,7 +42,11 @@
<td>{{ job.created }}</td> <td>{{ job.created }}</td>
<td>{{ job.done_at }}</td> <td>{{ job.done_at }}</td>
{% if job.task_result and job.task_result|is_url == True %} {% if job.task_result and job.task_result|is_url == True %}
<td><a href="{{ job.task_result }}">Result</a></td> <td>
<a href="{{ job.task_result }}" class="link link-primary"
>Result</a
>
</td>
{% elif job.task_result %} {% elif job.task_result %}
<td>{{ job.task_result|slice:"40" }}...</td> <td>{{ job.task_result|slice:"40" }}...</td>
{% else %} {% else %}
@ -70,19 +58,31 @@
</table> </table>
</div> </div>
</div> </div>
<!-- Unreviewable objects such as User / Group / Setting -->
<ul class="list-group">
{% for obj in objects %}
{% if object_type == 'user' %}
<a class="list-group-item" href="{{ obj.url }}"
>{{ obj.username }}</a
>
{% else %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
{% endif %}
{% endfor %}
</ul>
</div> </div>
{% if objects %}
<!-- Unreviewable objects such as User / Group / Setting -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<ul class="menu bg-base-200 rounded-box">
{% for obj in objects %}
{% if object_type == 'user' %}
<li>
<a href="{{ obj.url }}" class="hover:bg-base-300"
>{{ obj.username }}</a
>
</li>
{% else %}
<li>
<a href="{{ obj.url }}" class="hover:bg-base-300"
>{{ obj.name }}</a
>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,28 +1,32 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
{% if object_type != 'package' %} {# Serialize objects data for Alpine pagination #}
<div> {# prettier-ignore-start #}
<div id="load-all-error" style="display: none;"> {# FIXME: This is a hack to get the objects data into the JavaScript code. #}
<div class="alert alert-danger" role="alert"> <script>
<span window.reviewedObjects = [
class="glyphicon glyphicon-exclamation-sign" {% for obj in reviewed_objects %}
aria-hidden="true" { "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
></span> {% endfor %}
<span class="sr-only">Error:</span> ];
Getting objects failed! window.unreviewedObjects = [
</div> {% for obj in unreviewed_objects %}
</div> { "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
{% endfor %}
];
</script>
{# prettier-ignore-end #}
{% if object_type != 'package' %}
<div class="px-8 py-4">
<input <input
type="text" type="text"
id="object-search" id="object-search"
class="form-control" class="input input-bordered hidden w-full max-w-xs"
placeholder="Search by name" placeholder="Search by name"
style="display: none;"
/> />
<p></p>
</div> </div>
{% endif %} {% endif %}
@ -56,423 +60,474 @@
{% endif %} {% endif %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="reviewListAccordion"> <div class="px-8 py-4">
<div class="panel panel-default"> <!-- Header Section -->
<div <div class="card bg-base-100">
class="panel-heading" <div class="card-body px-0 py-4">
id="headingPanel" <div class="flex items-center justify-between">
style="font-size:2rem;height: 46px" <h2 class="card-title text-2xl">
> {% if object_type == 'package' %}
{% if object_type == 'package' %} Packages
Packages {% elif object_type == 'compound' %}
{% elif object_type == 'compound' %} Compounds
Compounds {% elif object_type == 'structure' %}
{% elif object_type == 'structure' %} Compound structures
Compound structures {% elif object_type == 'rule' %}
{% elif object_type == 'rule' %} Rules
Rules {% elif object_type == 'reaction' %}
{% elif object_type == 'reaction' %} Reactions
Reactions {% elif object_type == 'pathway' %}
{% elif object_type == 'pathway' %} Pathways
Pathways {% elif object_type == 'node' %}
{% elif object_type == 'node' %} Nodes
Nodes {% elif object_type == 'edge' %}
{% elif object_type == 'edge' %} Edges
Edges {% elif object_type == 'scenario' %}
{% elif object_type == 'scenario' %} Scenarios
Scenarios {% elif object_type == 'model' %}
{% elif object_type == 'model' %} Model
Model {% elif object_type == 'setting' %}
{% elif object_type == 'setting' %} Settings
Settings {% elif object_type == 'user' %}
{% elif object_type == 'user' %} Users
Users {% elif object_type == 'group' %}
{% elif object_type == 'group' %} Groups
Groups {% endif %}
{% endif %} </h2>
<div <div id="actionsButton" class="dropdown dropdown-end hidden">
id="actionsButton" <div tabindex="0" role="button" class="btn btn-ghost btn-sm">
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;" <svg
class="dropdown" xmlns="http://www.w3.org/2000/svg"
> width="16"
<a height="16"
href="#" viewBox="0 0 24 24"
class="dropdown-toggle" fill="none"
data-toggle="dropdown" stroke="currentColor"
role="button" stroke-width="2"
aria-haspopup="true" stroke-linecap="round"
aria-expanded="false" stroke-linejoin="round"
><span class="glyphicon glyphicon-wrench"></span> Actions class="lucide lucide-wrench"
<span class="caret"></span><span style="padding-right:1em"></span >
></a> <path
<ul id="actionsList" class="dropdown-menu"> d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
{% block actions %} />
{% if object_type == 'package' %} </svg>
{% include "actions/collections/package.html" %} Actions
{% elif object_type == 'compound' %} </div>
{% include "actions/collections/compound.html" %} <ul
{% elif object_type == 'structure' %} tabindex="-1"
{% include "actions/collections/compound_structure.html" %} class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
{% elif object_type == 'rule' %} >
{% include "actions/collections/rule.html" %} {% block actions %}
{% elif object_type == 'reaction' %} {% if object_type == 'package' %}
{% include "actions/collections/reaction.html" %} {% include "actions/collections/package.html" %}
{% elif object_type == 'setting' %} {% elif object_type == 'compound' %}
{% include "actions/collections/setting.html" %} {% include "actions/collections/compound.html" %}
{% elif object_type == 'scenario' %} {% elif object_type == 'structure' %}
{% include "actions/collections/scenario.html" %} {% include "actions/collections/compound_structure.html" %}
{% elif object_type == 'model' %} {% elif object_type == 'rule' %}
{% include "actions/collections/model.html" %} {% include "actions/collections/rule.html" %}
{% elif object_type == 'pathway' %} {% elif object_type == 'reaction' %}
{% include "actions/collections/pathway.html" %} {% include "actions/collections/reaction.html" %}
{% elif object_type == 'node' %} {% elif object_type == 'setting' %}
{% include "actions/collections/node.html" %} {% include "actions/collections/setting.html" %}
{% elif object_type == 'edge' %} {% elif object_type == 'scenario' %}
{% include "actions/collections/edge.html" %} {% include "actions/collections/scenario.html" %}
{% elif object_type == 'group' %} {% elif object_type == 'model' %}
{% include "actions/collections/group.html" %} {% include "actions/collections/model.html" %}
{% endif %} {% elif object_type == 'pathway' %}
{% endblock %} {% include "actions/collections/pathway.html" %}
</ul> {% elif object_type == 'node' %}
{% include "actions/collections/node.html" %}
{% elif object_type == 'edge' %}
{% include "actions/collections/edge.html" %}
{% elif object_type == 'group' %}
{% include "actions/collections/group.html" %}
{% endif %}
{% endblock %}
</ul>
</div>
</div> </div>
</div> <div class="mt-2">
<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 conditions.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/packages"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'compound' %}
<p>
A compound stores the structure of a molecule and can include
meta-information.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/compounds"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'structure' %}
<p>
The structures stored in this compound
<a
target="_blank"
href="https://wiki.envipath.org/index.php/compounds"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'rule' %}
<p>
A rule describes a biotransformation reaction template that is
defined as SMIRKS.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/Rules"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'reaction' %}
<p>
A reaction is a specific biotransformation from educt compounds to
product compounds.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/reactions"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'pathway' %}
<p>
A pathway displays the (predicted) biodegradation of a compound as
graph.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/pathways"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'node' %}
<p>
Nodes represent the (predicted) compounds in a graph.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/nodes"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'edge' %}
<p>
Edges represent the links between Nodes in a graph
<a
target="_blank"
href="https://wiki.envipath.org/index.php/edges"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'scenario' %}
<p>
A scenario contains meta-information that can be attached to other
data (compounds, rules, ..).
<a
target="_blank"
href="https://wiki.envipath.org/index.php/scenarios"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'model' %}
<p>
A model applies machine learning to limit the combinatorial
explosion.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/relative_reasoning"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'setting' %}
<p>
A setting includes configuration parameters for pathway predictions.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/settings"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'user' %}
<p>
Register now to create own packages and to submit and manage your
data.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/users"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'group' %}
<p>
Users can team up in groups to share packages.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/groups"
role="button"
>Learn more &gt;&gt;</a
>
</p>
{% endif %}
<!-- If theres nothing to show extend the text above -->
{% if reviewed_objects and unreviewed_objects %}
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
<p> <p>
Nothing found. There are two possible reasons: <br /><br />1. A package contains pathways, rules, etc. and can reflect specific
There is no content yet.<br />2. You have no reading experimental conditions.
permissions.<br /><br />Please be sure you have at least reading <a
permissions. target="_blank"
href="https://wiki.envipath.org/index.php/packages"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'compound' %}
<p>
A compound stores the structure of a molecule and can include
meta-information.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/compounds"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'structure' %}
<p>
The structures stored in this compound
<a
target="_blank"
href="https://wiki.envipath.org/index.php/compounds"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'rule' %}
<p>
A rule describes a biotransformation reaction template that is
defined as SMIRKS.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/Rules"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'reaction' %}
<p>
A reaction is a specific biotransformation from educt compounds to
product compounds.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/reactions"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'pathway' %}
<p>
A pathway displays the (predicted) biodegradation of a compound as
graph.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/pathways"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'node' %}
<p>
Nodes represent the (predicted) compounds in a graph.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/nodes"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'edge' %}
<p>
Edges represent the links between Nodes in a graph
<a
target="_blank"
href="https://wiki.envipath.org/index.php/edges"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'scenario' %}
<p>
A scenario contains meta-information that can be attached to other
data (compounds, rules, ..).
<a
target="_blank"
href="https://wiki.envipath.org/index.php/scenarios"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'model' %}
<p>
A model applies machine learning to limit the combinatorial
explosion.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/relative_reasoning"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'setting' %}
<p>
A setting includes configuration parameters for pathway
predictions.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/settings"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'user' %}
<p>
Register now to create own packages and to submit and manage your
data.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/users"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p>
{% elif object_type == 'group' %}
<p>
Users can team up in groups to share packages.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/groups"
class="link link-primary"
>Learn more &gt;&gt;</a
>
</p> </p>
{% endif %} {% endif %}
{% endif %}
<!-- If theres nothing to show extend the text above -->
{% if reviewed_objects and unreviewed_objects %}
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
<p class="mt-4">
Nothing found. There are two possible reasons: <br /><br />1.
There is no content yet.<br />2. You have no reading
permissions.<br /><br />Please be sure you have at least reading
permissions.
</p>
{% endif %}
{% endif %}
</div>
</div> </div>
</div>
<!-- Lists Container - Full Width with Reviewed on Right -->
<div class="w-full">
{% if reviewed_objects %} {% if reviewed_objects %}
{% if reviewed_objects|length > 0 %} {% if reviewed_objects|length > 0 %}
<!-- Reviewed -->
<div <div
class="panel panel-default panel-heading list-group-item" class="collapse-arrow bg-base-200 collapse order-2 w-full"
style="background-color:silver" x-data="paginatedList(window.reviewedObjects || [], { isReviewed: true, instanceId: 'reviewed' })"
> >
<h4 class="panel-title"> <input type="checkbox" checked />
<a <div class="collapse-title text-xl font-medium">
id="ReviewedLink" Reviewed
data-toggle="collapse" <span
data-parent="#reviewListAccordion" class="badge badge-sm badge-neutral ml-2"
href="#Reviewed" x-text="totalItems"
>Reviewed</a ></span>
</div>
<div class="collapse-content w-full">
<ul class="menu bg-base-100 rounded-box w-full">
<template x-for="obj in paginatedItems" :key="obj.url">
<li>
<a :href="obj.url" class="hover:bg-base-200">
<span x-text="obj.name"></span>
<span
class="tooltip tooltip-left ml-auto"
data-tip="Reviewed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-star"
>
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
/>
</svg>
</span>
</a>
</li>
</template>
</ul>
<!-- Pagination Controls -->
<div
x-show="totalPages > 1"
class="mt-4 flex items-center justify-between px-2"
> >
</h4> <span class="text-base-content/70 text-sm">
</div> Showing <span x-text="showingStart"></span>-<span
<div id="Reviewed" class="panel-collapse in collapse"> x-text="showingEnd"
<div class="panel-body list-group-item" id="ReviewedContent"> ></span>
{% if object_type == 'package' %} of <span x-text="totalItems"></span>
{% for obj in reviewed_objects %} </span>
<a class="list-group-item" href="{{ obj.url }}" <div class="join">
>{{ obj.name|safe }} <button
<span class="join-item btn btn-sm"
class="glyphicon glyphicon-star" :disabled="currentPage === 1"
aria-hidden="true" @click="prevPage()"
style="float:right" >
data-toggle="tooltip" «
data-placement="top" </button>
title="" <template x-for="item in pageNumbers" :key="item.key">
data-original-title="Reviewed" <button
> class="join-item btn btn-sm"
</span> :class="{ 'btn-active': item.page === currentPage }"
</a> :disabled="item.isEllipsis"
{% endfor %} @click="!item.isEllipsis && goToPage(item.page)"
{% else %} x-text="item.page"
{% for obj in reviewed_objects|slice:":50" %} ></button>
<a class="list-group-item" href="{{ obj.url }}" </template>
>{{ obj.name|safe }}{# <i>({{ obj.package.name }})</i> #} <button
<span class="join-item btn btn-sm"
class="glyphicon glyphicon-star" :disabled="currentPage === totalPages"
aria-hidden="true" @click="nextPage()"
style="float:right" >
data-toggle="tooltip" »
data-placement="top" </button>
title="" </div>
data-original-title="Reviewed" </div>
>
</span>
</a>
{% endfor %}
{% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if unreviewed_objects %} {% if unreviewed_objects %}
<!-- Unreviewed -->
<div <div
class="panel panel-default panel-heading list-group-item" class="collapse-arrow bg-base-200 collapse order-1 w-full"
style="background-color:silver" x-data="paginatedList(window.unreviewedObjects || [], { isReviewed: false, instanceId: 'unreviewed' })"
> >
<h4 class="panel-title"> <input
<a type="checkbox"
id="UnreviewedLink" {% if reviewed_objects|length == 0 or object_type == 'package' %}checked{% endif %}
data-toggle="collapse" />
data-parent="#unReviewListAccordion" <div class="collapse-title text-xl font-medium">
href="#Unreviewed" Unreviewed
>Unreviewed</a <span
class="badge badge-sm badge-neutral ml-2"
x-text="totalItems"
></span>
</div>
<div class="collapse-content w-full">
<ul class="menu bg-base-100 rounded-box w-full">
<template x-for="obj in paginatedItems" :key="obj.url">
<li>
<a
:href="obj.url"
class="hover:bg-base-200"
x-text="obj.name"
></a>
</li>
</template>
</ul>
<!-- Pagination Controls -->
<div
x-show="totalPages > 1"
class="mt-4 flex items-center justify-between px-2"
> >
</h4> <span class="text-base-content/70 text-sm">
</div> Showing <span x-text="showingStart"></span>-<span
<div x-text="showingEnd"
id="Unreviewed" ></span>
class="panel-collapse {% if reviewed_objects|length == 0 or object_type == 'package' %}in{% endif %} collapse" of <span x-text="totalItems"></span>
> </span>
<div class="panel-body list-group-item" id="UnreviewedContent"> <div class="join">
{% if object_type == 'package' %} <button
{% for obj in unreviewed_objects %} class="join-item btn btn-sm"
<a class="list-group-item" href="{{ obj.url }}" :disabled="currentPage === 1"
>{{ obj.name|safe }}</a @click="prevPage()"
> >
{% endfor %} «
{% else %} </button>
{% for obj in unreviewed_objects|slice:":50" %} <template x-for="item in pageNumbers" :key="item.key">
<a class="list-group-item" href="{{ obj.url }}" <button
>{{ obj.name|safe }}</a class="join-item btn btn-sm"
:class="{ 'btn-active': item.page === currentPage }"
:disabled="item.isEllipsis"
@click="!item.isEllipsis && goToPage(item.page)"
x-text="item.page"
></button>
</template>
<button
class="join-item btn btn-sm"
:disabled="currentPage === totalPages"
@click="nextPage()"
> >
{% endfor %} »
{% endif %} </button>
</div>
</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if objects %}
<!-- Unreviewable objects such as User / Group / Setting -->
<ul class="list-group">
{% for obj in objects %}
{% if object_type == 'user' %}
<a class="list-group-item" href="{{ obj.url }}"
>{{ obj.username|safe }}</a
>
{% else %}
<a class="list-group-item" href="{{ obj.url }}"
>{{ obj.name|safe }}</a
>
{% endif %}
{% endfor %}
</ul>
{% endif %}
</div> </div>
<style>
.spinner-widget {
position: fixed; /* stays in place on scroll */
bottom: 20px; /* distance from bottom */
right: 20px; /* distance from right */
z-index: 9999; /* above most elements */
width: 60px; /* adjust to gif size */
height: 60px;
}
.spinner-widget img { {% if objects %}
width: 100%; <!-- Unreviewable objects such as User / Group / Setting -->
height: auto; <div class="card bg-base-100">
} <div class="card-body">
</style> <ul class="menu bg-base-200 rounded-box">
{% for obj in objects %}
<div id="load-all-loading" class="spinner-widget" style="display: none"> {% if object_type == 'user' %}
<img <li>
id="loading-gif" <a href="{{ obj.url }}" class="hover:bg-base-300"
src="{% static '/images/wait.gif' %}" >{{ obj.username }}</a
alt="Loading..." >
/> </li>
</div> {% else %}
<li>
<a href="{{ obj.url }}" class="hover:bg-base-300"
>{{ obj.name }}</a
>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div> </div>
{# prettier-ignore-start #}
<script>
$(function () {
$('#object-search').show(); <script>
document.addEventListener("DOMContentLoaded", function () {
// Show actions button if there are actions
const actionsButton = document.getElementById("actionsButton");
const actionsList = actionsButton?.querySelector("ul");
if (actionsList && actionsList.children.length > 0) {
actionsButton?.classList.remove("hidden");
}
{% if object_type != 'package' and object_type != 'user' and object_type != 'group' %} // Show search input and connect to Alpine pagination
{% if reviewed_objects|length > 50 or unreviewed_objects|length > 50 %} const objectSearch = document.getElementById("object-search");
$('#load-all-loading').show() if (objectSearch) {
objectSearch.classList.remove("hidden");
setTimeout(function () { objectSearch.addEventListener("input", function () {
$('#load-all-error').hide(); const query = this.value;
// Dispatch search to all paginatedList components
$.getJSON('?all=true', function (resp) { document
$('#ReviewedContent').empty(); .querySelectorAll('[x-data*="paginatedList"]')
$('#UnreviewedContent').empty(); .forEach((el) => {
if (el._x_dataStack && el._x_dataStack[0]) {
for (o in resp.objects) { el._x_dataStack[0].search(query);
obj = resp.objects[o]; }
if (obj.reviewed) {
$('#ReviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + ' <span class="glyphicon glyphicon-star" aria-hidden="true" style="float:right" data-toggle="tooltip" data-placement="top" title="" data-original-title="Reviewed"></span></a>');
} else {
$('#UnreviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + '</a>');
}
}
$('#load-all-loading').hide();
$('#load-remaining').hide();
}).fail(function (resp) {
$('#load-all-loading').hide();
$('#load-all-error').show();
});
}, 2500);
{% endif %}
{% endif %}
$('#modal-form-delete-submit').on('click', function (e) {
e.preventDefault();
$('#modal-form-delete').submit();
}); });
$('#object-search').on('keyup', function () {
let query = $(this).val().toLowerCase();
$('a.list-group-item').each(function () {
let text = $(this).text().toLowerCase();
$(this).toggle(text.indexOf(query) !== -1);
});
});
}); });
</script> }
{# prettier-ignore-end #}
// Delete form submit handler
const deleteSubmit = document.getElementById("modal-form-delete-submit");
const deleteForm = document.getElementById("modal-form-delete");
if (deleteSubmit && deleteForm) {
deleteSubmit.addEventListener("click", function (e) {
e.preventDefault();
deleteForm.submit();
});
}
});
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,18 +1,77 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-error" role="alert"> <div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<h4 class="alert-heading">Bad Request!</h4> <div class="w-full max-w-2xl">
<p>Lorem</p> <div class="alert alert-error mb-6 shadow-lg">
<hr /> <svg
<p class="mb-0"> xmlns="http://www.w3.org/2000/svg"
You can find out more about permissions in our class="h-8 w-8 shrink-0 stroke-current"
<a fill="none"
target="_blank" viewBox="0 0 24 24"
href="https://wiki.envipath.org/index.php/packages" >
role="button" <path
>Wiki &gt;&gt;</a stroke-linecap="round"
> stroke-linejoin="round"
</p> stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<div class="flex flex-col">
<h3 class="text-lg font-bold">Bad Request</h3>
<p class="text-sm">The request you sent was invalid or malformed.</p>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4 text-2xl">What happened?</h2>
<p class="text-base-content/70 mb-4">
The server couldn't process your request because it contains invalid
data or parameters.
</p>
<div class="card-actions mt-6 justify-end">
<a href="/" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
Go Home
</a>
<a
href="https://wiki.envipath.org/index.php/packages"
target="_blank"
class="btn btn-outline"
>
Learn More
<svg
xmlns="http://www.w3.org/2000/svg"
class="ml-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,18 +1,80 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-error" role="alert"> <div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<h4 class="alert-heading">Access Denied!</h4> <div class="w-full max-w-2xl">
<p>Access to X denied.</p> <div class="alert alert-warning mb-6 shadow-lg">
<hr /> <svg
<p class="mb-0"> xmlns="http://www.w3.org/2000/svg"
You can find out more about permissions in our class="h-8 w-8 shrink-0 stroke-current"
<a fill="none"
target="_blank" viewBox="0 0 24 24"
href="https://wiki.envipath.org/index.php/packages" >
role="button" <path
>Wiki &gt;&gt;</a stroke-linecap="round"
> stroke-linejoin="round"
</p> stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<div class="flex flex-col">
<h3 class="text-lg font-bold">Access Denied</h3>
<p class="text-sm">
You don't have permission to access this resource.
</p>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4 text-2xl">Permission Required</h2>
<p class="text-base-content/70 mb-4">
You need the appropriate permissions to access this content. If you
believe this is an error, please contact the package owner or
administrator.
</p>
<div class="card-actions mt-6 justify-end">
<a href="/" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
Go Home
</a>
<a
href="https://wiki.envipath.org/index.php/packages"
target="_blank"
class="btn btn-outline"
>
Learn More
<svg
xmlns="http://www.w3.org/2000/svg"
class="ml-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,18 +1,77 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-error" role="alert"> <div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<h4 class="alert-heading">Not Found!</h4> <div class="w-full max-w-2xl">
<p>Does not exist</p> <div class="alert alert-info mb-6 shadow-lg">
<hr /> <svg
<p class="mb-0"> xmlns="http://www.w3.org/2000/svg"
You can find out more about permissions in our class="h-8 w-8 shrink-0 stroke-current"
<a fill="none"
target="_blank" viewBox="0 0 24 24"
href="https://wiki.envipath.org/index.php/packages" >
role="button" <path
>Wiki &gt;&gt;</a stroke-linecap="round"
> stroke-linejoin="round"
</p> stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div class="flex flex-col">
<h3 class="text-lg font-bold">Page Not Found</h3>
<p class="text-sm">The page you're looking for doesn't exist.</p>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4 text-2xl">404 Error</h2>
<p class="text-base-content/70 mb-4">
The page or resource you requested could not be found. It may have
been moved, deleted, or the URL might be incorrect.
</p>
<div class="card-actions mt-6 justify-end">
<a href="/" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
Go Home
</a>
<a
href="https://wiki.envipath.org/index.php/packages"
target="_blank"
class="btn btn-outline"
>
Learn More
<svg
xmlns="http://www.w3.org/2000/svg"
class="ml-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,9 +1,76 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-danger" role="alert"> <div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<h4 class="alert-heading">{{ error_message }}</h4> <div class="w-full max-w-2xl">
<hr /> <div class="alert alert-error mb-6 shadow-lg">
<p class="mb-0">{{ error_detail }}</p> <svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div class="flex flex-col">
<h3 class="text-lg font-bold">
{{ error_message|default:"An Error Occurred" }}
</h3>
<p class="text-sm">
{{ error_detail|default:"Something went wrong. Please try again later." }}
</p>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4 text-2xl">Oops! Something went wrong</h2>
<p class="text-base-content/70 mb-4">
{{ error_description|default:"We encountered an unexpected error while processing your request. Our team has been notified and is working to resolve the issue." }}
</p>
<div class="card-actions mt-6 justify-end">
<a href="/" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
Go Home
</a>
<button onclick="window.history.back()" class="btn btn-outline">
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 19l-7-7m0 0l7-7m-7 7h18"
/>
</svg>
Go Back
</button>
</div>
</div>
</div>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,11 +1,81 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="alert alert-danger" role="alert"> <div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<h4 class="alert-heading">Your account has not been activated yet!</h4> <div class="w-full max-w-2xl">
<p> <div class="alert alert-warning mb-6 shadow-lg">
Your account has not been activated yet. If you have questions <svg
<a href="mailto:admin@envipath.org">contact us.</a> xmlns="http://www.w3.org/2000/svg"
</p> class="h-8 w-8 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<div class="flex flex-col">
<h3 class="text-lg font-bold">Account Not Activated</h3>
<p class="text-sm">Your account is pending activation.</p>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4 text-2xl">Account Activation Required</h2>
<p class="text-base-content/70 mb-4">
Your account has not been activated yet. An administrator needs to
approve your account before you can access all features. This
process typically takes 24-48 hours.
</p>
<div class="divider"></div>
<p class="text-base-content/70 mb-4">
If you have questions or believe this is an error, please
<a href="mailto:admin@envipath.org" class="link link-primary"
>contact us</a
>.
</p>
<div class="card-actions mt-6 justify-end">
<a href="/" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
Go Home
</a>
<a href="mailto:admin@envipath.org" class="btn btn-outline">
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
Contact Admin
</a>
</div>
</div>
</div>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -228,15 +228,7 @@
>Documentation Wiki</a >Documentation Wiki</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>
@ -408,10 +400,5 @@
} }
}); });
</script> </script>
{% block modals %}
{% include "modals/cite_modal.html" %}
{% include "modals/predict_modal.html" %}
{% include "modals/batch_predict_modal.html" %}
{% endblock %}
</body> </body>
</html> </html>

View File

@ -21,8 +21,14 @@
type="text/css" type="text/css"
/> />
{# jQuery - Keep for compatibility with existing JS #} {# Alpine.js - For reactive components #}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script
defer
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
></script>
<script src="{% static 'js/alpine/index.js' %}"></script>
<script src="{% static 'js/alpine/search.js' %}"></script>
<script src="{% static 'js/alpine/pagination.js' %}"></script>
{# Font Awesome #} {# Font Awesome #}
<link <link
@ -35,21 +41,10 @@
<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
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
});
</script> </script>
{# 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 #}
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
{% if not debug %} {% if not debug %}
<!-- Matomo --> <!-- Matomo -->
@ -171,10 +166,11 @@
{% endblock %} {% endblock %}
<script> <script>
$(function () { document.addEventListener("DOMContentLoaded", function () {
// Hide actionsbutton if there's no action defined // Show actions button if there are actions defined
if ($("#actionsButton ul").children().length > 0) { const actionsButtonUl = document.querySelector("#actionsButton ul");
$("#actionsButton").show(); if (actionsButtonUl && actionsButtonUl.children.length > 0) {
document.getElementById("actionsButton").style.display = "";
} }
}); });

View File

@ -1,140 +1,267 @@
{% load static %} {% load static %}
{# Modern DaisyUI Navbar #} {# Modern DaisyUI Navbar with Mobile Drawer Menu #}
<div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg"> <div class="drawer drawer-mobile">
<div class="navbar-start"> <input id="drawer-toggle" type="checkbox" class="drawer-toggle" />
<a href="{{ meta.server_url }}" class="btn btn-ghost text-xl normal-case"> <div class="drawer-content flex flex-col">
<svg class="fill-base-content h-8" viewBox="0 0 104 26" role="img"> {# Navbar #}
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" /> <div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg">
</svg> <div class="navbar-start">
</a> {# Hamburger menu button - visible on mobile, hidden on desktop #}
</div> {% if not public_mode %}
<label
{% if not public_mode %} for="drawer-toggle"
<div class="navbar-center hidden lg:flex"> class="btn btn-square btn-ghost drawer-button lg:hidden"
<a >
href="{{ meta.server_url }}/predict" <svg
role="button" xmlns="http://www.w3.org/2000/svg"
class="btn btn-ghost" fill="none"
id="predictLink" viewBox="0 0 24 24"
>Predict</a class="inline-block h-5 w-5 stroke-current"
> >
<!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> --> <path
<!--<li><a href="{{ meta.server_url }}/browse" id="browseLink">Browse</a></li>--> stroke-linecap="round"
<div class="dropdown dropdown-center"> stroke-linejoin="round"
<div tabindex="0" role="button" class="btn btn-ghost">Browse</div> stroke-width="2"
<ul d="M4 6h16M4 12h16M4 18h16"
tabindex="-1" ></path>
class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm" </svg>
</label>
{% endif %}
<a
href="{{ meta.server_url }}"
class="btn btn-ghost text-xl normal-case"
> >
<li> <svg class="fill-base-content h-8" viewBox="0 0 104 26" role="img">
<a href="{{ meta.server_url }}/package" id="packageLink">Package</a> <use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
</li> </svg>
<li> </a>
<a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a> </div>
</li>
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li> {% if not public_mode %}
<li> {# Desktop menu - hidden on mobile, visible on desktop #}
<a href="{{ meta.server_url }}/compound" id="compoundLink" <div class="navbar-center hidden lg:flex">
>Compound</a <a
href="{{ meta.server_url }}/predict"
role="button"
class="btn btn-ghost"
id="predictLink"
>Predict</a
>
<div class="dropdown dropdown-center">
<div tabindex="0" role="button" class="btn btn-ghost">Browse</div>
<ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"
> >
</li> <li>
<li> <a href="{{ meta.server_url }}/package" id="packageLink"
<a href="{{ meta.server_url }}/reaction" id="reactionLink" >Package</a
>Reaction</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"
> >
</li> <svg
<li> xmlns="http://www.w3.org/2000/svg"
<a href="{{ meta.server_url }}/model" id="relative-reasoningLink" width="16"
>Model</a 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"
> >
</li> <svg
<li> xmlns="http://www.w3.org/2000/svg"
<a href="{{ meta.server_url }}/scenario" id="scenarioLink" width="24"
>Scenario</a 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> <li>
</ul> <a href="{{ meta.user.url }}" id="accountbutton">Settings</a>
</li>
<li>
<form
id="logoutForm"
action="{% url 'logout' %}"
method="post"
style="display: none;"
>
{% csrf_token %}
<input type="hidden" name="logout" value="true" />
</form>
<a
href="#"
id="logoutButton"
onclick="event.preventDefault(); document.getElementById('logoutForm').submit();"
>Logout</a
>
</li>
</ul>
</div>
{% endif %}
</div> </div>
</div> </div>
{% endif %} </div>
{# Mobile drawer menu - slides in from the left #}
<div class="navbar-end"> <div class="drawer-side">
{% if not public_mode %} <label for="drawer-toggle" class="drawer-overlay"></label>
<a id="search-trigger" role="button" class="cursor-pointer"> <ul class="menu min-h-full w-80 bg-base-200 p-4 text-base-content">
<div {# Drawer header with close button #}
class="badge badge-dash bg-base-200 text-base-content/50 m-1 flex items-center space-x-1 p-2" <li class="mb-4">
> <div class="flex items-center justify-between">
<svg <span class="font-bold text-lg">Menu</span>
xmlns="http://www.w3.org/2000/svg" <label
width="16" for="drawer-toggle"
height="16" class="btn btn-sm btn-circle btn-ghost"
viewBox="0 0 24 24" aria-label="Close menu"
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" /> <svg
<circle cx="11" cy="11" r="8" /> xmlns="http://www.w3.org/2000/svg"
</svg> class="h-6 w-6"
<span id="search-shortcut">⌘K</span> fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</label>
</div> </div>
</a> </li>
{% endif %} {% if not public_mode %}
{% if meta.user.username == 'anonymous' or public_mode %} {# Predict link #}
<a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a> <li>
{% else %} <a
<div class="dropdown dropdown-end"> href="{{ meta.server_url }}/predict"
<div class="text-lg"
tabindex="0" id="predictLinkMobile"
role="button" >Predict</a
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" /> </li>
<circle cx="12" cy="10" r="3" /> {# Browse menu with submenu #}
<path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662" /> <li>
</svg> <details>
</div> <summary class="text-lg">Browse</summary>
<ul <ul>
tabindex="-1" <li>
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-sm" <a href="{{ meta.server_url }}/package" id="packageLinkMobile"
> >Package</a
<li><a href="{{ meta.user.url }}" id="accountbutton">Settings</a></li> >
<li> </li>
<form <li>
id="logoutForm" <a href="{{ meta.server_url }}/pathway" id="pathwayLinkMobile"
action="{% url 'logout' %}" >Pathway</a
method="post" >
style="display: none;" </li>
> <li>
{% csrf_token %} <a href="{{ meta.server_url }}/rule" id="ruleLinkMobile"
<input type="hidden" name="logout" value="true" /> >Rule</a
</form> >
<a </li>
href="#" <li>
id="logoutButton" <a href="{{ meta.server_url }}/compound" id="compoundLinkMobile"
onclick="event.preventDefault(); document.getElementById('logoutForm').submit();" >Compound</a
>Logout</a >
> </li>
</li> <li>
</ul> <a href="{{ meta.server_url }}/reaction" id="reactionLinkMobile"
</div> >Reaction</a
{% endif %} >
</li>
<li>
<a
href="{{ meta.server_url }}/model"
id="relative-reasoningLinkMobile"
>Model</a
>
</li>
<li>
<a href="{{ meta.server_url }}/scenario" id="scenarioLinkMobile"
>Scenario</a
>
</li>
</ul>
</details>
</li>
{% endif %}
</ul>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@
> >
<div class="hero-overlay"></div> <div class="hero-overlay"></div>
<!-- Predict Pathway text over the image --> <!-- Predict Pathway text over the image -->
<div class="absolute bottom-40 left-1/8 z-10 -translate-x-8"> <div class="absolute bottom-40 left-1/8 -translate-x-8">
<h2 class="text-base-100 text-left text-3xl text-shadow-lg"> <h2 class="text-base-100 text-left text-3xl text-shadow-lg">
Predict Your Pathway Predict Your Pathway
</h2> </h2>
@ -20,16 +20,68 @@
<div class="bg-base-200 mx-auto max-w-5xl shadow-md"> <div class="bg-base-200 mx-auto max-w-5xl shadow-md">
<!-- Predict Pathway Section --> <!-- Predict Pathway Section -->
<div <div
class="relative z-20 mx-auto -mt-32 mb-10 w-full flex-col lg:flex-row-reverse" class="relative mx-auto -mt-32 mb-10 w-full flex-col lg:flex-row-reverse"
> >
<div <div
class="card bg-base-100 mx-auto w-3/4 shrink-0 shadow-xl transition-all duration-300 ease-in-out" class="card bg-base-100 mx-auto w-3/4 shrink-0 shadow-xl transition-all duration-300 ease-in-out"
x-data="{
drawMode: false,
smiles: '',
loadExample(smilesStr, linkEl) {
if (this.drawMode && window.indexKetcher && window.indexKetcher.setMolecule) {
window.indexKetcher.setMolecule(smilesStr);
} else {
this.smiles = smilesStr;
}
const original = linkEl.textContent;
linkEl.textContent = 'loaded!';
setTimeout(() => linkEl.textContent = original, 1000);
},
syncFromKetcher() {
const ketcher = getKetcherInstance('index-ketcher');
if (ketcher && ketcher.getSmiles) {
try {
const s = ketcher.getSmiles();
if (s && s.trim()) this.smiles = s;
} catch (err) {
console.error('Failed to sync from Ketcher:', err);
}
}
},
submitForm() {
let finalSmiles = '';
if (this.drawMode) {
const ketcher = getKetcherInstance('index-ketcher');
if (ketcher && ketcher.getSmiles) {
try {
finalSmiles = ketcher.getSmiles().trim();
} catch (err) {
console.error('Failed to get SMILES from Ketcher:', err);
alert('Unable to extract structure. Please try again or switch to SMILES input.');
return;
}
} else {
alert('The drawing editor is still loading. Please wait and try again.');
return;
}
} else {
finalSmiles = this.smiles.trim();
}
if (!finalSmiles) {
alert('Please enter a SMILES string or draw a structure.');
return;
}
document.getElementById('index-form-smiles').value = finalSmiles;
document.getElementById('index-form').submit();
}
}"
x-init="$watch('drawMode', value => { if (!value) syncFromKetcher(); })"
> >
<div class="card-body"> <div class="card-body">
<div class="my-4 ml-8 flex h-fit flex-row items-center justify-start"> <div class="my-4 ml-8 flex h-fit flex-row items-center justify-start">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<label class="swap btn btn-ghost btn-sm p-1" title="Input Mode"> <label class="swap btn btn-ghost btn-sm p-1" title="Input Mode">
<input type="checkbox" /> <input type="checkbox" x-model="drawMode" />
<span class="swap-on flex items-center gap-1"> <span class="swap-on flex items-center gap-1">
<div <div
class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1" class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1"
@ -82,16 +134,24 @@
<fieldset <fieldset
class="fieldset overflow-hidden transition-all duration-300 ease-in-out" class="fieldset overflow-hidden transition-all duration-300 ease-in-out"
:class="drawMode ? 'p-4' : 'p-8'"
> >
<form <form
id="index-form" id="index-form"
action="{{ meta.current_package.url }}/pathway" action="{{ meta.current_package.url }}/pathway"
method="POST" method="POST"
@submit.prevent="submitForm()"
> >
{% csrf_token %} {% csrf_token %}
<div <div
id="text-input-container" id="text-input-container"
class="scale-100 transform opacity-100 transition-all duration-300 ease-in-out" x-show="!drawMode"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
> >
<div class="join mx-auto w-full"> <div class="join mx-auto w-full">
<input <input
@ -99,6 +159,7 @@
id="index-form-text-input" id="index-form-text-input"
placeholder="canonical SMILES string" placeholder="canonical SMILES string"
class="input input-md join-item grow" class="input input-md join-item grow"
x-model="smiles"
/> />
<button class="btn btn-neutral join-item">Predict!</button> <button class="btn btn-neutral join-item">Predict!</button>
</div> </div>
@ -107,15 +168,15 @@
<a <a
href="#" href="#"
class="example-link hover:text-primary cursor-pointer" class="example-link hover:text-primary cursor-pointer"
data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
title="load example" title="load example"
@click.prevent="loadExample('CN1C=NC2=C1C(=O)N(C(=O)N2C)C', $el)"
>Caffeine</a >Caffeine</a
> >
<a <a
href="#" href="#"
class="example-link hover:text-primary cursor-pointer" 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" title="load example"
@click.prevent="loadExample('CC(C)CC1=CC=C(C=C1)C(C)C(=O)O', $el)"
>Ibuprofen</a >Ibuprofen</a
> >
</div> </div>
@ -128,7 +189,14 @@
</div> </div>
<div <div
id="ketcher-container" id="ketcher-container"
class="hidden w-full scale-95 transform opacity-0 transition-all duration-300 ease-in-out" x-show="drawMode"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="w-full"
> >
<iframe <iframe
id="index-ketcher" id="index-ketcher"
@ -337,166 +405,20 @@
// Make render function globally available // Make render function globally available
window.renderDiscourseTopics = renderDiscourseTopics; window.renderDiscourseTopics = renderDiscourseTopics;
// Toggle functionality with smooth animations // Ketcher iframe load handler - set up change event to sync SMILES
function toggleInputMode() { document.addEventListener("DOMContentLoaded", function () {
const toggle = $('input[type="checkbox"]'); const indexKetcher = document.getElementById("index-ketcher");
const textContainer = $("#text-input-container"); indexKetcher.addEventListener("load", function () {
const ketcherContainer = $("#ketcher-container");
const formCard = $(".card");
const fieldset = $(".fieldset");
if (toggle.is(":checked")) {
// Draw mode - show Ketcher, hide text input
textContainer.addClass("opacity-0 transform scale-95");
textContainer.removeClass("opacity-100 transform scale-100");
// Adjust fieldset padding for Ketcher mode - reduce padding and make more compact
fieldset.removeClass("p-8");
fieldset.addClass("p-4");
// Wait for fade out to complete, then hide and show new content
setTimeout(() => {
textContainer.addClass("hidden");
ketcherContainer.removeClass("hidden opacity-0 transform scale-95");
ketcherContainer.addClass("opacity-100 transform scale-100");
// Force re-evaluation of iframe size
const iframe = document.getElementById("index-ketcher");
if (iframe) {
iframe.style.height = "400px";
}
}, 300);
} else {
// SMILES mode - show text input, hide Ketcher
ketcherContainer.addClass("opacity-0 transform scale-95");
ketcherContainer.removeClass("opacity-100 transform scale-100");
// Restore fieldset padding for text input mode
fieldset.removeClass("p-4");
fieldset.addClass("p-8");
// Wait for fade out to complete, then hide and show new content
setTimeout(() => {
ketcherContainer.addClass("hidden");
textContainer.removeClass("hidden opacity-0 transform scale-95");
textContainer.addClass("opacity-100 transform scale-100");
}, 300);
// Transfer SMILES from Ketcher to text input if available
const ketcher = getKetcherInstance("index-ketcher");
if (ketcher && ketcher.getSmiles) {
try {
const smiles = ketcher.getSmiles();
if (smiles && smiles.trim() !== "") {
$("#index-form-text-input").val(smiles);
}
} catch (err) {
console.error("Failed to sync Ketcher to text input:", err);
// Non-critical error, just log it
}
}
}
}
// Ketcher integration
function indexKetcherToTextInput() {
$("#index-form-smiles").val(this.ketcher.getSmiles());
}
$(function () {
// Initialize fieldset with proper padding
$(".fieldset").addClass("p-8");
// Toggle event listener
$('input[type="checkbox"]').on("change", toggleInputMode);
// Ketcher iframe load handler
$("#index-ketcher").on("load", function () {
const checkKetcherReady = () => { const checkKetcherReady = () => {
const 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; window.indexKetcher = win.ketcher;
win.ketcher.editor.event.change.handlers.push({
once: false,
priority: 0,
f: indexKetcherToTextInput,
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")) {
// Use the robust getter function
const ketcher = getKetcherInstance("index-ketcher");
if (ketcher && ketcher.getSmiles) {
try {
textSmiles = ketcher.getSmiles().trim();
} catch (err) {
console.error("Failed to get SMILES from Ketcher:", err);
alert(
"Unable to extract structure from the drawing editor. Please try again or switch to SMILES input mode.",
);
return;
}
} else {
console.warn("Ketcher not available, possibly still loading");
alert(
"The drawing editor is still loading. Please wait a moment and try again.",
);
return;
}
} else {
textSmiles = $("#index-form-text-input").val().trim();
}
if (textSmiles === "") {
alert("Please enter a SMILES string or draw a structure.");
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 main_content %} {% endblock main_content %}

View File

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

View File

@ -1,70 +1,85 @@
<div <dialog
class="modal fade"
tabindex="-1"
id="import_legacy_package_modal" id="import_legacy_package_modal"
role="dialog" class="modal"
aria-labelledby="import_legacy_package_modal" x-data="modalForm()"
aria-hidden="true" @close="reset()"
> >
<div class="modal-dialog"> <div class="modal-box">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Import Package from Legacy System</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">Import Package from legacy System</h4> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<p>Create a Package based on the JSON Export of the legacy system.</p>
<form </button>
id="import-legacy-package-modal-form" </form>
accept-charset="UTF-8"
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
enctype="multipart/form-data" <p class="mb-4">
> Create a Package based on the JSON Export of the legacy system.
{% csrf_token %} </p>
<p> <form
<label class="btn btn-primary" for="legacyJsonFile"> id="import-legacy-package-modal-form"
<input accept-charset="UTF-8"
id="legacyJsonFile" method="post"
name="file" enctype="multipart/form-data"
type="file" >
style="display:none;" {% csrf_token %}
onchange="$('#upload-legacy-file-info').html(this.files[0].name)" <div class="form-control">
/> <label class="label">
Choose JSON File <span class="label-text">Legacy JSON File</span>
</label> </label>
<span class="label label-info" id="upload-legacy-file-info"></span> <input
<input type="file"
type="hidden" id="legacyJsonFile"
value="import-legacy-package-json" name="file"
name="hidden" class="file-input file-input-bordered w-full"
readonly="" accept=".json"
/> required
</p> />
</form> </div>
</div> <input
<div class="modal-footer"> type="hidden"
<a value="import-legacy-package-json"
id="import-legacy-package-modal-form-submit" name="hidden"
class="btn btn-primary" readonly
href="#" />
>Submit</a </form>
> </div>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel <!-- Footer -->
</button> <div class="modal-action">
</div> <button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('import-legacy-package-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Importing...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#import-legacy-package-modal-form-submit").on("click", function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#import-legacy-package-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,70 +1,83 @@
<div <dialog
class="modal fade"
tabindex="-1"
id="import_package_modal" id="import_package_modal"
role="dialog" class="modal"
aria-labelledby="import_package_modal" x-data="modalForm()"
aria-hidden="true" @close="reset()"
> >
<div class="modal-dialog"> <div class="modal-box">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Import Package</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">Import Package</h4> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<p>Create a Package based on a JSON Export.</p>
<form </button>
id="import-package-modal-form" </form>
accept-charset="UTF-8"
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
enctype="multipart/form-data" <p class="mb-4">Create a Package based on a JSON Export.</p>
> <form
{% csrf_token %} id="import-package-modal-form"
<p> accept-charset="UTF-8"
<label class="btn btn-primary" for="jsonFile"> method="post"
<input enctype="multipart/form-data"
id="jsonFile" >
name="file" {% csrf_token %}
type="file" <div class="form-control">
style="display:none;" <label class="label">
onchange="$('#upload-file-info').html(this.files[0].name)" <span class="label-text">JSON File</span>
/> </label>
Choose JSON File <input
</label> type="file"
<span class="label label-info" id="upload-file-info"></span> id="jsonFile"
<input name="file"
type="hidden" class="file-input file-input-bordered w-full"
value="import-package-json" accept=".json"
name="hidden" required
readonly="" />
/> </div>
</p> <input
</form> type="hidden"
</div> value="import-package-json"
<div class="modal-footer"> name="hidden"
<a readonly
id="import-package-modal-form-submit" />
class="btn btn-primary" </form>
href="#" </div>
>Submit</a
> <!-- Footer -->
<button type="button" class="btn btn-default" data-dismiss="modal"> <div class="modal-action">
Cancel <button
</button> type="button"
</div> class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('import-package-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Importing...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#import-package-modal-form-submit").on("click", function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#import-package-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,119 +1,137 @@
{% load static %} {% load static %}
<div
class="modal fade bs-modal-lg" <dialog
id="new_compound_modal" id="new_compound_modal"
tabindex="-1" class="modal"
aria-labelledby="new_compound_modal" x-data="modalForm()"
aria-modal="true" @close="reset()"
role="dialog"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-3xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Create a new Compound</h3>
<button
type="button" <!-- Close button (X) -->
class="close" <form method="dialog">
data-dismiss="modal" <button
aria-label="Close" class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
> :disabled="isSubmitting"
<span aria-hidden="true">×</span> >
</button>
<h4 class="modal-title">Create a new Compound</h4> </button>
</div> </form>
<div class="modal-body">
<form <!-- Body -->
id="new_compound_modal_form" <div class="py-4">
accept-charset="UTF-8" <form
action="{% url 'package compound list' meta.current_package.uuid %}" id="new-compound-modal-form"
data-remote="true" accept-charset="UTF-8"
method="post" action="{% url 'package compound list' meta.current_package.uuid %}"
> method="post"
{% csrf_token %} >
<label for="compound-name">Name</label> {% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="compound-name">
<span class="label-text">Name</span>
</label>
<input <input
id="compound-name" id="compound-name"
class="form-control" class="input input-bordered w-full"
name="compound-name" name="compound-name"
placeholder="Name" placeholder="Name"
required
/> />
<label for="compound-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="compound-description">
<span class="label-text">Description</span>
</label>
<input <input
id="compound-description" id="compound-description"
class="form-control" class="input input-bordered w-full"
name="compound-description" name="compound-description"
placeholder="Description" placeholder="Description"
/> />
<label for="compound-smiles">SMILES</label> </div>
<div class="form-control mb-3">
<label class="label" for="compound-smiles">
<span class="label-text">SMILES</span>
</label>
<input <input
type="text" type="text"
class="form-control" class="input input-bordered w-full"
name="compound-smiles" name="compound-smiles"
placeholder="SMILES" placeholder="SMILES"
id="compound-smiles" id="compound-smiles"
/> />
<p></p> </div>
<div>
<iframe <div class="mb-3">
id="new_compound_ketcher" <iframe
src="{% static '/js/ketcher2/ketcher.html' %}" id="new_compound_ketcher"
width="100%" src="{% static '/js/ketcher2/ketcher.html' %}"
height="510" width="100%"
></iframe> height="510"
</div> ></iframe>
<p></p> </div>
</form> </form>
</div> </div>
<div class="modal-footer">
<button <!-- Footer -->
type="button" <div class="modal-action">
class="btn btn-secondary pull-left" <button
data-dismiss="modal" type="button"
> class="btn"
Close onclick="this.closest('dialog').close()"
</button> :disabled="isSubmitting"
<button >
type="button" Close
class="btn btn-primary" </button>
id="new_compound_modal_form_submit" <button
> type="button"
Submit class="btn btn-primary"
</button> @click="submit('new-compound-modal-form')"
</div> :disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
function newCompoundModalketcherToNewCompoundModalTextInput() {
$("#compound-smiles").val(this.ketcher.getSmiles());
}
$(function () { <!-- Backdrop -->
$("#new_compound_ketcher").on("load", function () { <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>
<script>
document
.getElementById("new_compound_ketcher")
.addEventListener("load", function () {
const iframe = this;
const checkKetcherReady = () => { const checkKetcherReady = () => {
win = this.contentWindow; const win = iframe.contentWindow;
if (win.ketcher && "editor" in win.ketcher) { if (win.ketcher && "editor" in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({ win.ketcher.editor.event.change.handlers.push({
once: false, once: false,
priority: 0, priority: 0,
f: newCompoundModalketcherToNewCompoundModalTextInput, f: function () {
document.getElementById("compound-smiles").value =
this.ketcher.getSmiles();
},
ketcher: win.ketcher, ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
} }
}; };
checkKetcherReady(); checkKetcherReady();
}); });
$(function () {
$("#new_compound_modal_form_submit").on("click", function (e) {
e.preventDefault();
$(this).prop("disabled", true);
// submit form
$("#new_compound_modal_form").submit();
});
});
});
</script> </script>

View File

@ -1,70 +1,96 @@
<div {% load static %}
class="modal fade"
tabindex="-1" <dialog
id="new_group_modal" id="new_group_modal"
role="dialog" class="modal"
aria-labelledby="new_group_modal" x-data="modalForm()"
aria-hidden="true" @close="reset()"
> >
<div class="modal-dialog"> <div class="modal-box">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="font-bold text-lg">New Group</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">New Group</h4> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<p>
Create new Group. You can assign users to the group once it is </button>
created. Description can be changed after creation. </form>
</p>
<form <!-- Body -->
id="new_group_modal_form" <div class="py-4">
accept-charset="UTF-8" <p class="mb-4">
action="{{ SERVER_BASE }}/group" Create new Group. You can assign users to the group once it is created.
data-remote="true" Description can be changed after creation.
method="post" </p>
>
{% csrf_token %} <form
<p> id="new-group-modal-form"
<label for="name">Name</label> accept-charset="UTF-8"
<input action="{{ SERVER_BASE }}/group"
id="name" method="post"
type="text" >
name="group-name" {% csrf_token %}
class="form-control"
placeholder="Name" <div class="form-control mb-3">
/> <label class="label" for="group-name">
</p> <span class="label-text">Name</span>
<p> </label>
<label for="description">Description</label> <input
<input id="group-name"
id="description" class="input input-bordered w-full"
type="text" name="group-name"
class="form-control" placeholder="Name"
placeholder="Description..." required
name="group-description" />
/> </div>
</p>
</form> <div class="form-control mb-3">
</div> <label class="label" for="group-description">
<div class="modal-footer"> <span class="label-text">Description</span>
<a id="new_group_modal_form_submit" class="btn btn-primary" href="#" </label>
>Submit</a <input
> id="group-description"
<button type="button" class="btn btn-default" data-dismiss="modal"> type="text"
Cancel class="input input-bordered w-full"
</button> placeholder="Description..."
</div> name="group-description"
/>
</div>
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('new-group-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#new_group_modal_form_submit").on("click", function () { <button :disabled="isSubmitting">close</button>
$("#new_group_modal_form").submit(); </form>
}); </dialog>
});
</script>

View File

@ -1,30 +1,66 @@
<div <dialog
class="modal fade"
tabindex="-1"
id="new_model_modal" id="new_model_modal"
role="dialog" class="modal"
aria-labelledby="new_model_modal" x-data="{
aria-hidden="true" isSubmitting: false,
modelType: '',
buildAppDomain: false,
reset() {
this.isSubmitting = false;
this.modelType = '';
this.buildAppDomain = false;
},
get showMlrr() {
return this.modelType === 'mlrr';
},
get showRbrr() {
return this.modelType === 'rbrr';
},
get showEnviformer() {
return this.modelType === 'enviformer';
},
submit(formId) {
const form = document.getElementById(formId);
if (form && form.checkValidity()) {
this.isSubmitting = true;
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-3xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">New Model</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">New Model</h4> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<form
id="new_model_form" </button>
accept-charset="UTF-8" </form>
action="{{ meta.current_package.url }}/model"
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="new_model_form"
<div class="jumbotron"> accept-charset="UTF-8"
action="{{ meta.current_package.url }}/model"
method="post"
>
{% csrf_token %}
<div class="alert alert-info mb-4">
<span>
Create a new Model to limit the number of degradation products in Create a new Model to limit the number of degradation products in
the prediction. You just need to set a name and the packages you the prediction. You just need to set a name and the packages you
want the object to be based on. There are multiple types of models want the object to be based on. There are multiple types of models
@ -32,239 +68,270 @@
<a <a
target="_blank" target="_blank"
href="https://wiki.envipath.org/index.php/relative-reasoning" href="https://wiki.envipath.org/index.php/relative-reasoning"
role="button" class="link"
>wiki &gt;&gt;</a >wiki &gt;&gt;</a
> >
</div> </span>
<!-- Name --> </div>
<label for="model-name">Name</label>
<!-- Name -->
<div class="form-control mb-3">
<label class="label" for="model-name">
<span class="label-text">Name</span>
</label>
<input <input
id="model-name" id="model-name"
name="model-name" name="model-name"
class="form-control" class="input input-bordered w-full"
placeholder="Name" placeholder="Name"
required
/> />
</div>
<!-- Description --> <!-- Description -->
<label for="model-description">Description</label> <div class="form-control mb-3">
<label class="label" for="model-description">
<span class="label-text">Description</span>
</label>
<input <input
id="model-description" id="model-description"
name="model-description" name="model-description"
class="form-control" class="input input-bordered w-full"
placeholder="Description" placeholder="Description"
/> />
</div>
<!-- Model Type --> <!-- Model Type -->
<label for="model-type">Model Type</label> <div class="form-control mb-3">
<label class="label" for="model-type">
<span class="label-text">Model Type</span>
</label>
<select <select
id="model-type" id="model-type"
name="model-type" name="model-type"
class="form-control" class="select select-bordered w-full"
data-width="100%" x-model="modelType"
required
> >
<option disabled selected>Select Model Type</option> <option value="" disabled selected>Select Model Type</option>
{% for k, v in model_types.items %} {% for k, v in model_types.items %}
<option value="{{ v }}">{{ k }}</option> <option value="{{ v }}">{{ k }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div>
<!-- Rule Packages --> <!-- Rule Packages (MLRR, RBRR) -->
<div id="rule-packages" class="ep-model-param mlrr rbrr"> <div class="form-control mb-3" x-show="showMlrr || showRbrr" x-cloak>
<label for="model-rule-packages">Rule Packages</label> <label class="label" for="model-rule-packages">
<select <span class="label-text">Rule Packages</span>
id="model-rule-packages" </label>
name="model-rule-packages" <select
data-actions-box="true" id="model-rule-packages"
class="form-control" name="model-rule-packages"
multiple class="select select-bordered w-full h-32"
data-width="100%" multiple
> >
<option disabled>Reviewed Packages</option> <optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if obj.reviewed %} {% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
<option disabled>Unreviewed Packages</option> <optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if not obj.reviewed %} {% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </optgroup>
</div> </select>
<label class="label">
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
</label>
</div>
<!-- Data Packages --> <!-- Data Packages (MLRR, RBRR, Enviformer) -->
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer"> <div
<label for="model-data-packages">Data Packages</label> class="form-control mb-3"
<select x-show="showMlrr || showRbrr || showEnviformer"
id="model-data-packages" x-cloak
name="model-data-packages" >
data-actions-box="true" <label class="label" for="model-data-packages">
class="form-control" <span class="label-text">Data Packages</span>
multiple </label>
data-width="100%" <select
> id="model-data-packages"
<option disabled>Reviewed Packages</option> name="model-data-packages"
class="select select-bordered w-full h-32"
multiple
>
<optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if obj.reviewed %} {% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
<option disabled>Unreviewed Packages</option> <optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if not obj.reviewed %} {% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </optgroup>
</div> </select>
<label class="label">
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
</label>
</div>
<!-- Fingerprinter --> <!-- Fingerprinter (MLRR) -->
<div id="fingerprinter" class="ep-model-param mlrr"> <div class="form-control mb-3" x-show="showMlrr" x-cloak>
<label for="model-fingerprinter">Fingerprinter</label> <label class="label" for="model-fingerprinter">
<select <span class="label-text">Fingerprinter</span>
id="model-fingerprinter" </label>
name="model-fingerprinter" <select
data-actions-box="true" id="model-fingerprinter"
class="form-control" name="model-fingerprinter"
multiple class="select select-bordered w-full h-32"
data-width="100%" multiple
> >
<option value="MACCS" selected>MACCS Fingerprinter</option> <option value="MACCS" selected>MACCS Fingerprinter</option>
{% if meta.enabled_features.PLUGINS and additional_descriptors %} {% if meta.enabled_features.PLUGINS and additional_descriptors %}
<option disabled selected> <optgroup label="Additional Fingerprinter / Descriptor">
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 %}
{% endif %} </optgroup>
</select> {% endif %}
</div> </select>
<label class="label">
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
</label>
</div>
<!-- Threshold --> <!-- Threshold (MLRR, Enviformer) -->
<div id="threshold" class="ep-model-param mlrr enviformer"> <div
<label for="model-threshold">Threshold</label> class="form-control mb-3"
<input x-show="showMlrr || showEnviformer"
type="number" x-cloak
min="0" >
max="1" <label class="label" for="model-threshold">
step="0.05" <span class="label-text">Threshold</span>
value="0.5" </label>
id="model-threshold" <input
name="model-threshold" type="number"
class="form-control" min="0"
/> max="1"
</div> step="0.05"
value="0.5"
id="model-threshold"
name="model-threshold"
class="input input-bordered w-full"
/>
</div>
<div id="appdomain" class="ep-model-param mlrr"> <!-- Applicability Domain (MLRR) -->
{% if meta.enabled_features.APPLICABILITY_DOMAIN %} {% if meta.enabled_features.APPLICABILITY_DOMAIN %}
<!-- Build AD? --> <div x-show="showMlrr" x-cloak>
<div class="checkbox"> <div class="form-control mb-3">
<label> <label class="label cursor-pointer justify-start gap-3">
<input <input
type="checkbox" type="checkbox"
id="build-app-domain" id="build-app-domain"
name="build-app-domain" name="build-app-domain"
/>Also build an Applicability Domain? class="checkbox"
x-model="buildAppDomain"
/>
<span class="label-text"
>Also build an Applicability Domain?</span
>
</label>
</div>
<div x-show="buildAppDomain" x-cloak class="ml-4 space-y-3">
<div class="form-control">
<label class="label" for="num-neighbors">
<span class="label-text">Number of Neighbors</span>
</label> </label>
</div>
<div id="ad-params" style="display:none">
<!-- Num Neighbors -->
<label for="num-neighbors">Number of Neighbors</label>
<input <input
id="num-neighbors" id="num-neighbors"
name="num-neighbors" name="num-neighbors"
type="number" type="number"
class="form-control" class="input input-bordered w-full"
value="5" value="5"
step="1" step="1"
min="0" min="0"
max="10" max="10"
/> />
<!-- Local Compatibility --> </div>
<label for="local-compatibility-threshold"
>Local Compatibility Threshold</label <div class="form-control">
> <label class="label" for="local-compatibility-threshold">
<span class="label-text">Local Compatibility Threshold</span>
</label>
<input <input
id="local-compatibility-threshold" id="local-compatibility-threshold"
name="local-compatibility-threshold" name="local-compatibility-threshold"
type="number" type="number"
class="form-control" class="input input-bordered w-full"
value="0.5"
step="0.01"
min="0"
max="1"
/>
<!-- Reliability -->
<label for="reliability-threshold">Reliability Threshold</label>
<input
id="reliability-threshold"
name="reliability-threshold"
type="number"
class="form-control"
value="0.5" value="0.5"
step="0.01" step="0.01"
min="0" min="0"
max="1" max="1"
/> />
</div> </div>
{% endif %}
<div class="form-control">
<label class="label" for="reliability-threshold">
<span class="label-text">Reliability Threshold</span>
</label>
<input
id="reliability-threshold"
name="reliability-threshold"
type="number"
class="input input-bordered w-full"
value="0.5"
step="0.01"
min="0"
max="1"
/>
</div>
</div>
</div> </div>
</form> {% endif %}
</div> </form>
<div class="modal-footer"> </div>
<a id="new_model_modal_form_submit" class="btn btn-primary" href="#"
>Submit</a <!-- Footer -->
> <div class="modal-action">
<button type="button" class="btn btn-default" data-dismiss="modal"> <button
Cancel type="button"
</button> class="btn"
</div> onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('new_model_form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
// Built in Model Types <button :disabled="isSubmitting">close</button>
var nativeModelTypes = ["mlrr", "rbrr", "enviformer"]; </form>
</dialog>
// Initially hide all "specific" forms
$(".ep-model-param").each(function () {
$(this).hide();
});
$("#model-type").selectpicker();
$("#model-fingerprinter").selectpicker();
$("#model-rule-packages").selectpicker();
$("#model-data-packages").selectpicker();
$("#build-app-domain").change(function () {
if ($(this).is(":checked")) {
$("#ad-params").show();
} else {
$("#ad-params").hide();
}
});
// On change hide all and show only selected
$("#model-type").change(function () {
$(".ep-model-param").hide();
var modelType = $("#model-type").val();
if (nativeModelTypes.indexOf(modelType) !== -1) {
$("." + modelType).show();
} else {
// do nothing
}
});
$("#new_model_modal_form_submit").on("click", function (e) {
e.preventDefault();
$("#new_model_form").submit();
});
});
</script>

View File

@ -1,68 +1,93 @@
<div {% load static %}
class="modal fade"
tabindex="-1" <dialog
id="new_package_modal" id="new_package_modal"
role="dialog" class="modal"
aria-labelledby="new_package_modal" x-data="modalForm()"
aria-hidden="true" @close="reset()"
> >
<div class="modal-dialog"> <div class="modal-box">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="font-bold text-lg">New Package</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">New Package</h4> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<p>Create new package. Description can be changed later.</p>
<form </button>
id="new_package_modal_form" </form>
accept-charset="UTF-8"
action="" <!-- Body -->
data-remote="true" <div class="py-4">
method="post" <p class="mb-4">Create new package. Description can be changed later.</p>
>
{% csrf_token %} <form
<p> id="new-package-modal-form"
<label for="name">Name</label> accept-charset="UTF-8"
<input action=""
id="name" method="post"
class="form-control" >
name="package-name" {% csrf_token %}
placeholder="Name"
/> <div class="form-control mb-3">
</p> <label class="label" for="package-name">
<p> <span class="label-text">Name</span>
<label for="description">Description</label> </label>
<input <input
id="description" id="package-name"
type="text" class="input input-bordered w-full"
rows="3" name="package-name"
class="form-control" placeholder="Name"
placeholder="Description..." required
name="package-description" />
/> </div>
</p>
</form> <div class="form-control mb-3">
</div> <label class="label" for="package-description">
<div class="modal-footer"> <span class="label-text">Description</span>
<a id="new_package_modal_form_submit" class="btn btn-primary" href="#" </label>
>Submit</a <input
> id="package-description"
<button type="button" class="btn btn-default" data-dismiss="modal"> type="text"
Cancel class="input input-bordered w-full"
</button> placeholder="Description..."
</div> name="package-description"
/>
</div>
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('new-package-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#new_package_modal_form_submit").on("click", function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#new_package_modal_form").submit(); </dialog>
});
});
</script>

View File

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

View File

@ -1,185 +1,260 @@
{% load static %} {% load static %}
<div id="new_prediction_setting_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="new_prediction_setting_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="{
<h5 class="modal-title">Create a Prediction Setting</h5> isSubmitting: false,
<button tpMethod: '',
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
To create a Prediction Setting fill the form below and click "Create"
</p>
<form
id="new-prediction-setting-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %}
<label for="prediction-setting-name">Name</label> reset() {
this.isSubmitting = false;
this.tpMethod = '';
},
async submit() {
const form = document.getElementById('new-prediction-setting-modal-form');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
this.isSubmitting = true;
const formData = new FormData(form);
try {
const response = await fetch('/setting', {
method: 'POST',
body: new URLSearchParams(formData)
});
if (response.ok) {
location.reload();
}
} catch (error) {
console.error('Error creating setting:', error);
} finally {
this.isSubmitting = false;
}
}
}"
@close="reset()"
>
<div class="modal-box max-w-2xl">
<!-- Header -->
<h3 class="text-lg font-bold">Create a Prediction Setting</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<p class="mb-4">
To create a Prediction Setting fill the form below and click "Create"
</p>
<form
id="new-prediction-setting-modal-form"
accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="prediction-setting-name">
<span class="label-text">Name</span>
</label>
<input <input
id="prediction-setting-name" id="prediction-setting-name"
name="prediction-setting-name" name="prediction-setting-name"
class="form-control" class="input input-bordered w-full"
placeholder="Name" placeholder="Name"
required
/> />
<label for="prediction-setting-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="prediction-setting-description">
<span class="label-text">Description</span>
</label>
<input <input
id="prediction-setting-description" id="prediction-setting-description"
name="prediction-setting-description" name="prediction-setting-description"
class="form-control" class="input input-bordered w-full"
placeholder="Description" placeholder="Description"
/> />
</div>
<label for="prediction-setting-max-nodes">Max #Nodes</label> <div class="form-control mb-3">
<label class="label" for="prediction-setting-max-nodes">
<span class="label-text">Max #Nodes</span>
</label>
<input <input
id="prediction-setting-max-nodes" id="prediction-setting-max-nodes"
type="number" type="number"
class="form-control" class="input input-bordered w-full"
name="prediction-setting-max-nodes" name="prediction-setting-max-nodes"
value="30" value="30"
min="1" min="1"
max="50" max="50"
step="1" step="1"
/> />
<label for="prediction-setting-max-depth">Max Depth</label> </div>
<div class="form-control mb-3">
<label class="label" for="prediction-setting-max-depth">
<span class="label-text">Max Depth</span>
</label>
<input <input
id="prediction-setting-max-depth" id="prediction-setting-max-depth"
type="number" type="number"
class="form-control" class="input input-bordered w-full"
name="prediction-setting-max-depth" name="prediction-setting-max-depth"
value="5" value="5"
min="1" min="1"
max="8" max="8"
step="1" step="1"
/> />
</div>
<label for="tp-generation-method">TP Generation Method</label> <div class="form-control mb-3">
<label class="label" for="tp-generation-method">
<span class="label-text">TP Generation Method</span>
</label>
<select <select
id="tp-generation-method" id="tp-generation-method"
name="tp-generation-method" name="tp-generation-method"
class="form-control" class="select select-bordered w-full"
data-width="100%" x-model="tpMethod"
required
> >
<option disabled selected>Select how TPs are generated</option> <option value="" disabled selected>
Select how TPs are generated
</option>
<option value="rule-based-prediction-setting">Rule Based</option> <option value="rule-based-prediction-setting">Rule Based</option>
<option value="model-based-prediction-setting">Model Based</option> <option value="model-based-prediction-setting">Model Based</option>
</select> </select>
<div id="rule-based-prediction-setting-specific-form"> </div>
<!-- Rule Packages -->
<label>Rule Packages</label><br /> <!-- Rule Based Settings -->
<div x-show="tpMethod === 'rule-based-prediction-setting'" x-cloak>
<div class="form-control mb-3">
<label class="label">
<span class="label-text">Rule Packages</span>
</label>
<select <select
id="rule-based-prediction-setting-packages" id="rule-based-prediction-setting-packages"
name="rule-based-prediction-setting-packages" name="rule-based-prediction-setting-packages"
data-actions-box="true" class="select select-bordered w-full h-32"
class="form-control"
multiple multiple
data-width="100%"
> >
<option disabled>Reviewed Packages</option> <optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if obj.reviewed %} {% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
<option disabled>Unreviewed Packages</option> <optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if not obj.reviewed %} {% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
</select> </select>
<label class="label">
<span class="label-text-alt"
>Hold Ctrl/Cmd to select multiple</span
>
</label>
</div> </div>
<div id="model-based-prediction-setting-specific-form"> </div>
<label>Select Model</label><br />
<!-- Model Based Settings -->
<div x-show="tpMethod === 'model-based-prediction-setting'" x-cloak>
<div class="form-control mb-3">
<label class="label" for="model-based-prediction-setting-model">
<span class="label-text">Select Model</span>
</label>
<select <select
id="model-based-prediction-setting-model" id="model-based-prediction-setting-model"
name="model-based-prediction-setting-model" name="model-based-prediction-setting-model"
class="form-control" class="select select-bordered w-full"
data-width="100%"
> >
<option disabled selected>Select the model</option> <option value="" disabled selected>Select the model</option>
{% for m in models %} {% for m in models %}
<option value="{{ m.url }}">{{ m.name|safe }}</option> <option value="{{ m.url }}">{{ m.name|safe }}</option>
{% endfor %} {% endfor %}
</select> </select>
<label for="model-based-prediction-setting-threshold" </div>
>Threshold</label
> <div class="form-control mb-3">
<label class="label" for="model-based-prediction-setting-threshold">
<span class="label-text">Threshold</span>
</label>
<input <input
id="model-based-prediction-setting-threshold" id="model-based-prediction-setting-threshold"
name="model-based-prediction-setting-threshold" name="model-based-prediction-setting-threshold"
class="form-control" class="input input-bordered w-full"
placeholder="0.25" placeholder="0.25"
type="number" type="number"
min="0"
max="1"
step="0.05"
/> />
</div> </div>
</div>
<input <div class="form-control">
class="form-check-input" <label class="label cursor-pointer justify-start gap-3">
type="checkbox" <input
value="on" type="checkbox"
id="prediction-setting-new-default" class="checkbox"
name="prediction-setting-new-default" value="on"
/> id="prediction-setting-new-default"
<label class="form-check-label" for="prediction-setting-new-default" name="prediction-setting-new-default"
>Set this setting as new default</label />
> <span class="label-text">Set this setting as new default</span>
</form> </label>
</div> </div>
<div class="modal-footer"> </form>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> </div>
Close
</button> <!-- Footer -->
<button <div class="modal-action">
type="button" <button
class="btn btn-primary" type="button"
id="new-prediction-setting-modal-submit" class="btn"
> onclick="this.closest('dialog').close()"
Create :disabled="isSubmitting"
</button> >
</div> Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit()"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Create</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
$(function () {
// Initially hide all "specific" forms
$("div[id$='-specific-form']").each(function () {
$(this).hide();
});
$("#rule-based-prediction-setting-packages").selectpicker(); <!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
// On change hide all and show only selected <button :disabled="isSubmitting">close</button>
$("#tp-generation-method").change(function () { </form>
$("div[id$='-specific-form']").each(function () { </dialog>
$(this).hide();
});
val = $("option:selected", this).val();
$("#" + val + "-specific-form").show();
});
$("#new-prediction-setting-modal-submit").click(function (e) {
e.preventDefault();
// $('#new-prediction-setting-modal-form').submit();
const formData = $("#new-prediction-setting-modal-form").serialize();
$.post("/setting", formData, function (response) {
location.reload();
});
});
});
</script>

View File

@ -1,91 +1,105 @@
{% load static %} {% load static %}
<div
class="modal fade bs-modal-lg" <dialog
id="new_reaction_modal" id="new_reaction_modal"
tabindex="-1" class="modal"
aria-labelledby="new_reaction_modal" x-data="modalForm()"
aria-modal="true" @close="reset()"
role="dialog"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-3xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="font-bold text-lg">Create a new Reaction</h3>
<button
type="button" <!-- Close button (X) -->
class="close" <form method="dialog">
data-dismiss="modal" <button
aria-label="Close" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
> :disabled="isSubmitting"
<span aria-hidden="true">×</span> >
</button>
<h4 class="modal-title">Create a new Reaction</h4> </button>
</div> </form>
<div class="modal-body">
<form <!-- Body -->
id="new_reaction_modal_form" <div class="py-4">
accept-charset="UTF-8" <form
action="{% url 'package reaction list' meta.current_package.uuid %}" id="new-reaction-modal-form"
data-remote="true" accept-charset="UTF-8"
method="post" action="{% url 'package reaction list' meta.current_package.uuid %}"
> method="post"
{% csrf_token %} >
<label for="reaction-name">Name</label> {% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="reaction-name">
<span class="label-text">Name</span>
</label>
<input <input
id="reaction-name" id="reaction-name"
class="form-control" class="input input-bordered w-full"
name="reaction-name" name="reaction-name"
placeholder="Name" placeholder="Name"
required
/> />
<label for="reaction-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="reaction-description">
<span class="label-text">Description</span>
</label>
<input <input
id="reaction-description" id="reaction-description"
class="form-control" class="input input-bordered w-full"
name="reaction-description" name="reaction-description"
placeholder="Description" placeholder="Description"
/> />
<p></p> </div>
<div>
<iframe <div class="mb-3">
id="new_reaction_ketcher" <iframe
src="{% static '/js/ketcher2/ketcher.html' %}" id="new_reaction_ketcher"
width="100%" src="{% static '/js/ketcher2/ketcher.html' %}"
height="510" width="100%"
></iframe> height="510"
</div> ></iframe>
<input type="hidden" name="reaction-smirks" id="reaction-smirks" /> </div>
<p></p>
</form> <input type="hidden" name="reaction-smirks" id="reaction-smirks" />
</div> </form>
<div class="modal-footer"> </div>
<button
type="button" <!-- Footer -->
class="btn btn-secondary pull-left" <div class="modal-action">
data-dismiss="modal" <button
> type="button"
Close class="btn"
</button> onclick="this.closest('dialog').close()"
<button :disabled="isSubmitting"
type="button" >
class="btn btn-primary" Close
id="new_reaction_modal_form_submit" </button>
> <button
Submit type="button"
</button> class="btn btn-primary"
</div> @click="
const k = getKetcher('new_reaction_ketcher');
document.getElementById('reaction-smirks').value = k.getSmiles();
submit('new-reaction-modal-form');
"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
$(function () {
$("#new_reaction_modal_form_submit").on("click", function (e) {
e.preventDefault();
$(this).prop("disabled", true);
k = getKetcher("new_reaction_ketcher"); <!-- Backdrop -->
$("#reaction-smirks").val(k.getSmiles()); <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
// submit form </form>
$("#new_reaction_modal_form").submit(); </dialog>
});
});
</script>

View File

@ -1,120 +1,140 @@
{% load static %} {% load static %}
<div
class="modal fade bs-modal-lg" <dialog
id="new_rule_modal" id="new_rule_modal"
tabindex="-1" class="modal"
aria-labelledby="new_rule_modal" x-data="{
aria-modal="true" ...modalForm(),
role="dialog" smirksVizHtml: '',
updateSmirksViz() {
const smirks = document.getElementById('rule-smirks').value;
if (!smirks) {
this.smirksVizHtml = '';
return;
}
const img = new Image();
img.src = '{% url 'depict' %}?is_query_smirks=true&smirks=' + encodeURIComponent(smirks);
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'cover';
img.onload = () => {
this.smirksVizHtml = img.outerHTML;
};
img.onerror = () => {
this.smirksVizHtml = `
<div class='alert alert-error' role='alert'>
<h4 class='alert-heading'>Could not render SMIRKS!</h4>
<p>Could not render SMIRKS - Have you entered a valid SMIRKS?</p>
</div>`;
};
}
}"
@close="reset(); smirksVizHtml = ''"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-3xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="font-bold text-lg">Create a new Rule</h3>
<button
type="button" <!-- Close button (X) -->
class="close" <form method="dialog">
data-dismiss="modal" <button
aria-label="Close" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
> :disabled="isSubmitting"
<span aria-hidden="true">×</span> >
</button>
<h4 class="modal-title">Create a new Rule</h4> </button>
</div> </form>
<div class="modal-body">
<form <!-- Body -->
id="new_rule_modal_form" <div class="py-4">
accept-charset="UTF-8" <form
action="{% url 'package rule list' meta.current_package.uuid %}" id="new-rule-modal-form"
data-remote="true" accept-charset="UTF-8"
method="post" action="{% url 'package rule list' meta.current_package.uuid %}"
> method="post"
{% csrf_token %} >
<label for="rule-name">Name</label> {% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="rule-name">
<span class="label-text">Name</span>
</label>
<input <input
id="rule-name" id="rule-name"
class="form-control" class="input input-bordered w-full"
name="rule-name" name="rule-name"
placeholder="Name" placeholder="Name"
required
/> />
<label for="rule-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="rule-description">
<span class="label-text">Description</span>
</label>
<input <input
id="rule-description" id="rule-description"
class="form-control" class="input input-bordered w-full"
name="rule-description" name="rule-description"
placeholder="Description" placeholder="Description"
/> />
<label for="rule-smirks">SMIRKS</label> </div>
<div class="form-control mb-3">
<label class="label" for="rule-smirks">
<span class="label-text">SMIRKS</span>
</label>
<input <input
id="rule-smirks" id="rule-smirks"
class="form-control" class="input input-bordered w-full"
name="rule-smirks" name="rule-smirks"
placeholder="SMIRKS" placeholder="SMIRKS"
@input="updateSmirksViz()"
/> />
<p></p> </div>
<div id="rule-smirks-viz"></div>
<input <div id="rule-smirks-viz" class="mb-3" x-html="smirksVizHtml"></div>
type="hidden"
name="rule-type" <input
id="rule-type" type="hidden"
value="SimpleAmbitRule" name="rule-type"
/> id="rule-type"
<p></p> value="SimpleAmbitRule"
</form> />
</div> </form>
<div class="modal-footer"> </div>
<button
type="button" <!-- Footer -->
class="btn btn-secondary pull-left" <div class="modal-action">
data-dismiss="modal" <button
> type="button"
Close class="btn"
</button> onclick="this.closest('dialog').close()"
<button :disabled="isSubmitting"
type="button" >
class="btn btn-primary" Close
id="new_rule_modal_form_submit" </button>
> <button
Submit type="button"
</button> class="btn btn-primary"
</div> @click="submit('new-rule-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
$(function () {
$("#rule-smirks").on("input", function (e) {
$("#rule-smirks-viz").empty();
smirks = $("#rule-smirks").val(); <!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
const img = new Image(); <button :disabled="isSubmitting">close</button>
img.src = </form>
"{% url 'depict' %}?is_query_smirks=true&smirks=" + </dialog>
encodeURIComponent(smirks);
img.style.width = "100%";
img.style.height = "100%";
img.style.objectFit = "cover";
img.onload = function () {
$("#rule-smirks-viz").append(img);
};
img.onerror = function () {
error_tpl = `
<div class="alert alert-error" role="alert">
<h4 class="alert-heading">Could not render SMIRKS!</h4>
<p>Could not render SMIRKS - Have you entered a valid SMIRKS?</a>
</p>
</div>`;
$("#rule-smirks-viz").append(error_tpl);
};
});
$("#new_rule_modal_form_submit").on("click", function (e) {
e.preventDefault();
$(this).prop("disabled", true);
// submit form
$("#new_rule_modal_form").submit();
});
});
</script>

View File

@ -1,30 +1,45 @@
<div {% load static %}
class="modal fade"
tabindex="-1" <dialog
id="new_scenario_modal" id="new_scenario_modal"
role="dialog" class="modal"
aria-labelledby="new_scenario_modal" x-data="{
aria-hidden="true" ...modalForm(),
scenarioType: 'empty',
validateYear(el) {
if (el.value && el.value.length < 4) {
el.value = new Date().getFullYear();
}
}
}"
@close="reset()"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-3xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="font-bold text-lg">New Scenario</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">New Scenario</h4> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<form
id="new_scenario_form" </button>
accept-charset="UTF-8" </form>
action="{{ meta.current_package.url }}/scenario"
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="new-scenario-modal-form"
<div class="jumbotron"> accept-charset="UTF-8"
action="{{ meta.current_package.url }}/scenario"
method="post"
>
{% csrf_token %}
<div class="alert alert-info mb-4">
<span>
Please enter name, description, and date of scenario. Date should be Please enter name, description, and date of scenario. Date should be
associated to the data, not the current date. For example, this associated to the data, not the current date. For example, this
could reflect the publishing date of a study. You can leave all could reflect the publishing date of a study. You can leave all
@ -32,122 +47,131 @@
<a <a
target="_blank" target="_blank"
href="https://wiki.envipath.org/index.php/scenario" href="https://wiki.envipath.org/index.php/scenario"
role="button" class="link"
>wiki &gt;&gt;</a >wiki &gt;&gt;</a
> >
</div> </span>
<label for="scenario-name">Name</label> </div>
<div class="form-control mb-3">
<label class="label" for="scenario-name">
<span class="label-text">Name</span>
</label>
<input <input
id="scenario-name" id="scenario-name"
name="scenario-name" name="scenario-name"
class="form-control" class="input input-bordered w-full"
placeholder="Name" placeholder="Name"
required
/> />
<label for="scenario-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="scenario-description">
<span class="label-text">Description</span>
</label>
<input <input
id="scenario-description" id="scenario-description"
name="scenario-description" name="scenario-description"
class="form-control" class="input input-bordered w-full"
placeholder="Description" placeholder="Description"
/> />
<label id="dateField" for="dateYear">Date</label> </div>
<table>
<tr> <div class="form-control mb-3">
<th> <label class="label">
<input <span class="label-text">Date</span>
type="number" </label>
id="dateYear" <div class="flex gap-2">
name="scenario-date-year" <input
class="form-control" type="number"
placeholder="YYYY" id="dateYear"
max="{% now "Y" %}" name="scenario-date-year"
/> class="input input-bordered w-24"
</th> placeholder="YYYY"
<th> max="{% now 'Y' %}"
<input @blur="validateYear($el)"
type="number" />
id="dateMonth" <input
name="scenario-date-month" type="number"
min="1" id="dateMonth"
max="12" name="scenario-date-month"
class="form-control" min="1"
placeholder="MM" max="12"
/> class="input input-bordered w-20"
</th> placeholder="MM"
<th> />
<input <input
type="number" type="number"
id="dateDay" id="dateDay"
name="scenario-date-day" name="scenario-date-day"
min="1" min="1"
max="31" max="31"
class="form-control" class="input input-bordered w-20"
placeholder="DD" placeholder="DD"
/> />
</th> </div>
</tr> </div>
</table>
<label for="scenario-type">Scenario Type</label> <div class="form-control mb-3">
<label class="label" for="scenario-type">
<span class="label-text">Scenario Type</span>
</label>
<select <select
id="scenario-type" id="scenario-type"
name="scenario-type" name="scenario-type"
class="form-control" class="select select-bordered w-full"
data-width="100%" x-model="scenarioType"
> >
<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>
{% endfor %} {% endfor %}
</select> </select>
</div>
{% for type in scenario_types.values %} {% for type in scenario_types.values %}
<div id="{{ type.name }}-specific-inputs"> <div
{% for widget in type.widgets %} id="{{ type.name }}-specific-inputs"
{{ widget|safe }} x-show="scenarioType === '{{ type.name }}'"
{% endfor %} x-cloak
</div> >
{% endfor %} {% for widget in type.widgets %}
</form> {{ widget|safe }}
</div> {% endfor %}
<div class="modal-footer"> </div>
<a id="new_scenario_modal_form_submit" class="btn btn-primary" href="#" {% endfor %}
>Submit</a </form>
> </div>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel <!-- Footer -->
</button> <div class="modal-action">
</div> <button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('new-scenario-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
// Initially hide all "specific" forms <button :disabled="isSubmitting">close</button>
$("div[id$='-specific-inputs']").each(function () { </form>
$(this).hide(); </dialog>
});
// On change hide all and show only selected
$("#scenario-type").change(function () {
$("div[id$='-specific-inputs']").each(function () {
$(this).hide();
});
val = $("option:selected", this).val();
$("#" + val + "-specific-inputs").show();
});
$("#new_scenario_modal_form_submit").on("click", function (e) {
e.preventDefault();
$("#new_scenario_form").submit();
});
var dateYear = document.getElementById("dateYear");
dateYear.addEventListener("change", () => {
console.log("Final value after editing:", dateYear.value);
if (dateYear.value.length < 4) {
dateYear.value = new Date().getFullYear();
}
});
});
</script>

View File

@ -1,92 +1,117 @@
{% load static %} {% load static %}
<!-- Add Additional Information--> <!-- Add Additional Information -->
<div id="add_additional_information_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="add_additional_information_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="{
<button isSubmitting: false,
type="button" selectedType: '',
class="close"
data-dismiss="modal" reset() {
aria-label="Close" this.isSubmitting = false;
> this.selectedType = '';
<span aria-hidden="true">&times;</span> },
</button>
<h3 class="modal-title">Add Additional Information</h3> submit() {
</div> if (!this.selectedType) return;
<div class="modal-body">
const form = document.getElementById('add_' + this.selectedType + '_add-additional-information-modal-form');
if (form && form.checkValidity()) {
this.isSubmitting = true;
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
>
<div class="modal-box">
<!-- Header -->
<h3 class="text-lg font-bold">Add Additional Information</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<div class="form-control">
<label class="label" for="select-additional-information-type">
<span class="label-text">Select the type to add</span>
</label>
<select <select
id="select-additional-information-type" id="select-additional-information-type"
data-actions-box="true" class="select select-bordered w-full"
class="form-control" x-model="selectedType"
data-width="100%"
> >
<option selected disabled>Select the type to add</option> <option value="" 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 }}"> <option value="{{ add_inf.name }}">
{{ add_inf.display_name }} {{ add_inf.display_name }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
{% for add_inf in available_additional_information %}
<div class="aiform {{ add_inf.name }}" style="display: none;">
<form
id="add_{{ add_inf.name }}_add-additional-information-modal-form"
accept-charset="UTF-8"
action=""
data-remote="true"
method="post"
>
{% csrf_token %}
{{ add_inf.widget|safe }}
<input
type="hidden"
name="hidden"
value="add-additional-information"
/>
</form>
</div>
{% endfor %}
</div> </div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"> {% for add_inf in available_additional_information %}
Close <div
</button> class="mt-4"
<button x-show="selectedType === '{{ add_inf.name }}'"
type="button" x-cloak
class="btn btn-primary"
id="add-additional-information-modal-submit"
> >
Add <form
</button> id="add_{{ add_inf.name }}_add-additional-information-modal-form"
</div> accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
{{ add_inf.widget|safe }}
<input
type="hidden"
name="hidden"
value="add-additional-information"
/>
</form>
</div>
{% endfor %}
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit()"
:disabled="isSubmitting || !selectedType"
>
<span x-show="!isSubmitting">Add</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Adding...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
$(function () {
$("#select-additional-information-type").change(function (e) {
var selectedType = $(
"#select-additional-information-type :selected",
).val();
$(".aiform").hide();
$("." + selectedType).show();
});
$("#add-additional-information-modal-submit").click(function (e) { <!-- Backdrop -->
e.preventDefault(); <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
var selectedType = $( </form>
"#select-additional-information-type :selected", </dialog>
).val();
console.log(selectedType);
if (
selectedType !== null &&
selectedType !== undefined &&
selectedType !== ""
) {
$("." + selectedType + " >form").submit();
}
});
});
</script>

View File

@ -1,67 +1,107 @@
{% load static %} {% load static %}
<div <dialog
class="modal fade bs-modal-lg"
id="add_pathway_edge_modal" id="add_pathway_edge_modal"
tabindex="-1" class="modal"
aria-labelledby="add_pathway_edge_modal" x-data="{
aria-modal="true" isSubmitting: false,
role="dialog" reactionImageUrl: '',
reset() {
this.isSubmitting = false;
this.reactionImageUrl = '';
},
updateReactionImage() {
const substratesSelect = document.getElementById('add_pathway_edge_substrates');
const productsSelect = document.getElementById('add_pathway_edge_products');
const substrates = [];
for (const option of substratesSelect.selectedOptions) {
substrates.push(option.dataset.smiles);
}
const products = [];
for (const option of productsSelect.selectedOptions) {
products.push(option.dataset.smiles);
}
if (substrates.length > 0 && products.length > 0) {
const reaction = substrates.join('.') + '>>' + products.join('.');
this.reactionImageUrl = '{% url "depict" %}?smirks=' + encodeURIComponent(reaction);
} else {
this.reactionImageUrl = '';
}
},
submit() {
this.isSubmitting = true;
document.getElementById('add_pathway_edge_modal_form').submit();
}
}"
@close="reset()"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-4xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Add a Reaction</h3>
<button
type="button" <!-- Close button (X) -->
class="close" <form method="dialog">
data-dismiss="modal" <button
aria-label="Close" class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
> :disabled="isSubmitting"
<span aria-hidden="true">&times;</span> >
</button>
<h4 class="modal-title">Add a Reaction</h4> </button>
</div> </form>
<div class="modal-body">
<form <!-- Body -->
id="add_pathway_edge_modal_form" <div class="py-4">
accept-charset="UTF-8" <form
action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}" id="add_pathway_edge_modal_form"
data-remote="true" accept-charset="UTF-8"
method="post" action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}"
> data-remote="true"
{% csrf_token %} method="post"
<label for="edge-name">Name</label> >
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="edge-name">
<span class="label-text">Name</span>
</label>
<input <input
id="edge-name" id="edge-name"
class="form-control" type="text"
class="input input-bordered w-full"
name="edge-name" name="edge-name"
placeholder="Name" placeholder="Name"
/> />
<label for="edge-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="edge-description">
<span class="label-text">Description</span>
</label>
<input <input
id="edge-description" id="edge-description"
class="form-control" type="text"
class="input input-bordered w-full"
name="edge-description" name="edge-description"
placeholder="Description" placeholder="Description"
/> />
<p></p> </div>
<div class="row">
<div class="col-xs-5"> <div class="mb-3 grid grid-cols-11 gap-2">
<legend>Substrate(s)</legend> <div class="col-span-5">
</div> <div class="form-control">
<div class="col-xs-2"></div> <label class="label">
<div class="col-xs-5"> <span class="label-text font-semibold">Substrate(s)</span>
<legend>Product(s)</legend> </label>
</div>
</div>
<div class="row">
<div class="col-xs-5">
<select <select
id="add_pathway_edge_substrates" id="add_pathway_edge_substrates"
name="edge-substrates" name="edge-substrates"
data-actions-box="true" class="select select-bordered h-32 w-full"
class="form-control"
multiple multiple
data-width="100%" @change="updateReactionImage()"
> >
{% for n in pathway.nodes %} {% for n in pathway.nodes %}
<option <option
@ -73,20 +113,21 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div </div>
class="col-xs-2" <div class="col-span-1 flex items-center justify-center">
style="display: flex; justify-content: center; align-items: center;" <span class="text-2xl"></span>
> </div>
<i class="glyphicon glyphicon-arrow-right"></i> <div class="col-span-5">
</div> <div class="form-control">
<div class="col-xs-5"> <label class="label">
<span class="label-text font-semibold">Product(s)</span>
</label>
<select <select
id="add_pathway_edge_products" id="add_pathway_edge_products"
name="edge-products" name="edge-products"
data-actions-box="true" class="select select-bordered h-32 w-full"
class="form-control"
multiple multiple
data-width="100%" @change="updateReactionImage()"
> >
{% for n in pathway.nodes %} {% for n in pathway.nodes %}
<option <option
@ -99,76 +140,42 @@
</select> </select>
</div> </div>
</div> </div>
<div class="row"> </div>
<p></p>
<div class="col-xs-12" id="reaction_image"></div> <div class="mb-3" x-show="reactionImageUrl" x-cloak>
</div> <img :src="reactionImageUrl" class="w-full" alt="Reaction preview" />
</form> </div>
</div> </form>
<div class="modal-footer"> </div>
<button
type="button" <!-- Footer -->
class="btn btn-secondary pull-left" <div class="modal-action">
data-dismiss="modal" <button
> type="button"
Close class="btn"
</button> onclick="this.closest('dialog').close()"
<button :disabled="isSubmitting"
type="button" >
class="btn btn-primary" Close
id="add_pathway_edge_modal_form_submit" </button>
> <button
Submit type="button"
</button> class="btn btn-primary"
</div> @click="submit()"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Submitting...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
function reactionImage() {
var substrates = [];
$("#add_pathway_edge_substrates option:selected").each(function () {
var smiles = $(this).data("smiles"); // read data-smiles attribute
substrates.push(smiles);
});
var products = []; <!-- Backdrop -->
$("#add_pathway_edge_products option:selected").each(function () { <form method="dialog" class="modal-backdrop">
var smiles = $(this).data("smiles"); // read data-smiles attribute <button :disabled="isSubmitting">close</button>
products.push(smiles); </form>
}); </dialog>
if (substrates.length > 0 && products.length > 0) {
reaction = substrates.join(".") + ">>" + products.join(".");
$("#reaction_image").empty();
$("#reaction_image").append(
"<img width='100%' src='{% url 'depict' %}?smirks=" +
encodeURIComponent(reaction) +
"'>",
);
}
}
$(function () {
$("#add_pathway_edge_substrates").selectpicker();
$("#add_pathway_edge_products").selectpicker();
$("#add_pathway_edge_substrates").on("change", function (e) {
reactionImage();
});
$("#add_pathway_edge_products").on("change", function (e) {
reactionImage();
});
$(function () {
$("#add_pathway_edge_modal_form_submit").on("click", function (e) {
e.preventDefault();
$(this).prop("disabled", true);
// submit form
$("#add_pathway_edge_modal_form").submit();
});
});
});
</script>

View File

@ -1,119 +1,137 @@
{% load static %} {% load static %}
<div <dialog
class="modal fade bs-modal-lg"
id="add_pathway_node_modal" id="add_pathway_node_modal"
tabindex="-1" class="modal"
aria-labelledby="add_pathway_node_modal" x-data="modalForm()"
aria-modal="true" @close="reset()"
role="dialog"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-4xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Add a Node</h3>
<button
type="button" <!-- Close button (X) -->
class="close" <form method="dialog">
data-dismiss="modal" <button
aria-label="Close" class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
> :disabled="isSubmitting"
<span aria-hidden="true">&times;</span> >
</button>
<h4 class="modal-title">Add a Node</h4> </button>
</div> </form>
<div class="modal-body">
<form <!-- Body -->
id="add_pathway_node_modal_form" <div class="py-4">
accept-charset="UTF-8" <form
action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}" id="add_pathway_node_modal_form"
data-remote="true" accept-charset="UTF-8"
method="post" action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}"
> data-remote="true"
{% csrf_token %} method="post"
<label for="node-name">Name</label> >
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="node-name">
<span class="label-text">Name</span>
</label>
<input <input
id="node-name" id="node-name"
class="form-control" type="text"
class="input input-bordered w-full"
name="node-name" name="node-name"
placeholder="Name" placeholder="Name"
/> />
<label for="node-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="node-description">
<span class="label-text">Description</span>
</label>
<input <input
id="node-description" id="node-description"
class="form-control" type="text"
class="input input-bordered w-full"
name="node-description" name="node-description"
placeholder="Description" placeholder="Description"
/> />
<label for="node-smiles">SMILES</label> </div>
<div class="form-control mb-3">
<label class="label" for="node-smiles">
<span class="label-text">SMILES</span>
</label>
<input <input
type="text" type="text"
class="form-control" class="input input-bordered w-full"
name="node-smiles" name="node-smiles"
placeholder="SMILES" placeholder="SMILES"
id="node-smiles" id="node-smiles"
/> />
<p></p> </div>
<div>
<iframe <div class="mb-3">
id="add_node_ketcher" <iframe
src="{% static '/js/ketcher2/ketcher.html' %}" id="add_node_ketcher"
width="100%" src="{% static '/js/ketcher2/ketcher.html' %}"
height="510" width="100%"
></iframe> height="510"
</div> ></iframe>
<p></p> </div>
</form> </form>
</div> </div>
<div class="modal-footer">
<button <!-- Footer -->
type="button" <div class="modal-action">
class="btn btn-secondary pull-left" <button
data-dismiss="modal" type="button"
> class="btn"
Close onclick="this.closest('dialog').close()"
</button> :disabled="isSubmitting"
<button >
type="button" Close
class="btn btn-primary" </button>
id="add_pathway_node_modal_form_submit" <button
> type="button"
Submit class="btn btn-primary"
</button> @click="submit('add_pathway_node_modal_form')"
</div> :disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Submitting...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
function newStructureModalketcherToNewStructureModalTextInput() {
$("#node-smiles").val(this.ketcher.getSmiles());
}
$(function () { <!-- Backdrop -->
$("#add_node_ketcher").on("load", function () { <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>
<script>
document
.getElementById("add_node_ketcher")
.addEventListener("load", function () {
const iframe = this;
const checkKetcherReady = () => { const checkKetcherReady = () => {
win = this.contentWindow; const win = iframe.contentWindow;
if (win.ketcher && "editor" in win.ketcher) { if (win.ketcher && "editor" in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({ win.ketcher.editor.event.change.handlers.push({
once: false, once: false,
priority: 0, priority: 0,
f: newStructureModalketcherToNewStructureModalTextInput, f: function () {
document.getElementById("node-smiles").value =
this.ketcher.getSmiles();
},
ketcher: win.ketcher, ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
} }
}; };
checkKetcherReady(); checkKetcherReady();
}); });
$(function () {
$("#add_pathway_node_modal_form_submit").on("click", function (e) {
e.preventDefault();
$(this).prop("disabled", true);
// submit form
$("#add_pathway_node_modal_form").submit();
});
});
});
</script> </script>

View File

@ -1,119 +1,137 @@
{% load static %} {% load static %}
<div <dialog
class="modal fade bs-modal-lg"
id="add_structure_modal" id="add_structure_modal"
tabindex="-1" class="modal"
aria-labelledby="add_structure_modal" x-data="modalForm()"
aria-modal="true" @close="reset()"
role="dialog"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-4xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Create a new Structure</h3>
<button
type="button" <!-- Close button (X) -->
class="close" <form method="dialog">
data-dismiss="modal" <button
aria-label="Close" class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
> :disabled="isSubmitting"
<span aria-hidden="true">×</span> >
</button>
<h4 class="modal-title">Create a new Structure</h4> </button>
</div> </form>
<div class="modal-body">
<form <!-- Body -->
id="add_structure_modal_form" <div class="py-4">
accept-charset="UTF-8" <form
action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}" id="add_structure_modal_form"
data-remote="true" accept-charset="UTF-8"
method="post" action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}"
> data-remote="true"
{% csrf_token %} method="post"
<label for="structure-name">Name</label> >
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="structure-name">
<span class="label-text">Name</span>
</label>
<input <input
id="structure-name" id="structure-name"
class="form-control" type="text"
class="input input-bordered w-full"
name="structure-name" name="structure-name"
placeholder="Name" placeholder="Name"
/> />
<label for="structure-description">Description</label> </div>
<div class="form-control mb-3">
<label class="label" for="structure-description">
<span class="label-text">Description</span>
</label>
<input <input
id="structure-description" id="structure-description"
class="form-control" type="text"
class="input input-bordered w-full"
name="structure-description" name="structure-description"
placeholder="Description" placeholder="Description"
/> />
<label for="structure-smiles">SMILES</label> </div>
<div class="form-control mb-3">
<label class="label" for="structure-smiles">
<span class="label-text">SMILES</span>
</label>
<input <input
type="text" type="text"
class="form-control" class="input input-bordered w-full"
name="structure-smiles" name="structure-smiles"
placeholder="SMILES" placeholder="SMILES"
id="structure-smiles" id="structure-smiles"
/> />
<p></p> </div>
<div>
<iframe <div class="mb-3">
id="add_structure_ketcher" <iframe
src="{% static '/js/ketcher2/ketcher.html' %}" id="add_structure_ketcher"
width="100%" src="{% static '/js/ketcher2/ketcher.html' %}"
height="510" width="100%"
></iframe> height="510"
</div> ></iframe>
<p></p> </div>
</form> </form>
</div> </div>
<div class="modal-footer">
<button <!-- Footer -->
type="button" <div class="modal-action">
class="btn btn-secondary pull-left" <button
data-dismiss="modal" type="button"
> class="btn"
Close onclick="this.closest('dialog').close()"
</button> :disabled="isSubmitting"
<button >
type="button" Close
class="btn btn-primary" </button>
id="add_structure_modal_form_submit" <button
> type="button"
Submit class="btn btn-primary"
</button> @click="submit('add_structure_modal_form')"
</div> :disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Submitting...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
function newStructureModalketcherToNewStructureModalTextInput() {
$("#structure-smiles").val(this.ketcher.getSmiles());
}
$(function () { <!-- Backdrop -->
$("#add_structure_ketcher").on("load", function () { <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>
<script>
document
.getElementById("add_structure_ketcher")
.addEventListener("load", function () {
const iframe = this;
const checkKetcherReady = () => { const checkKetcherReady = () => {
win = this.contentWindow; const win = iframe.contentWindow;
if (win.ketcher && "editor" in win.ketcher) { if (win.ketcher && "editor" in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({ win.ketcher.editor.event.change.handlers.push({
once: false, once: false,
priority: 0, priority: 0,
f: newStructureModalketcherToNewStructureModalTextInput, f: function () {
document.getElementById("structure-smiles").value =
this.ketcher.getSmiles();
},
ketcher: win.ketcher, ketcher: win.ketcher,
}); });
} else { } else {
setTimeout(checkKetcherReady, 100); setTimeout(checkKetcherReady, 100);
} }
}; };
checkKetcherReady(); checkKetcherReady();
}); });
$(function () {
$("#add_structure_modal_form_submit").on("click", function (e) {
e.preventDefault();
$(this).prop("disabled", true);
// submit form
$("#add_structure_modal_form").submit();
});
});
});
</script> </script>

View File

@ -1,36 +1,48 @@
{% load static %} {% load static %}
<!-- Delete Edge --> <!-- Delete Edge -->
<div id="delete_pathway_edge_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="delete_pathway_edge_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })"
<h3 class="modal-title">Delete Edge</h3> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="text-lg font-bold">Delete Edge</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
<div class="modal-body"> :disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<p class="mb-4">
Deletes the Edge. Nodes referenced by this edge will remain. Deletes the Edge. Nodes referenced by this edge will remain.
<p></p> </p>
<form <form
id="delete-pathway-edge-modal-form" id="delete-pathway-edge-modal-form"
accept-charset="UTF-8" accept-charset="UTF-8"
action="" action=""
data-remote="true" method="post"
method="post" >
> {% csrf_token %}
{% csrf_token %} <div class="form-control">
<label class="label" for="delete_pathway_edge_edges">
<span class="label-text">Select Reaction to delete</span>
</label>
<select <select
id="delete_pathway_edge_edges" id="delete_pathway_edge_edges"
name="edge-url" name="edge-url"
data-actions-box="true" class="select select-bordered w-full"
class="form-control" x-model="selectedEdge"
data-width="100%" @change="imageUrl = selectedEdge ? selectedEdge + '?image=svg' : ''"
required
> >
<option value="" disabled selected> <option value="" disabled selected>
Select Reaction to delete Select Reaction to delete
@ -39,51 +51,44 @@
<option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option> <option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" id="hidden" name="hidden" value="delete" /> </div>
</form> <input type="hidden" id="hidden" name="hidden" value="delete" />
<p></p> </form>
<div id="delete_pathway_edge_image"></div>
</div> <!-- Image Preview -->
<div class="modal-footer"> <div class="mt-4" x-show="imageUrl" x-cloak>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> <img :src="imageUrl" class="w-full" alt="Edge preview" />
Close
</button>
<button
type="button"
class="btn btn-primary"
id="delete-pathway-edge-modal-submit"
>
Delete
</button>
</div> </div>
</div> </div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-error"
@click="setFormAction('delete-pathway-edge-modal-form', selectedEdge); submit('delete-pathway-edge-modal-form')"
:disabled="isSubmitting || !selectedEdge"
>
<span x-show="!isSubmitting">Delete</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Deleting...</span>
</button>
</div>
</div> </div>
</div>
<script>
$(function () {
$("#delete_pathway_edge_edges").selectpicker();
$("#delete_pathway_edge_edges").on("change", function (e) { <!-- Backdrop -->
edge_url = $("#delete_pathway_edge_edges option:selected").val(); <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
if (edge_url !== "") { </form>
$("#delete_pathway_edge_image").empty(); </dialog>
$("#delete_pathway_edge_image").append(
"<img width='100%' src='" + edge_url + "?image=svg'>",
);
}
});
$("#delete-pathway-edge-modal-submit").click(function (e) {
e.preventDefault();
edge_url = $("#delete_pathway_edge_edges option:selected").val();
if (edge_url === "") {
return;
}
$("#delete-pathway-edge-modal-form").attr("action", edge_url);
$("#delete-pathway-edge-modal-form").submit();
});
});
</script>

View File

@ -1,38 +1,49 @@
{% load static %} {% load static %}
<!-- Delete Node --> <!-- Delete Node -->
<div id="delete_pathway_node_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="delete_pathway_node_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })"
<h3 class="modal-title">Delete Node</h3> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="text-lg font-bold">Delete Node</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
<div class="modal-body"> :disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<p class="mb-4">
Deletes the Node. Edges having this Node as Substrate or Product will be Deletes the Node. Edges having this Node as Substrate or Product will be
removed as well. removed as well.
<p></p> </p>
<form <form
id="delete-pathway-node-modal-form" id="delete-pathway-node-modal-form"
accept-charset="UTF-8" accept-charset="UTF-8"
action="" action=""
data-remote="true" method="post"
method="post" >
> {% csrf_token %}
{% csrf_token %} <div class="form-control">
<label class="label" for="delete_pathway_node_nodes">
<span class="label-text">Select Compound to delete</span>
</label>
<select <select
id="delete_pathway_node_nodes" id="delete_pathway_node_nodes"
name="node-url" name="node-url"
data-actions-box="true" class="select select-bordered w-full"
class="form-control" x-model="selectedNode"
data-width="100%" @change="imageUrl = selectedNode ? selectedNode + '?image=svg' : ''"
required
> >
<option value="" disabled selected> <option value="" disabled selected>
Select Compound to delete Select Compound to delete
@ -43,51 +54,44 @@
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" id="hidden" name="hidden" value="delete" /> </div>
</form> <input type="hidden" id="hidden" name="hidden" value="delete" />
<p></p> </form>
<div id="delete_pathway_node_image"></div>
</div> <!-- Image Preview -->
<div class="modal-footer"> <div class="mt-4" x-show="imageUrl" x-cloak>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> <img :src="imageUrl" class="w-full" alt="Node preview" />
Close
</button>
<button
type="button"
class="btn btn-primary"
id="delete-pathway-node-modal-submit"
>
Delete
</button>
</div> </div>
</div> </div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-error"
@click="setFormAction('delete-pathway-node-modal-form', selectedNode); submit('delete-pathway-node-modal-form')"
:disabled="isSubmitting || !selectedNode"
>
<span x-show="!isSubmitting">Delete</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Deleting...</span>
</button>
</div>
</div> </div>
</div>
<script>
$(function () {
$("#delete_pathway_node_nodes").selectpicker();
$("#delete_pathway_node_nodes").on("change", function (e) { <!-- Backdrop -->
node_url = $("#delete_pathway_node_nodes option:selected").val(); <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
if (node_url !== "") { </form>
$("#delete_pathway_node_image").empty(); </dialog>
$("#delete_pathway_node_image").append(
"<img width='100%' src='" + node_url + "?image=svg'>",
);
}
});
$("#delete-pathway-node-modal-submit").click(function (e) {
e.preventDefault();
node_url = $("#delete_pathway_node_nodes option:selected").val();
if (node_url === "") {
return;
}
$("#delete-pathway-node-modal-form").attr("action", node_url);
$("#delete-pathway-node-modal-form").submit();
});
});
</script>

View File

@ -1,53 +1,69 @@
{% load static %} {% load static %}
<!-- Download Pathway -->
<div id="download_pathway_csv_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="download_pathway_csv_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h3 class="modal-title">Download Pathway as CSV</h3> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Download Pathway as CSV</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<p>
By clicking on Download the Pathway will be converted into a CSV and By clicking on Download the Pathway will be converted into a CSV and
directly downloaded. directly downloaded.
<form </p>
id="download-pathway-csv-modal-form"
accept-charset="UTF-8" <form
action="{{ pathway.url }}" id="download-pathway-csv-modal-form"
data-remote="true" accept-charset="UTF-8"
method="GET" action="{{ pathway.url }}"
> method="GET"
<input type="hidden" name="download" value="true" /> >
</form> <input type="hidden" name="download" value="true" />
</div> </form>
<div class="modal-footer"> </div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Close <!-- Footer -->
</button> <div class="modal-action">
<button <button
type="button" type="button"
class="btn btn-primary" class="btn"
id="download-pathway-csv-modal-submit" onclick="this.closest('dialog').close()"
> :disabled="isSubmitting"
Download >
</button> Close
</div> </button>
<button
type="button"
class="btn btn-primary"
@click="submit('download-pathway-csv-modal-form'); $el.closest('dialog').close();"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Download</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#download-pathway-csv-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#download-pathway-csv-modal-form").submit(); </dialog>
$("#download_pathway_csv_modal").modal("hide");
});
});
</script>

View File

@ -1,43 +1,57 @@
{% load static %} {% load static %}
<!-- Download Pathway -->
<div id="download_pathway_image_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="download_pathway_image_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h3 class="modal-title">Download Pathway as Image</h3> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Download Pathway as Image</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
By clicking on Download the Pathway will be saved as SVG. >
</div>
<div class="modal-footer"> </button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> </form>
Close
</button> <!-- Body -->
<button <div class="py-4">
type="button" <p>By clicking on Download the Pathway will be saved as SVG.</p>
class="btn btn-primary" </div>
id="download-pathway-image-modal-submit"
> <!-- Footer -->
Download <div class="modal-action">
</button> <button
</div> type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="isSubmitting = true; downloadSVG(document.getElementById('pwsvg'), '{{ pathway.name.split|join:'_' }}.svg'); $el.closest('dialog').close();"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Download</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#download-pathway-image-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
downloadSVG($("#pwsvg")[0], '{{ pathway.name.split|join:"_" }}.svg'); </dialog>
$("#download_pathway_image_modal").modal("hide");
});
});
</script>

View File

@ -1,70 +1,91 @@
{% load static %} {% load static %}
<!-- Edit Compound -->
<div id="edit_compound_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_compound_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Edit Compound</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Edit Compound</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>Edit Compound.</p> >
<form
id="edit-compound-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="edit-compound-modal-form"
<p> accept-charset="UTF-8"
<label for="compound-name">Name</label> action=""
<input method="post"
id="compound-name" >
class="form-control" {% csrf_token %}
name="compound-name"
value="{{ compound.name|safe }}" <div class="form-control mb-3">
/> <label class="label" for="compound-name">
</p> <span class="label-text">Name</span>
<p> </label>
<label for="compound-description">Description</label> <input
<input id="compound-name"
id="compound-description" class="input input-bordered w-full"
type="text" name="compound-name"
class="form-control" value="{{ compound.name|safe }}"
value="{{ compound.description|safe }}" required
name="compound-description" />
/> </div>
</p>
</form> <div class="form-control mb-3">
</div> <label class="label" for="compound-description">
<div class="modal-footer"> <span class="label-text">Description</span>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> </label>
Close <input
</button> id="compound-description"
<button type="text"
type="button" class="input input-bordered w-full"
class="btn btn-primary" value="{{ compound.description|safe }}"
id="edit-compound-modal-submit" name="compound-description"
> />
Update </div>
</button> </form>
</div> </div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-compound-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-compound-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-compound-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,70 +1,91 @@
{% load static %} {% load static %}
<!-- Edit Compound -->
<div id="edit_compound_structure_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_compound_structure_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Create a Compound</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Edit Compound Structure</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>Edit a Compound Structure.</p> >
<form
id="edit-compound-structure-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="edit-compound-structure-modal-form"
<p> accept-charset="UTF-8"
<label for="compound-structure-name">Name</label> action=""
<input method="post"
id="compound-structure-name" >
class="form-control" {% csrf_token %}
name="compound-structure-name"
value="{{ compound_structure.name|safe }}" <div class="form-control mb-3">
/> <label class="label" for="compound-structure-name">
</p> <span class="label-text">Name</span>
<p> </label>
<label for="compound-structure-description">Description</label> <input
<input id="compound-structure-name"
id="compound-structure-description" class="input input-bordered w-full"
type="text" name="compound-structure-name"
class="form-control" value="{{ compound_structure.name|safe }}"
value="{{ compound_structure.description|safe }}" required
name="compound-structure-description" />
/> </div>
</p>
</form> <div class="form-control mb-3">
</div> <label class="label" for="compound-structure-description">
<div class="modal-footer"> <span class="label-text">Description</span>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> </label>
Close <input
</button> id="compound-structure-description"
<button type="text"
type="button" class="input input-bordered w-full"
class="btn btn-primary" value="{{ compound_structure.description|safe }}"
id="edit-compound-structure-modal-submit" name="compound-structure-description"
> />
Create </div>
</button> </form>
</div> </div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-compound-structure-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-compound-structure-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-compound-structure-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,151 +1,150 @@
{% load static %} {% load static %}
<!-- Edit Package Permission --> <!-- Edit Group Member -->
<div id="edit_group_member_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_group_member_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="{
<h5 class="modal-title">Add or Remove Group Member</h5> isSubmitting: false,
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
To add member (either User or entire Groups) to this group select the
entity you want to add below and click the check mark.
<br />
To remove member simply click the <code>X</code> next to the member.
</p>
<div class="row"> reset() {
<div class="col-xs-8"> this.isSubmitting = false;
<legend>User or Group</legend> },
</div>
<div class="col-xs-4">
<legend>Add/Remove</legend>
</div>
</div>
<div class="row"> submitForm(form) {
<form if (form && form.checkValidity()) {
id="modal-form-group-member" form.submit();
class="form-inline" } else if (form) {
role="form" form.reportValidity();
accept-charset="UTF-8" }
action="" }
data-remote="true" }"
method="post" @close="reset()"
> >
{% csrf_token %} <div class="modal-box">
<div class="col-xs-8"> <!-- Header -->
<select <h3 class="text-lg font-bold">Add or Remove Group Member</h3>
id="select_member"
name="member" <!-- Close button (X) -->
data-actions-box="true" <form method="dialog">
class="selPackages" <button
data-width="100%" class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
> :disabled="isSubmitting"
<option disabled selected>User</option> >
</button>
</form>
<!-- Body -->
<div class="py-4">
<p class="mb-4">
To add member (either User or entire Groups) to this group select the
entity you want to add below and click the check mark.
<br />
To remove member simply click the X button next to the member.
</p>
<!-- Add Member Form -->
<form
id="modal-form-group-member"
accept-charset="UTF-8"
action=""
method="post"
class="mb-4"
>
{% csrf_token %}
<div class="flex gap-2 items-end">
<div class="form-control flex-1">
<label class="label">
<span class="label-text">User or Group</span>
</label>
<select
id="select_member"
name="member"
class="select select-bordered w-full"
required
>
<optgroup label="Users">
{% 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> </optgroup>
<optgroup label="Groups">
{% for g in groups %} {% for g in groups %}
<option value="{{ g.url }}">{{ g.name|safe }}</option> <option value="{{ g.url }}">{{ g.name|safe }}</option>
{% endfor %} {% endfor %}
</select> </optgroup>
<input type="hidden" name="action" value="add" /> </select>
</div> <input type="hidden" name="action" value="add" />
<div class="col-xs-2"></div> </div>
<div class="col-xs-2"> <button type="submit" class="btn btn-primary">Add</button>
<button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-ok"></span>
</button>
</div>
</form>
</div> </div>
<p></p> </form>
{% for u in group.user_member.all %}
<div class="row"> <!-- User Members -->
{% if group.user_member.all %}
<div class="divider">User Members</div>
<div class="space-y-2">
{% for u in group.user_member.all %}
<form <form
id="modal-form-group-member_{{ u.uuid }}" id="modal-form-group-member_{{ u.uuid }}"
class="form-inline"
role="form"
accept-charset="UTF-8" accept-charset="UTF-8"
action="" action=""
data-remote="true"
method="post" method="post"
> >
{% csrf_token %} {% csrf_token %}
<div class="col-xs-8"> <div class="flex items-center gap-2">
{{ u.username }} <span class="flex-1">{{ u.username }}</span>
<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> <button type="submit" class="btn btn-error btn-sm">
<div class="col-xs-2"></div> Remove
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-trash"></span>
</button> </button>
</div> </div>
</form> </form>
</div> {% endfor %}
{% endfor %} </div>
<p></p> {% endif %}
{% for g in group.group_member.all %}
<div class="row"> <!-- Group Members -->
{% if group.group_member.all %}
<div class="divider">Group Members</div>
<div class="space-y-2">
{% for g in group.group_member.all %}
<form <form
id="modal-form-group-member_{{ g.uuid }}" id="modal-form-group-member_{{ g.uuid }}"
class="form-inline"
role="form"
accept-charset="UTF-8" accept-charset="UTF-8"
action="" action=""
data-remote="true"
method="post" method="post"
> >
{% csrf_token %} {% csrf_token %}
<div class="col-xs-8"> <div class="flex items-center gap-2">
{{ g.name|safe }} <span class="flex-1">{{ g.name|safe }}</span>
<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> <button type="submit" class="btn btn-error btn-sm">
<div class="col-xs-2"></div> Remove
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-trash"></span>
</button> </button>
</div> </div>
</form> </form>
</div> {% endfor %}
{% endfor %} </div>
</div> {% endif %}
<div class="modal-footer"> </div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Close <!-- Footer -->
</button> <div class="modal-action">
<button <button
type="button" type="button"
class="btn btn-primary" class="btn"
id="edit-package-modal-submit" onclick="this.closest('dialog').close()"
> >
Update Close
</button> </button>
</div>
</div> </div>
</div> </div>
</div>
<script>
$(function () {
$("#edit-package-modal-submit").click(function (e) {
e.preventDefault();
$("#edit-package-modal-form").submit();
});
$("#select_member").selectpicker(); <!-- Backdrop -->
}); <form method="dialog" class="modal-backdrop">
</script> <button>close</button>
</form>
</dialog>

View File

@ -1,71 +1,94 @@
{% load static %} {% load static %}
<!-- Edit Model -->
<div id="edit_model_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_model_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<button @close="reset()"
type="button" >
class="close" <div class="modal-box">
data-dismiss="modal" <!-- Header -->
aria-label="Close" <h3 class="font-bold text-lg">Update Model</h3>
>
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
</button> <form method="dialog">
<h3 class="modal-title">Update Model</h3> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>Alter Name and Description of the Model.</p> >
<form
id="edit-model-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <p class="mb-4">Alter Name and Description of the Model.</p>
{% csrf_token %}
<p> <form
<label for="model-name">Name</label> id="edit-model-modal-form"
<input accept-charset="UTF-8"
id="model-name" action=""
type="text" method="post"
class="form-control" >
name="model-name" {% csrf_token %}
value="{{ model.name|safe }}"
/> <div class="form-control mb-3">
</p> <label class="label" for="model-name">
<p> <span class="label-text">Name</span>
<label for="model-description">Description</label> </label>
<input <input
id="model-description" id="model-name"
type="text" type="text"
class="form-control" class="input input-bordered w-full"
name="model-description" name="model-name"
value="{{ model.description|safe }}" value="{{ model.name|safe }}"
/> required
</p> />
</form> </div>
</div>
<div class="modal-footer"> <div class="form-control mb-3">
<button type="button" class="btn btn-secondary" data-dismiss="modal"> <label class="label" for="model-description">
Close <span class="label-text">Description</span>
</button> </label>
<button <input
type="button" id="model-description"
class="btn btn-primary" type="text"
id="edit-model-modal-submit" class="input input-bordered w-full"
> name="model-description"
Update value="{{ model.description|safe }}"
</button> />
</div> </div>
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-model-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-model-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-model-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,70 +1,91 @@
{% load static %} {% load static %}
<!-- Edit Node -->
<div id="edit_node_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_node_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Edit Node</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Edit Node</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>Edit Node.</p> >
<form
id="edit-node-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="edit-node-modal-form"
<p> accept-charset="UTF-8"
<label for="node-name">Name</label> action=""
<input method="post"
id="node-name" >
class="form-control" {% csrf_token %}
name="node-name"
value="{{ node.name|safe }}" <div class="form-control mb-3">
/> <label class="label" for="node-name">
</p> <span class="label-text">Name</span>
<p> </label>
<label for="node-description">Description</label> <input
<input id="node-name"
id="node-description" class="input input-bordered w-full"
type="text" name="node-name"
class="form-control" value="{{ node.name|safe }}"
value="{{ node.description|safe }}" required
name="node-description" />
/> </div>
</p>
</form> <div class="form-control mb-3">
</div> <label class="label" for="node-description">
<div class="modal-footer"> <span class="label-text">Description</span>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> </label>
Close <input
</button> id="node-description"
<button type="text"
type="button" class="input input-bordered w-full"
class="btn btn-primary" value="{{ node.description|safe }}"
id="edit-node-modal-submit" name="node-description"
> />
Create </div>
</button> </form>
</div> </div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-node-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-node-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-node-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,70 +1,91 @@
{% load static %} {% load static %}
<!-- Edit Package -->
<div id="edit_package_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_package_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Update Package</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Update Package</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>Edit a Package.</p> >
<form
id="edit-package-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="edit-package-modal-form"
<p> accept-charset="UTF-8"
<label for="package-name">Name</label> action=""
<input method="post"
id="package-name" >
class="form-control" {% csrf_token %}
name="package-name"
value="{{ package.name|safe }}" <div class="form-control mb-3">
/> <label class="label" for="package-name">
</p> <span class="label-text">Name</span>
<p> </label>
<label for="package-description">Description</label> <input
<input id="package-name"
id="package-description" class="input input-bordered w-full"
type="text" name="package-name"
class="form-control" value="{{ package.name|safe }}"
value="{{ package.description|safe }}" required
name="package-description" />
/> </div>
</p>
</form> <div class="form-control mb-3">
</div> <label class="label" for="package-description">
<div class="modal-footer"> <span class="label-text">Description</span>
<button type="button" class="btn btn-secondary" data-dismiss="modal"> </label>
Close <input
</button> id="package-description"
<button type="text"
type="button" class="input input-bordered w-full"
class="btn btn-primary" value="{{ package.description|safe }}"
id="edit-package-modal-submit" name="package-description"
> />
Update </div>
</button> </form>
</div> </div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-package-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-package-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-package-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,264 +1,271 @@
{% load static %} {% load static %}
<!-- Edit Package Permission --> <!-- Edit Package Permissions -->
<div id="edit_package_permissions_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_package_permissions_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="{
<h5 class="modal-title">Grant or Revoke Permissions</h5> updatePermissions(checkbox) {
<button const parts = checkbox.id.split('_');
type="button" const perm = parts[0];
class="close" const id = parts[1];
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
Modify permissions for this package. Note that if you give
<code>write</code> permissions to a user or group,
<code>read</code> permissions will be granted automatically.
<br />
To allow users to perform destructive actions, such as deleting the
package, <code>owner</code>
permissions must be granted.
</p>
<div class="row"> const readBox = document.getElementById('read_' + id);
<div class="col-xs-4"> const writeBox = document.getElementById('write_' + id);
<legend>User or Group</legend> const ownerBox = document.getElementById('owner_' + id);
</div>
<div class="col-xs-2">
<legend>Read</legend>
</div>
<div class="col-xs-2">
<legend>Write</legend>
</div>
<div class="col-xs-2">
<legend>Owner</legend>
</div>
</div>
<div class="row"> if (perm === 'read' && !readBox.checked) {
<form writeBox.checked = false;
id="modal-form-permissions" ownerBox.checked = false;
class="form-inline" }
role="form"
accept-charset="UTF-8" if (perm === 'write') {
action="" if (writeBox.checked) {
data-remote="true" readBox.checked = true;
method="post" } else {
> ownerBox.checked = false;
{% csrf_token %} }
<div class="col-xs-4"> }
<select
id="select_grantee" if (perm === 'owner' && ownerBox.checked) {
name="grantee" readBox.checked = true;
data-actions-box="true" writeBox.checked = true;
class="selPackages" }
data-width="100%" }
> }"
<option disabled selected>User</option> >
<div class="modal-box max-w-2xl">
<!-- Header -->
<h3 class="text-lg font-bold">Grant or Revoke Permissions</h3>
<!-- Close button (X) -->
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2">
</button>
</form>
<!-- Body -->
<div class="py-4">
<p class="mb-4">
Modify permissions for this package. Note that if you give
<code class="badge badge-ghost">write</code> permissions to a user or
group, <code class="badge badge-ghost">read</code> permissions will be
granted automatically.
<br />
To allow users to perform destructive actions, such as deleting the
package, <code class="badge badge-ghost">owner</code> permissions must
be granted.
</p>
<!-- Add New Permission -->
<form
id="modal-form-permissions"
accept-charset="UTF-8"
action=""
method="post"
class="mb-4"
>
{% csrf_token %}
<div class="grid grid-cols-12 gap-2 items-end">
<div class="col-span-5">
<label class="label">
<span class="label-text">User or Group</span>
</label>
<select
id="select_grantee"
name="grantee"
class="select select-bordered w-full select-sm"
required
>
<optgroup label="Users">
{% 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> </optgroup>
<optgroup label="Groups">
{% for g in groups %} {% for g in groups %}
<option value="{{ g.url }}">{{ g.name|safe }}</option> <option value="{{ g.url }}">{{ g.name|safe }}</option>
{% endfor %} {% endfor %}
</select> </optgroup>
</div> </select>
<div class="col-xs-2"> </div>
<input type="checkbox" name="read" id="read_new" /> <div class="col-span-2 text-center">
</div> <label class="label justify-center">
<div class="col-xs-2"> <span class="label-text">Read</span>
<input type="checkbox" name="write" id="write_new" /> </label>
</div> <input
<div class="col-xs-2"> type="checkbox"
<input type="checkbox" name="owner" id="owner_new" /> name="read"
</div> id="read_new"
<div class="col-xs-2"> class="checkbox"
<button @click="updatePermissions($el)"
type="submit" />
style="width:60%;" </div>
class="btn col-xs-2 modify-perm-button" <div class="col-span-2 text-center">
> <label class="label justify-center">
<span class="glyphicon glyphicon-plus"></span> <span class="label-text">Write</span>
</button> </label>
</div> <input
</form> type="checkbox"
name="write"
id="write_new"
class="checkbox"
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-2 text-center">
<label class="label justify-center">
<span class="label-text">Owner</span>
</label>
<input
type="checkbox"
name="owner"
id="owner_new"
class="checkbox"
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-1">
<button type="submit" class="btn btn-primary btn-sm">+</button>
</div>
</div> </div>
<p></p> </form>
{% for up in user_permissions %}
<div class="row"> <!-- User Permissions -->
{% if user_permissions %}
<div class="divider">User Permissions</div>
<div class="space-y-2">
{% for up in user_permissions %}
<form <form
id="modal-form-permissions_{{ up.user.uuid }}" id="modal-form-permissions_{{ up.user.uuid }}"
class="form-inline"
role="form"
accept-charset="UTF-8" accept-charset="UTF-8"
action="" action=""
data-remote="true"
method="post" method="post"
> >
{% csrf_token %} {% csrf_token %}
<div class="col-xs-4"> <div class="grid grid-cols-12 gap-2 items-center">
{{ up.user.username }} <div class="col-span-5 truncate">
<input type="hidden" name="grantee" value="{{ up.user.url }}" /> {{ up.user.username }}
</div> <input
<div class="col-xs-2"> type="hidden"
<input name="grantee"
type="checkbox" value="{{ up.user.url }}"
name="read" />
id="read_{{ up.user.uuid }}" </div>
{% if up.has_read %}checked{% endif %} <div class="col-span-2 text-center">
/> <input
</div> type="checkbox"
<div class="col-xs-2"> name="read"
<input id="read_{{ up.user.uuid }}"
type="checkbox" class="checkbox"
name="write" {% if up.has_read %}checked{% endif %}
id="write_{{ up.user.uuid }}" @click="updatePermissions($el)"
{% if up.has_write %}checked{% endif %} />
/> </div>
</div> <div class="col-span-2 text-center">
<div class="col-xs-2"> <input
<input type="checkbox"
type="checkbox" name="write"
name="owner" id="write_{{ up.user.uuid }}"
id="owner_{{ up.user.uuid }}" class="checkbox"
{% if up.has_all %}checked{% endif %} {% if up.has_write %}checked{% endif %}
/> @click="updatePermissions($el)"
</div> />
<div class="col-xs-2"> </div>
<button <div class="col-span-2 text-center">
type="submit" <input
style="width:60%;" type="checkbox"
class="btn col-xs-2 modify-perm-button" name="owner"
> id="owner_{{ up.user.uuid }}"
<span class="glyphicon glyphicon-ok"></span> class="checkbox"
</button> {% if up.has_all %}checked{% endif %}
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-1">
<button type="submit" class="btn btn-sm btn-ghost"></button>
</div>
</div> </div>
</form> </form>
</div> {% endfor %}
{% endfor %} </div>
<p></p> {% endif %}
{% for gp in group_permissions %}
<div class="row"> <!-- Group Permissions -->
{% if group_permissions %}
<div class="divider">Group Permissions</div>
<div class="space-y-2">
{% for gp in group_permissions %}
<form <form
id="modal-form-permissions_{{ gp.user.uuid }}" id="modal-form-permissions_{{ gp.group.uuid }}"
class="form-inline"
role="form"
accept-charset="UTF-8" accept-charset="UTF-8"
action="" action=""
data-remote="true"
method="post" method="post"
> >
{% csrf_token %} {% csrf_token %}
<div class="col-xs-4"> <div class="grid grid-cols-12 gap-2 items-center">
{{ gp.group.name|safe }} <div class="col-span-5 truncate">
<input {{ gp.group.name|safe }}
type="hidden" <input
name="grantee" type="hidden"
value="{{ gp.group.url }}" name="grantee"
/> value="{{ gp.group.url }}"
</div> />
<div class="col-xs-2"> </div>
<input <div class="col-span-2 text-center">
type="checkbox" <input
name="read" type="checkbox"
id="read_{{ gp.group.uuid }}" name="read"
{% if gp.has_read %}checked{% endif %} id="read_{{ gp.group.uuid }}"
/> class="checkbox"
</div> {% if gp.has_read %}checked{% endif %}
<div class="col-xs-2"> @click="updatePermissions($el)"
<input />
type="checkbox" </div>
name="write" <div class="col-span-2 text-center">
id="write_{{ gp.group.uuid }}" <input
{% if gp.has_write %}checked{% endif %} type="checkbox"
/> name="write"
</div> id="write_{{ gp.group.uuid }}"
<div class="col-xs-2"> class="checkbox"
<input {% if gp.has_write %}checked{% endif %}
type="checkbox" @click="updatePermissions($el)"
name="owner" />
id="owner_{{ gp.group.uuid }}" </div>
{% if gp.has_all %}checked{% endif %} <div class="col-span-2 text-center">
/> <input
</div> type="checkbox"
<div class="col-xs-2"> name="owner"
<button id="owner_{{ gp.group.uuid }}"
type="submit" class="checkbox"
style="width:60%;" {% if gp.has_all %}checked{% endif %}
class="btn col-xs-2 modify-perm-button" @click="updatePermissions($el)"
> />
<span class="glyphicon glyphicon-ok"></span> </div>
</button> <div class="col-span-1">
<button type="submit" class="btn btn-sm btn-ghost"></button>
</div>
</div> </div>
</form> </form>
</div> {% endfor %}
{% endfor %} </div>
</div> {% endif %}
<div class="modal-footer"> </div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Close <!-- Footer -->
</button> <div class="modal-action">
<button <button
type="button" type="button"
class="btn btn-primary" class="btn"
id="edit-package-modal-submit" onclick="this.closest('dialog').close()"
> >
Update Close
</button> </button>
</div>
</div> </div>
</div> </div>
</div>
<script>
function checkboxClick() {
// id looks like read_3cadef24-220e-4587-9fa5-0e9a17aca2da
parts = this.id.split("_");
perm = parts[0];
id = parts[1];
readbox = "#read_" + id; <!-- Backdrop -->
writebox = "#write_" + id; <form method="dialog" class="modal-backdrop">
ownerbox = "#owner_" + id; <button>close</button>
</form>
if (perm == "read" && !$(readbox).prop("checked")) { </dialog>
$(writebox).prop("checked", false);
$(ownerbox).prop("checked", false);
}
if (perm == "write") {
if ($(writebox).prop("checked")) {
$(readbox).prop("checked", true);
}
if (!$(writebox).prop("checked")) {
$(ownerbox).prop("checked", false);
}
}
if (perm == "owner") {
if ($(ownerbox).prop("checked")) {
$(readbox).prop("checked", true);
$(writebox).prop("checked", true);
}
}
}
$(function () {
$("#edit-package-modal-submit").click(function (e) {
e.preventDefault();
$("#edit-package-modal-form").submit();
});
$("#select_grantee").selectpicker();
// Add click functions to permission checkboxes
$('[id^="read_"]').on("click", checkboxClick);
$('[id^="write_"]').on("click", checkboxClick);
$('[id^="owner_"]').on("click", checkboxClick);
});
</script>

View File

@ -1,82 +1,119 @@
{% load static %} {% load static %}
<!-- Edit Package -->
<div id="edit_password_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_password_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Update your Password</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Update your Password</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>To change your password please fill out the following inputs</p> >
<form
id="edit-password-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <p class="mb-4">
{% csrf_token %} To change your password please fill out the following inputs
<p> </p>
<label for="old-password">Old Password</label>
<input <form
id="old-password" id="edit-password-modal-form"
class="form-control" accept-charset="UTF-8"
name="old-password" action=""
type="password" method="post"
autocomplete="current-password" >
/> {% csrf_token %}
</p> <input type="hidden" name="hidden" value="update-password" />
<p>
<label for="new-password">New Password</label> <div class="form-control mb-3">
<input <label class="label" for="old-password">
id="new-password" <span class="label-text">Old Password</span>
class="form-control" </label>
name="new-password" <input
type="password" id="old-password"
, class="input input-bordered w-full"
autocomplete="new-password" name="old-password"
/> type="password"
</p> autocomplete="current-password"
<p> required
<label for="new-password-repeat">Repeat New Password</label> />
<input </div>
id="new-password-repeat"
class="form-control" <div class="form-control mb-3">
name="new-password-repeat" <label class="label" for="new-password">
type="password" <span class="label-text">New Password</span>
autocomplete="new-password" </label>
/> <input
</p> id="new-password"
</form> class="input input-bordered w-full"
</div> name="new-password"
<div class="modal-footer"> type="password"
<button type="button" class="btn btn-secondary" data-dismiss="modal"> autocomplete="new-password"
Close required
</button> />
<button </div>
type="button"
class="btn btn-primary" <div class="form-control mb-3">
id="edit-password-modal-submit" <label class="label" for="new-password-repeat">
> <span class="label-text">Repeat New Password</span>
Update </label>
</button> <input
</div> id="new-password-repeat"
class="input input-bordered w-full"
name="new-password-repeat"
type="password"
autocomplete="new-password"
required
@input="validatePasswordMatch('new-password', 'new-password-repeat')"
/>
<label class="label" x-show="errors['new-password-repeat']">
<span
class="label-text-alt text-error"
x-text="errors['new-password-repeat']"
></span>
</label>
</div>
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="if (validatePasswordMatch('new-password', 'new-password-repeat')) submit('edit-password-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-password-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-password-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,72 +1,92 @@
{% load static %} {% load static %}
<!-- Edit Pathway -->
<div id="edit_pathway_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_pathway_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Edit Pathway</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Edit Pathway</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>Edit Pathway.</p> >
<form
id="edit-pathway-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="edit-pathway-modal-form"
<p> accept-charset="UTF-8"
<label for="pathway-name">Name</label> action=""
<input method="post"
id="pathway-name" >
class="form-control" {% csrf_token %}
name="pathway-name"
value="{{ pathway.name|safe }}" <div class="form-control mb-3">
/> <label class="label" for="pathway-name">
</p> <span class="label-text">Name</span>
<p> </label>
<label for="pathway-description">Description</label> <input
<textarea id="pathway-name"
id="pathway-description" class="input input-bordered w-full"
type="text" name="pathway-name"
class="form-control" value="{{ pathway.name|safe }}"
name="pathway-description" required
rows="10" />
> </div>
<div class="form-control mb-3">
<label class="label" for="pathway-description">
<span class="label-text">Description</span>
</label>
<textarea
id="pathway-description"
class="textarea textarea-bordered w-full"
name="pathway-description"
rows="10"
>
{{ pathway.description|safe }}</textarea {{ pathway.description|safe }}</textarea
> >
</p> </div>
</form> </form>
</div> </div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"> <!-- Footer -->
Close <div class="modal-action">
</button> <button
<button type="button"
type="button" class="btn"
class="btn btn-primary" onclick="this.closest('dialog').close()"
id="edit-pathway-modal-submit" :disabled="isSubmitting"
> >
Update Close
</button> </button>
</div> <button
type="button"
class="btn btn-primary"
@click="submit('edit-pathway-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-pathway-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-pathway-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,163 +1,156 @@
{% load static %} {% load static %}
<!-- Edit Package --> <!-- Edit Prediction Setting -->
<div id="update_prediction_settings_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="update_prediction_settings_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Update Prediction Setting</h5> @close="reset()"
<button >
type="button" <div class="modal-box max-w-3xl">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="text-lg font-bold">Update Prediction Setting</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
<div class="modal-body"> :disabled="isSubmitting"
<p> >
To update your prediction setting modify parameters in the form below
und click "Update" </button>
</p> </form>
<form
id="edit-prediction-setting-modal-form" <!-- Body -->
accept-charset="UTF-8" <div class="py-4">
action="" <p class="mb-4">
data-remote="true" To update your prediction setting modify parameters in the form below
method="post" and click "Update"
> </p>
{% csrf_token %} <form
<div id="prediction-setting" class="panel-collapse in collapse"> id="edit-prediction-setting-modal-form"
<div class="panel-body list-group-item"> accept-charset="UTF-8"
<table class="table-bordered table-hover table"> action=""
<tr style="background-color: rgba(0, 0, 0, 0.08);"> method="post"
<th scope="col" width="20%">Parameter</th> >
<th scope="col" width="80%">Value</th> {% csrf_token %}
<div class="overflow-x-auto">
<table class="table table-zebra w-full">
<thead>
<tr>
<th class="w-1/5">Parameter</th>
<th class="w-4/5">Value</th>
</tr>
</thead>
<tbody>
{% if 'model' in user.prediction_settings %}
<tr>
<td>Model</td>
<td>
<div class="form-control">
<select
id="model"
name="model"
class="select select-bordered w-full"
>
{% for m in models %}
<option
value="{{ m.id }}"
{% if user.prediction_settings.model.url == m.url %}selected{% endif %}
>
{{ m.name|safe }}
</option>
{% endfor %}
</select>
</div>
{% for k, v in user.prediction_settings.model_parameters.items %}
{% if k == 'threshold' %}
<div class="form-control mt-2">
<label class="label">
<span class="label-text">Threshold</span>
</label>
<input
type="number"
class="input input-bordered w-full"
name="{{ k }}"
value="{{ v }}"
min="0"
max="1"
step="0.05"
/>
</div>
{% endif %}
{% endfor %}
</td>
</tr> </tr>
<tbody> {% endif %}
{% if 'model' in user.prediction_settings %} {% for k, v in user.prediction_settings.truncator.items %}
<tr> <tr>
<td width="20%">Model</td> <td>
<td width="80%"> {% if k == 'max_nodes' %}
<table Max Nodes
width="100%" {% elif k == 'max_depth' %}
class="table-bordered table-hover table" Max Depth
> {% endif %}
<tbody> </td>
<tr> <td>
<td colspan="2"> {% if k == 'max_nodes' %}
<select <input
id="model" type="number"
name="model" class="input input-bordered w-full"
class="form-control" name="{{ k }}"
data-width="100%" value="{{ v }}"
> min="1"
{% for m in models %} max="50"
<option step="1"
value="{{ m.id }}" />
{% if user.prediction_settings.model.url == m.url %}selected{% endif %} {% elif k == 'max_depth' %}
> <input
{{ m.name|safe }} type="number"
</option> class="input input-bordered w-full"
{% endfor %} name="{{ k }}"
</select> value="{{ v }}"
</td> min="1"
</tr> max="8"
{% for k, v in user.prediction_settings.model_parameters.items %} step="1"
<tr> />
<th width="20%">Model Parameter</th> {% endif %}
<th width="80%">Parameter Value</th> </td>
</tr> </tr>
<tr> {% endfor %}
<td width="20%"> </tbody>
{% if k == 'threshold' %} </table>
Threshold </div>
{% endif %} </form>
</td> </div>
<td width="80%">
{% if k == 'threshold' %} <!-- Footer -->
<input <div class="modal-action">
type="number" <button
class="form-control" type="button"
name="{{ k }}" class="btn"
value="{{ v }}" onclick="this.closest('dialog').close()"
min="0" :disabled="isSubmitting"
max="1" >
step="0.05" Close
/> </button>
{% endif %} <button
</td> type="button"
</tr> class="btn btn-primary"
{% endfor %} @click="submit('edit-prediction-setting-modal-form')"
</tbody> :disabled="isSubmitting"
</table> >
</td> <span x-show="!isSubmitting">Update</span>
</tr> <span
{% endif %} x-show="isSubmitting"
{% for k, v in user.prediction_settings.truncator.items %} class="loading loading-spinner loading-sm"
<tr> ></span>
<td> <span x-show="isSubmitting">Updating...</span>
<p> </button>
{% if k == 'max_nodes' %}
Max Nodes
{% elif k == 'max_depth' %}
Max Depth
{% endif %}
</p>
</td>
<td>
<p>
{% if k == 'max_nodes' %}
<input
type="number"
class="form-control"
name="{{ k }}"
value="{{ v }}"
min="1"
max="50"
step="1"
/>
{% elif k == 'max_depth' %}
<input
type="number"
class="form-control"
name="{{ k }}"
value="{{ v }}"
min="1"
max="8"
step="1"
/>
{% endif %}
</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Close
</button>
<button
type="button"
class="btn btn-primary"
id="edit-prediction-setting-modal-submit"
>
Update
</button>
</div>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-prediction-setting-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-prediction-setting-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,69 +1,91 @@
{% load static %} {% load static %}
<!-- Edit Reaction -->
<div id="edit_reaction_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_reaction_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<button @close="reset()"
type="button" >
class="close" <div class="modal-box">
data-dismiss="modal" <!-- Header -->
aria-label="Close" <h3 class="font-bold text-lg">Update Reaction</h3>
>
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
</button> <form method="dialog">
<h3 class="modal-title">Update Reaction</h3> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<form >
id="edit-reaction-modal-form"
accept-charset="UTF-8" </button>
action="" </form>
data-remote="true"
method="post" <!-- Body -->
> <div class="py-4">
{% csrf_token %} <form
<p> id="edit-reaction-modal-form"
<label for="reaction-name">Name</label> accept-charset="UTF-8"
<input action=""
id="reaction-name" method="post"
class="form-control" >
name="reaction-name" {% csrf_token %}
value="{{ reaction.name|safe }}"
/> <div class="form-control mb-3">
</p> <label class="label" for="reaction-name">
<p> <span class="label-text">Name</span>
<label for="reaction-description">Description</label> </label>
<input <input
id="reaction-description" id="reaction-name"
type="text" class="input input-bordered w-full"
class="form-control" name="reaction-name"
value="{{ reaction.description|safe }}" value="{{ reaction.name|safe }}"
name="reaction-description" required
/> />
</p> </div>
</form>
</div> <div class="form-control mb-3">
<div class="modal-footer"> <label class="label" for="reaction-description">
<button type="button" class="btn btn-secondary" data-dismiss="modal"> <span class="label-text">Description</span>
Close </label>
</button> <input
<button id="reaction-description"
type="button" type="text"
class="btn btn-primary" class="input input-bordered w-full"
id="edit-reaction-modal-submit" value="{{ reaction.description|safe }}"
> name="reaction-description"
Update />
</button> </div>
</div> </form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-reaction-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-reaction-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-reaction-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,69 +1,91 @@
{% load static %} {% load static %}
<!-- Edit Rule -->
<div id="edit_rule_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_rule_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<button @close="reset()"
type="button" >
class="close" <div class="modal-box">
data-dismiss="modal" <!-- Header -->
aria-label="Close" <h3 class="font-bold text-lg">Update Rule</h3>
>
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
</button> <form method="dialog">
<h3 class="modal-title">Update Rule</h3> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<form >
id="edit-rule-modal-form"
accept-charset="UTF-8" </button>
action="" </form>
data-remote="true"
method="post" <!-- Body -->
> <div class="py-4">
{% csrf_token %} <form
<p> id="edit-rule-modal-form"
<label for="rule-name">Name</label> accept-charset="UTF-8"
<input action=""
id="rule-name" method="post"
class="form-control" >
name="rule-name" {% csrf_token %}
value="{{ rule.name|safe }}"
/> <div class="form-control mb-3">
</p> <label class="label" for="rule-name">
<p> <span class="label-text">Name</span>
<label for="rule-description">Description</label> </label>
<input <input
id="rule-description" id="rule-name"
type="text" class="input input-bordered w-full"
class="form-control" name="rule-name"
value="{{ rule.description|safe }}" value="{{ rule.name|safe }}"
name="rule-description" required
/> />
</p> </div>
</form>
</div> <div class="form-control mb-3">
<div class="modal-footer"> <label class="label" for="rule-description">
<button type="button" class="btn btn-secondary" data-dismiss="modal"> <span class="label-text">Description</span>
Close </label>
</button> <input
<button id="rule-description"
type="button" type="text"
class="btn btn-primary" class="input input-bordered w-full"
id="edit-rule-modal-submit" value="{{ rule.description|safe }}"
> name="rule-description"
Update />
</button> </div>
</div> </form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-rule-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-rule-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-rule-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,110 +1,128 @@
{% load static %} {% load static %}
<!-- Edit User -->
<div id="edit_user_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="edit_user_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Update User Defaults</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Update User Defaults</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
<p>Edit User Defaults.</p> >
<form
id="edit-user-modal-form" </button>
accept-charset="UTF-8" </form>
action=""
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="edit-user-modal-form"
<p> accept-charset="UTF-8"
<label for="default-package">Default Package</label> action=""
<select method="post"
id="default-package" >
name="default-package" {% csrf_token %}
class="form-control"
data-width="100%" <div class="form-control mb-3">
> <label class="label" for="default-package">
<option disabled>Select a Package</option> <span class="label-text">Default Package</span>
{% for p in meta.writeable_packages %} </label>
<option <select
value="{{ p.url }}" id="default-package"
{% if p.id == meta.user.default_package.id %}selected{% endif %} name="default-package"
> class="select select-bordered w-full"
{{ p.name|safe }} >
</option> <option disabled>Select a Package</option>
{% endfor %} {% for p in meta.writeable_packages %}
</select> <option
</p> value="{{ p.url }}"
<p> {% if p.id == meta.user.default_package.id %}selected{% endif %}
<label for="default-group">Default Group</label> >
<select {{ p.name|safe }}
id="default-group" </option>
name="default-group" {% endfor %}
class="form-control" </select>
data-width="100%" </div>
>
<option disabled>Select a Group</option> <div class="form-control mb-3">
{% for g in meta.available_groups %} <label class="label" for="default-group">
<option <span class="label-text">Default Group</span>
value="{{ g.url }}" </label>
{% if g.id == meta.user.default_group.id %}selected{% endif %} <select
> id="default-group"
{{ g.name|safe }} name="default-group"
</option> class="select select-bordered w-full"
{% endfor %} >
</select> <option disabled>Select a Group</option>
</p> {% for g in meta.available_groups %}
<p> <option
<label for="default-prediction-setting" value="{{ g.url }}"
>Default Prediction Setting</label {% if g.id == meta.user.default_group.id %}selected{% endif %}
> >
<select {{ g.name|safe }}
id="default-prediction-setting" </option>
name="default-prediction-setting" {% endfor %}
class="form-control" </select>
data-width="100%" </div>
>
<option disabled>Select a Setting</option> <div class="form-control mb-3">
{% for s in meta.available_settings %} <label class="label" for="default-prediction-setting">
<option <span class="label-text">Default Prediction Setting</span>
value="{{ s.url }}" </label>
{% if s.id == meta.user.default_setting.id %}selected{% endif %} <select
> id="default-prediction-setting"
{{ s.name|safe }} name="default-prediction-setting"
</option> class="select select-bordered w-full"
{% endfor %} >
</select> <option disabled>Select a Setting</option>
</p> {% for s in meta.available_settings %}
</form> <option
</div> value="{{ s.url }}"
<div class="modal-footer"> {% if s.id == meta.user.default_setting.id %}selected{% endif %}
<button type="button" class="btn btn-secondary" data-dismiss="modal"> >
Close {{ s.name|safe }}
</button> </option>
<button {% endfor %}
type="button" </select>
class="btn btn-primary" </div>
id="edit-user-modal-submit" </form>
> </div>
Update
</button> <!-- Footer -->
</div> <div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-user-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-user-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-user-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,93 +1,123 @@
<div <dialog
class="modal fade"
tabindex="-1"
id="evaluate_model_modal" id="evaluate_model_modal"
role="dialog" class="modal"
aria-labelledby="evaluate_model_modal" x-data="modalForm()"
aria-hidden="true" @close="reset()"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-3xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Evaluate Model</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">Evaluate Model</h4> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<form
id="evaluate_model_form" </button>
accept-charset="UTF-8" </form>
action="{{ current_object.url }}"
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
{% csrf_token %} id="evaluate_model_form"
<div class="jumbotron"> accept-charset="UTF-8"
action="{{ current_object.url }}"
method="post"
>
{% csrf_token %}
<div class="alert alert-info mb-4">
<span>
For evaluation, you need to select the packages you want to use. For evaluation, you need to select the packages you want to use.
While the model is evaluating, you can use the model for While the model is evaluating, you can use the model for
predictions. predictions.
</div> </span>
<!-- Evaluation Packages --> </div>
<label for="model-evaluation-packages">Evaluation Packages</label>
<!-- Evaluation Packages -->
<div class="form-control">
<label class="label" for="model-evaluation-packages">
<span class="label-text">Evaluation Packages</span>
</label>
<select <select
id="model-evaluation-packages" id="model-evaluation-packages"
name="model-evaluation-packages" name="model-evaluation-packages"
data-actions-box="true" class="select select-bordered w-full h-48"
class="form-control"
multiple multiple
data-width="100%" required
> >
<option disabled>Reviewed Packages</option> <optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if obj.reviewed %} {% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
<option disabled>Unreviewed Packages</option> <optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if not obj.reviewed %} {% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option> <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
</select> </select>
<label class="label">
<span class="label-text-alt"
>Hold Ctrl/Cmd to select multiple packages</span
>
</label>
</div>
<!-- Eval Type --> <!-- Eval Type -->
<label for="model-evaluation-type">Evaluation Type</label> <div class="form-control mt-4">
<label class="label" for="model-evaluation-type">
<span class="label-text">Evaluation Type</span>
</label>
<select <select
id="model-evaluation-type" id="model-evaluation-type"
name="model-evaluation-type" name="model-evaluation-type"
class="form-control" class="select select-bordered w-full"
required
> >
<option disabled selected>Select evaluation type</option> <option value="" disabled selected>Select evaluation type</option>
<option value="sg">Single Generation</option> <option value="sg">Single Generation</option>
<option value="mg">Multiple Generations</option> <option value="mg">Multiple Generations</option>
</select> </select>
</div>
<input type="hidden" name="hidden" value="evaluate" /> <input type="hidden" name="hidden" value="evaluate" />
</form> </form>
</div> </div>
<div class="modal-footer">
<a id="evaluate_model_form_submit" class="btn btn-primary" href="#" <!-- Footer -->
>Evaluate</a <div class="modal-action">
> <button
<button type="button" class="btn btn-default" data-dismiss="modal"> type="button"
Cancel class="btn"
</button> onclick="this.closest('dialog').close()"
</div> :disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('evaluate_model_form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Evaluate</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Evaluating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#model-evaluation-packages").selectpicker(); <button :disabled="isSubmitting">close</button>
</form>
$("#evaluate_model_form_submit").on("click", function (e) { </dialog>
e.preventDefault();
$("#evaluate_model_form").submit();
});
});
</script>

View File

@ -1,53 +1,56 @@
{% load static %} {% load static %}
<!-- Export Package -->
<div id="export_package_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="export_package_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h3 class="modal-title">Export Package as JSON</h3> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Export Package as JSON</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<div class="modal-body"> :disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<p>
By clicking on Export the Package will be serialized into a JSON and By clicking on Export the Package will be serialized into a JSON and
directly downloaded. opened in a new tab.
<form </p>
id="export-package-modal-form" </div>
accept-charset="UTF-8"
action="{{ package.url }}" <!-- Footer -->
data-remote="true" <div class="modal-action">
method="GET" <button
> type="button"
<input type="hidden" name="export" value="true" /> class="btn"
</form> onclick="this.closest('dialog').close()"
</div> :disabled="isSubmitting"
<div class="modal-footer"> >
<button type="button" class="btn btn-secondary" data-dismiss="modal"> Close
Close </button>
</button> <button
<button type="button"
type="button" class="btn btn-primary"
class="btn btn-primary" @click="window.open('{{ package.url }}?export=true', '_blank'); $el.closest('dialog').close();"
id="export-package-modal-form-submit" :disabled="isSubmitting"
> >
Export Export
</button> </button>
</div>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#export-package-modal-form-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#export-package-modal-form").submit(); </dialog>
$("#export_package_modal").modal("hide");
});
});
</script>

View File

@ -1,109 +1,142 @@
{% load static %} {% load static %}
<!-- Copy Object --> <!-- Copy Object -->
<div id="generic_copy_object_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="generic_copy_object_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="{
<h3 class="modal-title">Copy {{ object_type|capfirst }}</h3> isSubmitting: false,
<button errorMessage: '',
type="button" targetPackage: '',
class="close"
data-dismiss="modal" reset() {
aria-label="Close" this.isSubmitting = false;
> this.errorMessage = '';
<span aria-hidden="true">&times;</span> this.targetPackage = '';
</button> },
</div>
<div class="modal-body"> async submit() {
<form if (!this.targetPackage) return;
id="generic-copy-object-modal-form"
accept-charset="UTF-8" this.isSubmitting = true;
data-remote="true" this.errorMessage = '';
method="post"
> try {
{% csrf_token %} const response = await fetch(this.targetPackage, {
<label for="target-package" method: 'POST',
>Select the Target Package you want to copy this {{ object_type }} headers: {
into</label 'Content-Type': 'application/x-www-form-urlencoded',
> 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: new URLSearchParams({
hidden: 'copy',
object_to_copy: '{{ current_object.url }}'
})
});
const data = await response.json();
if (response.ok) {
window.location.href = data.success;
} else {
if (data.error && data.error.indexOf('to the same package') > -1) {
this.errorMessage = 'The target Package is the same as the source Package. Please select another target!';
} else {
this.errorMessage = data.error || 'An error occurred';
}
}
} catch (error) {
this.errorMessage = 'An error occurred while copying';
} finally {
this.isSubmitting = false;
}
}
}"
@close="reset()"
>
<div class="modal-box">
<!-- Header -->
<h3 class="text-lg font-bold">Copy {{ object_type|capfirst }}</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<form
id="generic-copy-object-modal-form"
accept-charset="UTF-8"
method="post"
>
{% csrf_token %}
<div class="form-control">
<label class="label" for="target-package">
<span class="label-text">
Select the Target Package you want to copy this {{ object_type }}
into
</span>
</label>
<select <select
id="target-package" id="target-package"
name="target-package" name="target-package"
data-actions-box="true" class="select select-bordered w-full"
class="form-control" x-model="targetPackage"
data-width="100%" required
> >
<option disabled selected>Select Target Package</option> <option value="" disabled selected>Select Target Package</option>
{% for p in meta.writeable_packages %} {% for p in meta.writeable_packages %}
<option value="{{ p.url }}">{{ p.name|safe }}</option> <option value="{{ p.url }}">{{ p.name|safe }}</option>
`
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" name="hidden" value="copy" /> </div>
</form> <input type="hidden" name="hidden" value="copy" />
<div </form>
id="copy-object-error-message"
class="alert alert-danger" <!-- Error Message -->
role="alert" <div
style="display: none" x-show="errorMessage"
></div> x-cloak
</div> class="alert alert-error mt-4"
<div class="modal-footer"> role="alert"
<button type="button" class="btn btn-secondary" data-dismiss="modal"> >
Close <span x-text="errorMessage"></span>
</button>
<button
type="button"
class="btn btn-primary"
id="generic-copy-object-modal-form-submit"
>
Copy
</button>
</div> </div>
</div> </div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit()"
:disabled="isSubmitting || !targetPackage"
>
<span x-show="!isSubmitting">Copy</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Copying...</span>
</button>
</div>
</div> </div>
</div>
<script>
$(function () {
$("#generic-copy-object-modal-form-submit").click(function (e) {
e.preventDefault();
$("#copy-object-error-message").hide();
const packageUrl = $("#target-package").find(":selected").val(); <!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
if ( <button :disabled="isSubmitting">close</button>
packageUrl === "Select Target Package" || </form>
packageUrl === "" || </dialog>
packageUrl === null ||
packageUrl === undefined
) {
return;
}
const formData = {
hidden: "copy",
object_to_copy: "{{ current_object.url }}",
};
$.ajax({
type: "post",
data: formData,
url: packageUrl,
success: function (data, textStatus) {
window.location.href = data.success;
},
error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.responseJSON.error.indexOf("to the same package") > -1) {
$("#copy-object-error-message").append(
"<p>The target Package is the same as the source Package. Please select another target!</p>",
);
} else {
$("#copy-object-error-message").append(
"<p>" + jqXHR.responseJSON.error + "</p>",
);
}
$("#copy-object-error-message").show();
},
});
});
});
</script>

View File

@ -1,58 +1,99 @@
{% load static %} {% load static %}
<!-- Delete Object --> <!--
<div id="generic_delete_modal" class="modal" tabindex="-1"> Generic Delete Modal - Delete object with confirmation
<div class="modal-dialog">
<div class="modal-content"> Migrated from Bootstrap + jQuery to DaisyUI + Alpine.js
<div class="modal-header"> Uses native <dialog> element with .showModal() API
<h3 class="modal-title">Delete {{ object_type|capfirst }}</h3> -->
<button
type="button" <dialog
class="close" id="generic_delete_modal"
data-dismiss="modal" class="modal"
aria-label="Close" x-data="modalForm()"
@close="reset()"
>
<div class="modal-box">
<!-- Header -->
<h3 class="font-bold text-lg">Delete {{ object_type|capfirst }}</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<!-- Warning message -->
<div class="alert alert-warning">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
> >
<span aria-hidden="true">&times;</span> <path
</button> stroke-linecap="round"
</div> stroke-linejoin="round"
<div class="modal-body"> stroke-width="2"
{% if object_type == 'user' %} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
Clicking "Delete" will <strong>permanently</strong> delete the User />
and associated data. This action can't be undone! </svg>
{% else %} <span>
Deletes the {{ object_type|capfirst }}. Related objects that depend on {% if object_type == 'user' %}
this {{ object_type|capfirst }} will be deleted as well. Clicking "Delete" will <strong>permanently</strong> delete the User
{% endif %} and associated data. This action can't be undone!
<form {% else %}
id="generic-delete-modal-form" Deletes the {{ object_type|capfirst }}. Related objects that depend
accept-charset="UTF-8" on this {{ object_type|capfirst }} will be deleted as well.
action="{{ current_object.url }}" {% endif %}
data-remote="true" </span>
method="post"
>
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete" />
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Close
</button>
<button
type="button"
class="btn btn-primary"
id="generic-delete-modal-form-submit"
>
Delete
</button>
</div> </div>
<!-- Hidden form -->
<form
id="generic-delete-modal-form"
accept-charset="UTF-8"
action="{{ current_object.url }}"
method="post"
>
{% csrf_token %}
<input type="hidden" name="hidden" value="delete" />
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-error"
@click="submit('generic-delete-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Delete</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Deleting...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop (click to close) -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#generic-delete-modal-form-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#generic-delete-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,213 +1,173 @@
{% load static %} {% load static %}
<style> <dialog
.alias-container {
display: flex;
flex-wrap: wrap;
align-items: center;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 6px;
cursor: text;
min-height: 38px;
background-color: #fff;
}
.alias {
display: inline-flex;
align-items: center;
background-color: #5bc0de;
color: white;
padding: 4px 8px;
margin: 3px 3px;
border-radius: 4px;
font-size: 13px;
line-height: 1.4;
}
.alias .remove {
margin-left: 6px;
cursor: pointer;
font-weight: bold;
line-height: 1;
}
.alias-input {
flex: 1;
min-width: 120px;
border: none;
outline: none;
margin: 3px 3px;
font-size: 14px;
}
.form-control.alias-container {
height: auto;
box-shadow: none;
}
</style>
<div
class="modal fade bs-modal-lg"
id="set_aliases_modal" id="set_aliases_modal"
tabindex="-1" class="modal"
aria-labelledby="set_aliases_modal" x-data="{
aria-modal="true" isSubmitting: false,
role="dialog" aliases: [{% for alias in current_object.aliases %}'{{ alias|escapejs }}'{% if not forloop.last %},{% endif %}{% endfor %}],
> newAlias: '',
<div class="modal-dialog modal-lg"> errorMessage: '',
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">
Set Aliases for {{ current_object.name|safe }}
</h4>
</div>
<div class="modal-body">
<form
id="set_aliases_modal_form"
accept-charset="UTF-8"
action="{{ current_object.url }}"
data-remote="true"
method="post"
>
{% csrf_token %}
<label for="alias-input">Aliases:</label>
<div class="form-control alias-container" id="alias-box">
{% for alias in current_object.aliases %}
<span class="alias"
>{{ alias|escape }}<span class="remove">&times;</span></span
>
{% endfor %}
<input
type="text"
id="alias-input"
class="alias-input"
placeholder="Add Alias..."
/>
</div>
</form>
<div
id="add-alias-error-message"
class="alert alert-danger"
role="alert"
style="display: none"
></div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
id="set_aliases_modal_form_submit"
>
Submit
</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
function addAlias(aliasText) {
aliasText = aliasText.trim();
if (aliasText === "") return;
// Avoid duplicate aliass reset() {
var exists = false; this.isSubmitting = false;
$("#alias-box .alias").each(function () { this.errorMessage = '';
if ( },
$(this).text().replace("×", "").trim().toLowerCase() ===
aliasText.toLowerCase() addAlias() {
) { const aliasText = this.newAlias.trim();
exists = true; if (aliasText === '') return;
return false;
} // Check for duplicates (case-insensitive)
}); const exists = this.aliases.some(
a => a.toLowerCase() === aliasText.toLowerCase()
);
if (!exists) { if (!exists) {
var aliasHtml = this.aliases.push(aliasText);
'<span class="alias">' +
$("<div>").text(aliasText).html() +
'<span class="remove">&times;</span></span>';
$(aliasHtml).insertBefore("#alias-input");
} }
$("#alias-input").val(""); this.newAlias = '';
} },
// Add alias when Enter is pressed removeAlias(index) {
$("#alias-input").on("keypress", function (e) { this.aliases.splice(index, 1);
if (e.which === 13) { },
handleKeypress(e) {
if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
addAlias($(this).val()); this.addAlias();
} }
}); },
// Add alias when input loses focus handleBlur() {
$("#alias-input").on("blur", function () { if (this.newAlias.trim() !== '') {
var val = $(this).val(); this.addAlias();
if (val.trim() !== "") {
addAlias(val);
} }
}); },
// Remove alias when clicking × async submit() {
$("#alias-box").on("click", ".remove", function () { this.isSubmitting = true;
$(this).closest(".alias").remove(); this.errorMessage = '';
});
// Focus input when clicking the container const formData = new URLSearchParams();
$("#alias-box").on("click", function () { if (this.aliases.length === 0) {
$("#alias-input").focus(); formData.append('aliases', '');
}); } else {
this.aliases.forEach(alias => {
$("#set_aliases_modal_form_submit").on("click", function (e) { formData.append('aliases', alias);
e.preventDefault(); });
let aliases = [];
$("#alias-box .alias").each(function () {
aliases.push($(this).text().replace("×", "").trim());
});
if (aliases.length === 0) {
// Set empty string for deletion of all aliases
// If empty list is sent, its gets removed entirely from post data
aliases = [""];
} }
formData = { try {
aliases: aliases, const response = await fetch('{{ current_object.url }}', {
}; method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData
});
$.ajax({ if (response.ok) {
type: "post", const data = await response.json();
data: formData,
url: "{{ current_object.url }}",
traditional: true,
success: function (data, textStatus) {
window.location.href = data.success; window.location.href = data.success;
}, } else {
error: function (jqXHR, textStatus, errorThrown) { this.errorMessage = 'Setting aliases failed!';
$("#add-alias-error-message").append( }
"<p>Setting aliases failed!</p>", } catch (error) {
); this.errorMessage = 'Setting aliases failed!';
$("#add-alias-error-message").show(); } finally {
}, this.isSubmitting = false;
}); }
}); }
}); }"
</script> @close="reset()"
>
<div class="modal-box max-w-4xl">
<!-- Header -->
<h3 class="text-lg font-bold">
Set Aliases for {{ current_object.name|safe }}
</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<div class="form-control">
<label class="label">
<span class="label-text">Aliases:</span>
</label>
<div
class="flex flex-wrap items-center gap-1 p-2 border border-base-300 rounded-lg bg-base-100 min-h-[38px] cursor-text"
@click="$refs.aliasInput.focus()"
>
<template x-for="(alias, index) in aliases" :key="index">
<span class="badge badge-info gap-1 py-3">
<span x-text="alias"></span>
<button
type="button"
class="btn btn-ghost btn-xs p-0 h-auto min-h-0"
@click.stop="removeAlias(index)"
>
</button>
</span>
</template>
<input
type="text"
x-ref="aliasInput"
x-model="newAlias"
class="flex-1 min-w-[120px] border-none outline-none bg-transparent text-sm"
placeholder="Add Alias..."
@keypress="handleKeypress($event)"
@blur="handleBlur()"
/>
</div>
</div>
<!-- Error Message -->
<div x-show="errorMessage" x-cloak class="alert alert-error mt-4">
<span x-text="errorMessage"></span>
</div>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit()"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Submitting...</span>
</button>
</div>
</div>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,97 +1,137 @@
{% load static %} {% load static %}
<!-- Delete Object --> <!-- Set External Reference -->
<div id="generic_set_external_reference_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="generic_set_external_reference_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="{
<h3 class="modal-title">Add External References</h3> isSubmitting: false,
<button selectedDatabase: '',
type="button" placeholder: '',
class="close"
data-dismiss="modal" reset() {
aria-label="Close" this.isSubmitting = false;
> this.selectedDatabase = '';
<span aria-hidden="true">&times;</span> this.placeholder = '';
</button> },
</div>
<div class="modal-body"> updatePlaceholder() {
<form if (this.selectedDatabase) {
id="generic-set-external-reference-modal-form" const option = document.querySelector('#database-select option[value=\'' + this.selectedDatabase + '\']');
accept-charset="UTF-8" if (option) {
action="{{ current_object.url }}" this.placeholder = option.dataset.inputPlaceholder || '';
data-remote="true" }
method="post" }
> },
{% csrf_token %}
<label for="database-select" submit(formId) {
>Select the Database you want to attach an External Reference const form = document.getElementById(formId);
for</label if (form && form.checkValidity()) {
> this.isSubmitting = true;
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
>
<div class="modal-box">
<!-- Header -->
<h3 class="text-lg font-bold">Add External References</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<form
id="generic-set-external-reference-modal-form"
accept-charset="UTF-8"
action="{{ current_object.url }}"
method="post"
>
{% csrf_token %}
<div class="form-control">
<label class="label" for="database-select">
<span class="label-text">
Select the Database you want to attach an External Reference for
</span>
</label>
<select <select
id="database-select" id="database-select"
name="selected-database" name="selected-database"
data-actions-box="true" class="select select-bordered w-full"
class="form-control" x-model="selectedDatabase"
data-width="100%" @change="updatePlaceholder()"
required
> >
<option disabled selected>Select Database</option> <option value="" disabled selected>Select Database</option>
{% for entity, databases in meta.external_databases.items %} {% for entity, databases in meta.external_databases.items %}
{% if entity == object_type %} {% if entity == object_type %}
{% for db in databases %} {% for db in databases %}
<option <option
id="db-select-{{ db.database.pk }}"
data-input-placeholder="{{ db.placeholder }}"
value="{{ db.database.id }}" value="{{ db.database.id }}"
data-input-placeholder="{{ db.placeholder }}"
> >
{{ db.database.name|safe }} {{ db.database.name|safe }}
</option> </option>
`
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </select>
<p></p> </div>
<div id="input-div" style="display: none">
<label for="identifier">The reference</label> <div class="form-control mt-4" x-show="selectedDatabase" x-cloak>
<input <label class="label" for="identifier">
type="text" <span class="label-text">The reference</span>
id="identifier" </label>
name="identifier" <input
class="form-control" type="text"
placeholder="" id="identifier"
/> name="identifier"
</div> class="input input-bordered w-full"
</form> :placeholder="placeholder"
</div> required
<div class="modal-footer"> />
<button type="button" class="btn btn-secondary" data-dismiss="modal"> </div>
Close </form>
</button> </div>
<button
type="button" <!-- Footer -->
class="btn btn-primary" <div class="modal-action">
id="generic-set-external-reference-modal-form-submit" <button
> type="button"
Submit class="btn"
</button> onclick="this.closest('dialog').close()"
</div> :disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('generic-set-external-reference-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Submitting...</span>
</button>
</div> </div>
</div> </div>
</div>
<script>
$(function () {
$("#database-select").on("change", function () {
let selected = $(this).val();
$("#identifier").attr(
"placeholder",
$("#db-select-" + selected).data("input-placeholder"),
);
$("#input-div").show();
});
$("#generic-set-external-reference-modal-form-submit").click(function (e) { <!-- Backdrop -->
e.preventDefault(); <form method="dialog" class="modal-backdrop">
$("#generic-set-external-reference-modal-form").submit(); <button :disabled="isSubmitting">close</button>
}); </form>
}); </dialog>
</script>

View File

@ -1,112 +1,142 @@
{% load static %} {% load static %}
<div <dialog
class="modal fade bs-modal-lg"
id="set_scenario_modal" id="set_scenario_modal"
tabindex="-1" class="modal"
aria-labelledby="set_scenario_modal" x-data="{
aria-modal="true" isSubmitting: false,
role="dialog" isLoading: false,
loaded: false,
scenarios: [],
attachedScenarios: [{% for scen in current_object.scenarios.all %}'{{ scen.url }}'{% if not forloop.last %},{% endif %}{% endfor %}],
reset() {
this.isSubmitting = false;
},
async loadScenarios() {
if (this.loaded) return;
this.isLoading = true;
try {
const response = await fetch('{% url "package scenario list" meta.current_package.uuid %}');
const data = await response.json();
this.scenarios = data;
this.loaded = true;
} catch (error) {
console.error('Error loading scenarios:', error);
} finally {
this.isLoading = false;
}
},
isSelected(url) {
return this.attachedScenarios.includes(url);
},
submit() {
this.isSubmitting = true;
const select = document.getElementById('scenario-select');
if (select.selectedOptions.length === 0) {
// Add hidden empty option to indicate clearing all scenarios
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.selected = true;
select.appendChild(emptyOption);
}
document.getElementById('set_scenario_modal_form').submit();
}
}"
@close="reset()"
x-init="$watch('$el.open', value => { if (value) loadScenarios(); })"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-4xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">
<button Set Scenarios for {{ current_object.name|safe }}
type="button" </h3>
class="close"
data-dismiss="modal" <!-- Close button (X) -->
aria-label="Close" <form method="dialog">
> <button
<span aria-hidden="true">×</span> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
</button> :disabled="isSubmitting"
<h4 class="modal-title"> >
Set Scenarios for {{ current_object.name|safe }}
</h4> </button>
</form>
<!-- Body -->
<div class="py-4">
<!-- Loading indicator -->
<div x-show="isLoading" x-cloak class="mb-4 text-center">
<span class="loading loading-spinner loading-lg"></span>
<div class="alert alert-info mt-2">Loading Scenarios...</div>
</div> </div>
<div class="modal-body">
<div id="loading_scenario_div" class="text-center"></div> <form
<form id="set_scenario_modal_form"
id="set_scenario_modal_form" accept-charset="UTF-8"
accept-charset="UTF-8" action="{{ current_object.url }}"
action="{{ current_object.url }}" data-remote="true"
data-remote="true" method="post"
method="post" x-show="!isLoading"
> >
{% csrf_token %} {% csrf_token %}
<label for="scenario-select">Scenarios</label> <div class="form-control">
<label class="label" for="scenario-select">
<span class="label-text">Scenarios</span>
</label>
<select <select
id="scenario-select" id="scenario-select"
name="selected-scenarios" name="selected-scenarios"
data-actions-box="true" class="select select-bordered h-48 w-full"
class="form-control"
multiple multiple
data-width="100%"
> >
<option disabled>Select Scenarios</option> <template x-for="scenario in scenarios" :key="scenario.url">
<option value="" hidden></option> <option
:value="scenario.url"
:selected="isSelected(scenario.url)"
x-text="scenario.name"
></option>
</template>
</select> </select>
</form> <label class="label">
</div> <span class="label-text-alt"
<div class="modal-footer"> >Hold Ctrl/Cmd to select multiple scenarios</span
<button >
type="button" </label>
class="btn btn-secondary pull-left" </div>
data-dismiss="modal" </form>
> </div>
Close
</button> <!-- Footer -->
<button <div class="modal-action">
type="button" <button
class="btn btn-primary" type="button"
id="set_scenario_modal_form_submit" class="btn"
> onclick="this.closest('dialog').close()"
Submit :disabled="isSubmitting"
</button> >
</div> Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit()"
:disabled="isSubmitting || isLoading"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Submitting...</span>
</button>
</div> </div>
</div> </div>
</div>
{# prettier-ignore-start #}
<script>
$(function () { <!-- Backdrop -->
var loaded = false; <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
var attachedScenarios = [] </form>
{% if current_object.scenarios.all %} </dialog>
{% for scen in current_object.scenarios.all %}
attachedScenarios.push('{{ scen.url }}')
{% endfor %}
{% endif %}
$('#scenario-select').selectpicker();
$('#set_scenario_modal').on('shown.bs.modal', function () {
if (!loaded) {
makeLoadingGif("#loading_scenario_div", "{% static '/images/wait.gif' %}");
$('#loading_scenario_div').append("<p></p><div class='alert alert-info'>Loading Scenarios...</div>");
$.getJSON("{% url 'package scenario list' meta.current_package.uuid %}").then(function (data) {
for(s in data) {
scenario = data[s]
var selected = attachedScenarios.includes(scenario.url);
$('#scenario-select').append(`<option value="${scenario.url}" ${selected ? 'selected' : ''}>${scenario.name}</option>`);
}
$('#scenario-select').selectpicker('refresh');
$("#loading_scenario_div").empty();
});
loaded = true;
}
$('#set_scenario_modal_form_submit').on('click', function (e) {
e.preventDefault();
if ($('#scenario-select').val().length == 0) {
$('#scenario-select').val("")
}
$('#set_scenario_modal_form').submit();
});
});
});
</script>
{# prettier-ignore-end #}

View File

@ -1,75 +1,89 @@
{% load static %} {% load static %}
<!-- Identify Missing Rules --> <!-- Identify Missing Rules -->
<div id="identify_missing_rules_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="identify_missing_rules_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h3 class="modal-title">Identify Missing Rules</h3> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="text-lg font-bold">Identify Missing Rules</h3>
aria-label="Close"
> <!-- Close button (X) -->
<span aria-hidden="true">&times;</span> <form method="dialog">
</button> <button
</div> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
<div class="modal-body"> :disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<p class="mb-4">
By clicking on Download we'll search the Pathway for Reactions that are By clicking on Download we'll search the Pathway for Reactions that are
not backed by a Rule or which can be assembled by chaining two rules. not backed by a Rule or which can be assembled by chaining two rules.
<form </p>
id="identify-missing-rules-modal-form" <form
accept-charset="UTF-8" id="identify-missing-rules-modal-form"
action="{{ pathway.url }}" accept-charset="UTF-8"
data-remote="true" action="{{ pathway.url }}"
method="GET" method="GET"
> >
<label for="rule-package">Select the Rule Package</label> <div class="form-control">
<label class="label" for="rule-package">
<span class="label-text">Select the Rule Package</span>
</label>
<select <select
id="rule-package" id="rule-package"
name="rule-package" name="rule-package"
data-actions-box="true" class="select select-bordered w-full"
class="form-control" required
data-width="100%"
> >
<option disabled>Reviewed Packages</option> <optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if obj.reviewed %} {% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name }}</option> <option value="{{ obj.url }}">{{ obj.name }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
<option disabled>Unreviewed Packages</option> <optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %} {% for obj in meta.readable_packages %}
{% if not obj.reviewed %} {% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name }}</option> <option value="{{ obj.url }}">{{ obj.name }}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</optgroup>
</select> </select>
<input type="hidden" name="identify-missing-rules" value="true" /> </div>
</form> <input type="hidden" name="identify-missing-rules" value="true" />
</div> </form>
<div class="modal-footer"> </div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Close <!-- Footer -->
</button> <div class="modal-action">
<button <button
type="button" type="button"
class="btn btn-primary" class="btn"
id="identify-missing-rules-modal-submit" onclick="this.closest('dialog').close()"
> :disabled="isSubmitting"
Download >
</button> Close
</div> </button>
<button
type="button"
class="btn btn-primary"
@click="document.getElementById('identify-missing-rules-modal-form').submit(); $el.closest('dialog').close()"
>
Download
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#identify-missing-rules-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#identify-missing-rules-modal-form").submit(); </dialog>
$("#identify_missing_rules_modal").modal("hide");
});
});
</script>

View File

@ -1,118 +1,174 @@
<div <dialog
class="modal fade"
tabindex="-1"
id="manage_api_token_modal" id="manage_api_token_modal"
role="dialog" class="modal"
aria-labelledby="manage_api_token_modal" x-data="{
aria-hidden="true" isSubmitting: false,
newToken: '',
tokens: [],
reset() {
this.isSubmitting = false;
this.newToken = '';
},
async requestToken() {
this.isSubmitting = true;
const form = document.getElementById('request_api_token_form');
const formData = new FormData(form);
try {
const response = await fetch('{{ meta.user.url }}', {
method: 'POST',
body: new URLSearchParams(formData)
});
const data = await response.json();
this.newToken = data.raw_token;
} catch (error) {
console.error('Error requesting token:', error);
} finally {
this.isSubmitting = false;
}
},
async deleteToken(form) {
const formData = new FormData(form);
try {
await fetch(form.action, {
method: 'POST',
body: new URLSearchParams(formData)
});
form.closest('.token-item').remove();
} catch (error) {
console.error('Error deleting token:', error);
}
}
}"
@close="reset()"
> >
<div class="modal-dialog"> <div class="modal-box">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Manage API Token</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h3 class="modal-title">Manage API Token</h3> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<p>
Requesting a Token will invalidate all potential existing tokens. The </button>
new token will only be shown once, so ensure to store it in secure </form>
place
</p> <!-- Body -->
<form <div class="py-4">
id="request_api_token_form" <p class="mb-4">
accept-charset="UTF-8" Requesting a Token will invalidate all potential existing tokens. The
action="" new token will only be shown once, so ensure to store it in a secure
data-remote="true" place.
method="post" </p>
>
{% csrf_token %} <form id="request_api_token_form" accept-charset="UTF-8" method="post">
<p> {% csrf_token %}
<label for="token-name">Name</label> <div class="form-control mb-3">
<input <label class="label" for="token-name">
type="text" <span class="label-text">Name</span>
id="token-name" </label>
class="form-control" <input
name="name" type="text"
placeholder="Generic Token for {{ meta.user.username }}" id="token-name"
/> class="input input-bordered w-full"
<label for="valid-for">Valid for (in days)</label> name="name"
<input placeholder="Generic Token for {{ meta.user.username }}"
type="number" />
id="valid-for"
class="form-control"
name="valid-for"
value="90"
/>
<input type="hidden" name="hidden" value="request-api-token" />
</p>
</form>
<div id="new-token">
<pre id="new-token-pre"></pre>
</div> </div>
<div id="existing-tokens"> <div class="form-control mb-3">
{% for t in tokens %} <label class="label" for="valid-for">
<span class="label-text">Valid for (in days)</span>
</label>
<input
type="number"
id="valid-for"
class="input input-bordered w-full"
name="valid-for"
value="90"
/>
</div>
<input type="hidden" name="hidden" value="request-api-token" />
</form>
<!-- New Token Display -->
<div x-show="newToken" x-cloak class="alert alert-success mb-4">
<div class="w-full">
<span class="font-bold">New Token:</span>
<pre
class="mt-2 p-2 bg-base-200 rounded overflow-x-auto"
x-text="newToken"
></pre>
</div>
</div>
<!-- Existing Tokens -->
<div class="space-y-2">
{% for t in tokens %}
<div class="token-item">
<form <form
id="delete-token-{{ forloop.counter0 }}" id="delete-token-{{ forloop.counter0 }}"
accept-charset="UTF-8" accept-charset="UTF-8"
action="{{ meta.user.url }}" action="{{ meta.user.url }}"
data-remote="true"
method="post" method="post"
> >
{% csrf_token %} {% csrf_token %}
<div class="input-group"> <div class="join w-full">
<input type="hidden" name="hidden" value="delete" /> <input type="hidden" name="hidden" value="delete" />
<input type="hidden" name="token-id" value="{{ t.pk }}" /> <input type="hidden" name="token-id" value="{{ t.pk }}" />
<input <input
type="text" type="text"
class="form-control" class="input input-bordered join-item w-full"
value="{{ t.name|safe }}" value="{{ t.name|safe }}"
disabled disabled
/> />
<span class="input-group-btn"> <button
<button type="submit" class="btn btn-danger">Delete</button> type="button"
</span> class="btn btn-error join-item"
@click="deleteToken($el.closest('form'))"
>
Delete
</button>
</div> </div>
</form> </form>
{% endfor %} </div>
</div> {% endfor %}
</div>
<div class="modal-footer">
<a id="manage_api_token_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>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="requestToken()"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Request Token</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Requesting...</span>
</button>
</div>
</div> </div>
</div>
<script>
$(function () {
$("#new-token").hide();
$("#manage_api_token_form_submit").on("click", function (e) { <!-- Backdrop -->
e.preventDefault(); <form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
const formData = $("#request_api_token_form").serialize(); </form>
$.post("", formData, function (response) { </dialog>
$("#new-token-pre").text(response.raw_token);
$("#new-token").show();
});
});
// Select all elements that start with 'delete-token-'
$("[id^='delete-token-']").on("submit", function (e) {
e.preventDefault();
const formData = $(this).serialize();
const form = $(this);
$.post(this.action, formData, function (response) {
console.log(this);
form.remove();
});
});
});
</script>

View File

@ -1,52 +1,85 @@
{% load static %} {% load static %}
<!-- Publish a Package -->
<div id="publish_package_modal" class="modal" tabindex="-1"> <dialog
<div class="modal-dialog"> id="publish_package_modal"
<div class="modal-content"> class="modal"
<div class="modal-header"> x-data="modalForm()"
<h5 class="modal-title">Publish Package</h5> @close="reset()"
<button >
type="button" <div class="modal-box">
class="close" <!-- Header -->
data-dismiss="modal" <h3 class="font-bold text-lg">Publish Package</h3>
aria-label="Close"
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<div class="alert alert-info">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 stroke-current"
> >
<span aria-hidden="true">&times;</span> <path
</button> stroke-linecap="round"
</div> stroke-linejoin="round"
<div class="modal-body"> stroke-width="2"
<p>Clicking on Publish will make this Package publicly available!</p> d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
<form ></path>
id="publish-package-modal-form" </svg>
accept-charset="UTF-8" <span
action="{{ current_package.url }}" >Clicking on Publish will make this Package publicly available!</span
data-remote="true"
method="post"
> >
{% csrf_token %}
<input type="hidden" name="hidden" value="publish-package" />
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Close
</button>
<button
type="button"
class="btn btn-primary"
id="publish-package-modal-form-submit"
>
Publish
</button>
</div> </div>
<form
id="publish-package-modal-form"
accept-charset="UTF-8"
action="{{ current_package.url }}"
method="post"
>
{% csrf_token %}
<input type="hidden" name="hidden" value="publish-package" />
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('publish-package-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Publish</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Publishing...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#publish-package-modal-form-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#publish-package-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,55 +1,72 @@
<div <dialog
class="modal fade"
tabindex="-1"
id="retrain_model_modal" id="retrain_model_modal"
role="dialog" class="modal"
aria-labelledby="retrain_model_modal" x-data="modalForm()"
aria-hidden="true" @close="reset()"
> >
<div class="modal-dialog modal-lg"> <div class="modal-box max-w-3xl">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Retrain Model</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h4 class="modal-title">Retrain Model</h4> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
</div> :disabled="isSubmitting"
<div class="modal-body"> >
<form
id="retrain_model_form" </button>
accept-charset="UTF-8" </form>
action="{{ meta.current_object.url }}"
data-remote="true" <!-- Body -->
method="post" <div class="py-4">
> <form
<div class="jumbotron"> id="retrain_model_form"
accept-charset="UTF-8"
action="{{ meta.current_object.url }}"
method="post"
>
{% csrf_token %}
<div class="alert alert-info mb-4">
<span>
To reflect changes in the rule or data packages, you can use the To reflect changes in the rule or data packages, you can use the
"Retrain" button, to let the model reflect the changes without "Retrain" button, to let the model reflect the changes without
creating a new model. While the model is retraining, it will be creating a new model. While the model is retraining, it will be
unavailable for prediction. unavailable for prediction.
</div> </span>
{% csrf_token %} </div>
<input type="hidden" name="hidden" value="retrain" /> <input type="hidden" name="hidden" value="retrain" />
</form> </form>
</div> </div>
<div class="modal-footer">
<a id="retrain_model_form_submit" class="btn btn-primary" href="#" <!-- Footer -->
>Retrain</a <div class="modal-action">
> <button
<button type="button" class="btn btn-default" data-dismiss="modal"> type="button"
Cancel class="btn"
</button> onclick="this.closest('dialog').close()"
</div> :disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('retrain_model_form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Retrain</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Retraining...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#retrain_model_form_submit").on("click", function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#retrain_model_form").submit(); </dialog>
});
});
</script>

View File

@ -1,198 +1,193 @@
<div <dialog
class="modal fade"
tabindex="-1"
id="set_license_modal" id="set_license_modal"
role="dialog" class="modal"
aria-labelledby="set_license_modal" x-data="{
aria-hidden="true" isSubmitting: false,
licenseType: '',
ccRemix: false,
ccNoCom: false,
ccAlike: false,
reset() {
this.isSubmitting = false;
this.licenseType = '';
this.ccRemix = false;
this.ccNoCom = false;
this.ccAlike = false;
},
get licenseString() {
if (this.licenseType !== 'cc') return 'no-license';
let str = 'by';
if (this.ccNoCom) str += '-nc';
if (!this.ccRemix) {
str += '-nd';
} else if (this.ccAlike) {
str += '-sa';
}
return str;
},
get licenseUrl() {
if (this.licenseType !== 'cc') return '';
return 'https://creativecommons.org/licenses/' + this.licenseString + '/4.0/';
},
get licenseImageUrl() {
if (this.licenseType !== 'cc') return '';
return 'https://licensebuttons.net/l/' + this.licenseString + '/4.0/88x31.png';
},
submit(formId) {
const form = document.getElementById(formId);
document.getElementById('license').value = this.licenseString;
if (form && form.checkValidity()) {
this.isSubmitting = true;
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
> >
<div class="modal-dialog"> <div class="modal-box">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Set License</h3>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <!-- Close button (X) -->
<span class="sr-only">Close</span> <form method="dialog">
</button> <button
<h3 class="modal-title">Set License</h3> class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<div class="flex gap-4 mb-4">
<div class="flex-1">
<p>
Set the license for all data in this package:
<br /><br />
(For more information please visit our
<a
target="_blank"
href="https://wiki.envipath.org/index.php/License"
class="link link-primary"
>wiki</a
>.)
</p>
</div>
<div class="flex-none" x-show="licenseType === 'cc'" x-cloak>
<a :href="licenseUrl" target="_blank">
<img :src="licenseImageUrl" alt="CC License" />
</a>
</div>
</div> </div>
<div class="modal-body"> <form
<div class="row"> id="set_license_form"
<div class="col-md-9"> accept-charset="UTF-8"
<p> action=""
Set the license for all data in this package: method="post"
<br /> >
<br /> {% csrf_token %}
(For more information please visit our
<a target="#" href="https://wiki.envipath.org/index.php/License" <div class="form-control">
>wiki</a <label class="label cursor-pointer justify-start gap-3">
>.) <input
</p> type="radio"
name="optlicense"
class="radio"
value="cc"
x-model="licenseType"
/>
<span class="label-text">Creative commons license</span>
</label>
</div>
<div class="ml-8 space-y-2" x-show="licenseType === 'cc'" x-cloak>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox" class="checkbox" x-model="ccRemix" />
<span class="label-text"
>Allow adaptations of your work to be shared</span
>
</label>
</div> </div>
<div class="col-md-3">
<div id="ccfig"></div> <div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox" class="checkbox" x-model="ccNoCom" />
<span class="label-text">Prohibit commercial use</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input
type="checkbox"
class="checkbox"
x-model="ccAlike"
:disabled="!ccRemix"
/>
<span class="label-text">Share only if others share alike</span>
</label>
</div> </div>
</div> </div>
<form
id="set_license_form" <div class="form-control mt-2">
accept-charset="UTF-8" <label class="label cursor-pointer justify-start gap-3">
action="" <input
data-remote="true" type="radio"
method="post" name="optlicense"
> class="radio"
{% csrf_token %} value="nocc"
<div class="input-group"> x-model="licenseType"
<div class="radio"> />
<label <span class="label-text"
><input >No creative commons license, contact package owner for license
type="radio" information</span
name="optlicense" >
onclick="cc()" </label>
id="ccradio" </div>
/>Creative commons license</label
> <input type="hidden" id="license" name="license" />
</div> </form>
<div> </div>
<div class="checkbox">
<label <!-- Footer -->
><input <div class="modal-action">
type="checkbox" <button
value="" type="button"
onclick="cc()" class="btn"
id="ccremix" onclick="this.closest('dialog').close()"
disabled :disabled="isSubmitting"
/>Allow adaptations of your work to be shared</label >
> Cancel
</div> </button>
<div class="checkbox"> <button
<label type="button"
><input class="btn btn-primary"
type="checkbox" @click="submit('set_license_form')"
value="" :disabled="isSubmitting || !licenseType"
onclick="cc()" >
id="ccnocom" <span x-show="!isSubmitting">Submit</span>
disabled <span
/>Prohibit commercial use</label x-show="isSubmitting"
> class="loading loading-spinner loading-sm"
</div> ></span>
<div class="checkbox"> <span x-show="isSubmitting">Submitting...</span>
<label </button>
><input
type="checkbox"
value=""
onclick="cc()"
id="ccalike"
disabled
/>Share only if others share alike</label
>
</div>
</div>
<div class="radio">
<label
><input
type="radio"
name="optlicense"
onclick="cc()"
id="noccradio"
/>No creative commons license, contact package owner for license
information</label
>
</div>
</div>
<input type="hidden" id="license" name="license" />
</form>
</div>
<div class="modal-footer">
<button id="set_license_form_submit" class="btn btn-primary">
Submit
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
</div>
</div> </div>
</div> </div>
</div>
<script>
function ccstring(ccremix, ccnocom, ccalike) {
var ccstring = "by";
if (ccnocom) { <!-- Backdrop -->
ccstring += "-nc"; <form method="dialog" class="modal-backdrop">
} <button :disabled="isSubmitting">close</button>
</form>
if (!ccremix) { </dialog>
ccstring += "-nd";
} else {
if (ccalike) {
ccstring += "-sa";
}
}
return ccstring;
}
function cc() {
var nocc = $("#noccradio").prop("checked");
var iscc = $("#ccradio").prop("checked");
if (nocc) {
$("#ccradio").prop("checked", false);
$("#ccremix").prop("checked", false);
$("#ccnocom").prop("checked", false);
$("#ccalike").prop("checked", false);
$("#ccremix").prop("disabled", true);
$("#ccnocom").prop("disabled", true);
$("#ccalike").prop("disabled", true);
} else if (iscc) {
$("#ccremix").prop("disabled", false);
$("#ccnocom").prop("disabled", false);
if ($("#ccremix").prop("checked")) {
$("#ccalike").prop("disabled", false);
} else {
$("#ccalike").prop("disabled", true);
}
}
var remix = $("#ccremix").prop("checked");
var nocom = $("#ccnocom").prop("checked");
var alike = $("#ccalike").prop("checked");
if (nocc) {
$("#set_license_form_submit").prop("disabled", false);
$("#ccfig").empty();
$("#license").val("no-license");
} else if (iscc) {
$("#set_license_form_submit").prop("disabled", false);
$("#ccfig").empty();
var ccstr = ccstring(remix, nocom, alike);
var link = `https://creativecommons.org/licenses/${ccstr}/4.0/`;
var imageLink = `https://licensebuttons.net/l/${ccstr}/4.0/88x31.png`;
var img_tpl = `<a href='${link}' target="_blank">
<img src='${imageLink}'>
</a>`;
$("#ccfig").append(img_tpl);
$("#license").val(ccstr);
} else {
$("#ccfig").empty();
$("#set_license_form_submit").prop("disabled", true);
$("#license").val("no-license");
}
}
$(function () {
// Disable by default as nothing is selected
cc();
$("#set_license_form_submit").prop("disabled", true);
$("#set_license_form_submit").on("click", function (e) {
e.preventDefault();
$("#set_license_form").submit();
});
});
</script>

View File

@ -1,62 +1,69 @@
{% load static %} {% load static %}
<!-- Update Scenario Additional Information-->
<div <dialog
id="update_scenario_additional_information_modal" id="update_scenario_additional_information_modal"
class="modal" class="modal"
tabindex="-1" x-data="modalForm()"
@close="reset()"
> >
<div class="modal-dialog"> <div class="modal-box">
<div class="modal-content"> <!-- Header -->
<div class="modal-header"> <h3 class="text-lg font-bold">Update Additional Information</h3>
<button
type="button" <!-- Close button (X) -->
class="close" <form method="dialog">
data-dismiss="modal" <button
aria-label="Close" class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
> :disabled="isSubmitting"
<span aria-hidden="true">&times;</span> >
</button>
<h3 class="modal-title">Update Additional Information</h3> </button>
</div> </form>
<div class="modal-body">
<form <!-- Body -->
id="edit-scenario-additional-information-modal-form" <div class="py-4">
accept-charset="UTF-8" <form
action="" id="edit-scenario-additional-information-modal-form"
data-remote="true" accept-charset="UTF-8"
method="post" action=""
> method="post"
{% csrf_token %} >
{% for widget in update_widgets %} {% csrf_token %}
{{ widget|safe }} {% for widget in update_widgets %}
{% endfor %} {{ widget|safe }}
<input {% endfor %}
type="hidden" <input type="hidden" name="hidden" value="set-additional-information" />
name="hidden" </form>
value="set-additional-information" </div>
/>
</form> <!-- Footer -->
</div> <div class="modal-action">
<div class="modal-footer"> <button
<button type="button" class="btn btn-secondary" data-dismiss="modal"> type="button"
Close class="btn"
</button> onclick="this.closest('dialog').close()"
<button :disabled="isSubmitting"
type="button" >
class="btn btn-primary" Close
id="edit-scenario-additional-information-modal-submit" </button>
> <button
Update type="button"
</button> class="btn btn-primary"
</div> @click="submit('edit-scenario-additional-information-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Update</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Updating...</span>
</button>
</div> </div>
</div> </div>
</div>
<script> <!-- Backdrop -->
$(function () { <form method="dialog" class="modal-backdrop">
$("#edit-scenario-additional-information-modal-submit").click(function (e) { <button :disabled="isSubmitting">close</button>
e.preventDefault(); </form>
$("#edit-scenario-additional-information-modal-form").submit(); </dialog>
});
});
</script>

View File

@ -1,178 +0,0 @@
{% load static %}
<div
class="modal fade bs-modal-lg"
id="predict_modal"
tabindex="-1"
aria-labelledby="predict_modal"
aria-modal="true"
role="dialog"
>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">Predict a Pathway</h4>
</div>
<div class="modal-body">
<form
id="predict_modal_form"
accept-charset="UTF-8"
action="{{ meta.current_package.url }}/pathway"
data-remote="true"
method="post"
>
{% csrf_token %}
<div class="row">
<div class="col-md-6">
<label for="name">Name</label>
<input
id="name"
class="form-control"
name="name"
placeholder="Name"
/>
<label for="description">Description</label>
<input
id="description"
class="form-control"
name="description"
placeholder="Description"
/>
</div>
<div class="col-md-6">
<label for="predict">Predict pathway or build yourself?</label>
<div class="radio" id="predict">
<p>
<label>
<input
type="radio"
name="predict"
id="radioPredict"
value="predict"
checked
/>Predict pathway
</label>
</p>
<p>
<label>
<input
type="radio"
name="predict"
id="radioIncremental"
value="incremental"
/>Incremental prediction
</label>
</p>
<p>
<label>
<input
type="radio"
name="predict"
id="radioBuild"
value="build"
/>Build pathway
</label>
</p>
</div>
</div>
</div>
<label for="predict-modal-smiles">SMILES</label>
<input
type="text"
class="form-control"
name="smiles"
placeholder="SMILES"
id="predict-modal-smiles"
/>
<p id="ketcher_container"></p>
<div>
<iframe
id="predict-modal-ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div>
<label for="prediction-setting">Default Prediction Setting</label>
<select
id="prediction-setting"
name="prediction-setting"
class="form-control"
data-width="100%"
>
<option disabled>Select a Setting</option>
{% for s in meta.available_settings %}
<option
value="{{ s.url }}"
{% if s.id == meta.user.default_setting.id %}selected{% endif %}
>
{{ s.name|safe }}{% if s.id == meta.user.default_setting.id %}
<i>(User default)</i>
{% endif %}
</option>
{% endfor %}
</select>
</form>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary pull-left"
data-dismiss="modal"
>
Close
</button>
<button type="button" class="btn btn-primary" id="predictButton">
Predict
</button>
</div>
</div>
</div>
</div>
<script>
function predictModalketcherToPredictModalTextInput() {
$("#predict-modal-smiles").val(this.ketcher.getSmiles());
}
$(function () {
$("#predict-modal-ketcher").on("load", function () {
const checkKetcherReady = () => {
win = this.contentWindow;
if (win.ketcher && "editor" in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({
once: false,
priority: 0,
f: predictModalketcherToPredictModalTextInput,
ketcher: win.ketcher,
});
} else {
setTimeout(checkKetcherReady, 100);
}
};
checkKetcherReady();
});
$("#predictButton").on("click", function (e) {
e.preventDefault();
$(this).prop("disabled", true);
// loading button
// validation
// existing pws...
// submit form
$("#predict_modal_form").submit();
});
});
</script>

View File

@ -1,5 +1,10 @@
{% load static %} {% load static %}
<dialog id="search_modal" class="modal @max-sm:modal-top justify-center"> <dialog
id="search_modal"
class="modal @max-sm:modal-top justify-center"
x-data="searchModal()"
@close="reset()"
>
<div class="modal-box h-full w-lvw p-1 sm:h-8/12 sm:w-[85vw] sm:max-w-5xl"> <div class="modal-box h-full w-lvw p-1 sm:h-8/12 sm:w-[85vw] sm:max-w-5xl">
<!-- Search Input and Mode Selector --> <!-- Search Input and Mode Selector -->
<div class="form-control mb-4 w-full shrink-0"> <div class="form-control mb-4 w-full shrink-0">
@ -23,7 +28,9 @@
<input <input
type="text" type="text"
autofocus autofocus
id="modal_searchbar" x-ref="searchbar"
x-model="query"
@keydown.enter="performSearch('{{ SERVER_BASE }}')"
placeholder="Benfuracarb" placeholder="Benfuracarb"
class="grow" class="grow"
aria-label="Search" aria-label="Search"
@ -35,12 +42,11 @@
<button <button
type="button" type="button"
tabindex="0" tabindex="0"
id="modal_mode_button"
popovertarget="search_dropdown_menu" popovertarget="search_dropdown_menu"
style="anchor-name:--1" style="anchor-name: --1"
class="btn join-item btn-ghost" class="btn join-item btn-ghost"
> >
Text <span x-text="searchModeLabel"></span>
<svg <svg
class="ml-1 h-4 w-4" class="ml-1 h-4 w-4"
fill="none" fill="none"
@ -59,13 +65,14 @@
tabindex="0" tabindex="0"
class="dropdown dropdown-end menu bg-base-200 rounded-box w-64 p-2 shadow-lg" class="dropdown dropdown-end menu bg-base-200 rounded-box w-64 p-2 shadow-lg"
popover popover
x-ref="modeDropdown"
id="search_dropdown_menu" id="search_dropdown_menu"
style="position-anchor:--anchor-2" style="position-anchor: --anchor-2"
> >
<li class="menu-title">Text</li> <li class="menu-title">Text</li>
<li> <li>
<a <a
id="modal_dropdown_text" @click.prevent="setSearchMode('text', 'Text')"
class="tooltip tooltip-left" class="tooltip tooltip-left"
data-tip="Search on object names and descriptions" data-tip="Search on object names and descriptions"
> >
@ -75,7 +82,7 @@
<li class="menu-title">SMILES</li> <li class="menu-title">SMILES</li>
<li> <li>
<a <a
id="modal_dropdown_smiles_default" @click.prevent="setSearchMode('smiles_default', 'Default')"
class="tooltip tooltip-left" class="tooltip tooltip-left"
data-tip="Ignores stereochemistry and charge" data-tip="Ignores stereochemistry and charge"
> >
@ -84,7 +91,7 @@
</li> </li>
<li> <li>
<a <a
id="modal_dropdown_smiles_canonical" @click.prevent="setSearchMode('smiles_canonical', 'Canonical')"
class="tooltip tooltip-left" class="tooltip tooltip-left"
data-tip="Ignores stereochemistry, preserves charge" data-tip="Ignores stereochemistry, preserves charge"
> >
@ -93,7 +100,7 @@
</li> </li>
<li> <li>
<a <a
id="modal_dropdown_smiles_exact" @click.prevent="setSearchMode('smiles_exact', 'Exact')"
class="tooltip tooltip-left" class="tooltip tooltip-left"
data-tip="Exact match for stereochemistry and charge" data-tip="Exact match for stereochemistry and charge"
> >
@ -103,7 +110,7 @@
<li class="menu-title">InChI</li> <li class="menu-title">InChI</li>
<li> <li>
<a <a
id="modal_dropdown_inchikey" @click.prevent="setSearchMode('inchikey', 'InChIKey')"
class="tooltip tooltip-left" class="tooltip tooltip-left"
data-tip="Search by InChIKey" data-tip="Search by InChIKey"
> >
@ -115,7 +122,7 @@
<button <button
type="button" type="button"
id="modal_search_button" @click="performSearch('{{ SERVER_BASE }}')"
class="btn btn-xs btn-ghost join-item" class="btn btn-xs btn-ghost join-item"
> >
<kbd class="kbd kbd-sm text-base-content/50 p-1"> <kbd class="kbd kbd-sm text-base-content/50 p-1">
@ -143,18 +150,62 @@
<div class="form-control mb-4 shrink-0"> <div class="form-control mb-4 shrink-0">
<!-- Pills Container --> <!-- Pills Container -->
<div <div
id="modal_package_pills_container"
class="border-base-300 m-3 flex min-h-12 flex-wrap items-center gap-2 rounded-lg border-2 border-dashed p-3" class="border-base-300 m-3 flex min-h-12 flex-wrap items-center gap-2 rounded-lg border-2 border-dashed p-3"
> >
<!-- Pills will be added here dynamically --> <!-- Dynamic Pills -->
<template x-for="pkg in selectedPackages" :key="pkg.url">
<span class="badge badge-outline gap-2 max-w-xs">
<span class="truncate" :title="pkg.name" x-text="pkg.name"></span>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 cursor-pointer hover:text-error shrink-0 rotate-45"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
@click="removePackage(pkg.url)"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
</span>
</template>
<!-- Add Package Button -->
<button
type="button"
popovertarget="package_dropdown_menu"
style="anchor-name: --anchor-packages"
class="btn btn-sm btn-ghost gap-2 text-base-content/50"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-plus-icon lucide-plus"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
Add Package
</button>
</div> </div>
<!-- Package Dropdown Menu --> <!-- Package Dropdown Menu -->
<ul <ul
class="dropdown dropdown-center menu bg-base-200 rounded-box max-h-96 w-80 overflow-y-auto p-2 shadow-lg" class="dropdown dropdown-center menu bg-base-200 rounded-box max-h-96 w-80 overflow-y-auto p-2 shadow-lg"
popover popover
x-ref="packageDropdown"
id="package_dropdown_menu" id="package_dropdown_menu"
style="position-anchor:--anchor-packages" style="position-anchor: --anchor-packages"
> >
{% if unreviewed_packages %} {% if unreviewed_packages %}
<li class="menu-title">Reviewed Packages</li> <li class="menu-title">Reviewed Packages</li>
@ -164,11 +215,13 @@
class="package-option flex items-center justify-between" class="package-option flex items-center justify-between"
data-package-url="{{ obj.url }}" data-package-url="{{ obj.url }}"
data-package-name="{{ obj.name }}" data-package-name="{{ obj.name }}"
@click.prevent.stop="togglePackage('{{ obj.url }}', '{{ obj.name }}')"
> >
<span>{{ obj.name }}</span> <span>{{ obj.name }}</span>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="package-checkmark hidden h-4 w-4" class="h-4 w-4"
:class="isPackageSelected('{{ obj.url }}') ? '' : 'hidden'"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -190,11 +243,13 @@
class="package-option flex items-center justify-between" class="package-option flex items-center justify-between"
data-package-url="{{ obj.url }}" data-package-url="{{ obj.url }}"
data-package-name="{{ obj.name }}" data-package-name="{{ obj.name }}"
@click.prevent.stop="togglePackage('{{ obj.url }}', '{{ obj.name }}')"
> >
<span>{{ obj.name }}</span> <span>{{ obj.name }}</span>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="package-checkmark hidden h-4 w-4" class="h-4 w-4"
:class="isPackageSelected('{{ obj.url }}') ? '' : 'hidden'"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -217,11 +272,13 @@
class="package-option flex items-center justify-between" class="package-option flex items-center justify-between"
data-package-url="{{ obj.url }}" data-package-url="{{ obj.url }}"
data-package-name="{{ obj.name }}" data-package-name="{{ obj.name }}"
@click.prevent.stop="togglePackage('{{ obj.url }}', '{{ obj.name }}')"
> >
<span>{{ obj.name }}</span> <span>{{ obj.name }}</span>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="package-checkmark hidden h-4 w-4" class="h-4 w-4"
:class="isPackageSelected('{{ obj.url }}') ? '' : 'hidden'"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -241,12 +298,201 @@
</div> </div>
<!-- Loading Indicator --> <!-- Loading Indicator -->
<div id="search_loading" class="hidden shrink-0 justify-center py-8"> <div x-show="isSearching" class="flex shrink-0 justify-center py-8">
<span class="loading loading-spinner loading-lg text-primary"></span> <span class="loading loading-spinner loading-lg text-primary"></span>
</div> </div>
<!-- Results Container - scrollable --> <!-- Results Container -->
<div id="search_results" class="min-h-0 flex-1 overflow-y-auto p-2"></div> <div class="min-h-0 flex-1 overflow-y-auto p-2">
<!-- No packages selected error -->
<template x-if="results && results.error === 'no_packages'">
<div class="alert alert-info">
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Please select at least one package</span>
</div>
</template>
<!-- Search error -->
<template x-if="error">
<div class="alert alert-error">
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span x-text="error"></span>
</div>
</template>
<!-- No results -->
<template x-if="results && !results.error && !hasResults()">
<div class="alert alert-warning">
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<span>No results found</span>
</div>
</template>
<!-- Results display -->
<template x-if="results && !results.error && hasResults()">
<div class="mb-2">
<div class="text-sm font-semibold text-base-content/70 mb-2">
Results
</div>
<!-- Compounds -->
<template x-if="results.Compounds && results.Compounds.length > 0">
<div class="collapse collapse-arrow bg-base-200 mb-2">
<input type="checkbox" checked />
<div class="collapse-title font-medium">
Compounds
<span
class="badge badge-neutral badge-sm ml-2"
x-text="results.Compounds.length"
></span>
</div>
<div class="collapse-content">
<template x-for="item in results.Compounds" :key="item.url">
<a
:href="item.url"
class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors"
x-text="item.name"
></a>
</template>
</div>
</div>
</template>
<!-- Compound Structures -->
<template
x-if="results['Compound Structures'] && results['Compound Structures'].length > 0"
>
<div class="collapse collapse-arrow bg-base-200 mb-2">
<input type="checkbox" checked />
<div class="collapse-title font-medium">
Compound Structures
<span
class="badge badge-neutral badge-sm ml-2"
x-text="results['Compound Structures'].length"
></span>
</div>
<div class="collapse-content">
<template
x-for="item in results['Compound Structures']"
:key="item.url"
>
<a
:href="item.url"
class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors"
x-text="item.name"
></a>
</template>
</div>
</div>
</template>
<!-- Rules -->
<template x-if="results.Rules && results.Rules.length > 0">
<div class="collapse collapse-arrow bg-base-200 mb-2">
<input type="checkbox" checked />
<div class="collapse-title font-medium">
Rules
<span
class="badge badge-neutral badge-sm ml-2"
x-text="results.Rules.length"
></span>
</div>
<div class="collapse-content">
<template x-for="item in results.Rules" :key="item.url">
<a
:href="item.url"
class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors"
x-text="item.name"
></a>
</template>
</div>
</div>
</template>
<!-- Reactions -->
<template x-if="results.Reactions && results.Reactions.length > 0">
<div class="collapse collapse-arrow bg-base-200 mb-2">
<input type="checkbox" checked />
<div class="collapse-title font-medium">
Reactions
<span
class="badge badge-neutral badge-sm ml-2"
x-text="results.Reactions.length"
></span>
</div>
<div class="collapse-content">
<template x-for="item in results.Reactions" :key="item.url">
<a
:href="item.url"
class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors"
x-text="item.name"
></a>
</template>
</div>
</div>
</template>
<!-- Pathways -->
<template x-if="results.Pathways && results.Pathways.length > 0">
<div class="collapse collapse-arrow bg-base-200 mb-2">
<input type="checkbox" checked />
<div class="collapse-title font-medium">
Pathways
<span
class="badge badge-neutral badge-sm ml-2"
x-text="results.Pathways.length"
></span>
</div>
<div class="collapse-content">
<template x-for="item in results.Pathways" :key="item.url">
<a
:href="item.url"
class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors"
x-text="item.name"
></a>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div> </div>
<!-- Backdrop to close --> <!-- Backdrop to close -->
@ -254,433 +500,3 @@
<button>close</button> <button>close</button>
</form> </form>
</dialog> </dialog>
<script>
(function () {
// Package Selector Module - Data-driven multiselect package selection
const PackageSelector = {
// Single source of truth: array of selected packages
selectedPackages: [],
elements: {
pillsContainer: null,
packageDropdown: null,
packageOptions: null,
},
init() {
this.cacheElements();
this.loadInitialSelection();
this.attachEventListeners();
this.render();
},
cacheElements() {
this.elements.pillsContainer = document.getElementById(
"modal_package_pills_container",
);
this.elements.packageDropdown = document.getElementById(
"package_dropdown_menu",
);
this.elements.packageOptions =
document.querySelectorAll(".package-option");
},
loadInitialSelection() {
// Load pre-selected packages from server-rendered pills
const existingPills =
this.elements.pillsContainer.querySelectorAll(".badge");
existingPills.forEach((pill) => {
this.selectedPackages.push({
url: pill.dataset.packageUrl,
name: pill.dataset.packageName,
});
});
// If no pills found, select all reviewed packages by default
if (this.selectedPackages.length === 0) {
// Iterate through all menu items and collect reviewed packages
const menuItems =
this.elements.packageDropdown.querySelectorAll("li");
for (const item of menuItems) {
// Check if this is the "Unreviewed Packages" menu title
if (
item.classList.contains("menu-title") &&
item.textContent.trim() === "Unreviewed Packages"
) {
break; // Stop processing after this point
}
// Check for package options (only reviewed packages reach here)
const packageOption = item.querySelector(".package-option");
if (packageOption) {
this.selectedPackages.push({
url: packageOption.dataset.packageUrl,
name: packageOption.dataset.packageName,
});
}
}
}
},
attachEventListeners() {
// Toggle package selection on dropdown item click
this.elements.packageOptions.forEach((option) => {
option.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation(); // Prevent dropdown from closing
const packageUrl = option.dataset.packageUrl;
const packageName = option.dataset.packageName;
this.togglePackageSelection(packageUrl, packageName);
});
});
// Remove package when X is clicked (using event delegation)
this.elements.pillsContainer.addEventListener("click", (e) => {
if (
e.target.classList.contains("package-remove-btn") ||
e.target.closest(".package-remove-btn")
) {
const pill = e.target.closest(".badge");
if (pill) {
const packageUrl = pill.dataset.packageUrl;
this.removePackage(packageUrl);
}
}
});
},
togglePackageSelection(packageUrl, packageName) {
const index = this.selectedPackages.findIndex(
(pkg) => pkg.url === packageUrl,
);
if (index !== -1) {
// Remove from selection
this.selectedPackages.splice(index, 1);
} else {
// Add to selection
this.selectedPackages.push({ url: packageUrl, name: packageName });
}
this.render();
},
removePackage(packageUrl) {
const index = this.selectedPackages.findIndex(
(pkg) => pkg.url === packageUrl,
);
if (index !== -1) {
this.selectedPackages.splice(index, 1);
this.render();
}
},
render() {
this.renderPills();
this.renderAddButton();
this.renderCheckmarks();
},
renderPills() {
// Clear existing pills and button (except placeholder)
const pills = this.elements.pillsContainer.querySelectorAll(".badge");
pills.forEach((pill) => pill.remove());
const existingButton = this.elements.pillsContainer.querySelector(
"#modal_package_add_button",
);
if (existingButton) {
existingButton.remove();
}
// Create pills from data
this.selectedPackages.forEach((pkg) => {
const pill = this.createPillElement(pkg.url, pkg.name);
this.elements.pillsContainer.appendChild(pill);
});
},
renderAddButton() {
// Only render button if there are packages available
if (this.elements.packageOptions.length === 0) {
return;
}
const button = document.createElement("button");
button.type = "button";
button.id = "modal_package_add_button";
button.setAttribute("popovertarget", "package_dropdown_menu");
button.style.cssText = "anchor-name:--anchor-packages";
button.className = "btn btn-sm btn-ghost gap-2 text-base-content/50";
button.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus-icon lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
Add Package
`;
this.elements.pillsContainer.appendChild(button);
},
createPillElement(packageUrl, packageName) {
const pill = document.createElement("span");
pill.className = "badge badge-outline gap-2 max-w-xs";
pill.dataset.packageUrl = packageUrl;
pill.dataset.packageName = packageName;
pill.innerHTML = `
<span class="truncate" title="${packageName}">${packageName}</span>
<svg xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 cursor-pointer hover:text-error package-remove-btn flex-shrink-0 rotate-45"
viewBox="0 0 24 24"
fill="none" stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14"/><path d="M12 5v14"/>
</svg>
`;
return pill;
},
renderCheckmarks() {
// Update all checkmarks based on selected packages
this.elements.packageOptions.forEach((option) => {
const packageUrl = option.dataset.packageUrl;
const isSelected = this.selectedPackages.some(
(pkg) => pkg.url === packageUrl,
);
const checkmark = option.querySelector(".package-checkmark");
if (checkmark) {
checkmark.classList.toggle("hidden", !isSelected);
}
});
},
getSelectedPackages() {
return this.selectedPackages.map((pkg) => pkg.url);
},
};
// Modal and Search Management
const modal = document.getElementById("search_modal");
const searchbar = document.getElementById("modal_searchbar");
const searchButton = document.getElementById("modal_search_button");
const modeButton = document.getElementById("modal_mode_button");
const resultsDiv = document.getElementById("search_results");
const loadingDiv = document.getElementById("search_loading");
// MutationObserver to detect when modal opens
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === "open" && modal.open) {
PackageSelector.render();
// Delay focus to allow CSS transitions to complete (modal has 0.3s transition)
setTimeout(() => {
searchbar.focus();
}, 320);
}
});
});
observer.observe(modal, { attributes: true });
// Close modal when clicking outside (on the backdrop)
// According to DaisyUI docs: https://daisyui.com/components/modal/
// The backdrop form with method="dialog" should handle closing automatically when its button is clicked.
// We also handle clicks directly on the dialog element (backdrop area) or the backdrop form.
modal.addEventListener("click", function (event) {
const backdrop = modal.querySelector(".modal-backdrop");
const modalBox = modal.querySelector(".modal-box");
// Close if clicking directly on the dialog element (backdrop area)
// or on the backdrop form (but ensure we're not clicking on modal-box content)
if (
event.target === modal ||
(backdrop &&
(event.target === backdrop || backdrop.contains(event.target)) &&
!modalBox.contains(event.target))
) {
modal.close();
}
});
// Clear results when modal closes
modal.addEventListener("close", function () {
resultsDiv.innerHTML = "";
loadingDiv.classList.add("hidden");
searchbar.value = "";
});
// Mode dropdown handlers
const dropdownMenu = document.getElementById("search_dropdown_menu");
const modeButtons = [
{ id: "modal_dropdown_text", text: "Text" },
{ id: "modal_dropdown_smiles_default", text: "Default" },
{ id: "modal_dropdown_smiles_canonical", text: "Canonical" },
{ id: "modal_dropdown_smiles_exact", text: "Exact" },
{ id: "modal_dropdown_inchikey", text: "InChIKey" },
];
modeButtons.forEach(({ id, text }) => {
document.getElementById(id).addEventListener("click", function (e) {
e.preventDefault();
modeButton.innerHTML =
text +
` <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>`;
// Close dropdown using popover API
if (dropdownMenu && typeof dropdownMenu.hidePopover === "function") {
dropdownMenu.hidePopover();
}
});
});
// Initialize Package Selector
PackageSelector.init();
// Search Response Handler
function handleSearchResponse(data) {
resultsDiv.innerHTML = "";
function makeContent(objs) {
let links = "";
objs.forEach((obj) => {
links += `<a href="${obj.url}" class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors">${obj.name}</a>`;
});
return links;
}
let allEmpty = true;
let content = "";
// Category order for better UX
const categoryOrder = [
"Compounds",
"Compound Structures",
"Rules",
"Reactions",
"Pathways",
];
categoryOrder.forEach((key) => {
if (!data[key] || data[key].length < 1) {
return;
}
allEmpty = false;
content += `
<div class="collapse collapse-arrow bg-base-200 mb-2">
<input type="checkbox" checked />
<div class="collapse-title font-medium">
${key} <span class="badge badge-neutral badge-sm ml-2">${data[key].length}</span>
</div>
<div class="collapse-content">
${makeContent(data[key])}
</div>
</div>
`;
});
if (allEmpty) {
resultsDiv.innerHTML = `
<div class="alert alert-warning">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<span>No results found</span>
</div>
`;
} else {
resultsDiv.innerHTML = `
<div class="mb-2">
<div class="text-sm font-semibold text-base-content/70 mb-2">Results</div>
${content}
</div>
`;
}
}
// Search Execution
function performSearch(e) {
e.preventDefault();
const query = searchbar.value.trim();
if (!query) {
console.log("Search phrase empty, won't do search");
return;
}
const selPacks = PackageSelector.getSelectedPackages();
if (selPacks.length < 1) {
console.log("No package selected, won't do search");
resultsDiv.innerHTML = `
<div class="alert alert-info">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Please select at least one package</span>
</div>
`;
return;
}
const mode = modeButton.textContent.trim().toLowerCase();
const params = new URLSearchParams();
selPacks.forEach((pack) => params.append("packages", pack));
params.append("search", query);
params.append("mode", mode);
// Show loading
loadingDiv.classList.remove("hidden");
resultsDiv.innerHTML = "";
fetch(`{{ SERVER_BASE }}/search?${params.toString()}`, {
method: "GET",
headers: {
Accept: "application/json",
},
})
.then((response) => {
if (!response.ok) {
throw new Error("Search request failed");
}
return response.json();
})
.then((result) => {
loadingDiv.classList.add("hidden");
handleSearchResponse(result);
})
.catch((error) => {
loadingDiv.classList.add("hidden");
console.error("Search error:", error);
resultsDiv.innerHTML = `
<div class="alert alert-error">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Search failed. Please try again.</span>
</div>
`;
});
}
// Event listeners for search
searchButton.addEventListener("click", performSearch);
searchbar.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
performSearch(e);
}
});
})();
</script>

View File

@ -1,4 +1,4 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% block content %} {% block content %}
@ -10,171 +10,146 @@
{% include "modals/objects/generic_delete_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="rule-detail"> <div class="space-y-2 p-4">
<div class="panel panel-default"> <!-- Header Section -->
<div <div class="card bg-base-100">
class="panel-heading" <div class="card-body">
id="headingPanel" <div class="flex items-center justify-between">
style="font-size:2rem;height: 46px" <h2 class="card-title text-2xl">{{ rule.name }}</h2>
> <div id="actionsButton" class="dropdown dropdown-end hidden">
{{ rule.name|safe }} <div tabindex="0" role="button" class="btn btn-ghost btn-sm">
<div <svg
id="actionsButton" xmlns="http://www.w3.org/2000/svg"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;" width="16"
class="dropdown" height="16"
> viewBox="0 0 24 24"
<a fill="none"
href="#" stroke="currentColor"
class="dropdown-toggle" stroke-width="2"
data-toggle="dropdown" stroke-linecap="round"
role="button" stroke-linejoin="round"
aria-haspopup="true" class="lucide lucide-wrench"
aria-expanded="false" >
><span class="glyphicon glyphicon-wrench"></span> Actions <path
<span class="caret"></span><span style="padding-right:1em"></span d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
></a> />
<ul id="actionsList" class="dropdown-menu"> </svg>
{% block actions %} Actions
{% include "actions/objects/rule.html" %} </div>
{% endblock %} <ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
>
{% block actions %}
{% include "actions/objects/rule.html" %}
{% endblock %}
</ul>
</div>
</div>
<p class="mt-2">{{ rule.description|safe }}</p>
</div>
</div>
{% if rule.aliases %}
<!-- Aliases -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Aliases</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for alias in rule.aliases %}
<li><a class="hover:bg-base-200">{{ alias }}</a></li>
{% endfor %}
</ul> </ul>
</div> </div>
</div> </div>
<div class="panel-body"> {% endif %}
<p>{{ rule.description|safe }}</p>
</div>
{% if rule.aliases %} <!-- Reaction Patterns -->
<!-- Aliases --> <div class="collapse-arrow bg-base-200 collapse">
<div <input type="checkbox" checked />
class="panel panel-default panel-heading list-group-item" <div class="collapse-title text-xl font-medium">Reaction Patterns</div>
style="background-color:silver" <div class="collapse-content">
> <div class="space-y-4">
<h4 class="panel-title">
<a
id="rule-aliases-link"
data-toggle="collapse"
data-parent="#rule-detail"
href="#rule-aliases"
>Aliases</a
>
</h4>
</div>
<div id="rule-aliases" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for alias in rule.aliases %}
<a class="list-group-item">{{ alias }}</a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Reaction Patterns -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="rule-reaction-patterns-link"
data-toggle="collapse"
data-parent="#rule-detail"
href="#rule-reaction-patterns"
>Reaction Patterns</a
>
</h4>
</div>
<div id="rule-reaction-patterns" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for r in rule.srs %} {% for r in rule.srs %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }}</a> <div class="card bg-base-100">
<div align="center"> <div class="card-body">
<p>{{ r.as_svg|safe }}</p> <a href="{{ r.url }}" class="link link-primary font-semibold"
>{{ r.name }}</a
>
<div class="mt-2 flex justify-center">{{ r.as_svg|safe }}</div>
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div>
<!-- Scenarios --> <!-- Scenarios -->
{% if rule.scenarios.all %} {% if rule.scenarios.all %}
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Scenarios</div>
> <div class="collapse-content">
<h4 class="panel-title"> <ul class="menu bg-base-100 rounded-box">
<a
id="rule-scenario-link"
data-toggle="collapse"
data-parent="#rule-detail"
href="#rule-scenario"
>Scenarios</a
>
</h4>
</div>
<div id="rule-scenario" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for s in rule.scenarios.all %} {% for s in rule.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}" <li>
>{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a <a href="{{ s.url }}" class="hover:bg-base-200"
> >{{ s.name }} <i>({{ s.package.name }})</i></a
>
</li>
{% endfor %} {% endfor %}
</div> </ul>
</div> </div>
{% endif %} </div>
{% endif %}
{% if rule.enzymelinks %} {% if rule.enzymelinks %}
<!-- EC Numbers --> <!-- EC Numbers -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">EC Numbers</div>
> <div class="collapse-content">
<h4 class="panel-title"> <div class="space-y-2">
<a
id="rule-ec-numbers-link"
data-toggle="collapse"
data-parent="#rule-detail"
href="#rule-ec-numbers"
>EC Numbers</a
>
</h4>
</div>
<div id="rule-ec-numbers" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for k, v in rule.get_grouped_enzymelinks.items %} {% for k, v in rule.get_grouped_enzymelinks.items %}
<div <div class="collapse-arrow bg-base-100 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-lg font-medium">{{ k }}</div>
> <div class="collapse-content">
<h4 class="panel-title"> <ul class="menu bg-base-200 rounded-box">
<a {% for enzyme in v %}
id="{{ k|slugify }}_Link" <li>
data-toggle="collapse" <a href="{{ enzyme.url }}" class="hover:bg-base-300">
data-parent="#{{ k|slugify }}_Accordion" <div class="flex w-full items-center justify-between">
href="#{{ k|slugify }}" <span>{{ enzyme.ec_number }}</span>
> <span class="text-sm opacity-70"
{{ k }} >{{ enzyme.linking_method }}</span
</a> >
</h4> </div>
</div> <div class="text-sm opacity-60">
<div id="{{ k|slugify }}" class="panel-collapse in collapse"> {{ enzyme.name }}
<div class="panel-body list-group-item"> </div>
{% for enzyme in v %} </a>
<a class="list-group-item" href="{{ enzyme.url }}"> </li>
{{ enzyme.ec_number }} {% endfor %}
<div style="position:absolute;bottom:10px;left:100px;"> </ul>
{{ enzyme.name }}
</div>
<div style="float:right;">
{{ enzyme.linking_method }}
</div>
</a>
{% endfor %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %} </div>
</div> {% endif %}
</div> </div>
<script>
// Show actions button if there are actions
document.addEventListener("DOMContentLoaded", function () {
const actionsButton = document.getElementById("actionsButton");
const actionsList = actionsButton?.querySelector("ul");
if (actionsList && actionsList.children.length > 0) {
actionsButton?.classList.remove("hidden");
}
});
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,4 +1,4 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% block content %} {% block content %}
@ -12,360 +12,261 @@
{% include "modals/objects/generic_delete_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="compound-detail"> <div class="space-y-2 p-4">
<div class="panel panel-default"> <!-- Header Section -->
<div <div class="card bg-base-100">
class="panel-heading" <div class="card-body">
id="headingPanel" <div class="flex items-center justify-between">
style="font-size:2rem;height: 46px" <h2 class="card-title text-2xl">{{ compound.name }}</h2>
> <div id="actionsButton" class="dropdown dropdown-end hidden">
{{ compound.name|safe }} <div tabindex="0" role="button" class="btn btn-ghost btn-sm">
<div <svg
id="actionsButton" xmlns="http://www.w3.org/2000/svg"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;" width="16"
class="dropdown" height="16"
> viewBox="0 0 24 24"
<a fill="none"
href="#" stroke="currentColor"
class="dropdown-toggle" stroke-width="2"
data-toggle="dropdown" stroke-linecap="round"
role="button" stroke-linejoin="round"
aria-haspopup="true" class="lucide lucide-wrench"
aria-expanded="false" >
><span class="glyphicon glyphicon-wrench"></span> Actions <path
<span class="caret"></span><span style="padding-right:1em"></span d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
></a> />
<ul id="actionsList" class="dropdown-menu"> </svg>
{% block actions %} Actions
{% include "actions/objects/compound.html" %} </div>
{% endblock %} <ul
</ul> tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
>
{% block actions %}
{% include "actions/objects/compound.html" %}
{% endblock %}
</ul>
</div>
</div> </div>
</div> <p class="mt-2">
<div class="panel-body">
<p>
The structures stored in this compound can be found at The structures stored in this compound can be found at
<a target="_blank" href="{{ compound.url }}/structure" role="button" <a
target="_blank"
href="{{ compound.url }}/structure"
class="link link-primary"
>Compound structures &gt;&gt;</a >Compound structures &gt;&gt;</a
> >
</p> </p>
</div> </div>
</div>
{% if compound.aliases %} {% if compound.aliases %}
<!-- Aliases --> <!-- Aliases -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Aliases</div>
> <div class="collapse-content">
<h4 class="panel-title"> <ul class="menu bg-base-100 rounded-box">
<a
id="compound-aliases-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-aliases"
>Aliases</a
>
</h4>
</div>
<div id="compound-aliases" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for alias in compound.aliases %} {% for alias in compound.aliases %}
<a class="list-group-item">{{ alias }}</a> <li><a class="hover:bg-base-200">{{ alias }}</a></li>
{% endfor %} {% endfor %}
</div> </ul>
</div> </div>
{% endif %} </div>
{% endif %}
<!-- Description --> <!-- Description -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Description</div>
> <div class="collapse-content">{{ compound.description }}</div>
<h4 class="panel-title"> </div>
<a
id="compound-desc-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-desc"
>Description</a
>
</h4>
</div>
<div id="compound-desc" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{{ compound.description|safe }}
</div>
</div>
<!-- Image --> <!-- Image Representation -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Image Representation</div>
> <div class="collapse-content">
<h4 class="panel-title"> <div class="flex justify-center">
<a {{ compound.default_structure.as_svg|safe }}
id="compound-image-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-image"
>Image Representation</a
>
</h4>
</div>
<div id="compound-image" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
{{ compound.default_structure.as_svg|safe }}
</div>
</div> </div>
</div> </div>
</div>
<!-- SMILES --> <!-- SMILES Representation -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">
> SMILES Representation
<h4 class="panel-title">
<a
id="compound-smiles-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-smiles"
>SMILES Representation</a
>
</h4>
</div> </div>
<div id="compound-smiles" class="panel-collapse in collapse"> <div class="collapse-content">
<div class="panel-body list-group-item"> {{ compound.default_structure.smiles }}
{{ compound.default_structure.smiles }}
</div>
</div> </div>
</div>
<!-- Canonical SMILES --> <!-- Canonical SMILES Representation -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">
> Canonical SMILES Representation
<h4 class="panel-title">
<a
id="compound-canonical-smiles-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-canonical-smiles"
>Canonical SMILES Representation</a
>
</h4>
</div> </div>
<div id="compound-canonical-smiles" class="panel-collapse in collapse"> <div class="collapse-content">
<div class="panel-body list-group-item"> {{ compound.default_structure.canonical_smiles }}
{{ compound.default_structure.canonical_smiles }}
</div>
</div> </div>
</div>
<!-- InChiKey --> <!-- InChIKey -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">InChIKey</div>
> <div class="collapse-content">
<h4 class="panel-title"> {{ compound.default_structure.inchikey }}
<a
id="compound-inchi-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-inchi"
>InChIKey</a
>
</h4>
</div>
<div id="compound-inchi" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{{ compound.default_structure.inchikey }}
</div>
</div> </div>
</div>
<!-- Reactions --> <!-- Reactions -->
{% if compound.related_reactions %} {% if compound.related_reactions %}
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Reactions</div>
> <div class="collapse-content">
<h4 class="panel-title"> <ul class="menu bg-base-100 rounded-box">
<a
id="compound-reaction-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-reaction"
>Reactions</a
>
</h4>
</div>
<div id="compound-reaction" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for r in compound.related_reactions %} {% for r in compound.related_reactions %}
<a class="list-group-item" href="{{ r.url }}" <li>
>{{ r.name|safe }} <i>({{ r.package.name|safe }})</i></a <a href="{{ r.url }}" class="hover:bg-base-200"
> >{{ r.name }} <i>({{ r.package.name }})</i></a
>
</li>
{% endfor %} {% endfor %}
</div> </ul>
</div> </div>
{% endif %} </div>
{% endif %}
<!-- Pathways --> <!-- Pathways -->
{% if compound.related_pathways %} {% if compound.related_pathways %}
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Pathways</div>
> <div class="collapse-content">
<h4 class="panel-title"> <ul class="menu bg-base-100 rounded-box">
<a
id="compound-pathway-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-pathway"
>Pathways</a
>
</h4>
</div>
<div id="compound-pathway" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for r in compound.related_pathways %} {% for r in compound.related_pathways %}
<a class="list-group-item" href="{{ r.url }}" <li>
>{{ r.name|safe }} <i>({{ r.package.name|safe }})</i></a <a href="{{ r.url }}" class="hover:bg-base-200"
> >{{ r.name }} <i>({{ r.package.name }})</i></a
>
</li>
{% endfor %} {% endfor %}
</div> </ul>
</div> </div>
{% endif %} </div>
{% endif %}
<!-- Scenarios --> <!-- Scenarios -->
{% if compound.scenarios.all %} {% if compound.scenarios.all %}
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Scenarios</div>
> <div class="collapse-content">
<h4 class="panel-title"> <ul class="menu bg-base-100 rounded-box">
<a
id="compound-scenario-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-scenario"
>Scenarios</a
>
</h4>
</div>
<div id="compound-scenario" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for s in compound.scenarios.all %} {% for s in compound.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}" <li>
>{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a <a href="{{ s.url }}" class="hover:bg-base-200"
> >{{ s.name }} <i>({{ s.package.name }})</i></a
>
</li>
{% endfor %} {% endfor %}
</div> </ul>
</div> </div>
{% endif %} </div>
{% endif %}
<!-- External Identifiers --> <!-- External Identifiers -->
{% if compound.get_external_identifiers %} {% if compound.get_external_identifiers %}
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">
> External Identifier
<h4 class="panel-title">
<a
id="compound-external-identifier-link"
data-toggle="collapse"
data-parent="#compound-detail"
href="#compound-external-identifier"
>External Identifier</a
>
</h4>
</div> </div>
<div <div class="collapse-content">
id="compound-external-identifier" <div class="space-y-2">
class="panel-collapse in collapse"
>
<div class="panel-body list-group-item">
{% if compound.get_pubchem_compound_identifiers %} {% if compound.get_pubchem_compound_identifiers %}
<div <div class="collapse-arrow bg-base-100 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-lg font-medium">
> PubChem Compound Identifier
<h4 class="panel-title"> </div>
<a <div class="collapse-content">
id="compound-pubchem-identifier-link" <ul class="menu bg-base-200 rounded-box">
data-toggle="collapse" {% for eid in compound.get_pubchem_compound_identifiers %}
data-parent="#compound-external-identifier" <li>
href="#compound-pubchem-identifier" <a
>PubChem Compound Identifier</a href="{{ eid.external_url }}"
> class="hover:bg-base-300"
</h4> >CID{{ eid.identifier_value }}</a
</div> >
<div </li>
id="compound-pubchem-identifier" {% endfor %}
class="panel-collapse in collapse" </ul>
> </div>
{% for eid in compound.get_pubchem_compound_identifiers %}
<a class="list-group-item" href="{{ eid.external_url }}"
>CID{{ eid.identifier_value }}</a
>
{% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if compound.get_pubchem_substance_identifiers %} {% if compound.get_pubchem_substance_identifiers %}
<div <div class="collapse-arrow bg-base-100 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-lg font-medium">
> PubChem Substance Identifier
<h4 class="panel-title"> </div>
<a <div class="collapse-content">
id="compound-pubchem-identifier-link" <ul class="menu bg-base-200 rounded-box">
data-toggle="collapse" {% for eid in compound.get_pubchem_substance_identifiers %}
data-parent="#compound-external-identifier" <li>
href="#compound-pubchem-identifier" <a
>PubChem Substance Identifier</a href="{{ eid.external_url }}"
> class="hover:bg-base-300"
</h4> >SID{{ eid.identifier_value }}</a
</div> >
<div </li>
id="compound-pubchem-identifier" {% endfor %}
class="panel-collapse in collapse" </ul>
> </div>
{% for eid in compound.get_pubchem_substance_identifiers %}
<a class="list-group-item" href="{{ eid.external_url }}"
>SID{{ eid.identifier_value }}</a
>
{% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if compound.get_chebi_identifiers %} {% if compound.get_chebi_identifiers %}
<div <div class="collapse-arrow bg-base-100 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-lg font-medium">
> ChEBI Identifier
<h4 class="panel-title"> </div>
<a <div class="collapse-content">
id="compound-chebi-identifier-link" <ul class="menu bg-base-200 rounded-box">
data-toggle="collapse" {% for eid in compound.get_chebi_identifiers %}
data-parent="#compound-external-identifier" <li>
href="#compound-chebi-identifier" <a
>ChEBI Identifier</a href="{{ eid.external_url }}"
> class="hover:bg-base-300"
</h4> >CHEBI:{{ eid.identifier_value }}</a
</div> >
<div </li>
id="compound-chebi-identifier" {% endfor %}
class="panel-collapse in collapse" </ul>
> </div>
{% for eid in compound.get_chebi_identifiers %}
<a class="list-group-item" href="{{ eid.external_url }}"
>CHEBI:{{ eid.identifier_value }}</a
>
{% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %} </div>
</div> {% endif %}
</div> </div>
<script>
// Show actions button if there are actions
document.addEventListener("DOMContentLoaded", function () {
const actionsButton = document.getElementById("actionsButton");
const actionsList = actionsButton?.querySelector("ul");
if (actionsList && actionsList.children.length > 0) {
actionsButton?.classList.remove("hidden");
}
});
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,4 +1,4 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% block content %} {% block content %}
@ -10,141 +10,109 @@
{% include "modals/objects/generic_delete_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="compound-structure-detail"> <div class="space-y-2 p-4">
<div class="panel panel-default"> <!-- Header Section -->
<div <div class="card bg-base-100">
class="panel-heading" <div class="card-body">
id="headingPanel" <div class="flex items-center justify-between">
style="font-size:2rem;height: 46px" <h2 class="card-title text-2xl">{{ compound_structure.name }}</h2>
> <div id="actionsButton" class="dropdown dropdown-end hidden">
{{ compound_structure.name|safe }} <div tabindex="0" role="button" class="btn btn-ghost btn-sm">
<div <svg
id="actionsButton" xmlns="http://www.w3.org/2000/svg"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;" width="16"
class="dropdown" height="16"
> viewBox="0 0 24 24"
<a fill="none"
href="#" stroke="currentColor"
class="dropdown-toggle" stroke-width="2"
data-toggle="dropdown" stroke-linecap="round"
role="button" stroke-linejoin="round"
aria-haspopup="true" class="lucide lucide-wrench"
aria-expanded="false" >
><span class="glyphicon glyphicon-wrench"></span> Actions <path
<span class="caret"></span><span style="padding-right:1em"></span d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
></a> />
<ul id="actionsList" class="dropdown-menu"> </svg>
{% block actions %} Actions
{% include "actions/objects/compound_structure.html" %} </div>
{% endblock %} <ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
>
{% block actions %}
{% include "actions/objects/compound_structure.html" %}
{% endblock %}
</ul>
</div>
</div>
<p class="mt-2">{{ compound_structure.description }}</p>
</div>
</div>
<!-- Image Representation -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Image Representation</div>
<div class="collapse-content">
<div class="flex justify-center">
{{ compound_structure.as_svg|safe }}
</div>
</div>
</div>
<!-- SMILES Representation -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">
SMILES Representation
</div>
<div class="collapse-content">{{ compound_structure.smiles }}</div>
</div>
{% if compound_structure.aliases %}
<!-- Aliases -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Aliases</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for alias in compound_structure.aliases %}
<li><a class="hover:bg-base-200">{{ alias }}</a></li>
{% endfor %}
</ul> </ul>
</div> </div>
</div> </div>
<div class="panel-body"> {% endif %}
<p>{{ compound_structure.description|safe }}</p>
</div>
<!-- Image --> {% if compound_structure.scenarios.all %}
<div <!-- Scenarios -->
class="panel panel-default panel-heading list-group-item" <div class="collapse-arrow bg-base-200 collapse">
style="background-color:silver" <input type="checkbox" checked />
> <div class="collapse-title text-xl font-medium">Scenarios</div>
<h4 class="panel-title"> <div class="collapse-content">
<a <ul class="menu bg-base-100 rounded-box">
id="compound-structure-image-link"
data-toggle="collapse"
data-parent="#compound-structure-detail"
href="#compound-structure-image"
>Image Representation</a
>
</h4>
</div>
<div id="compound-structure-image" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
{{ compound_structure.as_svg|safe }}
</div>
</div>
</div>
<!-- SMILES -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="compound-structure-smiles-link"
data-toggle="collapse"
data-parent="#compound-structure-detail"
href="#compound-structure-smiles"
>SMILES Representation</a
>
</h4>
</div>
<div id="compound-structure-smiles" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{{ compound_structure.smiles }}
</div>
</div>
{% if compound_structure.aliases %}
<!-- Aliases -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="compound-structure-aliases-link"
data-toggle="collapse"
data-parent="#compound-structure-detail"
href="#compound-structure-aliases"
>Aliases</a
>
</h4>
</div>
<div id="compound-structure-aliases" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for alias in compound_structure.aliases %}
<a class="list-group-item">{{ alias }}</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if compound_structure.scenarios.all %}
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="compound-structure-scenario-link"
data-toggle="collapse"
data-parent="#compound-structure-detail"
href="#compound-structure-scenario"
>Scenarios</a
>
</h4>
</div>
<div
id="compound-structure-scenario"
class="panel-collapse in collapse"
>
<div class="panel-body list-group-item">
{% for s in compound_structure.scenarios.all %} {% for s in compound_structure.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}" <li>
>{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a <a href="{{ s.url }}" class="hover:bg-base-200"
> >{{ s.name }} <i>({{ s.package.name }})</i></a
>
</li>
{% endfor %} {% endfor %}
</div> </ul>
</div> </div>
{% endif %} </div>
{% endif %}
<!-- Reactions -->
<!-- Pathways -->
</div>
</div> </div>
<script>
// Show actions button if there are actions
document.addEventListener("DOMContentLoaded", function () {
const actionsButton = document.getElementById("actionsButton");
const actionsList = actionsButton?.querySelector("ul");
if (actionsList && actionsList.children.length > 0) {
actionsButton?.classList.remove("hidden");
}
});
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,4 +1,4 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% block content %} {% block content %}
@ -9,214 +9,165 @@
{% include "modals/objects/generic_delete_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="edge-detail"> <div class="space-y-2 p-4">
<div class="panel panel-default"> <!-- Header Section -->
<div <div class="card bg-base-100">
class="panel-heading" <div class="card-body">
id="headingPanel" <div class="flex items-center justify-between">
style="font-size:2rem;height: 46px" <h2 class="card-title text-2xl">{{ edge.edge_label.name }}</h2>
> <div id="actionsButton" class="dropdown dropdown-end hidden">
{{ edge.edge_label.name|safe }} <div tabindex="0" role="button" class="btn btn-ghost btn-sm">
<div <svg
id="actionsButton" xmlns="http://www.w3.org/2000/svg"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;" width="16"
class="dropdown" height="16"
> viewBox="0 0 24 24"
<a fill="none"
href="#" stroke="currentColor"
class="dropdown-toggle" stroke-width="2"
data-toggle="dropdown" stroke-linecap="round"
role="button" stroke-linejoin="round"
aria-haspopup="true" class="lucide lucide-wrench"
aria-expanded="false" >
><span class="glyphicon glyphicon-wrench"></span> Actions <path
<span class="caret"></span><span style="padding-right:1em"></span d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
></a> />
<ul id="actionsList" class="dropdown-menu"> </svg>
{% block actions %} Actions
{% include "actions/objects/edge.html" %} </div>
{% endblock %} <ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
>
{% block actions %}
{% include "actions/objects/edge.html" %}
{% endblock %}
</ul>
</div>
</div>
</div>
</div>
<!-- Description -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Description</div>
<div class="collapse-content">{{ edge.description }}</div>
</div>
{% if edge.aliases %}
<!-- Aliases -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Aliases</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for alias in edge.aliases %}
<li><a class="hover:bg-base-200">{{ alias }}</a></li>
{% endfor %}
</ul> </ul>
</div> </div>
</div> </div>
{% endif %}
<!-- Description --> <!-- Image Representation -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Image Representation</div>
> <div class="collapse-content">
<h4 class="panel-title"> <div class="flex justify-center">{{ edge.edge_label.as_svg|safe }}</div>
<a
id="edge-desc-link"
data-toggle="collapse"
data-parent="#edge-detail"
href="#edge-desc"
>Description</a
>
</h4>
</div> </div>
<div id="edge-desc" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{{ edge.description|safe }}
</div>
</div>
{% if edge.aliases %}
<!-- Aliases -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="edge-aliases-link"
data-toggle="collapse"
data-parent="#edge-detail"
href="#edge-aliases"
>Aliases</a
>
</h4>
</div>
<div id="edge-aliases" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for alias in edge.aliases %}
<a class="list-group-item">{{ alias }}</a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Image -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="edge-image-link"
data-toggle="collapse"
data-parent="#edge-detail"
href="#edge-image"
>Image Representation</a
>
</h4>
</div>
<div id="edge-image" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
{{ edge.edge_label.as_svg|safe }}
</div>
</div>
</div>
<!-- Reaction Description -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="edge-description-link"
data-toggle="collapse"
data-parent="#edge-description-detail"
href="#edge-description-smiles"
>Reaction Description</a
>
</h4>
</div>
<div id="edge-description-smiles" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for educt in edge.start_nodes.all %}
<a class="btn btn-default" href="{{ educt.url }}"
>{{ educt.name|safe }}</a
>
{% endfor %}
<span
class="glyphicon glyphicon-arrow-right"
style="margin-left:5em;margin-right:5em;"
aria-hidden="true"
></span>
{% for product in edge.end_nodes.all %}
<a class="btn btn-default" href="{{ product.url }}"
>{{ product.name|safe }}</a
>
{% endfor %}
</div>
</div>
<!-- SMIRKS -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="edge-smirks-link"
data-toggle="collapse"
data-parent="#edge-detail"
href="#edge-smirks"
>SMIRKS Representation</a
>
</h4>
</div>
<div id="edge-smirks" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{{ edge.edge_label.smirks }}
</div>
</div>
{% if edge.edge_label.rules.all %}
<!-- Rules -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="edge-rules-link"
data-toggle="collapse"
data-parent="#edge-detail"
href="#edge-rules"
>Rules</a
>
</h4>
</div>
<div id="edge-rules" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for r in edge.edge_label.rules.all %}
<a class="list-group-item" href="{{ r.url }}"
>{{ r.name|safe }}</a
>
{% endfor %}
</div>
</div>
{% endif %}
{% if edge.scenarios.all %}
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="edge-scenario-link"
data-toggle="collapse"
data-parent="#edge-detail"
href="#edge-scenario"
>Scenarios</a
>
</h4>
</div>
<div id="edge-scenario" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for s in edge.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}"
>{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a
>
{% endfor %}
</div>
</div>
{% endif %}
</div> </div>
<!-- Reaction Description -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Reaction Description</div>
<div class="collapse-content">
<div class="flex flex-wrap items-center justify-center gap-4">
{% for educt in edge.start_nodes.all %}
<a href="{{ educt.url }}" class="btn btn-outline btn-sm"
>{{ educt.name }}</a
>
{% endfor %}
<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-arrow-right"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
{% for product in edge.end_nodes.all %}
<a href="{{ product.url }}" class="btn btn-outline btn-sm"
>{{ product.name }}</a
>
{% endfor %}
</div>
</div>
</div>
<!-- SMIRKS Representation -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">
SMIRKS Representation
</div>
<div class="collapse-content">{{ edge.edge_label.smirks }}</div>
</div>
{% if edge.edge_label.rules.all %}
<!-- Rules -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Rules</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for r in edge.edge_label.rules.all %}
<li>
<a href="{{ r.url }}" class="hover:bg-base-200">{{ r.name }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if edge.scenarios.all %}
<!-- Scenarios -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Scenarios</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for s in edge.scenarios.all %}
<li>
<a href="{{ s.url }}" class="hover:bg-base-200"
>{{ s.name }} <i>({{ s.package.name }})</i></a
>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div> </div>
<script>
// Show actions button if there are actions
document.addEventListener("DOMContentLoaded", function () {
const actionsButton = document.getElementById("actionsButton");
const actionsList = actionsButton?.querySelector("ul");
if (actionsList && actionsList.children.length > 0) {
actionsButton?.classList.remove("hidden");
}
});
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,163 +1,109 @@
{% extends "framework.html" %} {% extends "framework_modern.html" %}
{% block content %} {% block content %}
<div class="panel-group" id="enzyme-detail"> <div class="space-y-2 p-4">
<div class="panel panel-default"> <!-- Header Section -->
<div <div class="card bg-base-100">
class="panel-heading" <div class="card-body">
id="headingPanel" <h2 class="card-title text-2xl">{{ enzymelink.ec_number }}</h2>
style="font-size:2rem;height: 46px"
>
{{ enzymelink.ec_number }}
</div> </div>
</div>
<!-- Name --> <!-- Enzyme Name -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Enzyme Name</div>
> <div class="collapse-content">{{ enzymelink.name }}</div>
<h4 class="panel-title"> </div>
<a
id="enzyme-name-link"
data-toggle="collapse"
data-parent="#enzyme-detail"
href="#enzyme-name"
>Enzyme Name</a
>
</h4>
</div>
<div id="enzyme-name" class="panel-collapse in collapse">
<div class="panel-body list-group-item">{{ enzymelink.name }}</div>
</div>
<!-- Linking Method --> <!-- Linking Method -->
<div <div class="collapse-arrow bg-base-200 collapse">
class="panel panel-default panel-heading list-group-item" <input type="checkbox" checked />
style="background-color:silver" <div class="collapse-title text-xl font-medium">Linking Method</div>
> <div class="collapse-content">
<h4 class="panel-title"> {{ enzymelink.linking_method }}. &nbsp;<a
<a href="https://wiki.envipath.org/index.php/Rules#EnzymeLinks"
id="enzyme-linking-link" target="_blank"
data-toggle="collapse" class="link link-primary"
data-parent="#enzyme-detail" >Learn more &gt;&gt;</a
href="#enzyme-linking"
>Linking Method</a
>
</h4>
</div>
<div id="enzyme-linking" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{{ enzymelink.linking_method }}. &nbsp;<a
href="https://wiki.envipath.org/index.php/Rules#EnzymeLinks"
target="#"
>Learn more &gt;&gt;</a
>
</div>
</div>
{% if enzymelink.kegg_reaction_links %}
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
> >
<h4 class="panel-title"> </div>
<a </div>
id="enzyme-evidence-link"
data-toggle="collapse" {% if enzymelink.kegg_reaction_links %}
data-parent="#enzyme-detail" <!-- Linking Evidence -->
href="#enzyme-evidence" <div class="collapse-arrow bg-base-200 collapse">
>Linking Evidence</a <input type="checkbox" checked />
> <div class="collapse-title text-xl font-medium">Linking Evidence</div>
</h4> <div class="collapse-content">
</div> <ul class="menu bg-base-100 rounded-box">
<div id="enzyme-evidence" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for kl in enzymelink.kegg_reaction_links %} {% for kl in enzymelink.kegg_reaction_links %}
<a class="list-group-item" href="{{ kl.external_url }}" <li>
>{{ kl.identifier_value }}</a <a href="{{ kl.external_url }}" class="hover:bg-base-200"
> >{{ kl.identifier_value }}</a
>
</li>
{% endfor %} {% endfor %}
</div> </ul>
</div> </div>
{% endif %}
{% if enzymelink.reaction_evidence.all %}
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="enzyme-reaction-evidence-link"
data-toggle="collapse"
data-parent="#enzyme-detail"
href="#enzyme-reaction-evidence"
>Linking Evidence - enviPath Reactions</a
>
</h4>
</div>
<div id="enzyme-reaction-evidence" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for r in enzymelink.reaction_evidence.all %}
<a class="list-group-item" href="{{ r.url }}"
>{{ r.name }} <i>({{ r.package.name }})</i></a
>
{% endfor %}
</div>
</div>
{% endif %}
{% if enzymelink.edge_evidence.all %}
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="enzyme-edge-evidence-link"
data-toggle="collapse"
data-parent="#enzyme-detail"
href="#enzyme-edge-evidence"
>Linking Evidence - enviPath Pathways</a
>
</h4>
</div>
<div id="enzyme-edge-evidence" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% for e in enzymelink.edge_evidence.all %}
<a class="list-group-item" href="{{ e.pathway.url }}"
>{{ e.pathway.name }} <i>({{ r.package.name }})</i></a
>
{% endfor %}
</div>
</div>
{% endif %}
<!-- External DB Reference -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="enzyme-external-identifier-link"
data-toggle="collapse"
data-parent="#enzyme-detail"
href="#enzyme-external-identifier"
>External DB References</a
>
</h4>
</div> </div>
<div id="enzyme-external-identifier" class="panel-collapse in collapse"> {% endif %}
<div class="panel-body list-group-item">
<a {% if enzymelink.reaction_evidence.all %}
class="list-group-item" <!-- Linking Evidence - enviPath Reactions -->
href="http://www.brenda-enzymes.org/enzyme.php?ecno={{ enzymelink.ec_number }}" <div class="collapse-arrow bg-base-200 collapse">
target="_blank" <input type="checkbox" checked />
> <div class="collapse-title text-xl font-medium">
Brenda entry for {{ enzymelink.ec_number }}</a Linking Evidence - enviPath Reactions
>
</div> </div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for r in enzymelink.reaction_evidence.all %}
<li>
<a href="{{ r.url }}" class="hover:bg-base-200"
>{{ r.name }} <i>({{ r.package.name }})</i></a
>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if enzymelink.edge_evidence.all %}
<!-- Linking Evidence - enviPath Pathways -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">
Linking Evidence - enviPath Pathways
</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for e in enzymelink.edge_evidence.all %}
<li>
<a href="{{ e.pathway.url }}" class="hover:bg-base-200"
>{{ e.pathway.name }} <i>({{ e.pathway.package.name }})</i></a
>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<!-- External DB References -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">
External DB References
</div>
<div class="collapse-content">
<a
href="http://www.brenda-enzymes.org/enzyme.php?ecno={{ enzymelink.ec_number }}"
target="_blank"
class="link link-primary"
>Brenda entry for {{ enzymelink.ec_number }}</a
>
</div> </div>
</div> </div>
</div> </div>

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