Skip to content

Commit

Permalink
iframes [wip]
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelle committed Jun 26, 2023
1 parent 2581c86 commit f10dbde
Show file tree
Hide file tree
Showing 39 changed files with 1,457 additions and 187 deletions.
9 changes: 9 additions & 0 deletions dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ All `props` are *optional*.
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
| [`generateIdForFile`](#generateidforfile) | `function` | _ | Allows you to override `id` generation for files added on canvas |
| [`iframeURLWhitelist`](#iframeURLWhitelist) | `RegExp[]` | _ | Set this prop to extend the list of allowed sites to be included in iFrames |
| [`renderCustomIFrame`](/docs/@excalidraw/excalidraw/api/props/render-props#renderCustomIFrame) |`function` | _ | Render function that can override the built-in iFrame |

### Storing custom data on Excalidraw elements

Expand Down Expand Up @@ -228,3 +230,10 @@ Allows you to override `id` generation for files added on canvas (images). By de
(file: File) => string | Promise<string>
```

### iframeURLWhitelist

This is an optional property. Default sites allowed by Excalidraw include YouTube, Vimeo, Figma, Excalidraw and Twitter. You may allow additional sites to be included in iFrames. To allow all sites set the prop to `[/.*/]`.

```tsx
iframeURLWhitelist?: RegExp[];
```
22 changes: 22 additions & 0 deletions dev-docs/docs/@excalidraw/excalidraw/api/props/render-props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,25 @@ function App() {
);
}
```

### renderCustomIFrame

<pre>
(
element: NonDeletedExcalidrawElement, //the rectangle element hosting the embedded content
radius: number, //calculated border radius in px
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>
) => JSX.Element | null
</pre>

The renderCustomIFrame function allows you to customize the rendering of a JSX component instead of using the default iFrame. By setting props.renderCustomIFrame, you can provide a custom implementation for rendering the element.

#### Parameters:

- element (NonDeletedExcalidrawElement): The element to be rendered.
- radius (number): The calculated border radius in pixels.
- appState (UIAppState): The current state of the UI.

#### Return value:

JSX.Element | null: The JSX component representing the custom rendering, or null if the default iFrame should be rendered.
6 changes: 5 additions & 1 deletion src/actions/actionBoundText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
TEXT_ALIGN,
} from "../constants";
import { getNonDeletedElements, isTextElement, newElement } from "../element";
import { hideActionForIFrame } from "../element/iframe";
import { mutateElement } from "../element/mutateElement";
import {
computeBoundTextPosition,
Expand Down Expand Up @@ -92,7 +93,7 @@ export const actionBindText = register({
name: "bindText",
contextItemLabel: "labels.bindText",
trackEvent: { category: "element" },
predicate: (elements, appState) => {
predicate: (elements, appState, appProps) => {
const selectedElements = getSelectedElements(elements, appState);

if (selectedElements.length === 2) {
Expand All @@ -106,6 +107,9 @@ export const actionBindText = register({
} else if (isTextBindableContainer(selectedElements[1])) {
bindingContainer = selectedElements[1];
}
if (hideActionForIFrame(bindingContainer, appProps)) {
return false;
}
if (
textElement &&
bindingContainer &&
Expand Down
2 changes: 2 additions & 0 deletions src/actions/actionCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ export const actionToggleEraserTool = register({
...appState,
selectedElementIds: {},
selectedGroupIds: {},
activeIFrame: null,
activeTool,
},
commitToHistory: true,
Expand Down Expand Up @@ -414,6 +415,7 @@ export const actionToggleHandTool = register({
...appState,
selectedElementIds: {},
selectedGroupIds: {},
activeIFrame: null,
activeTool,
},
commitToHistory: true,
Expand Down
1 change: 1 addition & 0 deletions src/actions/actionDeleteSelected.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export const actionDeleteSelected = register({
...nextAppState,
activeTool: updateActiveTool(appState, { type: "selection" }),
multiElement: null,
activeIFrame: null,
},
commitToHistory: isSomeElementSelected(
getNonDeletedElements(elements),
Expand Down
1 change: 1 addition & 0 deletions src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export type ActionName =
| "removeAllElementsFromFrame"
| "toggleFrameRendering"
| "setFrameAsActiveTool"
| "setIFrameAsActiveTool"
| "createContainerFromText"
| "wrapTextInContainer";

Expand Down
2 changes: 2 additions & 0 deletions src/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const getDefaultAppState = (): Omit<
currentItemStrokeWidth: DEFAULT_ELEMENT_PROPS.strokeWidth,
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
cursorButton: "up",
activeIFrame: null,
draggingElement: null,
editingElement: null,
editingGroupId: null,
Expand Down Expand Up @@ -140,6 +141,7 @@ const APP_STATE_STORAGE_CONF = (<
currentItemStrokeWidth: { browser: true, export: false, server: false },
currentItemTextAlign: { browser: true, export: false, server: false },
cursorButton: { browser: true, export: false, server: false },
activeIFrame: { browser: false, export: false, server: false },
draggingElement: { browser: false, export: false, server: false },
editingElement: { browser: false, export: false, server: false },
editingGroupId: { browser: true, export: false, server: false },
Expand Down
118 changes: 86 additions & 32 deletions src/components/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {

import "./Actions.scss";
import DropdownMenu from "./dropdownMenu/DropdownMenu";
import { extraToolsIcon, frameToolIcon } from "./icons";
import { EmbedIcon, extraToolsIcon, frameToolIcon } from "./icons";
import { KEYS } from "../keys";

export const SelectedShapeActions = ({
Expand Down Expand Up @@ -283,39 +283,76 @@ export const ShapesSwitcher = ({
<div className="App-toolbar__divider" />
{/* TEMP HACK because dropdown doesn't work well inside mobile toolbar */}
{device.isMobile ? (
<ToolButton
className={clsx("Shape", { fillable: false })}
type="radio"
icon={frameToolIcon}
checked={activeTool.type === "frame"}
name="editor-current-shape"
title={`${capitalizeString(
t("toolBar.frame"),
)}${KEYS.F.toLocaleUpperCase()}`}
keyBindingLabel={KEYS.F.toLocaleUpperCase()}
aria-label={capitalizeString(t("toolBar.frame"))}
aria-keyshortcuts={KEYS.F.toLocaleUpperCase()}
data-testid={`toolbar-frame`}
onPointerDown={({ pointerType }) => {
if (!appState.penDetected && pointerType === "pen") {
<>
<ToolButton
className={clsx("Shape", { fillable: false })}
type="radio"
icon={frameToolIcon}
checked={activeTool.type === "frame"}
name="editor-current-shape"
title={`${capitalizeString(
t("toolBar.frame"),
)}${KEYS.F.toLocaleUpperCase()}`}
keyBindingLabel={KEYS.F.toLocaleUpperCase()}
aria-label={capitalizeString(t("toolBar.frame"))}
aria-keyshortcuts={KEYS.F.toLocaleUpperCase()}
data-testid={`toolbar-frame`}
onPointerDown={({ pointerType }) => {
if (!appState.penDetected && pointerType === "pen") {
setAppState({
penDetected: true,
penMode: true,
});
}
}}
onChange={({ pointerType }) => {
trackEvent("toolbar", "frame", "ui");
const nextActiveTool = updateActiveTool(appState, {
type: "frame",
});
setAppState({
penDetected: true,
penMode: true,
activeTool: nextActiveTool,
multiElement: null,
selectedElementIds: {},
activeIFrame: null,
});
}
}}
onChange={({ pointerType }) => {
trackEvent("toolbar", "frame", "ui");
const nextActiveTool = updateActiveTool(appState, {
type: "frame",
});
setAppState({
activeTool: nextActiveTool,
multiElement: null,
selectedElementIds: {},
});
}}
/>
}}
/>
<ToolButton
className={clsx("Shape", { fillable: false })}
type="radio"
icon={EmbedIcon}
checked={activeTool.type === "iframe"}
name="editor-current-shape"
title={`${capitalizeString(
t("toolBar.iframe"),
)}${KEYS.W.toLocaleUpperCase()}`}
keyBindingLabel={KEYS.W.toLocaleUpperCase()}
aria-label={capitalizeString(t("toolBar.iframe"))}
aria-keyshortcuts={KEYS.W.toLocaleUpperCase()}
data-testid={`toolbar-iframe`}
onPointerDown={({ pointerType }) => {
if (!appState.penDetected && pointerType === "pen") {
setAppState({
penDetected: true,
penMode: true,
});
}
}}
onChange={({ pointerType }) => {
trackEvent("toolbar", "iframe", "ui");
const nextActiveTool = updateActiveTool(appState, {
type: "iframe",
});
setAppState({
activeTool: nextActiveTool,
multiElement: null,
selectedElementIds: {},
activeIFrame: null,
});
}}
/>
</>
) : (
<DropdownMenu open={isExtraToolsMenuOpen}>
<DropdownMenu.Trigger
Expand Down Expand Up @@ -347,6 +384,23 @@ export const ShapesSwitcher = ({
>
{t("toolBar.frame")}
</DropdownMenu.Item>
<DropdownMenu.Item
onSelect={() => {
const nextActiveTool = updateActiveTool(appState, {
type: "iframe",
});
setAppState({
activeTool: nextActiveTool,
multiElement: null,
selectedElementIds: {},
});
}}
icon={EmbedIcon}
shortcut={KEYS.W.toLocaleUpperCase()}
data-testid="toolbar-iframe"
>
{t("toolBar.iframe")}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
)}
Expand Down
Loading

0 comments on commit f10dbde

Please sign in to comment.