From 6d0ef158a4a21b278ac3a7c4e23ac7f91fdb1eb0 Mon Sep 17 00:00:00 2001 From: Jeff-Emmett Date: Wed, 12 Feb 2025 18:20:33 +0100 Subject: [PATCH] deploy embed minimize function --- src/shapes/EmbedShapeUtil.tsx | 474 +++++++++++++++++++++------------- 1 file changed, 295 insertions(+), 179 deletions(-) diff --git a/src/shapes/EmbedShapeUtil.tsx b/src/shapes/EmbedShapeUtil.tsx index 02da099..65e5337 100644 --- a/src/shapes/EmbedShapeUtil.tsx +++ b/src/shapes/EmbedShapeUtil.tsx @@ -8,6 +8,7 @@ export type IEmbedShape = TLBaseShape< w: number h: number url: string | null + isMinimized?: boolean interactionState?: { scrollPosition?: { x: number; y: number } currentTime?: number // for videos @@ -91,19 +92,19 @@ const transformUrl = (url: string): string => { const getDefaultDimensions = (url: string): { w: number; h: number } => { // YouTube default dimensions (16:9 ratio) if (url.match(/(?:youtube\.com|youtu\.be)/)) { - return { w: 560, h: 315 } + return { w: 800, h: 450 } } // Twitter/X default dimensions if (url.match(/(?:twitter\.com|x\.com)/)) { if (url.match(/\/status\/|\/tweets\//)) { - return { w: 500, h: 420 } // For individual tweets + return { w: 800, h: 600 } // For individual tweets } } // Google Maps default dimensions if (url.includes("google.com/maps") || url.includes("goo.gl/maps")) { - return { w: 600, h: 450 } + return { w: 800, h: 600 } } // Gather.town default dimensions @@ -112,7 +113,36 @@ const getDefaultDimensions = (url: string): { w: number; h: number } => { } // Default dimensions for other embeds - return { w: 640, h: 480 } + return { w: 800, h: 600 } +} + +const getFaviconUrl = (url: string): string => { + try { + const urlObj = new URL(url) + return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32` + } catch { + return '' // Return empty if URL is invalid + } +} + +const getDisplayTitle = (url: string): string => { + try { + const urlObj = new URL(url) + // Handle special cases + if (urlObj.hostname.includes('youtube.com')) { + return 'YouTube' + } + if (urlObj.hostname.includes('twitter.com') || urlObj.hostname.includes('x.com')) { + return 'Twitter/X' + } + if (urlObj.hostname.includes('google.com/maps')) { + return 'Google Maps' + } + // Default: return clean hostname + return urlObj.hostname.replace('www.', '') + } catch { + return url // Return original URL if parsing fails + } } export class EmbedShape extends BaseBoxShapeUtil { @@ -121,16 +151,21 @@ export class EmbedShape extends BaseBoxShapeUtil { getDefaultProps(): IEmbedShape["props"] { return { url: null, - w: 640, - h: 480, + w: 800, + h: 600, + isMinimized: false, } } indicator(shape: IEmbedShape) { return ( - - - + ) } @@ -179,15 +214,6 @@ export class EmbedShape extends BaseBoxShapeUtil { }) } - const wrapperStyle = { - width: `${shape.props.w}px`, - height: `${shape.props.h}px`, - padding: "15px", - boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", - backgroundColor: "#F0F0F0", - borderRadius: "4px", - } - const contentStyle = { pointerEvents: isSelected ? "none" as const : "all" as const, width: "100%", @@ -200,21 +226,153 @@ export class EmbedShape extends BaseBoxShapeUtil { overflow: "hidden", } - if (!shape.props.url) { + const wrapperStyle = { + position: 'relative' as const, + width: `${shape.props.w}px`, + height: `${shape.props.isMinimized ? 40 : shape.props.h}px`, + backgroundColor: "#F0F0F0", + borderRadius: "4px", + transition: "height 0.3s, width 0.3s", + overflow: "hidden", + } + + // Update control button styles + const controlButtonStyle = { + border: "none", + background: "#666666", // Grey background + color: "white", // White text + padding: "4px 12px", + margin: "0 4px", + borderRadius: "4px", + cursor: "pointer", + fontSize: "12px", + pointerEvents: "all" as const, + whiteSpace: "nowrap" as const, + transition: "background-color 0.2s", + "&:hover": { + background: "#4D4D4D", // Darker grey on hover + } + } + + const controlsContainerStyle = { + position: "absolute" as const, + top: "8px", + right: "8px", + display: "flex", + gap: "8px", + zIndex: 1, + } + + const handleToggleMinimize = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + this.editor.updateShape({ + id: shape.id, + type: "Embed", + props: { + ...shape.props, + isMinimized: !shape.props.isMinimized, + }, + }) + } + + const controls = (url: string) => ( +
+ + + +
+ ) + + // For minimized state, show URL and all controls + if (shape.props.url && shape.props.isMinimized) { return (
document.querySelector("input")?.focus()} - onPointerDown={(e) => { - e.preventDefault() - document.querySelector("input")?.focus() - }} - onTouchStart={(e) => { - e.preventDefault() - document.querySelector("input")?.focus() + style={{ + ...contentStyle, + height: "40px", + alignItems: "center", + padding: "0 15px", + position: "relative", + display: "flex", + gap: "8px", }} > + { + // Hide broken favicon + (e.target as HTMLImageElement).style.display = 'none' + }} + /> +
+ + {getDisplayTitle(shape.props.url)} + + + {shape.props.url} + +
+ {controls(shape.props.url)} +
+
+ ) + } + + // For empty state + if (!shape.props.url) { + return ( +
+ {controls("")} +
{ ) } - if (shape.props.url?.includes("medium.com")) { + // For medium.com and twitter profile views + if (shape.props.url?.includes("medium.com") || + (shape.props.url && shape.props.url.match(/(?:twitter\.com|x\.com)\/[^\/]+$/))) { return (
+ {controls(shape.props.url)}
{ ) } - if ( - shape.props.url && - shape.props.url.match(/(?:twitter\.com|x\.com)\/[^\/]+$/) - ) { - const username = shape.props.url.split("/").pop() - return ( - - ) - } - + // For normal embed view return (
-
-