Skip to content

Commit

Permalink
Misc. UI Enhancements.
Browse files Browse the repository at this point in the history
* implementing layer ordering buttons, recreating text annotations as children of a pin marker icon and adds hover/click functionality for annotations when svg is loaded as markup, fixing styling for select to show dropshadow over any item that would be selected when clicking, populating layers when loading an existing observation, adding ability to set default tool when a form is loaded instead of being hardcoded to path tool, fixing svg view size style update in firefox

* implementing negative viewbox origin, fixing move tool functionalitty, exclude background rect and ignored layers from highlighting, changing some flexbox behavior, addressing issue where <tspan>s get different x,y than parent <text>, removing clipMove behavior

* fixing pin placement to match clicked location

* fixing issue where svg.js adds multiple duplicate xmlns attributes causing server side validation failures
  • Loading branch information
kml27 authored and mks-d committed Nov 12, 2019
1 parent 6816752 commit b0d03c9
Show file tree
Hide file tree
Showing 6 changed files with 6,318 additions and 5,751 deletions.
Expand Up @@ -16,28 +16,29 @@
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;

import org.openmrs.api.context.Context;
import org.openmrs.Concept; import org.openmrs.Concept;
import org.openmrs.Obs; import org.openmrs.Obs;
import org.openmrs.module.drawing.DrawingConstants; import org.openmrs.api.context.Context;
import org.openmrs.module.drawing.AnnotatedImage; import org.openmrs.module.drawing.AnnotatedImage;
import org.openmrs.module.drawing.DrawingConstants;
import org.openmrs.module.drawing.DrawingUtil; import org.openmrs.module.drawing.DrawingUtil;
import org.openmrs.module.htmlformentry.FormEntryContext; import org.openmrs.module.htmlformentry.FormEntryContext;
import org.openmrs.module.htmlformentry.FormEntryContext.Mode; import org.openmrs.module.htmlformentry.FormEntryContext.Mode;
import org.openmrs.module.htmlformentry.FormEntrySession; import org.openmrs.module.htmlformentry.FormEntrySession;
import org.openmrs.module.htmlformentry.FormSubmissionActions;
import org.openmrs.module.htmlformentry.FormSubmissionError; import org.openmrs.module.htmlformentry.FormSubmissionError;
import org.openmrs.module.htmlformentry.HtmlFormEntryUtil; import org.openmrs.module.htmlformentry.HtmlFormEntryUtil;
import org.openmrs.module.htmlformentry.action.FormSubmissionControllerAction; import org.openmrs.module.htmlformentry.action.FormSubmissionControllerAction;
import org.openmrs.module.htmlformentry.element.HtmlGeneratorElement; import org.openmrs.module.htmlformentry.element.HtmlGeneratorElement;
//import org.openmrs.module.htmlformentry.schema.ObsGroup;
//import org.openmrs.module.htmlformentry.action.ObsGroupAction;
import org.openmrs.obs.ComplexData; import org.openmrs.obs.ComplexData;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;


