Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ expression: "fixture_audit_snapshot(\"cyclic_fk.sql\")"
},
"surfaces": {
"html": {
"bytes": 453018,
"bytes": 456843,
"contains_embedded_svg": true,
"contains_metadata": true,
"contains_viewport": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ expression: "fixture_audit_snapshot(\"ecommerce.sql\")"
},
"surfaces": {
"html": {
"bytes": 426912,
"bytes": 430737,
"contains_embedded_svg": true,
"contains_metadata": true,
"contains_viewport": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ expression: "fixture_audit_snapshot(\"join_heavy.sql\")"
},
"surfaces": {
"html": {
"bytes": 608344,
"bytes": 612169,
"contains_embedded_svg": true,
"contains_metadata": true,
"contains_viewport": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ expression: "fixture_audit_snapshot(\"multi_schema.sql\")"
},
"surfaces": {
"html": {
"bytes": 419939,
"bytes": 423764,
"contains_embedded_svg": true,
"contains_metadata": true,
"contains_viewport": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ expression: "fixture_audit_snapshot(\"simple_blog.sql\")"
},
"surfaces": {
"html": {
"bytes": 380109,
"bytes": 383934,
"contains_embedded_svg": true,
"contains_metadata": true,
"contains_viewport": true
Expand Down
3 changes: 2 additions & 1 deletion crates/relune-render-html/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ pub(crate) fn build_viewer_controls_html() -> String {
<button type="button" class="viewer-control-button" id="zoom-out" title="Zoom out"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 12h14"/></svg></button>
<span class="viewer-control-status" id="zoom-level">100%</span>
<button type="button" class="viewer-control-button viewer-control-fit" id="zoom-fit" title="Fit to screen"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg></button>
<button type="button" class="viewer-control-button viewer-control-minimap" id="minimap-toggle" aria-pressed="false" aria-label="Minimap" aria-controls="minimap-shell" aria-keyshortcuts="M" title="Show minimap (M)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z"/><path d="M9 4v16M15 6v16"/></svg></button>
</div>
<div class="minimap-shell" id="minimap-shell" aria-label="Diagram minimap">
<div class="minimap-shell" id="minimap-shell" aria-label="Diagram minimap" hidden>
<div class="minimap-header">
<span>Minimap</span>
<span class="minimap-hint">Viewport</span>
Expand Down
8 changes: 8 additions & 0 deletions crates/relune-render-html/src/css.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,14 @@ pub(crate) fn build_css(
background: color-mix(in srgb, var(--panel-bg) 82%, var(--accent-soft));
}

.viewer-control-button[aria-pressed=false] {
opacity: 0.55;
}

.viewer-control-button[aria-pressed=false]:hover {
opacity: 1;
}

.minimap-shell {
position: fixed;
right: 12px;
Expand Down
8 changes: 8 additions & 0 deletions crates/relune-render-html/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,14 @@ mod tests {
assert!(html.contains(r#"id="zoom-out""#));
assert!(html.contains(r#"id="zoom-level""#));
assert!(html.contains(r#"id="zoom-fit""#));
assert!(html.contains(r#"id="minimap-toggle""#));
assert!(html.contains(r#"aria-pressed="false""#));
assert!(html.contains(r#"aria-label="Minimap""#));
assert!(html.contains(r#"aria-controls="minimap-shell""#));
assert!(html.contains(r#"aria-keyshortcuts="M""#));
assert!(html.contains(r#"title="Show minimap (M)""#));
assert!(html.contains(r#"id="minimap-shell""#));
assert!(html.contains(r#"id="minimap-shell" aria-label="Diagram minimap" hidden"#));
assert!(html.contains(r#"id="minimap""#));
// Verify SVG icons replaced text labels
assert!(html.contains("<svg"));
Expand Down
67 changes: 66 additions & 1 deletion crates/relune-render-html/src/js/minimap.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/relune-render-html/src/js/pan_zoom.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions crates/relune-render-html/src/js/shortcuts.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions crates/relune-render-html/src/js/url_state.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 40 additions & 2 deletions crates/relune-render-html/ts/minimap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { getViewerRuntime, type DiagramBounds, type ViewportState } from './viewer_api';
import {
emitViewerEvent,
getViewerRuntime,
markViewerModuleReady,
type DiagramBounds,
type ViewportState,
} from './viewer_api';

interface MinimapNode {
id: string;
Expand All @@ -9,12 +15,44 @@ interface MinimapNode {
}

{
const runtime = getViewerRuntime();
const minimapShell = document.getElementById('minimap-shell');
const minimapToggleBtn = document.getElementById('minimap-toggle');

if (minimapShell instanceof HTMLElement) {
const isHidden = (): boolean => minimapShell.hasAttribute('hidden');
const setHidden = (hidden: boolean, options?: { silent?: boolean }): void => {
const changed = isHidden() !== hidden;
if (hidden) {
minimapShell.setAttribute('hidden', '');
} else {
minimapShell.removeAttribute('hidden');
}
if (minimapToggleBtn instanceof HTMLButtonElement) {
minimapToggleBtn.setAttribute('aria-pressed', String(!hidden));
const action = hidden ? 'Show minimap' : 'Hide minimap';
minimapToggleBtn.setAttribute('title', `${action} (M)`);
}
if (changed && options?.silent !== true) {
emitViewerEvent('relune:minimap-toggled', { hidden });
}
};
runtime.minimap = { isHidden, setHidden };
markViewerModuleReady('minimap');

if (minimapToggleBtn instanceof HTMLButtonElement) {
minimapToggleBtn.addEventListener('click', () => {
setHidden(!isHidden());
runtime.viewport?.fit();
});
}
}

const host = document.getElementById('minimap');
const svgRoot = document.querySelector('.canvas svg');
if (!(host instanceof SVGSVGElement) || svgRoot === null) {
// Minimap host or source SVG not available.
} else {
const runtime = getViewerRuntime();
const hostSvg = host;
hostSvg.innerHTML = '';

Expand Down
2 changes: 1 addition & 1 deletion crates/relune-render-html/ts/pan_zoom_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function computeFit(
return null;
}

const padding = 40;
const padding = 20;
const scale = clamp(
Math.min(
(available.width - padding * 2) / diagram.width,
Expand Down
13 changes: 13 additions & 0 deletions crates/relune-render-html/ts/shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ import { getViewerRuntime, isEditableTarget } from './viewer_api';
event.preventDefault();
document.getElementById('group-panel-collapse')?.dispatchEvent(new MouseEvent('click'));
break;
case 'm':
case 'M':
// Skip when a modifier is held (Cmd+M, Ctrl+M, Alt+M) so we don't
// shadow OS- or browser-owned shortcuts.
if (event.ctrlKey || event.metaKey || event.altKey) {
break;
}
event.preventDefault();
if (runtime.minimap !== undefined) {
runtime.minimap.setHidden(!runtime.minimap.isHidden());
runtime.viewport?.fit();
}
break;
case '+':
case '=':
event.preventDefault();
Expand Down
15 changes: 15 additions & 0 deletions crates/relune-render-html/ts/url_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getViewerRuntime, waitForViewerModules, type ViewerModule } from './vie
const PARAM_FILTER_MODE = 'fm';
const PARAM_HIDDEN_GROUPS = 'hg';
const PARAM_COLLAPSED = 'c';
const PARAM_MINIMAP_VISIBLE = 'mv';

type FacetUrlParam = { param: string; facetId: string };
const FACET_PARAMS: FacetUrlParam[] = [
Expand Down Expand Up @@ -147,6 +148,10 @@ import { getViewerRuntime, waitForViewerModules, type ViewerModule } from './vie
params.set(PARAM_COLLAPSED, collapsed.join(','));
}

if (runtime.minimap?.isHidden() === false) {
params.set(PARAM_MINIMAP_VISIBLE, '1');
}

return params;
}

Expand Down Expand Up @@ -174,6 +179,12 @@ import { getViewerRuntime, waitForViewerModules, type ViewerModule } from './vie

function restoreFromHash(): void {
const params = readHash();
// Sync minimap visibility unconditionally so popstate back to a clean hash
// restores the hidden default instead of leaving it stuck visible. Use the
// silent option to avoid emitting `relune:minimap-toggled` — that event
// would queue a debounced hash write after restoringFromPopstate has
// already flipped back to false, causing a spurious pushState.
runtime.minimap?.setHidden(params.get(PARAM_MINIMAP_VISIBLE) !== '1', { silent: true });
if (params.toString() === '') {
return;
}
Expand Down Expand Up @@ -259,6 +270,9 @@ import { getViewerRuntime, waitForViewerModules, type ViewerModule } from './vie
if (document.getElementById('canvas')?.querySelector('svg') !== null) {
modules.push('collapse');
}
if (document.getElementById('minimap-shell') !== null) {
modules.push('minimap');
}
return modules;
}

Expand All @@ -273,6 +287,7 @@ import { getViewerRuntime, waitForViewerModules, type ViewerModule } from './vie
document.addEventListener('relune:filters-changed', scheduleDiscreteWrite);
document.addEventListener('relune:groups-changed', scheduleDiscreteWrite);
document.addEventListener('relune:collapse-changed', scheduleDiscreteWrite);
document.addEventListener('relune:minimap-toggled', scheduleDiscreteWrite);

// ---------------------------------------------------------------------------
// popstate: re-apply state when the user navigates back/forward or edits hash
Expand Down
Loading
Loading