diff --git a/app/components/chat/Artifact.tsx b/app/components/chat/Artifact.tsx deleted file mode 100644 index fc40f5d5..00000000 --- a/app/components/chat/Artifact.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import { useStore } from '@nanostores/react'; -import { AnimatePresence, motion } from 'framer-motion'; -import { computed } from 'nanostores'; -import { memo, useEffect, useRef, useState } from 'react'; -import { createHighlighter, type BundledLanguage, type BundledTheme, type HighlighterGeneric } from 'shiki'; -import type { ActionState } from '~/lib/runtime/action-runner'; -import { workbenchStore } from '~/lib/stores/workbench'; -import { classNames } from '~/utils/classNames'; -import { cubicEasingFn } from '~/utils/easings'; -import { WORK_DIR } from '~/utils/constants'; -import { createAsyncSuspenseValue } from '~/lib/asyncSuspenseValue'; - -const highlighterOptions = { - langs: ['shell'], - themes: ['light-plus', 'dark-plus'], -}; - -const shellHighlighter = createAsyncSuspenseValue(async () => { - const shellHighlighterPromise: Promise> = - import.meta.hot?.data.shellHighlighterPromise ?? createHighlighter(highlighterOptions); - - if (import.meta.hot) { - import.meta.hot.data.shellHighlighterPromise = shellHighlighterPromise; - } - - return shellHighlighterPromise; -}); - -if (typeof document !== 'undefined') { - shellHighlighter.preload(); -} - -interface ArtifactProps { - messageId: string; -} - -export const Artifact = memo(({ messageId }: ArtifactProps) => { - const userToggledActions = useRef(false); - const [showActions, setShowActions] = useState(false); - const [allActionFinished, setAllActionFinished] = useState(false); - - const artifacts = useStore(workbenchStore.artifacts); - const artifact = artifacts[messageId]; - - const actions = useStore( - computed(artifact.runner.actions, (actions) => { - return Object.values(actions); - }), - ); - - const toggleActions = () => { - userToggledActions.current = true; - setShowActions(!showActions); - }; - - useEffect(() => { - if (actions.length && !showActions && !userToggledActions.current) { - setShowActions(true); - } - - if (actions.length !== 0 && artifact.type === 'bundled') { - const finished = !actions.find((action) => action.status !== 'complete'); - - if (allActionFinished !== finished) { - setAllActionFinished(finished); - } - } - }, [actions]); - - return ( -
-
- -
- - {actions.length && artifact.type !== 'bundled' && ( - -
-
-
-
- )} -
-
- - {artifact.type !== 'bundled' && showActions && actions.length > 0 && ( - -
- -
- -
- - )} - -
- ); -}); - -interface ShellCodeBlockProps { - classsName?: string; - code: string; -} - -function ShellCodeBlock({ classsName, code }: ShellCodeBlockProps) { - return ( -
- ); -} - -interface ActionListProps { - actions: ActionState[]; -} - -const actionVariants = { - hidden: { opacity: 0, y: 20 }, - visible: { opacity: 1, y: 0 }, -}; - -function openArtifactInWorkbench(filePath: any) { - if (workbenchStore.currentView.get() !== 'code') { - workbenchStore.currentView.set('code'); - } - - workbenchStore.setSelectedFile(`${WORK_DIR}/${filePath}`); -} - -const ActionList = memo(({ actions }: ActionListProps) => { - return ( - -
    - {actions.map((action, index) => { - const { status, type, content } = action; - const isLast = index === actions.length - 1; - - return ( - -
    -
    - {status === 'running' ? ( - <> - {type !== 'start' ? ( -
    - ) : ( -
    - )} - - ) : status === 'pending' ? ( -
    - ) : status === 'complete' ? ( -
    - ) : status === 'failed' || status === 'aborted' ? ( -
    - ) : null} -
    - {type === 'file' ? ( -
    - Create{' '} - openArtifactInWorkbench(action.filePath)} - > - {action.filePath} - -
    - ) : type === 'shell' ? ( -
    - Run command -
    - ) : type === 'start' ? ( - { - e.preventDefault(); - workbenchStore.currentView.set('preview'); - }} - className="flex items-center w-full min-h-[28px]" - > - Start Application - - ) : null} -
    - {(type === 'shell' || type === 'start') && ( - - )} -
    - ); - })} -
-
- ); -}); - -function getIconColor(status: ActionState['status']) { - switch (status) { - case 'pending': { - return 'text-bolt-elements-textTertiary'; - } - case 'running': { - return 'text-bolt-elements-loader-progress'; - } - case 'complete': { - return 'text-bolt-elements-icon-success'; - } - case 'aborted': { - return 'text-bolt-elements-textSecondary'; - } - case 'failed': { - return 'text-bolt-elements-icon-error'; - } - default: { - return undefined; - } - } -} diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 61436c3e..9afad2ec 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -7,7 +7,7 @@ import type { Message } from 'ai'; import { useAnimate } from 'framer-motion'; import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { cssTransition, toast, ToastContainer } from 'react-toastify'; -import { useMessageParser, useShortcuts, useSnapScroll } from '~/lib/hooks'; +import { useMessageParser, useSnapScroll } from '~/lib/hooks'; import { description, useChatHistory } from '~/lib/persistence'; import { chatStore } from '~/lib/stores/chat'; import { workbenchStore } from '~/lib/stores/workbench'; @@ -170,8 +170,7 @@ async function clearActiveChat() { gActiveChatMessageTelemetry = undefined; if (gUpdateSimulationAfterChatMessage) { - const { contentBase64 } = await workbenchStore.generateZipBase64(); - await simulationRepositoryUpdated(contentBase64); + await simulationRepositoryUpdated(); gUpdateSimulationAfterChatMessage = false; } } @@ -180,8 +179,7 @@ export async function onRepositoryFileWritten() { if (gActiveChatMessageTelemetry) { gUpdateSimulationAfterChatMessage = true; } else { - const { contentBase64 } = await workbenchStore.generateZipBase64(); - await simulationRepositoryUpdated(contentBase64); + await simulationRepositoryUpdated(); } } @@ -189,10 +187,14 @@ function buildMessageId(prefix: string, chatId: string) { return `${prefix}-${chatId}`; } +const EnhancedPromptPrefix = 'enhanced-prompt'; + +export function isEnhancedPromptMessage(message: Message): boolean { + return message.id.startsWith(EnhancedPromptPrefix); +} + export const ChatImpl = memo( ({ description, initialMessages, storeMessageHistory, importChat, exportChat }: ChatProps) => { - useShortcuts(); - const textareaRef = useRef(null); const [chatStarted, setChatStarted] = useState(initialMessages.length > 0); const [uploadedFiles, setUploadedFiles] = useState([]); // Move here @@ -321,7 +323,7 @@ export const ChatImpl = memo( } const enhancedPromptMessage: Message = { - id: buildMessageId('enhanced-prompt', chatId), + id: buildMessageId(EnhancedPromptPrefix, chatId), role: 'assistant', content: message, }; diff --git a/app/components/chat/GitCloneButton.tsx b/app/components/chat/GitCloneButton.tsx index 376d59d6..f300907f 100644 --- a/app/components/chat/GitCloneButton.tsx +++ b/app/components/chat/GitCloneButton.tsx @@ -1,7 +1,6 @@ import ignore from 'ignore'; import { useGit } from '~/lib/hooks/useGit'; import type { Message } from 'ai'; -import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands'; import { generateId } from '~/utils/fileUtils'; import { useState } from 'react'; import { toast } from 'react-toastify'; @@ -59,22 +58,16 @@ export default function GitCloneButton({ importChat }: GitCloneButtonProps) { const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath)); console.log(filePaths); - const textDecoder = new TextDecoder('utf-8'); - const fileContents = filePaths .map((filePath) => { - const { data: content, encoding } = data[filePath]; + const file = data[filePath]; return { path: filePath, - content: - encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '', + content: file?.content, }; }) .filter((f) => f.content); - const commands = await detectProjectCommands(fileContents); - const commandsMessage = createCommandsMessage(commands); - const filesMessage: Message = { role: 'assistant', content: `Cloning the repo ${repoUrl} into ${workdir} @@ -94,10 +87,6 @@ ${file.content} const messages = [filesMessage]; - if (commandsMessage) { - messages.push(commandsMessage); - } - await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages); } } catch (error) { diff --git a/app/components/chat/Markdown.tsx b/app/components/chat/Markdown.tsx index 07b6a673..b5484b7d 100644 --- a/app/components/chat/Markdown.tsx +++ b/app/components/chat/Markdown.tsx @@ -3,7 +3,6 @@ import ReactMarkdown, { type Components } from 'react-markdown'; import type { BundledLanguage } from 'shiki'; import { createScopedLogger } from '~/utils/logger'; import { rehypePlugins, remarkPlugins, allowedHTMLElements } from '~/utils/markdown'; -import { Artifact } from './Artifact'; import { CodeBlock } from './CodeBlock'; import styles from './Markdown.module.scss'; @@ -22,16 +21,6 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false const components = useMemo(() => { return { div: ({ className, children, node, ...props }) => { - if (className?.includes('__boltArtifact__')) { - const messageId = node?.properties.dataMessageId as string; - - if (!messageId) { - logger.error(`Invalid message id ${messageId}`); - } - - return ; - } - return (
{children} diff --git a/app/components/git/GitUrlImport.client.tsx b/app/components/git/GitUrlImport.client.tsx index fe8b346b..1ab8cde7 100644 --- a/app/components/git/GitUrlImport.client.tsx +++ b/app/components/git/GitUrlImport.client.tsx @@ -7,7 +7,6 @@ import { BaseChat } from '~/components/chat/BaseChat'; import { Chat } from '~/components/chat/Chat.client'; import { useGit } from '~/lib/hooks/useGit'; import { useChatHistory } from '~/lib/persistence'; -import { createCommandsMessage, detectProjectCommands } from '~/utils/projectCommands'; import { LoadingOverlay } from '~/components/ui/LoadingOverlay'; import { toast } from 'react-toastify'; @@ -59,18 +58,14 @@ export function GitUrlImport() { const fileContents = filePaths .map((filePath) => { - const { data: content, encoding } = data[filePath]; + const file = data[filePath]; return { path: filePath, - content: - encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '', + content: file?.content, }; }) .filter((f) => f.content); - const commands = await detectProjectCommands(fileContents); - const commandsMessage = createCommandsMessage(commands); - const filesMessage: Message = { role: 'assistant', content: `Cloning the repo ${repoUrl} into ${workdir} @@ -90,10 +85,6 @@ ${file.content} const messages = [filesMessage]; - if (commandsMessage) { - messages.push(commandsMessage); - } - await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages); } } catch (error) { diff --git a/app/components/sidebar/date-binning.ts b/app/components/sidebar/date-binning.ts index 0a364ffe..748ebf90 100644 --- a/app/components/sidebar/date-binning.ts +++ b/app/components/sidebar/date-binning.ts @@ -4,7 +4,7 @@ import type { ChatHistoryItem } from '~/lib/persistence'; type Bin = { category: string; items: ChatHistoryItem[] }; export function binDates(_list: ChatHistoryItem[]) { - const list = _list.toSorted((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)); + const list = [..._list].sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)); const binLookup: Record = {}; const bins: Array = []; diff --git a/app/components/workbench/EditorPanel.tsx b/app/components/workbench/EditorPanel.tsx index f68e030e..53bdd645 100644 --- a/app/components/workbench/EditorPanel.tsx +++ b/app/components/workbench/EditorPanel.tsx @@ -13,13 +13,10 @@ import { PanelHeader } from '~/components/ui/PanelHeader'; import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton'; import type { FileMap } from '~/lib/stores/files'; import { themeStore } from '~/lib/stores/theme'; -import { WORK_DIR } from '~/utils/constants'; import { renderLogger } from '~/utils/logger'; import { isMobile } from '~/utils/mobile'; import { FileBreadcrumb } from './FileBreadcrumb'; import { FileTree } from './FileTree'; -import { DEFAULT_TERMINAL_SIZE, TerminalTabs } from './terminal/TerminalTabs'; -import { workbenchStore } from '~/lib/stores/workbench'; interface EditorPanelProps { files?: FileMap; @@ -34,7 +31,7 @@ interface EditorPanelProps { onFileReset?: () => void; } -const DEFAULT_EDITOR_SIZE = 100 - DEFAULT_TERMINAL_SIZE; +const DEFAULT_EDITOR_SIZE = 100; const editorSettings: EditorSettings = { tabSize: 2 }; @@ -54,7 +51,6 @@ export const EditorPanel = memo( renderLogger.trace('EditorPanel'); const theme = useStore(themeStore); - const showTerminal = useStore(workbenchStore.showTerminal); const activeFileSegments = useMemo(() => { if (!editorDocument) { @@ -70,7 +66,7 @@ export const EditorPanel = memo( return ( - +
@@ -81,9 +77,7 @@ export const EditorPanel = memo( @@ -126,7 +120,6 @@ export const EditorPanel = memo( - ); }, diff --git a/app/components/workbench/FileBreadcrumb.tsx b/app/components/workbench/FileBreadcrumb.tsx index 0ffbb2fd..79620501 100644 --- a/app/components/workbench/FileBreadcrumb.tsx +++ b/app/components/workbench/FileBreadcrumb.tsx @@ -3,13 +3,10 @@ import { AnimatePresence, motion, type Variants } from 'framer-motion'; import { memo, useEffect, useRef, useState } from 'react'; import type { FileMap } from '~/lib/stores/files'; import { classNames } from '~/utils/classNames'; -import { WORK_DIR } from '~/utils/constants'; import { cubicEasingFn } from '~/utils/easings'; import { renderLogger } from '~/utils/logger'; import FileTree from './FileTree'; -const WORK_DIR_REGEX = new RegExp(`^${WORK_DIR.split('/').slice(0, -1).join('/').replaceAll('/', '\\/')}/`); - interface FileBreadcrumbProps { files?: FileMap; pathSegments?: string[]; @@ -76,10 +73,6 @@ export const FileBreadcrumb = memo(({ files, pathSegments = const path = pathSegments.slice(0, index).join('/'); - if (!WORK_DIR_REGEX.test(path)) { - return null; - } - const isActive = activeIndex === index; return ( @@ -121,8 +114,6 @@ export const FileBreadcrumb = memo(({ files, pathSegments =
void; - rootFolder?: string; - hideRoot?: boolean; collapsed?: boolean; allowFolderSelection?: boolean; hiddenFiles?: Array; @@ -27,8 +25,6 @@ export const FileTree = memo( files = {}, onFileSelect, selectedFile, - rootFolder, - hideRoot = false, collapsed = false, allowFolderSelection = false, hiddenFiles, @@ -40,8 +36,8 @@ export const FileTree = memo( const computedHiddenFiles = useMemo(() => [...DEFAULT_HIDDEN_FILES, ...(hiddenFiles ?? [])], [hiddenFiles]); const fileList = useMemo(() => { - return buildFileList(files, rootFolder, hideRoot, computedHiddenFiles); - }, [files, rootFolder, hideRoot, computedHiddenFiles]); + return buildFileList(files, computedHiddenFiles); + }, [files, computedHiddenFiles]); const [collapsedFolders, setCollapsedFolders] = useState(() => { return collapsed @@ -121,7 +117,7 @@ export const FileTree = memo( const onCopyRelativePath = (fileOrFolder: FileNode | FolderNode) => { try { - navigator.clipboard.writeText(fileOrFolder.fullPath.substring((rootFolder || '').length)); + navigator.clipboard.writeText(fileOrFolder.fullPath); } catch (error) { logger.error(error); } @@ -334,21 +330,11 @@ interface FolderNode extends BaseNode { kind: 'folder'; } -function buildFileList( - files: FileMap, - rootFolder = '/', - hideRoot: boolean, - hiddenFiles: Array, -): Node[] { +function buildFileList(files: FileMap, hiddenFiles: Array): Node[] { const folderPaths = new Set(); const fileList: Node[] = []; - let defaultDepth = 0; - - if (rootFolder === '/' && !hideRoot) { - defaultDepth = 1; - fileList.push({ kind: 'folder', name: '/', depth: 0, id: 0, fullPath: '/' }); - } + const defaultDepth = 0; for (const [filePath, dirent] of Object.entries(files)) { const segments = filePath.split('/').filter((segment) => segment); @@ -358,21 +344,16 @@ function buildFileList( continue; } - let currentPath = ''; + let fullPath = ''; let i = 0; let depth = 0; while (i < segments.length) { const name = segments[i]; - const fullPath = (currentPath += `/${name}`); + fullPath += (fullPath.length ? '/' : '') + name; - if (!fullPath.startsWith(rootFolder) || (hideRoot && fullPath === rootFolder)) { - i++; - continue; - } - - if (i === segments.length - 1 && dirent?.type === 'file') { + if (i === segments.length - 1) { fileList.push({ kind: 'file', id: fileList.length, @@ -397,7 +378,7 @@ function buildFileList( } } - return sortFileList(rootFolder, fileList, hideRoot); + return sortFileList(fileList); } function isHiddenFile(filePath: string, fileName: string, hiddenFiles: Array) { @@ -410,6 +391,16 @@ function isHiddenFile(filePath: string, fileName: string, hiddenFiles: Array(); @@ -435,15 +426,13 @@ function sortFileList(rootFolder: string, nodeList: Node[], hideRoot: boolean): for (const node of nodeList) { nodeMap.set(node.fullPath, node); - const parentPath = node.fullPath.slice(0, node.fullPath.lastIndexOf('/')); + const parentPath = getParentPath(node.fullPath); - if (parentPath !== rootFolder.slice(0, rootFolder.lastIndexOf('/'))) { - if (!childrenMap.has(parentPath)) { - childrenMap.set(parentPath, []); - } - - childrenMap.get(parentPath)?.push(node); + if (!childrenMap.has(parentPath)) { + childrenMap.set(parentPath, []); } + + childrenMap.get(parentPath)?.push(node); } const sortedList: Node[] = []; @@ -468,15 +457,10 @@ function sortFileList(rootFolder: string, nodeList: Node[], hideRoot: boolean): } }; - if (hideRoot) { - // if root is hidden, start traversal from its immediate children - const rootChildren = childrenMap.get(rootFolder) || []; + const rootChildren = childrenMap.get('') || []; - for (const child of rootChildren) { - depthFirstTraversal(child.fullPath); - } - } else { - depthFirstTraversal(rootFolder); + for (const child of rootChildren) { + depthFirstTraversal(child.fullPath); } return sortedList; diff --git a/app/components/workbench/PortDropdown.tsx b/app/components/workbench/PortDropdown.tsx deleted file mode 100644 index 13457b2d..00000000 --- a/app/components/workbench/PortDropdown.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { memo, useEffect, useRef } from 'react'; -import { IconButton } from '~/components/ui/IconButton'; -import type { PreviewInfo } from '~/lib/stores/previews'; - -interface PortDropdownProps { - activePreviewIndex: number; - setActivePreviewIndex: (index: number) => void; - isDropdownOpen: boolean; - setIsDropdownOpen: (value: boolean) => void; - setHasSelectedPreview: (value: boolean) => void; - previews: PreviewInfo[]; -} - -export const PortDropdown = memo( - ({ - activePreviewIndex, - setActivePreviewIndex, - isDropdownOpen, - setIsDropdownOpen, - setHasSelectedPreview, - previews, - }: PortDropdownProps) => { - const dropdownRef = useRef(null); - - // sort previews, preserving original index - const sortedPreviews = previews - .map((previewInfo, index) => ({ ...previewInfo, index })) - .sort((a, b) => a.port - b.port); - - // close dropdown if user clicks outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsDropdownOpen(false); - } - }; - - if (isDropdownOpen) { - window.addEventListener('mousedown', handleClickOutside); - } else { - window.removeEventListener('mousedown', handleClickOutside); - } - - return () => { - window.removeEventListener('mousedown', handleClickOutside); - }; - }, [isDropdownOpen]); - - return ( -
- setIsDropdownOpen(!isDropdownOpen)} /> - {isDropdownOpen && ( -
-
- Ports -
- {sortedPreviews.map((preview) => ( -
{ - setActivePreviewIndex(preview.index); - setIsDropdownOpen(false); - setHasSelectedPreview(true); - }} - > - - {preview.port} - -
- ))} -
- )} -
- ); - }, -); diff --git a/app/components/workbench/Preview.tsx b/app/components/workbench/Preview.tsx index 07bc70bd..2a9fec6a 100644 --- a/app/components/workbench/Preview.tsx +++ b/app/components/workbench/Preview.tsx @@ -3,7 +3,6 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { IconButton } from '~/components/ui/IconButton'; import { workbenchStore } from '~/lib/stores/workbench'; import { simulationReloaded } from '~/lib/replay/SimulationPrompt'; -import { PortDropdown } from './PortDropdown'; import { PointSelector } from './PointSelector'; type ResizeSide = 'left' | 'right' | null; @@ -19,18 +18,16 @@ export const Preview = memo(() => { const containerRef = useRef(null); const inputRef = useRef(null); - const [activePreviewIndex, setActivePreviewIndex] = useState(0); const [isPortDropdownOpen, setIsPortDropdownOpen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); - const hasSelectedPreview = useRef(false); - const previews = useStore(workbenchStore.previews); - const activePreview = previews[activePreviewIndex]; const [url, setUrl] = useState(''); const [iframeUrl, setIframeUrl] = useState(); const [isSelectionMode, setIsSelectionMode] = useState(false); const [selectionPoint, setSelectionPoint] = useState<{ x: number; y: number } | null>(null); + const previewURL = useStore(workbenchStore.previewURL); + // Toggle between responsive mode and device mode const [isDeviceModeOn, setIsDeviceModeOn] = useState(false); @@ -51,78 +48,17 @@ export const Preview = memo(() => { gCurrentIFrame = iframeRef.current ?? undefined; useEffect(() => { - if (!activePreview) { + if (!previewURL) { setUrl(''); setIframeUrl(undefined); + setSelectionPoint(null); return; } - const { baseUrl } = activePreview; - setUrl(baseUrl); - setIframeUrl(baseUrl); - }, [activePreview]); - - // Trim any long base URL from the start of the provided URL. - const displayUrl = (url: string) => { - if (!activePreview) { - return url; - } - - const { baseUrl } = activePreview; - - if (url.startsWith(baseUrl)) { - const trimmedUrl = url.slice(baseUrl.length); - - if (trimmedUrl.startsWith('/')) { - return trimmedUrl; - } - - return '/' + trimmedUrl; - } - - return url; - }; - - const validateUrl = useCallback( - (value: string) => { - if (!activePreview) { - return null; - } - - const { baseUrl } = activePreview; - - if (value === baseUrl) { - return value; - } else if (value.startsWith(baseUrl)) { - if (['/', '?', '#'].includes(value.charAt(baseUrl.length))) { - return value; - } - } - - if (value.startsWith('/')) { - return baseUrl + value; - } - - return null; - }, - [activePreview], - ); - - const findMinPortIndex = useCallback( - (minIndex: number, preview: { port: number }, index: number, array: { port: number }[]) => { - return preview.port < array[minIndex].port ? index : minIndex; - }, - [], - ); - - // When previews change, display the lowest port if user hasn't selected a preview - useEffect(() => { - if (previews.length > 1 && !hasSelectedPreview.current) { - const minPortIndex = previews.reduce(findMinPortIndex, 0); - setActivePreviewIndex(minPortIndex); - } - }, [previews, findMinPortIndex]); + setUrl(previewURL); + setIframeUrl(previewURL); + }, [previewURL]); const reloadPreview = () => { if (iframeRef.current) { @@ -279,14 +215,14 @@ export const Preview = memo(() => { ref={inputRef} className="w-full bg-transparent outline-none" type="text" - value={displayUrl(url)} + value={url} onChange={(event) => { setUrl(event.target.value); }} onKeyDown={(event) => { let newUrl; - if (event.key === 'Enter' && (newUrl = validateUrl(url))) { + if (event.key === 'Enter') { setIframeUrl(newUrl); if (inputRef.current) { @@ -297,17 +233,6 @@ export const Preview = memo(() => { />
- {previews.length > 1 && ( - (hasSelectedPreview.current = value)} - setIsDropdownOpen={setIsPortDropdownOpen} - previews={previews} - /> - )} - {/* Device mode toggle button */} { display: 'flex', }} > - {activePreview ? ( + {previewURL ? ( <>