//public class DrawingSubmissionElement {
public class DrawingSubmissionElement implements HtmlGeneratorElement, FormSubmissionControllerAction { public class DrawingSubmissionElement implements HtmlGeneratorElement, FormSubmissionControllerAction {


private static final Log log = LogFactory.getLog(DrawingSubmissionElement.class); private static final Log log = LogFactory.getLog(DrawingSubmissionElement.class);
Expand All @@ -46,7 +47,7 @@ public class DrawingSubmissionElement implements HtmlGeneratorElement, FormSubmi


private Concept questionConcept; private Concept questionConcept;


private Obs existingObs; private Obs parentObs;


private enum DisplayMode { private enum DisplayMode {
Invalid, Invalid,
Expand All @@ -66,6 +67,8 @@ private enum DisplayMode {


private String fixedHeight; private String fixedHeight;


private String defaultTool;

private Document svgWidgetTemplate; private Document svgWidgetTemplate;


public DrawingSubmissionElement(FormEntryContext context, Map<String, String> parameters) public DrawingSubmissionElement(FormEntryContext context, Map<String, String> parameters)
Expand Down Expand Up @@ -185,9 +188,10 @@ public String apply(DisplayMode arg0) {
} }


Map<Concept, List<Obs>> prevObs = context.getExistingObs(); Map<Concept, List<Obs>> prevObs = context.getExistingObs();

if (prevObs != null && prevObs.size() > 0) { if (prevObs != null && prevObs.size() > 0) {


existingObs = prevObs.get(questionConcept).get(0); parentObs = prevObs.get(questionConcept).get(0);


//make sure the instance is loaded with complexData, according //make sure the instance is loaded with complexData, according
//to getComplexData() comments it won't be guaranteed unless //to getComplexData() comments it won't be guaranteed unless
Expand All @@ -199,7 +203,7 @@ public String apply(DisplayMode arg0) {
//(and thereby deletes the complex obs file) //(and thereby deletes the complex obs file)
//by not triggering the set of the dirty flag, saveObsNotDirty //by not triggering the set of the dirty flag, saveObsNotDirty
//will be called in ObsServiceImpl.saveObs() instead //will be called in ObsServiceImpl.saveObs() instead
existingObs = Context.getObsService().getObs(existingObs.getId()); parentObs = Context.getObsService().getObs(parentObs.getId());
} }


//TODO move to static (or does it require autowiring?) //TODO move to static (or does it require autowiring?)
Expand All @@ -208,6 +212,19 @@ public String apply(DisplayMode arg0) {
//(which over head is greater? clone or read from disk?) //(which over head is greater? clone or read from disk?)
svgWidgetTemplate = DrawingUtil.loadEditorMarkup(DrawingConstants.EDITOR_HTML_PATH); svgWidgetTemplate = DrawingUtil.loadEditorMarkup(DrawingConstants.EDITOR_HTML_PATH);


//opening the form for view/edit should not remove the existing observation
//TODO evaluate supporting the same concept multiple times in the same form
//Obs o = context.removeExistingObs(questionConcept, (Concept) null);

defaultTool = parameters.get("defaultTool");

//if no mode was specified, or using signature mode,
//default to path tool, while it's not strictly required as default
//for annotation mode, it currently should be for signature mode
if (defaultTool == null || instancePurpose.equals(DisplayMode.Signature)) {
defaultTool = "path-tool";
}

} }


@Override @Override
Expand All @@ -225,6 +242,8 @@ public Collection<FormSubmissionError> validateSubmission(FormEntryContext conte
@Override @Override
public void handleSubmission(FormEntrySession session, HttpServletRequest submission) { public void handleSubmission(FormEntrySession session, HttpServletRequest submission) {


FormSubmissionActions actions = session.getSubmissionActions();

String svgDOM = submission.getParameter("svgDOM"); String svgDOM = submission.getParameter("svgDOM");


//only create a "new" obs if this parameter is posted //only create a "new" obs if this parameter is posted
Expand All @@ -237,7 +256,7 @@ public void handleSubmission(FormEntrySession session, HttpServletRequest submis


if (svgDOM == null || StringUtils.isBlank(svgDOM)) { if (svgDOM == null || StringUtils.isBlank(svgDOM)) {


String markup = new String((byte[]) existingObs.getComplexData().getData()); String markup = new String((byte[]) parentObs.getComplexData().getData());


ai = new AnnotatedImage(markup); ai = new AnnotatedImage(markup);


Expand All @@ -248,23 +267,25 @@ public void handleSubmission(FormEntrySession session, HttpServletRequest submis
if (ai.getParsingError() != DrawingUtil.ErrorStatus.NONE) if (ai.getParsingError() != DrawingUtil.ErrorStatus.NONE)
throw new RuntimeException("Error parsing the SVG document" + ai.getParsingError()); throw new RuntimeException("Error parsing the SVG document" + ai.getParsingError());


byte[] svgByteArray = DrawingUtil.documentToString(ai.getImageDocument()).getBytes();

try { try {


byte[] svgByteArray = DrawingUtil.documentToString(ai.getImageDocument()).getBytes();

//TODO verify this update pipeline works, and behaviors between //TODO verify this update pipeline works, and behaviors between
//both update pipelines match //both update pipelines match
if (session.getContext().getMode() == Mode.EDIT && existingObs != null) { if (session.getContext().getMode() == Mode.EDIT && parentObs != null) {
session.getSubmissionActions().modifyObs(existingObs, questionConcept,
actions.modifyObs(parentObs, questionConcept,
new ComplexData(DrawingConstants.BASE_COMPLEX_OBS_FILENAME, svgByteArray), null, null); new ComplexData(DrawingConstants.BASE_COMPLEX_OBS_FILENAME, svgByteArray), null, null);


//should the modify codepath already handle setting previousObs? //should the modify codepath already handle setting previousObs?
//the new HFE code should handle this
Obs[] newObs = session.getSubmissionActions().getObsToCreate().parallelStream().filter(new Predicate<Obs>() { Obs[] newObs = session.getSubmissionActions().getObsToCreate().parallelStream().filter(new Predicate<Obs>() {


@Override @Override
public boolean test(Obs curObsToCreate) { public boolean test(Obs curObsToCreate) {
if (curObsToCreate.isComplex() && curObsToCreate.getComplexData() != null if (curObsToCreate.isComplex() && curObsToCreate.getComplexData() != null
&& svgByteArray.equals((byte[]) curObsToCreate.getComplexData().getData())) { && svgByteArray.equals(curObsToCreate.getComplexData().getData())) {
return true; return true;
} }


Expand All @@ -273,11 +294,18 @@ public boolean test(Obs curObsToCreate) {


}).toArray(Obs[]::new); }).toArray(Obs[]::new);


newObs[0].setPreviousVersion(existingObs); newObs[0].setPreviousVersion(parentObs);


} else { } else {
session.getSubmissionActions().createObs(questionConcept,
new ComplexData(DrawingConstants.BASE_COMPLEX_OBS_FILENAME, svgByteArray), null, null); //this is the first version of this module that supports versioning,
//so it will note this is the "1st version"... this version
//does not directly map to the module version and may not change with
//subsequent module versions, but could if more information is added, removed
//or how the information is stored changes

actions.createObs(questionConcept, new ComplexData(DrawingConstants.BASE_COMPLEX_OBS_FILENAME, svgByteArray),
null, null);
} }


} }
Expand Down Expand Up @@ -311,7 +339,7 @@ public String generateHtml(FormEntryContext context) {


//TODO for now, during testing if the file can't be loaded, return early //TODO for now, during testing if the file can't be loaded, return early
if (svgWidgetTemplate == null) { if (svgWidgetTemplate == null) {
return ""; return "<span style='color:red;'>Error loading drawing element</span>";
} }


//always hide save button when using editor in HFE forms, the submit button will //always hide save button when using editor in HFE forms, the submit button will
Expand All @@ -336,13 +364,12 @@ public String generateHtml(FormEntryContext context) {


//TODO add translated messages, parse HTML/XML template or provide //TODO add translated messages, parse HTML/XML template or provide
//to JS? //to JS?
DrawingUtil.translateLanguageKey("drawing.save"); //DrawingUtil.translateLanguageKey("drawing.save");


//in both view and edit, load the existing svg if it exists //in both view and edit, load the existing svg if it exists
if ((ctxMode == Mode.VIEW || ctxMode == Mode.EDIT) && existingObs != null) { if ((ctxMode == Mode.VIEW || ctxMode == Mode.EDIT) && parentObs != null) {


AnnotatedImage ai = (AnnotatedImage) new AnnotatedImage( AnnotatedImage ai = new AnnotatedImage(new String((byte[]) parentObs.getComplexData().getData()));
new String((byte[]) existingObs.getComplexData().getData()));


//TODO after SVG DOM insertion is implemented, handle this section appropriately //TODO after SVG DOM insertion is implemented, handle this section appropriately
Document svgDoc = ai.getImageDocument(); Document svgDoc = ai.getImageDocument();
Expand All @@ -354,11 +381,13 @@ public String generateHtml(FormEntryContext context) {


//in view mode, disable interaction //in view mode, disable interaction
if (ctxMode == Mode.VIEW) { if (ctxMode == Mode.VIEW) {

//in view mode only allow select
defaultTool = "select-tool";

//set disabled on all buttons //set disabled on all buttons


//set css pointer-events to none on root-svg rootSvg.getAttribute("class");
String rootClasses = rootSvg.getAttribute("class");
rootSvg.setAttribute("class", rootClasses + " disabledPointerEvents");


NodeList buttons = svgWidgetTemplate.getElementsByTagName("button"); NodeList buttons = svgWidgetTemplate.getElementsByTagName("button");
//Element clearAllButton = svgWidgetTemplate.getElementById(""); //Element clearAllButton = svgWidgetTemplate.getElementById("");
Expand All @@ -367,7 +396,7 @@ public String generateHtml(FormEntryContext context) {


Element button = (Element) buttons.item(i); Element button = (Element) buttons.item(i);


addClasses(button, "hidden"); addClasses(button, "hidden disabled");


} }


Expand All @@ -387,15 +416,18 @@ public String generateHtml(FormEntryContext context) {
Element imageNode = svgWidgetTemplate.createElement("image"); Element imageNode = svgWidgetTemplate.createElement("image");


imageNode.setAttribute("href", base64preload); imageNode.setAttribute("href", base64preload);
imageNode.setAttribute("data-ignore-layer", "true");
rootSvg.appendChild(imageNode); rootSvg.appendChild(imageNode);
} }


} }


//these elements will be used to set explicit heights or visibility for rootSvg.setAttribute("data-default-tool", defaultTool);

//these elements will be used to set explicit heights or visiblity for
//different purposes //different purposes
Element parentDiv = DrawingUtil.getElementById("view-layer-parent", svgWidgetTemplate); Element parentDiv = DrawingUtil.getElementById("view-layer-parent", svgWidgetTemplate);
Element layerParentDiv = DrawingUtil.getElementById("layer-list-div", svgWidgetTemplate); Element layerParentDiv = DrawingUtil.getElementById("layer-div", svgWidgetTemplate);
Element layerArrangeDiv = DrawingUtil.getElementById("arrange", svgWidgetTemplate); Element layerArrangeDiv = DrawingUtil.getElementById("arrange", svgWidgetTemplate);


switch (instancePurpose) { switch (instancePurpose) {
Expand Down Expand Up @@ -467,6 +499,7 @@ public String generateHtml(FormEntryContext context) {


default: default:
throw new RuntimeException("Invalid <drawing> displayMode"); throw new RuntimeException("Invalid <drawing> displayMode");

} }


Element[] fixedHeightDivs = { parentDiv, layerParentDiv, layerArrangeDiv }; Element[] fixedHeightDivs = { parentDiv, layerParentDiv, layerArrangeDiv };
Expand All @@ -486,9 +519,9 @@ public String generateHtml(FormEntryContext context) {


css += elemCss + "}"; css += elemCss + "}";
} }

} }
css += "</style>"; css += "</style>";

} }


//add a hidden input to provide the SVG on submission //add a hidden input to provide the SVG on submission
Expand All @@ -513,12 +546,10 @@ public String generateHtml(FormEntryContext context) {
widgetMarkup = "<span style='color:red;'>Error loading drawing element</span>"; widgetMarkup = "<span style='color:red;'>Error loading drawing element</span>";
} }


//TODO use "correct" method to build the path to these js resources
String js = "<script src=\"../../moduleResources/drawing/svg.js\"></script>" String js = "<script src=\"../../moduleResources/drawing/svg.js\"></script>"
+ "<script src=\"../../moduleResources/drawing/svg.draw.js\"></script>" + "<script src=\"../../moduleResources/drawing/svg.draw.js\"></script>"
+ "<script src=\"../../moduleResources/drawing/ui.js\"></script>"; + "<script src=\"../../moduleResources/drawing/ui.js\"></script>";


return css + widgetMarkup + js; return css + widgetMarkup + js;
} }

} }
Expand Up @@ -611,7 +611,7 @@ public void testBlankFormHtml(String html) {
String[] ids = { "text-tool", "template-button", "load-image-button", "path-tool", "line-tool", String[] ids = { "text-tool", "template-button", "load-image-button", "path-tool", "line-tool",
"circle-tool", "clear-all", "layer-info-vis-toggle", "edit-tool", "move-tool", "circle-tool", "clear-all", "layer-info-vis-toggle", "edit-tool", "move-tool",
"send-to-front-tool", "move-forward-tool", "move-backward-tool", "send-to-back-tool", "send-to-front-tool", "move-forward-tool", "move-backward-tool", "send-to-back-tool",
"void-tool", "delete-tool" }; "delete-tool" };


for (String id : ids) { for (String id : ids) {


Expand Down

0 comments on commit b0d03c9

Please sign in to comment.