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 @@ -59,6 +59,8 @@ public PluginParamsDefinition parameters() {
params.add("aspectRatio", PluginParamType.STRING, "aspect ratio for video embedding", "\"16:9\"");
params.add("light", PluginParamType.OBJECT, "CSS properties override for light theme", "{ \"--color\": \"#333\" }");
params.add("dark", PluginParamType.OBJECT, "CSS properties override for dark theme", "{ \"--color\": \"#eee\" }");
params.add("zoomEnabled", PluginParamType.BOOLEAN, "enable full-screen zoom button in the title bar", "true");
params.add("newTabEnabled", PluginParamType.BOOLEAN, "enable open in new tab button in the title bar", "true");
return params;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Add: iframe zoom and open in new tab
28 changes: 28 additions & 0 deletions znai-docs/znai/visuals/iframe.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,34 @@ Use `wide` to take all the available horizontal space:
wide: true
}

# Zoom And New Tab

Use `zoomEnabled` to add a full-screen zoom button to the title bar.
Clicking the button opens the iframe in a full-screen overlay with a close button. Press `Escape` or click the close button to exit.

Use `newTabEnabled` to add an open-in-new-tab button to the title bar.

Note: Requires `title` to be set.

```markdown {highlight: ["zoomEnabled", "newTabEnabled"]}
:include-iframe: iframe/custom-multi-line.html {
title: "parameters reference",
fit: true,
maxHeight: 120,
zoomEnabled: true,
newTabEnabled: true
}
```

:include-iframe: iframe/custom-multi-line.html {
title: "parameters reference",
fit: true,
maxHeight: 120,
zoomEnabled: true,
newTabEnabled: true
}


# Embedding Video

Use `include-iframe` to embed media from other places. By default, aspect ratio is set to `16:9`.
Expand Down
52 changes: 52 additions & 0 deletions znai-docs/znai/visuals/iframe/custom-multi-line.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<html lang="en">
<head>
<style>
:root {
--backgroundColor: #eee;
--color: #222;
}

body { margin: 0 }

.content {
background-color: var(--backgroundColor);
color: var(--color);
font-size: 16px;
padding: 16px;
line-height: 1.6;
}

table {
border-collapse: collapse;
width: 100%;
margin-top: 12px;
}

th, td {
border: 1px solid #999;
padding: 8px 12px;
text-align: left;
}

th {
background-color: rgba(128, 128, 128, 0.15);
}
</style>
<title>multi line content</title>
</head>

<body>
<div class="content">
<p>This is an example with more content to demonstrate zoom functionality.</p>
<table>
<tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
<tr><td>src</td><td>string</td><td>URL of the content to embed</td></tr>
<tr><td>fit</td><td>boolean</td><td>Auto resize iframe to fit content</td></tr>
<tr><td>title</td><td>string</td><td>Title bar text</td></tr>
<tr><td>wide</td><td>boolean</td><td>Take all available horizontal space</td></tr>
<tr><td>zoomEnabled</td><td>boolean</td><td>Show full-screen zoom button</td></tr>
<tr><td>newTabEnabled</td><td>boolean</td><td>Show open in new tab button</td></tr>
</table>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions znai-reactjs/src/doc-elements/container/Container.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

.znai-container.wide .znai-container-title-wrapper {
border: none;
padding-right: 0;
}

