ontobdc-view 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ontobdc_view/__init__.py +236 -0
- ontobdc_view/component/__init__.py +0 -0
- ontobdc_view/component/adapter/component_source.py +127 -0
- ontobdc_view/component/asset/onto-csv-file-tile.js +373 -0
- ontobdc_view/component/asset/onto-data-container-tile.js +168 -0
- ontobdc_view/component/asset/onto-file-tree-tile.js +420 -0
- ontobdc_view/component/asset/onto-generic-file-tile.js +249 -0
- ontobdc_view/component/asset/onto-image-file-tile.js +247 -0
- ontobdc_view/component/asset/onto-language-tile.js +112 -0
- ontobdc_view/component/asset/onto-logo-tile.js +134 -0
- ontobdc_view/component/asset/onto-pdf-file-tile.js +261 -0
- ontobdc_view/component/asset/onto-photo-tile.js +180 -0
- ontobdc_view/component/asset/onto-presentation-surface.js +465 -0
- ontobdc_view/component/asset/onto-theme-tile.js +158 -0
- ontobdc_view/component/asset/onto-workstream-tile.js +386 -0
- ontobdc_view/component/plugin/__init__.py +0 -0
- ontobdc_view/component/plugin/csv_file_tile.py +24 -0
- ontobdc_view/component/plugin/data_container_tile.py +35 -0
- ontobdc_view/component/plugin/file_tree_tile.py +28 -0
- ontobdc_view/component/plugin/generic_file_tile.py +30 -0
- ontobdc_view/component/plugin/image_file_tile.py +24 -0
- ontobdc_view/component/plugin/language_tile.py +29 -0
- ontobdc_view/component/plugin/logo_tile.py +29 -0
- ontobdc_view/component/plugin/pdf_file_tile.py +24 -0
- ontobdc_view/component/plugin/photo_tile.py +31 -0
- ontobdc_view/component/plugin/theme_tile.py +29 -0
- ontobdc_view/component/plugin/workstream_tile.py +42 -0
- ontobdc_view/page/asset/work_stream_view.css +628 -0
- ontobdc_view/page/asset/work_stream_view.html.j2 +53 -0
- ontobdc_view/page/asset/work_stream_view.js +968 -0
- ontobdc_view/page/plugin/work_stream_view.py +30 -0
- ontobdc_view-0.2.0.dist-info/METADATA +183 -0
- ontobdc_view-0.2.0.dist-info/RECORD +35 -0
- ontobdc_view-0.2.0.dist-info/WHEEL +4 -0
- ontobdc_view-0.2.0.dist-info/licenses/LICENSE +201 -0
ontobdc_view/__init__.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
import pkgutil
|
|
7
|
+
from importlib.resources import files
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"__version__",
|
|
12
|
+
"component_source",
|
|
13
|
+
"page_asset_root",
|
|
14
|
+
"page_asset_path",
|
|
15
|
+
"read_page_asset",
|
|
16
|
+
"render_entity_view",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def component_source(tag: str) -> Optional[str]:
|
|
23
|
+
"""Return a Tile's ready-to-embed JS source by custom-element tag.
|
|
24
|
+
|
|
25
|
+
Delegates to `ComponentSourceAdapter` (`ontobdc.shared`'s
|
|
26
|
+
`ComponentSourcePort` implementation) — this function is just the
|
|
27
|
+
package's public entry point, matching how every other capability here
|
|
28
|
+
is exposed at the top level.
|
|
29
|
+
"""
|
|
30
|
+
from .component.adapter.component_source import ComponentSourceAdapter
|
|
31
|
+
|
|
32
|
+
return ComponentSourceAdapter().component_source(tag)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def page_asset_root():
|
|
36
|
+
"""Return the Traversable root containing Page HTML/CSS/JS assets."""
|
|
37
|
+
return files("ontobdc_view").joinpath("page", "asset")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def page_asset_path(name: str):
|
|
41
|
+
"""Return a packaged Page asset by filename."""
|
|
42
|
+
return page_asset_root().joinpath(name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def read_page_asset(name: str, *, encoding: str = "utf-8") -> str:
|
|
46
|
+
"""Read a packaged Page asset (template/CSS/JS) as text."""
|
|
47
|
+
return page_asset_path(name).read_text(encoding=encoding)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _iter_page_descriptors():
|
|
51
|
+
from ontobdc.shared.domain.port.page import PagePort
|
|
52
|
+
|
|
53
|
+
package = importlib.import_module("ontobdc_view.page.plugin")
|
|
54
|
+
package_prefix = f"{package.__name__}."
|
|
55
|
+
for _, name, _ in pkgutil.walk_packages(package.__path__, package_prefix):
|
|
56
|
+
module = importlib.import_module(name)
|
|
57
|
+
for _, obj in inspect.getmembers(module):
|
|
58
|
+
if not inspect.isclass(obj):
|
|
59
|
+
continue
|
|
60
|
+
try:
|
|
61
|
+
if not issubclass(obj, PagePort) or obj is PagePort:
|
|
62
|
+
continue
|
|
63
|
+
except TypeError:
|
|
64
|
+
continue
|
|
65
|
+
yield obj
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _matching_page_descriptor(entity_type_uris: List[str]):
|
|
69
|
+
type_set = set(entity_type_uris)
|
|
70
|
+
for descriptor in _iter_page_descriptors():
|
|
71
|
+
required = set(descriptor.METADATA.required_uris)
|
|
72
|
+
if required & type_set:
|
|
73
|
+
return descriptor
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_ANNOTATION_MODULE_LOAD_ORDER = (
|
|
78
|
+
"annotation_model.js",
|
|
79
|
+
"annotation_visual_contract.js",
|
|
80
|
+
"annotation_store.js",
|
|
81
|
+
"annotation_surface.js",
|
|
82
|
+
"annotation_visual_resolver.js",
|
|
83
|
+
"annotation_renderer.js",
|
|
84
|
+
"annotation_field_factory.js",
|
|
85
|
+
"annotation_form_registry.js",
|
|
86
|
+
"annotation_editor.js",
|
|
87
|
+
"annotation_geometry_controller.js",
|
|
88
|
+
"annotation_lifecycle.js",
|
|
89
|
+
"annotation_workspace.js",
|
|
90
|
+
"annotation_subject_page.js",
|
|
91
|
+
"annotation_runtime.js",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _annotation_runtime_script() -> Optional[str]:
|
|
96
|
+
"""Concatenate ontobdc's native annotation modules, in dependency order.
|
|
97
|
+
|
|
98
|
+
These live in `ontobdc` (`view/plugin/asset/js/annotation/`), not here
|
|
99
|
+
— `ontobdc_view` already depends on `ontobdc`, so reading its packaged
|
|
100
|
+
JS assets to inline them into a Page is a legitimate use of that
|
|
101
|
+
dependency, not a layering violation. Returns None if the modules
|
|
102
|
+
aren't installed/found, so a Page can render without annotations
|
|
103
|
+
rather than fail outright.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
root = files("ontobdc").joinpath("view", "plugin", "asset", "js", "annotation")
|
|
107
|
+
parts = [
|
|
108
|
+
root.joinpath(name).read_text(encoding="utf-8")
|
|
109
|
+
for name in _ANNOTATION_MODULE_LOAD_ORDER
|
|
110
|
+
]
|
|
111
|
+
except (FileNotFoundError, OSError, ModuleNotFoundError):
|
|
112
|
+
return None
|
|
113
|
+
return "\n".join(parts)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_ANNOTATION_CSS_FILES = (
|
|
117
|
+
"annotation_visual.css",
|
|
118
|
+
"annotation_editor.css",
|
|
119
|
+
"annotation_workspace.css",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _annotation_runtime_style() -> str:
|
|
124
|
+
try:
|
|
125
|
+
root = files("ontobdc").joinpath("view", "plugin", "asset", "css")
|
|
126
|
+
parts = [
|
|
127
|
+
root.joinpath(name).read_text(encoding="utf-8")
|
|
128
|
+
for name in _ANNOTATION_CSS_FILES
|
|
129
|
+
]
|
|
130
|
+
except (FileNotFoundError, OSError, ModuleNotFoundError):
|
|
131
|
+
return ""
|
|
132
|
+
return "\n".join(parts)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _workstream_context_script() -> str:
|
|
136
|
+
"""Read ontobdc's workstream_context.js (dimension URI helper).
|
|
137
|
+
|
|
138
|
+
Same cross-package-read rationale as `_annotation_runtime_script()`.
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
return (
|
|
142
|
+
files("ontobdc")
|
|
143
|
+
.joinpath("view", "plugin", "asset", "js", "workstream", "workstream_context.js")
|
|
144
|
+
.read_text(encoding="utf-8")
|
|
145
|
+
)
|
|
146
|
+
except (FileNotFoundError, OSError, ModuleNotFoundError):
|
|
147
|
+
return ""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _escape_for_script_embedding(json_text: str) -> str:
|
|
151
|
+
# A literal `</script>` inside the JSON payload would otherwise close
|
|
152
|
+
# the embedding <script> tag early — same concern/fix as the Surface's
|
|
153
|
+
# own JSON-LD embedding.
|
|
154
|
+
return json_text.replace("</", "<\\/")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
_DCTERMS_IDENTIFIER = "http://purl.org/dc/terms/identifier"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _literal(entity_data: dict, property_uri: str) -> str:
|
|
161
|
+
values = entity_data.get(property_uri)
|
|
162
|
+
if not isinstance(values, list) or not values:
|
|
163
|
+
return ""
|
|
164
|
+
picked = values[0]
|
|
165
|
+
if isinstance(picked, dict):
|
|
166
|
+
return str(picked.get("@value") or picked.get("@id") or "").strip()
|
|
167
|
+
return str(picked).strip()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _resolve_identifier(entity_data: dict) -> str:
|
|
171
|
+
# Same fallback order as each Tile's own client-side #literal()
|
|
172
|
+
# resolution, so the file this writes to matches the URL a Tile's
|
|
173
|
+
# "Open" link already constructs.
|
|
174
|
+
return _literal(entity_data, _DCTERMS_IDENTIFIER) or str(entity_data.get("@id", ""))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def render_entity_view(
|
|
178
|
+
entity_type_uris: List[str],
|
|
179
|
+
entity_data: dict,
|
|
180
|
+
*,
|
|
181
|
+
graph_nodes: Optional[List[dict]] = None,
|
|
182
|
+
language: str = "en",
|
|
183
|
+
) -> Optional[Dict[str, str]]:
|
|
184
|
+
"""Render a standalone detail Page for one entity.
|
|
185
|
+
|
|
186
|
+
Returns `None` if no Page descriptor's `required_uris` matches any of
|
|
187
|
+
`entity_type_uris` — the caller (ontobdc's orchestration capability)
|
|
188
|
+
should skip that entity in that case, which is what makes this
|
|
189
|
+
mechanism generic across entity types without any per-type code on the
|
|
190
|
+
`ontobdc` side.
|
|
191
|
+
|
|
192
|
+
Otherwise returns `{"html": ..., "path_segment": ..., "identifier": ...}`
|
|
193
|
+
— the caller writes `html` to `view/<path_segment>/<identifier>.html`.
|
|
194
|
+
All of that routing knowledge stays here, not in `ontobdc`.
|
|
195
|
+
|
|
196
|
+
`entity_data` is the entity's own JSON-LD node. `graph_nodes` (defaults
|
|
197
|
+
to just `[entity_data]` when omitted) is the wider Surface graph the
|
|
198
|
+
Page may need to build a resource list from (e.g. the WorkStream Page's
|
|
199
|
+
Related/Found tree) — embedded verbatim for the Page's own client-side
|
|
200
|
+
script to read, mirroring how Tiles read the Surface-wide JSON-LD.
|
|
201
|
+
"""
|
|
202
|
+
descriptor = _matching_page_descriptor(entity_type_uris)
|
|
203
|
+
if descriptor is None:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
from jinja2 import Template
|
|
207
|
+
|
|
208
|
+
metadata = descriptor.METADATA
|
|
209
|
+
base_name = metadata.template.removesuffix(".html.j2")
|
|
210
|
+
template_text = read_page_asset(metadata.template)
|
|
211
|
+
css_content = read_page_asset(f"{base_name}.css")
|
|
212
|
+
js_content = read_page_asset(f"{base_name}.js")
|
|
213
|
+
|
|
214
|
+
entity_id = str(entity_data.get("@id", ""))
|
|
215
|
+
nodes = graph_nodes if graph_nodes is not None else [entity_data]
|
|
216
|
+
entity_json = _escape_for_script_embedding(
|
|
217
|
+
json.dumps(nodes, ensure_ascii=False)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
html = Template(template_text).render(
|
|
221
|
+
language=language,
|
|
222
|
+
page_title=metadata.name,
|
|
223
|
+
entity_id=entity_id,
|
|
224
|
+
entity_json=entity_json,
|
|
225
|
+
css_content=css_content,
|
|
226
|
+
js_content=js_content,
|
|
227
|
+
annotation_runtime_js=_annotation_runtime_script() or "",
|
|
228
|
+
annotation_runtime_css=_annotation_runtime_style(),
|
|
229
|
+
workstream_context_js=_workstream_context_script(),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"html": html,
|
|
234
|
+
"path_segment": metadata.path_segment,
|
|
235
|
+
"identifier": _resolve_identifier(entity_data),
|
|
236
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from importlib.resources import files
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
from ontobdc.shared.domain.port.component_source import ComponentSourcePort
|
|
9
|
+
|
|
10
|
+
_BUILD_PLACEHOLDER = "__ONTOBDC_BUILD_"
|
|
11
|
+
|
|
12
|
+
_THEMES = [
|
|
13
|
+
{
|
|
14
|
+
"name": "light",
|
|
15
|
+
"label": "Light",
|
|
16
|
+
"tokens": {
|
|
17
|
+
"background": "#ffffff",
|
|
18
|
+
"foreground": "#0f172a",
|
|
19
|
+
"accent": "#0284c7",
|
|
20
|
+
"borderColor": "rgba(2,132,199,.35)",
|
|
21
|
+
},
|
|
22
|
+
"options": {"transparentBackground": False},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "dark",
|
|
26
|
+
"label": "Dark",
|
|
27
|
+
"tokens": {
|
|
28
|
+
"background": "#000000",
|
|
29
|
+
"foreground": "#f8fafc",
|
|
30
|
+
"accent": "#38bdf8",
|
|
31
|
+
"borderColor": "rgba(56,189,248,.55)",
|
|
32
|
+
},
|
|
33
|
+
"options": {"transparentBackground": False},
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
_BRAND = {
|
|
38
|
+
"name": "OntoBDC",
|
|
39
|
+
"mark_svg": (
|
|
40
|
+
'<svg viewBox="0 0 64 64" aria-hidden="true">'
|
|
41
|
+
'<circle cx="32" cy="32" r="25" fill="none" stroke="currentColor" stroke-width="8"/>'
|
|
42
|
+
'<circle cx="32" cy="32" r="7" fill="currentColor"/></svg>'
|
|
43
|
+
),
|
|
44
|
+
"logotype_svg": (
|
|
45
|
+
'<svg viewBox="0 0 260 64" aria-hidden="true">'
|
|
46
|
+
'<circle cx="32" cy="32" r="23" fill="none" stroke="var(--onto-theme-accent, currentColor)" stroke-width="7"/>'
|
|
47
|
+
'<circle cx="32" cy="32" r="6" fill="var(--onto-theme-accent, currentColor)"/>'
|
|
48
|
+
'<text x="68" y="42" fill="currentColor" font-family="system-ui, sans-serif" '
|
|
49
|
+
'font-size="31" font-weight="700">OntoBDC</text></svg>'
|
|
50
|
+
),
|
|
51
|
+
"slogan": "Data with Brains",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_LANGUAGES = [
|
|
55
|
+
{"code": "pt-BR", "label": "Portugues", "short_label": "PT"},
|
|
56
|
+
{"code": "en", "label": "English", "short_label": "EN"},
|
|
57
|
+
{"code": "es", "label": "Espanol", "short_label": "ES"},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _stub_photo_data_uri() -> str:
|
|
62
|
+
svg = (
|
|
63
|
+
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 800'>"
|
|
64
|
+
"<rect width='1200' height='800' fill='#0f172a'/>"
|
|
65
|
+
"<circle cx='785' cy='285' r='95' fill='#38bdf8' opacity='.85'/>"
|
|
66
|
+
"<text x='120' y='170' fill='white' font-family='system-ui,sans-serif' "
|
|
67
|
+
"font-size='58' font-weight='700'>OntoBDC</text>"
|
|
68
|
+
"</svg>"
|
|
69
|
+
)
|
|
70
|
+
return "data:image/svg+xml;charset=UTF-8," + quote(svg)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_PHOTO = {
|
|
74
|
+
"id": "stub-photo",
|
|
75
|
+
"data_uri": _stub_photo_data_uri(),
|
|
76
|
+
"alt": "OntoBDC placeholder photo",
|
|
77
|
+
"caption": "",
|
|
78
|
+
"author": "",
|
|
79
|
+
"date": "",
|
|
80
|
+
"location": "",
|
|
81
|
+
"mode": "photo",
|
|
82
|
+
"fit": "cover",
|
|
83
|
+
"focal_x": 0.5,
|
|
84
|
+
"focal_y": 0.5,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_BUILD_PAYLOAD_BY_PLACEHOLDER = {
|
|
88
|
+
"__ONTOBDC_BUILD_THEMES__": _THEMES,
|
|
89
|
+
"__ONTOBDC_BUILD_BRAND__": _BRAND,
|
|
90
|
+
"__ONTOBDC_BUILD_LANGUAGES__": _LANGUAGES,
|
|
91
|
+
"__ONTOBDC_BUILD_PHOTO__": _PHOTO,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ComponentSourceAdapter(ComponentSourcePort):
|
|
96
|
+
"""Reads a Tile's JS asset and resolves its build-time placeholder, if any.
|
|
97
|
+
|
|
98
|
+
A plain content Tile's JS has no placeholder and is returned as-is. A
|
|
99
|
+
"system" Tile's JS (logo/theme/language/photo) carries exactly one of
|
|
100
|
+
the placeholders in `_BUILD_PAYLOAD_BY_PLACEHOLDER`, substituted here
|
|
101
|
+
with project-default data — there is currently no per-project
|
|
102
|
+
customization path, since nothing in the active pipeline ever supplied
|
|
103
|
+
anything other than these defaults.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def component_source(self, tag: str) -> Optional[str]:
|
|
107
|
+
try:
|
|
108
|
+
source = files("ontobdc_view").joinpath(
|
|
109
|
+
"component", "asset", f"{tag}.js"
|
|
110
|
+
).read_text(encoding="utf-8")
|
|
111
|
+
except (FileNotFoundError, OSError):
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
if not source.strip():
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
for placeholder, payload in _BUILD_PAYLOAD_BY_PLACEHOLDER.items():
|
|
118
|
+
if placeholder in source:
|
|
119
|
+
encoded = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
|
120
|
+
return source.replace(placeholder, encoded)
|
|
121
|
+
|
|
122
|
+
if _BUILD_PLACEHOLDER in source:
|
|
123
|
+
# An unresolved placeholder we don't recognize — embedding it
|
|
124
|
+
# as-is would ship broken JS.
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
return source
|