feat: wireframe-to-code (#7334)

This commit is contained in:
David Luzar 2023-11-23 23:07:53 +01:00 committed by GitHub
parent d1e4421823
commit c7ee46e7f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 2106 additions and 444 deletions

View file

@ -6,25 +6,19 @@ import { getFontString, updateActiveTool } from "../utils";
import { setCursorForShape } from "../cursor";
import { newTextElement } from "./newElement";
import { getContainerElement, wrapText } from "./textElement";
import { isEmbeddableElement } from "./typeChecks";
import {
isFrameLikeElement,
isIframeElement,
isIframeLikeElement,
} from "./typeChecks";
import {
ExcalidrawElement,
ExcalidrawEmbeddableElement,
ExcalidrawIframeLikeElement,
IframeData,
NonDeletedExcalidrawElement,
Theme,
} from "./types";
type EmbeddedLink =
| ({
aspectRatio: { w: number; h: number };
warning?: string;
} & (
| { type: "video" | "generic"; link: string }
| { type: "document"; srcdoc: (theme: Theme) => string }
))
| null;
const embeddedLinkCache = new Map<string, EmbeddedLink>();
const embeddedLinkCache = new Map<string, IframeData>();
const RE_YOUTUBE =
/^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
@ -67,11 +61,13 @@ const ALLOWED_DOMAINS = new Set([
"dddice.com",
]);
const createSrcDoc = (body: string) => {
export const createSrcDoc = (body: string) => {
return `<html><body>${body}</body></html>`;
};
export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
export const getEmbedLink = (
link: string | null | undefined,
): IframeData | null => {
if (!link) {
return null;
}
@ -104,8 +100,12 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
break;
}
aspectRatio = isPortrait ? { w: 315, h: 560 } : { w: 560, h: 315 };
embeddedLinkCache.set(originalLink, { link, aspectRatio, type });
return { link, aspectRatio, type };
embeddedLinkCache.set(originalLink, {
link,
intrinsicSize: aspectRatio,
type,
});
return { link, intrinsicSize: aspectRatio, type };
}
const vimeoLink = link.match(RE_VIMEO);
@ -119,8 +119,12 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
aspectRatio = { w: 560, h: 315 };
//warning deliberately ommited so it is displayed only once per link
//same link next time will be served from cache
embeddedLinkCache.set(originalLink, { link, aspectRatio, type });
return { link, aspectRatio, type, warning };
embeddedLinkCache.set(originalLink, {
link,
intrinsicSize: aspectRatio,
type,
});
return { link, intrinsicSize: aspectRatio, type, warning };
}
const figmaLink = link.match(RE_FIGMA);
@ -130,27 +134,35 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
link,
)}`;
aspectRatio = { w: 550, h: 550 };
embeddedLinkCache.set(originalLink, { link, aspectRatio, type });
return { link, aspectRatio, type };
embeddedLinkCache.set(originalLink, {
link,
intrinsicSize: aspectRatio,
type,
});
return { link, intrinsicSize: aspectRatio, type };
}
const valLink = link.match(RE_VALTOWN);
if (valLink) {
link =
valLink[1] === "embed" ? valLink[0] : valLink[0].replace("/v", "/embed");
embeddedLinkCache.set(originalLink, { link, aspectRatio, type });
return { link, aspectRatio, type };
embeddedLinkCache.set(originalLink, {
link,
intrinsicSize: aspectRatio,
type,
});
return { link, intrinsicSize: aspectRatio, type };
}
if (RE_TWITTER.test(link)) {
let ret: EmbeddedLink;
let ret: IframeData;
// assume embed code
if (/<blockquote/.test(link)) {
const srcDoc = createSrcDoc(link);
ret = {
type: "document",
srcdoc: () => srcDoc,
aspectRatio: { w: 480, h: 480 },
intrinsicSize: { w: 480, h: 480 },
};
// assume regular tweet url
} else {
@ -160,7 +172,7 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
createSrcDoc(
`<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
),
aspectRatio: { w: 480, h: 480 },
intrinsicSize: { w: 480, h: 480 },
};
}
embeddedLinkCache.set(originalLink, ret);
@ -168,14 +180,14 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
}
if (RE_GH_GIST.test(link)) {
let ret: EmbeddedLink;
let ret: IframeData;
// assume embed code
if (/<script>/.test(link)) {
const srcDoc = createSrcDoc(link);
ret = {
type: "document",
srcdoc: () => srcDoc,
aspectRatio: { w: 550, h: 720 },
intrinsicSize: { w: 550, h: 720 },
};
// assume regular url
} else {
@ -190,26 +202,26 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
.gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
</style>
`),
aspectRatio: { w: 550, h: 720 },
intrinsicSize: { w: 550, h: 720 },
};
}
embeddedLinkCache.set(link, ret);
return ret;
}
embeddedLinkCache.set(link, { link, aspectRatio, type });
return { link, aspectRatio, type };
embeddedLinkCache.set(link, { link, intrinsicSize: aspectRatio, type });
return { link, intrinsicSize: aspectRatio, type };
};
export const isEmbeddableOrLabel = (
export const isIframeLikeOrItsLabel = (
element: NonDeletedExcalidrawElement,
): Boolean => {
if (isEmbeddableElement(element)) {
if (isIframeLikeElement(element)) {
return true;
}
if (element.type === "text") {
const container = getContainerElement(element);
if (container && isEmbeddableElement(container)) {
if (container && isFrameLikeElement(container)) {
return true;
}
}
@ -217,10 +229,16 @@ export const isEmbeddableOrLabel = (
};
export const createPlaceholderEmbeddableLabel = (
element: ExcalidrawEmbeddableElement,
element: ExcalidrawIframeLikeElement,
): ExcalidrawElement => {
const text =
!element.link || element?.link === "" ? "Empty Web-Embed" : element.link;
let text: string;
if (isIframeElement(element)) {
text = "IFrame element";
} else {
text =
!element.link || element?.link === "" ? "Empty Web-Embed" : element.link;
}
const fontSize = Math.max(
Math.min(element.width / 2, element.width / text.length),
element.width / 30,