Attempt at javascript.

This commit is contained in:
lwark
2025-06-02 10:23:28 -05:00
parent a2e1c52f22
commit 26292e275c
14 changed files with 415 additions and 138 deletions

View File

@@ -1207,7 +1207,7 @@ class ProcedureType(BaseClass):
# NOTE: An overly complicated list comprehension create a list of sample locations
# NOTE: next will return a blank cell if no value found for row/column
env = jinja_template_loading()
template = env.get_template("plate_map.html")
template = env.get_template("support/plate_map.html")
html = template.render(plate_rows=self.plate_rows, plate_columns=self.plate_columns, samples=sample_dicts, vw=vw)
return html + "<br/>"

View File

@@ -682,7 +682,7 @@ class Run(BaseClass, LogMixin):
for row in rows
for column in columns]
env = jinja_template_loading()
template = env.get_template("plate_map.html")
template = env.get_template("support/plate_map.html")
html = template.render(samples=output_samples, PLATE_ROWS=plate_rows, PLATE_COLUMNS=plate_columns)
return html + "<br/>"
@@ -1626,7 +1626,7 @@ class ClientSubmissionSampleAssociation(BaseClass):
# NOTE: Since there is no PCR, negliable result is necessary.
sample = self.to_sub_dict()
env = jinja_template_loading()
template = env.get_template("tooltip.html")
template = env.get_template("support/tooltip.html")
tooltip_text = template.render(fields=sample)
try:
control = self.sample.control
@@ -1880,7 +1880,7 @@ class RunSampleAssociation(BaseClass):
# NOTE: Since there is no PCR, negliable result is necessary.
sample = self.to_sub_dict()
env = jinja_template_loading()
template = env.get_template("tooltip.html")
template = env.get_template("support/tooltip.html")
tooltip_text = template.render(fields=sample)
try:
control = self.sample.control

View File

