Explicit header, text and bullet blocks rather than one HTML block in ui spec
This commit is contained in:
parent
85ebfef44a
commit
c4d0c0e9da
5 changed files with 82 additions and 41 deletions
|
|
@ -42,7 +42,8 @@ from openflexure_microscope_server.things.stage import BaseStage
|
|||
from openflexure_microscope_server.ui import (
|
||||
UI_ELEMENT_RESPONSE,
|
||||
Accordion,
|
||||
HTMLBlock,
|
||||
HeaderBlock,
|
||||
TextBlock,
|
||||
UIElementList,
|
||||
action_button_for,
|
||||
property_control_for,
|
||||
|
|
@ -642,7 +643,8 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
|
|||
|
||||
return UIElementList(
|
||||
[
|
||||
HTMLBlock(html=f"<h4>{self.display_name}</h4><p>{self.ui_blurb}<p>"),
|
||||
HeaderBlock(text=self.display_name, level=4),
|
||||
TextBlock(text=self.ui_blurb),
|
||||
Accordion(title="Background Detect", children=background_ui),
|
||||
Accordion(
|
||||
title="Scan Settings",
|
||||
|
|
@ -750,7 +752,8 @@ class RegularGridWorkflow(RectGridWorkflow[RegularGridSettingsModel]):
|
|||
)
|
||||
return UIElementList(
|
||||
[
|
||||
HTMLBlock(html=f"<h4>{self.display_name}</h4><p>{self.ui_blurb}<p>"),
|
||||
HeaderBlock(text=self.display_name, level=4),
|
||||
TextBlock(text=self.ui_blurb),
|
||||
Accordion(
|
||||
title="Scan Settings",
|
||||
children=scan_settings,
|
||||
|
|
|
|||
|
|
@ -5,24 +5,12 @@ from html.parser import HTMLParser
|
|||
from typing import Annotated, Any, Literal, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pydantic import BaseModel, BeforeValidator, RootModel
|
||||
from pydantic import BaseModel, BeforeValidator, Field, RootModel
|
||||
|
||||
import labthings_fastapi as lt
|
||||
|
||||
ALLOWED_TAGS = {
|
||||
"p",
|
||||
"i",
|
||||
"b",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"ul",
|
||||
"li",
|
||||
"a",
|
||||
}
|
||||
# Only allowed tags are italic, bold, and link. Also allows self closing br tags.
|
||||
ALLOWED_TAGS = {"i", "b", "a"}
|
||||
|
||||
ALLOWED_SCHEMES = {"http", "https"}
|
||||
|
||||
|
|
@ -116,8 +104,7 @@ class SafeHTMLParser(HTMLParser):
|
|||
def sanitise_html(html: str) -> str:
|
||||
"""Santitise HTML to only have a small list of allowed tags.
|
||||
|
||||
Tags allowed without attrs: ``<p>, <i>, <b>, <h1>, <h2>, <h3>, <h4>, <h5>, <h6>,``
|
||||
``<ul>, <li>``
|
||||
Tags allowed without attrs: ``<i>, <b>,``
|
||||
|
||||
Tags allowed with attrs: ``<a>`` is allowed with ``href`` only. This automatically
|
||||
appends ``target="_blank" rel="noopener noreferrer"`` so the link opens externally.
|
||||
|
|
@ -137,14 +124,31 @@ def sanitise_html(html: str) -> str:
|
|||
HtmlFragment = Annotated[str, BeforeValidator(sanitise_html)]
|
||||
|
||||
|
||||
class HTMLBlock(BaseModel):
|
||||
"""The data required for creating a block of HTML for the UI.
|
||||
class HeaderBlock(BaseModel):
|
||||
"""The data required for header."""
|
||||
|
||||
The HTML is limited to a small number of tags.
|
||||
"""
|
||||
element_type: Literal["header_block"] = "header_block"
|
||||
text: HtmlFragment
|
||||
"""The header text (can include basic HTML tags)."""
|
||||
|
||||
element_type: Literal["html_block"] = "html_block"
|
||||
html: HtmlFragment
|
||||
level: int = Field(default=2, ge=1, le=6)
|
||||
"""The header level. A number from 1-6."""
|
||||
|
||||
|
||||
class TextBlock(BaseModel):
|
||||
"""The data required for creating a block text."""
|
||||
|
||||
element_type: Literal["text_block"] = "text_block"
|
||||
text: HtmlFragment
|
||||
"""The text (can include basic HTML tags)."""
|
||||
|
||||
|
||||
class BulletBlock(BaseModel):
|
||||
"""The data required for creating a block text."""
|
||||
|
||||
element_type: Literal["bullet_block"] = "bullet_block"
|
||||
bullets: list[HtmlFragment]
|
||||
"""A list of text elements (can include basic HTML tags)."""
|
||||
|
||||
|
||||
class ActionButton(BaseModel):
|
||||
|
|
@ -304,7 +308,15 @@ class Container(BaseModel):
|
|||
children: "UIElementList"
|
||||
|
||||
|
||||
UIElementModels = HTMLBlock | PropertyControl | ActionButton | Accordion | Container
|
||||
UIElementModels = (
|
||||
HeaderBlock
|
||||
| TextBlock
|
||||
| BulletBlock
|
||||
| PropertyControl
|
||||
| ActionButton
|
||||
| Accordion
|
||||
| Container
|
||||
)
|
||||
|
||||
|
||||
class UIElementList(RootModel[list[UIElementModels]]):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ The script is removed but the contents remain.
|
|||
### Result
|
||||
```html
|
||||
alert("x")
|
||||
<p>Hello</p>
|
||||
Hello
|
||||
```
|
||||
## Test script in bold tag
|
||||
|
||||
|
|
@ -82,3 +82,14 @@ Remove the javascript from the links.
|
|||
```html
|
||||
<a href="https://openflexure.org" target="_blank" rel="noopener noreferrer">OpenFlexure</a>
|
||||
```
|
||||
|
||||
## Test basic formatting preserved
|
||||
Check basic elements are not changed
|
||||
### Input
|
||||
```html
|
||||
This is <b>bold</b> <i>italic</i> and on a new <br/> line.
|
||||
```
|
||||
### Result
|
||||
```html
|
||||
This is <b>bold</b> <i>italic</i> and on a new <br/> line.
|
||||
```
|
||||
|
|
@ -21,8 +21,9 @@ from openflexure_microscope_server.things.scan_workflows import (
|
|||
from openflexure_microscope_server.ui import (
|
||||
Accordion,
|
||||
ActionButton,
|
||||
HTMLBlock,
|
||||
HeaderBlock,
|
||||
PropertyControl,
|
||||
TextBlock,
|
||||
UIElementList,
|
||||
)
|
||||
|
||||
|
|
@ -385,20 +386,24 @@ def test_histo_workflow_settings_ui(histo_workflow, mocker):
|
|||
|
||||
ui = histo_workflow.settings_ui().root
|
||||
|
||||
# The UI is in 3 blocks
|
||||
assert len(ui) == 3
|
||||
# The top HTML block
|
||||
assert isinstance(ui[0], HTMLBlock)
|
||||
assert "<h4>Histo Scan</h4>" in ui[0].html
|
||||
# The UI is in 4 blocks
|
||||
assert len(ui) == 4
|
||||
# The top is a header
|
||||
assert isinstance(ui[0], HeaderBlock)
|
||||
assert ui[0].text == "Histo Scan"
|
||||
# The then some text (note the & is escaped)
|
||||
assert isinstance(ui[1], TextBlock)
|
||||
fragment = "This scan workflow is optimised for scanning H&E stained biopsies."
|
||||
assert fragment in ui[1].text
|
||||
# Followed by a background detect accordion
|
||||
assert isinstance(ui[1], Accordion)
|
||||
assert ui[1].title == "Background Detect"
|
||||
# And a Scan Settings accordion
|
||||
assert isinstance(ui[2], Accordion)
|
||||
assert ui[2].title == "Scan Settings"
|
||||
assert ui[2].title == "Background Detect"
|
||||
# And a Scan Settings accordion
|
||||
assert isinstance(ui[3], Accordion)
|
||||
assert ui[3].title == "Scan Settings"
|
||||
|
||||
# Background UI is ...
|
||||
background_ui = ui[1].children.root
|
||||
background_ui = ui[2].children.root
|
||||
# what the detector specifies as it settings ...
|
||||
assert background_ui[0] is mock_prop_control
|
||||
# plus 2 action buttons
|
||||
|
|
@ -409,7 +414,7 @@ def test_histo_workflow_settings_ui(histo_workflow, mocker):
|
|||
assert background_ui[2].action == "check_background"
|
||||
|
||||
# Finally check the contents of the scan settings accordion
|
||||
scan_settings = ui[2].children.root
|
||||
scan_settings = ui[3].children.root
|
||||
assert len(scan_settings) == 8
|
||||
for element in scan_settings:
|
||||
assert isinstance(element, PropertyControl)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
<template>
|
||||
<div v-for="(element, index) in elements" :key="index" class="uk-margin">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div v-if="element.element_type === 'html_block'" v-html="element.html"></div>
|
||||
<component :is="'h' + element.level" v-if="element.element_type === 'header_block'">
|
||||
<span v-html="element.text"></span>
|
||||
</component>
|
||||
<p v-if="element.element_type === 'text_block'" v-html="element.text"></p>
|
||||
<ul v-if="element.element_type === 'bullet_block'">
|
||||
<li
|
||||
v-for="(bulletText, bulletIndex) in element.bullets"
|
||||
:key="'bullet-' + bulletIndex"
|
||||
v-html="bulletText"
|
||||
></li>
|
||||
</ul>
|
||||
<!-- eslint-enable -->
|
||||
<server-specified-property-control
|
||||
v-else-if="element.element_type === 'property_control'"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue