Start of custom context menu for procedure creation
This commit is contained in:
@@ -1297,7 +1297,7 @@ class Run(BaseClass, LogMixin):
|
|||||||
submission_rank = self.get_submission_rank_of_sample(sample=sample)
|
submission_rank = self.get_submission_rank_of_sample(sample=sample)
|
||||||
if submission_rank != 0:
|
if submission_rank != 0:
|
||||||
row, column = plate_dict[submission_rank]
|
row, column = plate_dict[submission_rank]
|
||||||
ranked_samples.append(dict(sample_id=sample.sample_id, row=row, column=column, submission_rank=submission_rank, background_color="#6ffe1d"))
|
ranked_samples.append(dict(well_id=sample.sample_id, sample_id=sample.sample_id, row=row, column=column, submission_rank=submission_rank, background_color="#6ffe1d"))
|
||||||
else:
|
else:
|
||||||
unranked_samples.append(sample)
|
unranked_samples.append(sample)
|
||||||
possible_ranks = (item for item in list(plate_dict.keys()) if item not in [sample['submission_rank'] for sample in ranked_samples])
|
possible_ranks = (item for item in list(plate_dict.keys()) if item not in [sample['submission_rank'] for sample in ranked_samples])
|
||||||
@@ -1310,21 +1310,18 @@ class Run(BaseClass, LogMixin):
|
|||||||
continue
|
continue
|
||||||
row, column = plate_dict[submission_rank]
|
row, column = plate_dict[submission_rank]
|
||||||
ranked_samples.append(
|
ranked_samples.append(
|
||||||
dict(sample_id=sample.sample_id, row=row, column=column, submission_rank=submission_rank,
|
dict(well_id=sample.sample_id, sample_id=sample.sample_id, row=row, column=column, submission_rank=submission_rank,
|
||||||
background_color="#6ffe1d"))
|
background_color="#6ffe1d"))
|
||||||
padded_list = []
|
padded_list = []
|
||||||
for iii in range(1, proceduretype.total_wells+1):
|
for iii in range(1, proceduretype.total_wells+1):
|
||||||
sample = next((item for item in ranked_samples if item['submission_rank']==iii),
|
sample = next((item for item in ranked_samples if item['submission_rank']==iii),
|
||||||
dict(sample_id="", row=0, column=0, submission_rank=iii)
|
dict(well_id=f"blank_{iii}", sample_id="", row=0, column=0, submission_rank=iii, background_color="#ffffff")
|
||||||
)
|
)
|
||||||
padded_list.append(sample)
|
padded_list.append(sample)
|
||||||
logger.debug(f"Final padded list:\n{pformat(list(sorted(padded_list, key=itemgetter('submission_rank'))))}")
|
# logger.debug(f"Final padded list:\n{pformat(list(sorted(padded_list, key=itemgetter('submission_rank'))))}")
|
||||||
return list(sorted(padded_list, key=itemgetter('submission_rank')))
|
return list(sorted(padded_list, key=itemgetter('submission_rank')))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SampleType(BaseClass):
|
class SampleType(BaseClass):
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
name = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||||
|
|||||||
@@ -1397,53 +1397,17 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
self.reagentrole = {}
|
self.reagentrole = {}
|
||||||
self.possible_kits.insert(0, self.possible_kits.pop(self.possible_kits.index(kittype)))
|
self.possible_kits.insert(0, self.possible_kits.pop(self.possible_kits.index(kittype)))
|
||||||
|
|
||||||
def shuffle_samples(self, source_row: int, source_column: int, destination_row: int, destination_column=int):
|
def update_samples(self, sample_list: List[dict]):
|
||||||
logger.debug(f"Attempting sample shuffle.")
|
logger.debug(f"Incoming sample_list:\n{pformat(sample_list)}")
|
||||||
try:
|
for sample_dict in sample_list:
|
||||||
source_sample = next(
|
try:
|
||||||
(sample for sample in self.samples if sample.row == source_row and sample.column == source_column))
|
sample = next((item for item in self.samples if item.sample_id.upper()==sample_dict['sample_id'].upper()))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise StopIteration("Couldn't find proper sample.")
|
continue
|
||||||
logger.debug(f"Source Well: {source_row}, {source_column}")
|
row, column = self.proceduretype.ranked_plate[sample_dict['index']]
|
||||||
logger.debug(f"Destination Well: {destination_row}, {destination_column}")
|
sample.row = row
|
||||||
updateable_samples = []
|
sample.column = column
|
||||||
if source_row > destination_row and source_column >= destination_column:
|
logger.debug(f"Updated samples:\n{pformat(self.samples)}")
|
||||||
logger.debug(f"Sample was moved ahead.")
|
|
||||||
movement = "pos"
|
|
||||||
for sample in self.samples:
|
|
||||||
if sample.row >= destination_row and sample.column >= destination_column:
|
|
||||||
if sample.row <= source_row and sample.column <= source_column:
|
|
||||||
updateable_samples.append(sample)
|
|
||||||
|
|
||||||
elif source_row < destination_row and source_column <= destination_column:
|
|
||||||
logger.debug(f"Sample was moved back.")
|
|
||||||
movement = "neg"
|
|
||||||
for sample in self.samples:
|
|
||||||
if sample.row <= destination_row and sample.column <= destination_column:
|
|
||||||
if sample.row >= source_row and sample.column >= source_column:
|
|
||||||
updateable_samples.append(sample)
|
|
||||||
else:
|
|
||||||
logger.debug(f"Don't know what happened.")
|
|
||||||
logger.debug(f"Samples to be updated: {pformat(updateable_samples)}")
|
|
||||||
for sample in updateable_samples:
|
|
||||||
if sample.row == source_row and sample.column == source_column:
|
|
||||||
sample.row = destination_row
|
|
||||||
sample.column = destination_column
|
|
||||||
else:
|
|
||||||
match movement:
|
|
||||||
case "pos":
|
|
||||||
if sample.row + 1 > 8:
|
|
||||||
sample.column += 1
|
|
||||||
sample.row = 1
|
|
||||||
else:
|
|
||||||
sample.row += 1
|
|
||||||
case "neg":
|
|
||||||
if sample.row - 1 <= 0:
|
|
||||||
sample.column -= 1
|
|
||||||
sample.row = 8
|
|
||||||
else:
|
|
||||||
sample.row -= 1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PydClientSubmission(PydBaseClass):
|
class PydClientSubmission(PydBaseClass):
|
||||||
|
|||||||
117
src/submissions/frontend/widgets/procedure_creation.py
Normal file
117
src/submissions/frontend/widgets/procedure_creation.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import sys, logging
|
||||||
|
from pathlib import Path
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from PyQt6.QtCore import pyqtSlot, Qt
|
||||||
|
from PyQt6.QtGui import QContextMenuEvent, QAction
|
||||||
|
from PyQt6.QtWebChannel import QWebChannel
|
||||||
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
from PyQt6.QtWidgets import QDialog, QGridLayout, QMenu
|
||||||
|
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 backend.validators import PydProcedure
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
|
class ProcedureCreation(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, run: Run, proceduretype: ProcedureType):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.run = run
|
||||||
|
self.proceduretype = proceduretype
|
||||||
|
self.setWindowTitle(f"New {proceduretype.name} for { run.rsl_plate_num }")
|
||||||
|
self.created_procedure = self.proceduretype.construct_dummy_procedure(run=self.run)
|
||||||
|
self.created_procedure.update_kittype_reagentroles(kittype=self.created_procedure.possible_kits[0])
|
||||||
|
self.created_procedure.samples = self.run.constuct_sample_dicts_for_proceduretype(proceduretype=self.proceduretype)
|
||||||
|
# logger.debug(f"Samples to map\n{pformat(self.created_procedure.samples)}")
|
||||||
|
self.plate_map = self.proceduretype.construct_plate_map(sample_dicts=self.created_procedure.samples)
|
||||||
|
# logger.debug(f"Plate map: {self.plate_map}")
|
||||||
|
# logger.debug(f"Created dummy: {self.created_procedure}")
|
||||||
|
self.app = get_application_from_parent(parent)
|
||||||
|
self.webview = QWebEngineView(parent=self)
|
||||||
|
self.webview.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
|
||||||
|
self.webview.setMinimumSize(1200, 800)
|
||||||
|
self.webview.setMaximumWidth(1200)
|
||||||
|
# NOTE: Decide if exporting should be allowed.
|
||||||
|
# self.webview.loadFinished.connect(self.activate_export)
|
||||||
|
self.layout = QGridLayout()
|
||||||
|
# NOTE: button to export a pdf version
|
||||||
|
self.layout.addWidget(self.webview, 1, 0, 10, 10)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self.setFixedWidth(self.webview.width() + 20)
|
||||||
|
# NOTE: setup channel
|
||||||
|
self.channel = QWebChannel()
|
||||||
|
self.channel.registerObject('backend', self)
|
||||||
|
self.set_html()
|
||||||
|
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)
|
||||||
|
with open("procedure.html", "w") as f:
|
||||||
|
f.write(html)
|
||||||
|
self.webview.setHtml(html)
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
|
def text_changed(self, key: str, new_value: str):
|
||||||
|
# logger.debug(f"New value for {key}: {new_value}")
|
||||||
|
attribute = getattr(self.created_procedure, key)
|
||||||
|
attribute['value'] = new_value
|
||||||
|
|
||||||
|
@pyqtSlot(str, bool)
|
||||||
|
def check_toggle(self, key: str, ischecked: bool):
|
||||||
|
# logger.debug(f"{key} is checked: {ischecked}")
|
||||||
|
setattr(self.created_procedure, key, ischecked)
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def update_kit(self, kittype):
|
||||||
|
self.created_procedure.update_kittype_reagentroles(kittype=kittype)
|
||||||
|
logger.debug({k: v for k, v in self.created_procedure.__dict__.items() if k != "plate_map"})
|
||||||
|
self.set_html()
|
||||||
|
|
||||||
|
@pyqtSlot(list)
|
||||||
|
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
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# class ProcedureWebViewer(QWebEngineView):
|
||||||
|
#
|
||||||
|
# def __init__(self, *args, **kwargs):
|
||||||
|
# super().__init__(*args, **kwargs)
|
||||||
|
#
|
||||||
|
# def contextMenuEvent(self, event: QContextMenuEvent):
|
||||||
|
# self.menu = self.page().createStandardContextMenu()
|
||||||
|
# self.menu = self.createStandardContextMenu()
|
||||||
|
# add_sample = QAction("Add Sample")
|
||||||
|
# self.menu = QMenu()
|
||||||
|
# self.menu.addAction(add_sample)
|
||||||
|
# self.menu.popup(event.globalPos())
|
||||||
@@ -110,3 +110,13 @@ div.gallery {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu--active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="plate" id="plate-container" style="grid-template-columns: repeat({{ plate_columns }}, {{ vw }}vw);grid-template-rows: repeat({{ plate_rows }}, {{ vw }}vw);">
|
<div class="plate" id="plate-container" style="grid-template-columns: repeat({{ plate_columns }}, {{ vw }}vw);grid-template-rows: repeat({{ plate_rows }}, {{ vw }}vw);">
|
||||||
{% for sample in samples %}
|
{% for sample in samples %}
|
||||||
<div class="well" draggable="true" id="sample_{{ sample['submission_rank'] }}" style="background-color: {{ sample['background_color'] }};">
|
<div class="well" draggable="true" id="{{ sample['well_id'] }}" style="background-color: {{ sample['background_color'] }};">
|
||||||
<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}</p>
|
<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}</p>
|
||||||
<!-- <div class="tooltip" style="font-size: 0.5em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}-->
|
<!-- <div class="tooltip" style="font-size: 0.5em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}-->
|
||||||
<!-- <span class="tooltiptext">{{ sample['tooltip'] }}</span>-->
|
<!-- <span class="tooltiptext">{{ sample['tooltip'] }}</span>-->
|
||||||
|
|||||||
@@ -45,6 +45,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
{% block script %}
|
{% block script %}
|
||||||
@@ -114,11 +133,25 @@
|
|||||||
output = [];
|
output = [];
|
||||||
fullGrid = [...gridContainer.children];
|
fullGrid = [...gridContainer.children];
|
||||||
fullGrid.forEach(function(item, index) {
|
fullGrid.forEach(function(item, index) {
|
||||||
output.push({item: item, index: index})
|
output.push({sample_id: item.id, index: index + 1})
|
||||||
});
|
});
|
||||||
backend.replow(output);
|
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");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user