.znai-container.wide .znai-container-title {
Expand Down
3 changes: 3 additions & 0 deletions znai-reactjs/src/doc-elements/container/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface Props extends ContainerCommonProps {
onCollapseToggle?(): void;
additionalTitleClassNames?: string;
additionalTitleContainerClassNames?: string;
titleActions?: React.ReactNode;
titleContainerStyle?: CSSProperties;
style?: CSSProperties;
onClick?(): void;
Expand Down Expand Up @@ -68,6 +69,7 @@ export function Container({
onCollapseToggle,
additionalTitleClassNames,
additionalTitleContainerClassNames,
titleActions,
titleContainerStyle,
style,
onClick,
Expand Down Expand Up @@ -96,6 +98,7 @@ export function Container({
onCollapseToggle={onCollapseToggle}
additionalTitleClassNames={additionalTitleClassNames}
additionalContainerClassNames={additionalTitleContainerClassNames}
titleActions={titleActions}
containerStyle={titleContainerStyle}
/>
) : null;
Expand Down
23 changes: 23 additions & 0 deletions znai-reactjs/src/doc-elements/container/ContainerTitle.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
.znai-container-title {
display: flex;
align-items: center;
flex: 1;
color: var(--znai-snippets-title-color);
padding: 8px 0 8px 16px;
}
Expand Down Expand Up @@ -62,4 +63,26 @@
.znai-container-title-anchor .znai-icon svg {
width: 14px;
height: 14px;
}

.znai-container-title-actions {
margin-left: auto;
display: flex;
align-items: center;
gap: 4px;
}

.znai-container-title-actions .znai-icon {
cursor: pointer;
opacity: 0.6;
}

.znai-container-title-actions .znai-icon:hover {
opacity: 1;
}

.znai-container-title-actions .znai-icon,
.znai-container-title-actions .znai-icon svg {
width: 14px;
height: 14px;
}
3 changes: 3 additions & 0 deletions znai-reactjs/src/doc-elements/container/ContainerTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface Props extends ContainerTitleCommonProps {
additionalContainerClassNames?: string;
additionalTitleClassNames?: string;
containerStyle?: React.CSSProperties;
titleActions?: React.ReactNode;

onCollapseToggle?(): void;
}
Expand All @@ -45,6 +46,7 @@ export function ContainerTitle({
containerStyle,
collapsed,
anchorId,
titleActions,
onCollapseToggle,
}: Props) {
const collapsible = collapsed !== undefined;
Expand Down Expand Up @@ -77,6 +79,7 @@ export function ContainerTitle({
</a>
</div>
)}
{titleActions && <div className="znai-container-title-actions">{titleActions}</div>}
</div>
</div>
);
Expand Down
43 changes: 42 additions & 1 deletion znai-reactjs/src/doc-elements/iframe/Iframe.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,47 @@
}

.znai-iframe-title {
padding: 4px 16px;
padding-top: 4px;
padding-bottom: 4px;
font-size: var(--znai-smaller-text-size);
}

.znai-iframe-zoomed {
display: flex;
flex-direction: column;
width: calc(100vw - 32px);
height: calc(100vh - 32px);
background: var(--znai-background-color);
}

.znai-iframe-zoomed-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 4px 8px 16px;
background: var(--znai-snippets-title-background-color);
color: var(--znai-snippets-title-color);
font-size: var(--znai-smaller-text-size);
}

.znai-iframe-zoomed-close {
cursor: pointer;
opacity: 0.6;
}

.znai-iframe-zoomed-close:hover {
opacity: 1;
}

.znai-iframe-zoomed-close.znai-icon,
.znai-iframe-zoomed-close.znai-icon svg {
width: 18px;
height: 18px;
}

.znai-iframe-zoomed-content {
flex: 1;
border: 0;
width: 100%;
height: 100%;
}
83 changes: 66 additions & 17 deletions znai-reactjs/src/doc-elements/iframe/Iframe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import React, { useEffect, useRef, useState } from "react";
import { Container } from "../container/Container";
import { Icon } from "../icons/Icon";
import { zoom } from "../zoom/Zoom";

import "./Iframe.css";

Expand All @@ -29,6 +31,8 @@ interface Props {
wide?: boolean;
height?: number;
maxHeight?: number;
zoomEnabled?: boolean;
newTabEnabled?: boolean;
// changes on every page regen to force iframe reload
previewMarker?: string;
}
Expand All @@ -44,7 +48,7 @@ export function Iframe(props: Props) {
const initialIframeHeight = 14;

let activeElement: any = null;
export function IframeFit({ src, title, wide, height, maxHeight, light, dark, previewMarker }: Props) {
export function IframeFit({ src, title, wide, height, maxHeight, light, dark, zoomEnabled, newTabEnabled, previewMarker }: Props) {
const containerRef = useRef<HTMLDivElement>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);
const mutationObserverRef = useRef<MutationObserver | null>(null);
Expand All @@ -60,20 +64,9 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
iframeRef!.current!.src += "";
}, [previewMarker]);

// handle site theme switching
useEffect(() => {
// TODO theme integration via context
// @ts-ignore
window.znaiTheme.addChangeHandler(onThemeChange);

// @ts-ignore
return () => window.znaiTheme.removeChangeHandler(onThemeChange);

function onThemeChange() {
injectCssProperties(iframeRef, dark, light);
updateScrollBarToMatch(containerRef, iframeRef);
}
}, [dark, light]);
const { syncTheme } = useIframeThemeSync(iframeRef, dark, light, () => {
updateScrollBarToMatch(containerRef, iframeRef);
});

useEffect(() => {
return () => {
Expand All @@ -89,8 +82,15 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
activeElement = document.activeElement;
}

const titleActions = (zoomEnabled || newTabEnabled) ? (
<>
{zoomEnabled && <Icon id="maximize-2" onClick={zoomIframe} />}
{newTabEnabled && <Icon id="external-link" onClick={openInNewTab} />}
</>
) : undefined;

return (
<Container wide={wide} title={title} additionalTitleClassNames="znai-iframe-title">
<Container wide={wide} title={title} additionalTitleClassNames="znai-iframe-title" titleActions={titleActions}>
<div ref={containerRef}></div>
<iframe
title={title}
Expand All @@ -104,6 +104,14 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
</Container>
);

function zoomIframe() {
zoom.zoom(<IframeZoomed src={src} title={title} light={light} dark={dark} />);
}

function openInNewTab() {
window.open(src, "_blank");
}

function onLoad() {
handleSize();
updateScrollBarToMatch(containerRef, iframeRef);
Expand Down Expand Up @@ -153,7 +161,7 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
setTimeout(() => {
const newHeight = measureContentHeight();

injectCssProperties(iframeRef, dark, light);
syncTheme();
setCalculatedIframeHeight(newHeight);
setVisible(true);

Expand All @@ -168,6 +176,26 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
}
}

function useIframeThemeSync(iframeRef: React.RefObject<HTMLIFrameElement | null>, dark: any, light: any, onThemeChangeExtra?: () => void) {
useEffect(() => {
// @ts-ignore
window.znaiTheme.addChangeHandler(onThemeChange);
// @ts-ignore
return () => window.znaiTheme.removeChangeHandler(onThemeChange);

function onThemeChange() {
syncTheme();
onThemeChangeExtra?.();
}
}, [dark, light]);

function syncTheme() {
injectCssProperties(iframeRef, dark, light);
}

return { syncTheme };
}

function updateScrollBarToMatch(containerRef: any, iframeRef: any) {
const div = containerRef!.current;

Expand Down Expand Up @@ -260,6 +288,27 @@ export function calcAspectRatioPaddingTop(aspectRatio: string): string {
return ((Number(height) / Number(width)) * 100.0).toFixed(2) + "%";
}

function IframeZoomed({ src, title, light, dark }: Pick<Props, "src" | "title" | "light" | "dark">) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const { syncTheme } = useIframeThemeSync(iframeRef, dark, light);

return (
<div className="znai-iframe-zoomed" onClick={(e) => e.stopPropagation()}>
<div className="znai-iframe-zoomed-header">
<span className="znai-iframe-zoomed-title">{title}</span>
<Icon id="x" className="znai-iframe-zoomed-close" onClick={() => zoom.clearZoom()} />
</div>
<iframe
title={title}
src={src}
className="znai-iframe-zoomed-content"
onLoad={syncTheme}
ref={iframeRef}
/>
</div>
);
}

export const presentationIframe = {
component: Iframe,
numberOfSlides: () => 1,
Expand Down
Loading
Loading