generated from john/python-template
Removed the image viewer which wasn't working anyway.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
"""Media viewer components for high-contrast image inspection."""
|
"""Simple media display components."""
|
||||||
|
|
||||||
from nicegui import ui
|
from nicegui import ui
|
||||||
|
|
||||||
@@ -9,116 +9,14 @@ def dark_room_viewer(
|
|||||||
*,
|
*,
|
||||||
container_height: str = "500px",
|
container_height: str = "500px",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Isolated high-contrast container for image inspection with pan and zoom capabilities."""
|
"""Render a plain responsive image that fills available width."""
|
||||||
with ui.card().classes("ui-bg-viewer ui-border-viewer rounded-sm p-3 flex flex-col justify-between w-full"):
|
del count_label
|
||||||
# Viewer Header Bar
|
|
||||||
with ui.row().classes("w-full justify-between items-center mb-2 ui-text-inverse text-xs"):
|
|
||||||
ui.label("SOURCE MEDIA VIEWER").classes("font-mono font-bold tracking-wider")
|
|
||||||
ui.label(count_label).classes("ui-text-accent")
|
|
||||||
|
|
||||||
# Interactive Pan/Zoom Canvas Area
|
if image_path:
|
||||||
if image_path:
|
ui.image(image_path).classes("w-full rounded-sm block").style("height: auto;")
|
||||||
# Container with fixed height and hidden overflow for contained panning/zooming
|
return
|
||||||
with ui.element("div").classes(
|
|
||||||
"relative w-full overflow-hidden border ui-border-viewer ui-bg-viewer-overlay rounded-sm flex items-center justify-center cursor-grab active:cursor-grabbing"
|
|
||||||
).style(f"height: {container_height};") as viewport:
|
|
||||||
|
|
||||||
# Image element targeted by client-side pan/zoom JS
|
with ui.column().classes(
|
||||||
img = (
|
"w-full items-center justify-center border ui-border-viewer ui-bg-viewer-overlay-soft rounded-sm p-8"
|
||||||
ui.image(image_path)
|
).style(f"min-height: {container_height};"):
|
||||||
.classes("max-h-full max-w-full select-none transition-transform duration-75 ease-out")
|
ui.label("No source media available for inspection.").classes("ui-text-muted text-xs italic")
|
||||||
.style("transform-origin: center center;")
|
|
||||||
)
|
|
||||||
|
|
||||||
viewer_key = str(img.id)
|
|
||||||
|
|
||||||
# Client-side JavaScript state management for smooth panning and scaling
|
|
||||||
js_pan_zoom = f"""
|
|
||||||
(() => {{
|
|
||||||
const viewport = document.getElementById('{viewport.id}');
|
|
||||||
const img = document.getElementById('{img.id}');
|
|
||||||
if (!viewport || !img) return;
|
|
||||||
|
|
||||||
let scale = 1;
|
|
||||||
let pointX = 0;
|
|
||||||
let pointY = 0;
|
|
||||||
let startX = 0;
|
|
||||||
let startY = 0;
|
|
||||||
let isDragging = false;
|
|
||||||
|
|
||||||
function updateTransform() {{
|
|
||||||
img.style.transform = `translate(${{pointX}}px, ${{pointY}}px) scale(${{scale}})`;
|
|
||||||
}}
|
|
||||||
|
|
||||||
function zoomIn() {{
|
|
||||||
scale = Math.min(scale * 1.25, 8);
|
|
||||||
updateTransform();
|
|
||||||
}}
|
|
||||||
|
|
||||||
function zoomOut() {{
|
|
||||||
scale = Math.max(scale / 1.25, 0.5);
|
|
||||||
updateTransform();
|
|
||||||
}}
|
|
||||||
|
|
||||||
function resetZoom() {{
|
|
||||||
scale = 1;
|
|
||||||
pointX = 0;
|
|
||||||
pointY = 0;
|
|
||||||
updateTransform();
|
|
||||||
}}
|
|
||||||
|
|
||||||
viewport.addEventListener('wheel', (e) => {{
|
|
||||||
e.preventDefault();
|
|
||||||
const delta = -e.deltaY;
|
|
||||||
if (delta > 0) zoomIn();
|
|
||||||
else zoomOut();
|
|
||||||
}}, {{ passive: false }});
|
|
||||||
|
|
||||||
viewport.addEventListener('mousedown', (e) => {{
|
|
||||||
e.preventDefault();
|
|
||||||
startX = e.clientX - pointX;
|
|
||||||
startY = e.clientY - pointY;
|
|
||||||
isDragging = true;
|
|
||||||
}});
|
|
||||||
|
|
||||||
window.addEventListener('mouseup', () => {{
|
|
||||||
isDragging = false;
|
|
||||||
}});
|
|
||||||
|
|
||||||
viewport.addEventListener('mousemove', (e) => {{
|
|
||||||
if (!isDragging) return;
|
|
||||||
e.preventDefault();
|
|
||||||
pointX = e.clientX - startX;
|
|
||||||
pointY = e.clientY - startY;
|
|
||||||
updateTransform();
|
|
||||||
}});
|
|
||||||
|
|
||||||
window.__darkRoomViewer = window.__darkRoomViewer || {{}};
|
|
||||||
window.__darkRoomViewer['{viewer_key}'] = {{ zoomIn, zoomOut, resetZoom }};
|
|
||||||
}})();
|
|
||||||
"""
|
|
||||||
ui.run_javascript(js_pan_zoom)
|
|
||||||
|
|
||||||
# Control Toolbar
|
|
||||||
with ui.row().classes("w-full justify-center items-center gap-2 mt-2 pt-2 border-t ui-border-viewer"):
|
|
||||||
ui.button(
|
|
||||||
icon="zoom_in",
|
|
||||||
on_click=lambda: ui.run_javascript(f"window.__darkRoomViewer?.['{viewer_key}']?.zoomIn?.()"),
|
|
||||||
).props("flat round dense color=white text-xs").tooltip("Zoom In")
|
|
||||||
|
|
||||||
ui.button(
|
|
||||||
icon="zoom_out",
|
|
||||||
on_click=lambda: ui.run_javascript(f"window.__darkRoomViewer?.['{viewer_key}']?.zoomOut?.()"),
|
|
||||||
).props("flat round dense color=white text-xs").tooltip("Zoom Out")
|
|
||||||
|
|
||||||
ui.button(
|
|
||||||
icon="center_focus_strong",
|
|
||||||
on_click=lambda: ui.run_javascript(f"window.__darkRoomViewer?.['{viewer_key}']?.resetZoom?.()"),
|
|
||||||
).props("flat round dense color=white text-xs").tooltip("Reset View")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Fallback state when no image source is linked
|
|
||||||
with ui.column().classes(
|
|
||||||
"w-full flex-grow items-center justify-center border ui-border-viewer ui-bg-viewer-overlay-soft rounded-sm p-8"
|
|
||||||
).style(f"min-height: {container_height};"):
|
|
||||||
ui.label("No source media available for inspection.").classes("ui-text-muted text-xs italic")
|
|
||||||
@@ -421,7 +421,7 @@ def _render_document_form_fields(
|
|||||||
|
|
||||||
|
|
||||||
def _render_bento_viewer_zone(document: Document) -> None:
|
def _render_bento_viewer_zone(document: Document) -> None:
|
||||||
with ui.column().classes("col-span-12 lg:col-span-5"):
|
with ui.column().classes("col-span-12 lg:col-span-4"):
|
||||||
source_path = document.sources[0].file_path if document.sources else None
|
source_path = document.sources[0].file_path if document.sources else None
|
||||||
dark_room_viewer(source_path, count_label=f"{len(document.sources)} Source(s) Linked")
|
dark_room_viewer(source_path, count_label=f"{len(document.sources)} Source(s) Linked")
|
||||||
with ui.row().classes("w-full justify-between items-center mt-2"):
|
with ui.row().classes("w-full justify-between items-center mt-2"):
|
||||||
@@ -450,7 +450,7 @@ def _render_bento_metadata_zone(document: Document) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _render_bento_relations_zone(document: Document) -> None:
|
def _render_bento_relations_zone(document: Document) -> None:
|
||||||
with ui.column().classes("col-span-12 lg:col-span-3 gap-4"):
|
with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
|
||||||
with archival_card(title="Related People"):
|
with archival_card(title="Related People"):
|
||||||
if not document.document_people:
|
if not document.document_people:
|
||||||
render_empty_state("No linked people yet.", italic=True)
|
render_empty_state("No linked people yet.", italic=True)
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ def register_page() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _render_source_viewer_zone(source: Source, *, settings: Settings, request: Request) -> None:
|
def _render_source_viewer_zone(source: Source, *, settings: Settings, request: Request) -> None:
|
||||||
with ui.column().classes("col-span-12 lg:col-span-6"):
|
with ui.column().classes("col-span-12 lg:col-span-4"):
|
||||||
dark_room_viewer(
|
dark_room_viewer(
|
||||||
_resolve_source_media_src(source.file_path, settings=settings, request=request),
|
_resolve_source_media_src(source.file_path, settings=settings, request=request),
|
||||||
count_label=f"Page {source.page_number}",
|
count_label=f"Page {source.page_number}",
|
||||||
@@ -232,7 +232,7 @@ def _render_source_transcription_column(
|
|||||||
latest_job_source: JobSource | None,
|
latest_job_source: JobSource | None,
|
||||||
sources_service: TranscriptionService,
|
sources_service: TranscriptionService,
|
||||||
) -> None:
|
) -> None:
|
||||||
with ui.column().classes("col-span-12 lg:col-span-3 gap-4"):
|
with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
|
||||||
_render_source_transcription_zone(
|
_render_source_transcription_zone(
|
||||||
source=source,
|
source=source,
|
||||||
original_transcription=original_transcription,
|
original_transcription=original_transcription,
|
||||||
@@ -242,7 +242,7 @@ def _render_source_transcription_column(
|
|||||||
|
|
||||||
|
|
||||||
def _render_source_metadata_column(*, source: Source, latest_job_source: JobSource | None) -> None:
|
def _render_source_metadata_column(*, source: Source, latest_job_source: JobSource | None) -> None:
|
||||||
with ui.column().classes("col-span-12 lg:col-span-3 gap-4"):
|
with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
|
||||||
_render_source_metadata_zone(source)
|
_render_source_metadata_zone(source)
|
||||||
_render_source_job_metadata_zone(latest_job_source)
|
_render_source_job_metadata_zone(latest_job_source)
|
||||||
_render_source_revision_logistics_zone(source)
|
_render_source_revision_logistics_zone(source)
|
||||||
|
|||||||
Reference in New Issue
Block a user