@@ -2,6 +2,8 @@
"""
from __future__ import annotations
import os
import sys, logging
from pathlib import Path
from pprint import pformat
@@ -15,7 +17,7 @@ from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from backend.db.models import Run, ProcedureType
from tools import jinja_template_loading, get_application_from_parent
from tools import jinja_template_loading, get_application_from_parent, render_details_template
from backend.validators import PydProcedure
logger = logging.getLogger(f"submissions.{__name__}")
@@ -54,13 +56,15 @@ class ProcedureCreation(QDialog):
self.webview.page().setWebChannel(self.channel)
def set_html(self):
env = jinja_template_loading()
template = env.get_template("procedure_creation.html")
template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read()
html = template.render(proceduretype=self.proceduretype.as_dict, run=self.run.to_dict(),
procedure=self.created_procedure.__dict__, plate_map=self.plate_map, css=css)
html = render_details_template(
template_name="procedure_creation",
# css_in=['new_context_menu'],
js_in=["procedure_form", "grid_drag", "context_menu"],
proceduretype=self.proceduretype.as_dict,
run=self.run.to_dict(),
procedure=self.created_procedure.__dict__,
plate_map=self.plate_map
)
with open("procedure.html", "w") as f:
f.write(html)
self.webview.setHtml(html)
@@ -86,21 +90,9 @@ class ProcedureCreation(QDialog):
def rearrange_plate(self, sample_list: list):
self.created_procedure.update_samples(sample_list=sample_list)
@pyqtSlot(str, str)
def log_drag(self, source_well: str, destination_well: str):
logger.debug(f"Source Index: {source_well} Destination Index: {destination_well}")
# source_well = source_well.split("-")
# destination_well = destination_well.split("-")
# source_row = int(source_well[0])
# source_column = int(source_well[1])
# destination_row = int(destination_well[0])
# destination_column = int(destination_well[1])
# self.created_procedure.shuffle_samples(
# source_row=source_row,
# source_column=source_column,
# destination_row=destination_row,
# destination_column=destination_column
# )
@pyqtSlot(str)
def log(self, logtext: str):
logger.debug(logtext)
# class ProcedureWebViewer(QWebEngineView):

View File

@@ -115,6 +115,9 @@ div.gallery {
display: none;
position: absolute;
z-index: 10;
background-color: rgba(229, 231, 228, 0.7);
border-radius: 2px;
border-color: black;
}
.context-menu--active {

View File

@@ -5,7 +5,10 @@
<meta charset="UTF-8">
{% if css %}
<style>
{{ css }}
{% for c in css %}
{{ c }}
{% endfor %}
</style>
{% endif %}
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
@@ -18,12 +21,12 @@
{% endblock %}
{% block signing_button %}{% endblock %}
</body>
<script>
{% block script %}
var backend;
new QWebChannel(qt.webChannelTransport, function (channel) {
backend = channel.objects.backend;
});
{% endblock %}
{% for j in js%}
<script>
{{ j }}
</script>
{% endfor %}
{% endblock %}
</html>

View File

@@ -0,0 +1,260 @@
//function openMulti() {
// if (document.querySelector(".selectWrapper").style.pointerEvents == "all") {
// document.querySelector(".selectWrapper").style.opacity = 0;
// document.querySelector(".selectWrapper").style.pointerEvents = "none";
// resetAllMenus();
// } else {
// document.querySelector(".selectWrapper").style.opacity = 1;
// document.querySelector(".selectWrapper").style.pointerEvents = "all";
// }
//}
//function nextMenu(e) {
// menuIndex = eval(event.target.parentNode.id.slice(-1));
// document.querySelectorAll(".multiSelect")[menuIndex].style.transform =
// "translateX(-100%)";
// // document.querySelectorAll(".multiSelect")[menuIndex].style.clipPath = "polygon(0 0, 0 0, 0 100%, 0% 100%)";
// document.querySelectorAll(".multiSelect")[menuIndex].style.clipPath =
// "polygon(100% 0, 100% 0, 100% 100%, 100% 100%)";
// document.querySelectorAll(".multiSelect")[menuIndex + 1].style.transform =
// "translateX(0)";
// document.querySelectorAll(".multiSelect")[menuIndex + 1].style.clipPath =
// "polygon(0 0, 100% 0, 100% 100%, 0% 100%)";
//}
//function prevMenu(e) {
// menuIndex = eval(event.target.parentNode.id.slice(-1));
// document.querySelectorAll(".multiSelect")[menuIndex].style.transform =
// "translateX(100%)";
// document.querySelectorAll(".multiSelect")[menuIndex].style.clipPath =
// "polygon(0 0, 0 0, 0 100%, 0% 100%)";
// document.querySelectorAll(".multiSelect")[menuIndex - 1].style.transform =
// "translateX(0)";
// document.querySelectorAll(".multiSelect")[menuIndex - 1].style.clipPath =
// "polygon(0 0, 100% 0, 100% 100%, 0% 100%)";
//}
//function resetAllMenus() {
// setTimeout(function () {
// var x = document.getElementsByClassName("multiSelect");
// var i;
// for (i = 1; i < x.length; i++) {
// x[i].style.transform = "translateX(100%)";
// x[i].style.clipPath = "polygon(0 0, 0 0, 0 100%, 0% 100%)";
// }
// document.querySelectorAll(".multiSelect")[0].style.transform =
// "translateX(0)";
// document.querySelectorAll(".multiSelect")[0].style.clipPath =
// "polygon(0 0, 100% 0, 100% 100%, 0% 100%)";
// }, 300);
//}
///////////////////////////////////////
///////////////////////////////////////
//
// H E L P E R F U N C T I O N S
//
///////////////////////////////////////
///////////////////////////////////////
function clickInsideElement( e, className ) {
var el = e.srcElement || e.target;
if ( el.classList.contains(className) ) {
return el;
} else {
while ( el = el.parentNode ) {
if ( el.classList && el.classList.contains(className) ) {
return el;
}
}
}
return false;
}
function getPosition(e) {
var posx = 0;
var posy = 0;
if (!e) var e = window.event;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
} else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return {
x: posx,
y: posy
}
}
// updated positionMenu function
function positionMenu(e) {
clickCoords = getPosition(e);
clickCoordsX = clickCoords.x;
clickCoordsY = clickCoords.y;
menuWidth = menu.offsetWidth + 4;
menuHeight = menu.offsetHeight + 4;
windowWidth = window.innerWidth;
windowHeight = window.innerHeight;
if ( (windowWidth - clickCoordsX) < menuWidth ) {
menu.style.left = windowWidth - menuWidth + "px";
} else {
menu.style.left = clickCoordsX + "px";
}
if ( (windowHeight - clickCoordsY) < menuHeight ) {
menu.style.top = windowHeight - menuHeight + "px";
} else {
menu.style.top = clickCoordsY + "px";
}
}
function menuItemListener( link ) {
const contextIndex = [...gridContainer.children].indexOf(taskItemInContext);
const task_id = taskItemInContext.getAttribute("id")
backend.log("Task action - " + link.getAttribute("data-action"))
switch (link.getAttribute("data-action")) {
case "InsertSample":
insertSample(contextIndex, task_id);
break;
case "InsertControl":
insertControl(contextIndex);
break;
case "RemoveSample":
removeSample(contextIndex);
break;
default:
backend.log("default");
break;
}
toggleMenuOff();
}
///////////////////////////////////////
///////////////////////////////////////
//
// C O R E F U N C T I O N S
//
///////////////////////////////////////
///////////////////////////////////////
/**
* Variables.
*/
var contextMenuClassName = "context-menu";
var contextMenuItemClassName = "context-menu__item";
var contextMenuLinkClassName = "context-menu__link";
var contextMenuActive = "context-menu--active";
var taskItemClassName = "well";
var taskItemInContext;
var clickCoords;
var clickCoordsX;
var clickCoordsY;
var menu = document.getElementById(contextMenuClassName);
var menuItems = menu.getElementsByClassName(contextMenuItemClassName);
var menuHeader = document.getElementById("menu-header");
var menuState = 0;
var menuWidth;
var menuHeight;
var menuPosition;
var menuPositionX;
var menuPositionY;
var windowWidth;
var windowHeight;
/**
* Initialise our application's code.
*/
function init() {
contextListener();
clickListener();
keyupListener();
resizeListener();
}
/**
* Listens for contextmenu events.
*/
function contextListener() {
document.addEventListener( "contextmenu", function(e) {
taskItemInContext = clickInsideElement( e, taskItemClassName );
if ( taskItemInContext ) {
e.preventDefault();
menuHeader.innerText = taskItemInContext.id;
toggleMenuOn();
positionMenu(e);
} else {
taskItemInContext = null;
menuHeader.text = "";
toggleMenuOff();
}
});
}
/**
* Listens for click events.
*/
function clickListener() {
document.addEventListener( "click", function(e) {
var clickeElIsLink = clickInsideElement( e, contextMenuLinkClassName );
if ( clickeElIsLink ) {
e.preventDefault();
menuItemListener( clickeElIsLink );
} else {
var button = e.which || e.button;
if ( button === 1 ) {
toggleMenuOff();
}
}
});
}
/**
* Listens for keyup events.
*/
function keyupListener() {
window.onkeyup = function(e) {
if ( e.keyCode === 27 ) {
toggleMenuOff();
}
}
}
/**
* Turns the custom context menu on.
*/
function toggleMenuOn() {
if ( menuState !== 1 ) {
menuState = 1;
menu.classList.add(contextMenuActive);
}
}
function toggleMenuOff() {
if ( menuState !== 0 ) {
menuState = 0;
menu.classList.remove(contextMenuActive);
}
}
///////////////////////////////////////
///////////////////////////////////////
//
// B A C K E N D F U N C T I O N S
//
///////////////////////////////////////
///////////////////////////////////////
function insertSample( index ) {
backend.log( "Index - " + index + ", InsertSample");
}
function insertControl( index ) {
backend.log( "Index - " + index + ", InsertEN");
var existing_ens = document.getElementsByClassName("EN");
backend.log(existing_ens.length);
}
function removeSample( index ) {
backend.log( "Index - " + index + ", RemoveSample");
}
/**
* Run the app.
*/
init();

View File

@@ -0,0 +1,4 @@
var backend;
new QWebChannel(qt.webChannelTransport, function (channel) {
backend = channel.objects.backend;
});

View File

@@ -0,0 +1,51 @@
const gridContainer = document.getElementById("plate-container");
let draggedItem = null;
//Handle Drag start
gridContainer.addEventListener("dragstart", (e) => {
draggedItem = e.target;
draggedItem.style.opacity = "0.5";
});
//Handle Drag End
gridContainer.addEventListener("dragend", (e) => {
e.target.style.opacity = "1";
draggedItem = null;
});
//handle dragging ove grid items
gridContainer.addEventListener("dragover", (e) => {
e.preventDefault();
});
//Handle Drop
gridContainer.addEventListener("drop", (e) => {
e.preventDefault();
const targetItem = e.target;
if (
targetItem &&
targetItem !== draggedItem &&
targetItem.classList.contains("well")
) {
const draggedIndex = [...gridContainer.children].indexOf(draggedItem);
const targetIndex = [...gridContainer.children].indexOf(targetItem);
if (draggedIndex < targetIndex) {
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Lesser");
gridContainer.insertBefore(draggedItem, targetItem.nextSibling);
} else {
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Greater");
gridContainer.insertBefore(draggedItem, targetItem);
}
output = [];
fullGrid = [...gridContainer.children];
fullGrid.forEach(function(item, index) {
output.push({sample_id: item.id, index: index + 1})
});
backend.rearrange_plate(output);
}
});

View File

@@ -0,0 +1,19 @@
document.getElementById("kittype").addEventListener("change", function() {
backend.update_kit(this.value);
})
var formchecks = document.getElementsByClassName('form_check');
for(let i = 0; i < formchecks.length; i++) {
formchecks[i].addEventListener("change", function() {
backend.check_toggle(formchecks[i].id, formchecks[i].checked);
})
};
var formtexts = document.getElementsByClassName('form_text');
for(let i = 0; i < formtexts.length; i++) {
formtexts[i].addEventListener("input", function() {
backend.text_changed(formtexts[i].id, formtexts[i].value);
})
};

View File

@@ -44,114 +44,15 @@
{% endif %}
</div>
</div>
{% include 'support/context_menu.html' %}
{% endblock %}
<nav class="context-menu">
<ul class="context-menu__items">
<li class="context-menu__item">
<a href="#" class="context-menu__link">
<i class="fa fa-eye"></i> View Task
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link">
<i class="fa fa-edit"></i> Edit Task
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link">
<i class="fa fa-times"></i> Delete Task
</a>
</li>
</ul>
</nav>
</body>
<script>
{% block script %}
{{ super() }}
document.getElementById("kittype").addEventListener("change", function() {
backend.update_kit(this.value);
})
var formchecks = document.getElementsByClassName('form_check');
for(let i = 0; i < formchecks.length; i++) {
formchecks[i].addEventListener("change", function() {
backend.check_toggle(formchecks[i].id, formchecks[i].checked);
})
};
var formtexts = document.getElementsByClassName('form_text');
for(let i = 0; i < formtexts.length; i++) {
formtexts[i].addEventListener("input", function() {
backend.text_changed(formtexts[i].id, formtexts[i].value);
})
};
const gridContainer = document.getElementById("plate-container");
let draggedItem = null;
//Handle Drag start
gridContainer.addEventListener("dragstart", (e) => {
draggedItem = e.target;
draggedItem.style.opacity = "0.5";
});
//Handle Drag End
gridContainer.addEventListener("dragend", (e) => {
e.target.style.opacity = "1";
draggedItem = null;
});
//handle dragging ove grid items
gridContainer.addEventListener("dragover", (e) => {
e.preventDefault();
});
//Handle Drop
gridContainer.addEventListener("drop", (e) => {
e.preventDefault();
const targetItem = e.target;
if (
targetItem &&
targetItem !== draggedItem &&
targetItem.classList.contains("well")
) {
const draggedIndex = [...gridContainer.children].indexOf(draggedItem);
const targetIndex = [...gridContainer.children].indexOf(targetItem);
if (draggedIndex < targetIndex) {
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Lesser");
gridContainer.insertBefore(draggedItem, targetItem.nextSibling);
} else {
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Greater");
gridContainer.insertBefore(draggedItem, targetItem);
}
output = [];
fullGrid = [...gridContainer.children];
fullGrid.forEach(function(item, index) {
output.push({sample_id: item.id, index: index + 1})
});
backend.rearrange_plate(output);
}
});
// Context Menu Functionality
var wells = document.querySelectorAll(".well");
for ( var i = 0, len = wells.length; i < len; i++ ) {
var welloi = wells[i];
welloi.addEventListener( "contextmenu", function(e) {
well = e.target
backend.log_drag(well.id, "el");
});
};
{% block script %}
{{ super() }}
{% endblock %}
{% endblock %}
</script>
</html>

View File

@@ -0,0 +1,20 @@
<nav class="context-menu" id="context-menu">
<h4 id="menu-header"></h4>
<ul class="context-menu__items">
<li class="context-menu__item">
<a href="#" class="context-menu__link" data-action="InsertSample">
<i class="fa fa-eye"></i> Insert Sample
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link" data-action="InsertControl">
<i class="fa fa-edit"></i> Insert EN
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link" data-action="RemoveSample">
<i class="fa fa-times"></i> Remove Sample
</a>
</li>
</ul>
</nav>

View File

@@ -443,6 +443,30 @@ def jinja_template_loading() -> Environment:
return env
def render_details_template(template_name:str, css_in:List[str]|str=[], js_in:List[str]|str=[], **kwargs) -> str:
if isinstance(css_in, str):
css_in = [css_in]
css_in = ["styles"] + css_in
css_in = [project_path.joinpath("src", "submissions", "templates", "css", f"{c}.css") for c in css_in]
if isinstance(js_in, str):
js_in = [js_in]
js_in = ["details"] + js_in
js_in = [project_path.joinpath("src", "submissions", "templates", "js", f"{j}.js") for j in js_in]
env = jinja_template_loading()
template = env.get_template(f"{template_name}.html")
# template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
css_out = []
for css in css_in:
with open(css, "r") as f:
css_out.append(f.read())
js_out = []
for js in js_in:
with open(js, "r") as f:
js_out.append(f.read())
return template.render(css=css_out, js=js_out, **kwargs)
def convert_well_to_row_column(input_str: str) -> Tuple[int, int]:
"""
Converts typical alphanumeric (i.e. "A2") to row, column