diff --git a/apps/web/client/messages/en.json b/apps/web/client/messages/en.json index 5907b83fbe..20a0195e63 100644 --- a/apps/web/client/messages/en.json +++ b/apps/web/client/messages/en.json @@ -1,367 +1,370 @@ { - "projects": { - "create": { - "settings": { - "title": "Settings", - "tooltip": "Configure new project settings" - }, - "success": "Project created successfully.", - "steps": { - "count": "{current} of {total}", - "error": "Project data is missing." - }, - "methods": { - "new": "Create New Project", - "load": "Load Existing Project" - }, - "loading": { - "title": "Setting up your new Onlook app...", - "description": "This may take a few seconds", - "cancel": "Cancel" - }, - "error": { - "title": "Error creating your Onlook app", - "backToPrompt": "Back to Prompting" - } - }, + "projects": { + "create": { + "settings": { + "title": "Settings", + "tooltip": "Configure new project settings" + }, + "success": "Project created successfully.", + "steps": { + "count": "{current} of {total}", + "error": "Project data is missing." + }, + "methods": { + "new": "Create New Project", + "load": "Load Existing Project" + }, + "loading": { + "title": "Setting up your new Onlook app...", + "description": "This may take a few seconds", + "cancel": "Cancel" + }, + "error": { + "title": "Error creating your Onlook app", + "backToPrompt": "Back to Prompting" + } + }, + "select": { + "empty": "No projects found", + "sort": { + "recent": "Recently Updated", + "name": "Project Name" + }, + "lastEdited": "Last edited {time} ago" + }, + "actions": { + "import": "Import Project", + "close": "Close", + "about": "About Onlook", + "signOut": "Sign out", + "editApp": "Edit App", + "projectSettings": "Project settings", + "showInExplorer": "Show in Explorer", + "renameProject": "Rename Project", + "deleteProject": "Delete Project", + "cloneProject": "Clone Project", + "cancel": "Cancel", + "delete": "Delete", + "rename": "Rename", + "clone": "Clone", + "goToAllProjects": "Go to all Projects", + "newProject": "New Project", + "blankProject": "Blank Project", + "startFromScratch": "Start from scratch", + "importProject": "Import a project", + "subscriptions": "Subscriptions", + "settings": "Settings", + "downloadCode": "Download Code", + "downloadingCode": "Preparing download...", + "downloadSuccess": "Download started successfully", + "downloadError": "Failed to prepare download", + "recentProjects": "Recent Projects" + }, + "dialogs": { + "delete": { + "title": "Are you sure you want to delete this project?", + "description": "This action cannot be undone. This will permanently delete your project and remove all associated data.", + "moveToTrash": "Also move folder to trash" + }, + "rename": { + "title": "Rename Project", + "label": "Project Name", + "error": "Project name can't be empty" + }, + "clone": { + "title": "Clone Project", + "label": "Project Name", + "placeholder": "Enter name for cloned project", + "error": "Project name can't be empty" + } + }, + "prompt": { + "title": "What kind of website do you want to make?", + "description": "Tell us a bit about your project. Be as detailed as possible.", + "input": { + "placeholder": "Paste a reference screenshot, write a novel, get creative...", + "imageUpload": "Upload Image Reference", + "fileReference": "File Reference", + "submit": "Start building your site" + }, + "blankStart": "Start from a blank page" + } + }, + "welcome": { + "title": "Welcome to Onlook", + "titleReturn": "Welcome back to Onlook", + "description": "A next-generation visual code editor that lets designers and product managers craft web experiences with AI.", + "alpha": "Alpha", + "login": { + "github": "Login with GitHub", + "google": "Login with Google", + "lastUsed": "You used this last time", + "loginToEdit": "Login to Edit", + "shareProjects": "No credit card required • Get a site in seconds" + }, + "terms": { + "agreement": "By signing up, you agree to our", + "privacy": "Privacy Policy", + "and": "and", + "tos": "Terms of Service" + }, + "version": "Version {version}" + }, + "pricing": { + "plans": { + "basic": { + "name": "Basic", + "price": "$0/month", + "description": "Prototype and experiment in code with ease.", + "features": [ + "Visual code editor access", + "Unlimited projects", + "{dailyMessages} AI chat messages a day", + "{monthlyMessages} AI messages a month", + "Limited to 1 screenshot per chat" + ] + }, + "pro": { + "name": "Pro", + "price": "$20/month", + "description": "Creativity – unconstrained. Build stunning sites with AI.", + "features": [ + "Visual code editor access", + "Unlimited projects", + "Unlimited AI chat messages a day", + "Unlimited monthly chats", + "Remove built with Onlook watermark", + "1 free custom domain hosted with Onlook", + "Priority support" + ] + }, + "launch": { + "name": "Launch", + "price": "$50/month", + "description": "Perfect for startups and growing teams", + "features": [ + "Unlimited daily messages", + "Priority support", + "Advanced integrations", + "Team collaboration features" + ] + }, + "scale": { + "name": "Scale", + "price": "$100/month", + "description": "Enterprise-grade features for large teams", + "features": [ + "Everything in Launch plan", + "Dedicated account manager", + "Custom integrations", + "Advanced analytics", + "24/7 premium support" + ] + } + }, + "titles": { + "choosePlan": "Choose your plan", + "proMember": "Thanks for being a Pro member!" + }, + "buttons": { + "currentPlan": "Current Plan", + "getPro": "Get Pro", + "manageSubscription": "Manage Subscription" + }, + "loading": { + "checkingPayment": "Checking for payment..." + }, + "toasts": { + "checkingOut": { + "title": "Checking out", + "description": "You will now be redirected to Stripe to complete the payment." + }, + "redirectingToStripe": { + "title": "Redirecting to Stripe", + "description": "You will now be redirected to Stripe to manage your subscription." + }, + "error": { + "title": "Error", + "description": "Could not initiate checkout process. Please try again." + } + }, + "footer": { + "unusedMessages": "Unused chat messages will roll over to the next month." + } + }, + "editor": { + "modes": { + "design": { + "name": "Design", + "description": "Edit and modify your website's design", + "tooltip": "Switch to design mode" + }, + "code": { + "name": "Code", + "description": "Edit and modify your website's code", + "tooltip": "Switch to code mode" + }, + "preview": { + "name": "Preview", + "description": "Preview and test your website's functionality", + "tooltip": "Switch to Preview mode" + } + }, + "toolbar": { + "tools": { "select": { - "empty": "No projects found", - "sort": { - "recent": "Recently Updated", - "name": "Project Name" - }, - "lastEdited": "Last edited {time} ago" + "name": "Select", + "tooltip": "Select and modify elements" }, - "actions": { - "import": "Import Project", - "close": "Close", - "about": "About Onlook", - "signOut": "Sign out", - "editApp": "Edit App", - "projectSettings": "Project settings", - "showInExplorer": "Show in Explorer", - "renameProject": "Rename Project", - "deleteProject": "Delete Project", - "cloneProject": "Clone Project", - "cancel": "Cancel", - "delete": "Delete", - "rename": "Rename", - "clone": "Clone", - "goToAllProjects": "Go to all Projects", - "newProject": "New Project", - "blankProject": "Blank Project", - "startFromScratch": "Start from scratch", - "importProject": "Import a project", - "subscriptions": "Subscriptions", - "settings": "Settings", - "downloadCode": "Download Code", - "downloadingCode": "Preparing download...", - "downloadSuccess": "Download started successfully", - "downloadError": "Failed to prepare download", - "recentProjects": "Recent Projects" + "pan": { + "name": "Pan", + "tooltip": "Pan and move around the canvas" }, - "dialogs": { - "delete": { - "title": "Are you sure you want to delete this project?", - "description": "This action cannot be undone. This will permanently delete your project and remove all associated data.", - "moveToTrash": "Also move folder to trash" - }, - "rename": { - "title": "Rename Project", - "label": "Project Name", - "error": "Project name can't be empty" - }, - "clone": { - "title": "Clone Project", - "label": "Project Name", - "placeholder": "Enter name for cloned project", - "error": "Project name can't be empty" - } + "insertDiv": { + "name": "Insert Container", + "tooltip": "Add a new container element" }, - "prompt": { - "title": "What kind of website do you want to make?", - "description": "Tell us a bit about your project. Be as detailed as possible.", - "input": { - "placeholder": "Paste a reference screenshot, write a novel, get creative...", - "imageUpload": "Upload Image Reference", - "fileReference": "File Reference", - "submit": "Start building your site" - }, - "blankStart": "Start from a blank page" + "insertText": { + "name": "Insert Text", + "tooltip": "Add a new text element" } + }, + "versionHistory": "Version History" }, - "welcome": { - "title": "Welcome to Onlook", - "titleReturn": "Welcome back to Onlook", - "description": "A next-generation visual code editor that lets designers and product managers craft web experiences with AI.", - "alpha": "Alpha", - "login": { - "github": "Login with GitHub", - "google": "Login with Google", - "lastUsed": "You used this last time", - "loginToEdit": "Login to Edit", - "shareProjects": "No credit card required • Get a site in seconds" - }, - "terms": { - "agreement": "By signing up, you agree to our", - "privacy": "Privacy Policy", - "and": "and", - "tos": "Terms of Service" - }, - "version": "Version {version}" - }, - "pricing": { - "plans": { - "basic": { - "name": "Basic", - "price": "$0/month", - "description": "Prototype and experiment in code with ease.", - "features": [ - "Visual code editor access", - "Unlimited projects", - "{dailyMessages} AI chat messages a day", - "{monthlyMessages} AI messages a month", - "Limited to 1 screenshot per chat" - ] - }, - "pro": { - "name": "Pro", - "price": "$20/month", - "description": "Creativity – unconstrained. Build stunning sites with AI.", - "features": [ - "Visual code editor access", - "Unlimited projects", - "Unlimited AI chat messages a day", - "Unlimited monthly chats", - "Remove built with Onlook watermark", - "1 free custom domain hosted with Onlook", - "Priority support" - ] - }, - "launch": { - "name": "Launch", - "price": "$50/month", - "description": "Perfect for startups and growing teams", - "features": [ - "Unlimited daily messages", - "Priority support", - "Advanced integrations", - "Team collaboration features" - ] + "panels": { + "edit": { + "tabs": { + "chat": { + "name": "Chat", + "emptyState": "Select an element to chat with AI", + "emptyStateStart": "Start the project to chat", + "input": { + "placeholder": "Type your message...", + "tooltip": "Chat with AI about the selected element" }, - "scale": { - "name": "Scale", - "price": "$100/month", - "description": "Enterprise-grade features for large teams", - "features": [ - "Everything in Launch plan", - "Dedicated account manager", - "Custom integrations", - "Advanced analytics", - "24/7 premium support" - ] - } - }, - "titles": { - "choosePlan": "Choose your plan", - "proMember": "Thanks for being a Pro member!" - }, - "buttons": { - "currentPlan": "Current Plan", - "getPro": "Get Pro", - "manageSubscription": "Manage Subscription" - }, - "loading": { - "checkingPayment": "Checking for payment..." - }, - "toasts": { - "checkingOut": { - "title": "Checking out", - "description": "You will now be redirected to Stripe to complete the payment." + "mode": { + "tooltip": "Switch between Build and Ask modes" }, - "redirectingToStripe": { - "title": "Redirecting to Stripe", - "description": "You will now be redirected to Stripe to manage your subscription." + "controls": { + "newChat": "New Chat", + "history": "Chat History" }, - "error": { - "title": "Error", - "description": "Could not initiate checkout process. Please try again." - } - }, - "footer": { - "unusedMessages": "Unused chat messages will roll over to the next month." - } - }, - "editor": { - "modes": { - "design": { - "name": "Design", - "description": "Edit and modify your website's design", - "tooltip": "Switch to design mode" + "settings": { + "showSuggestions": "Show suggestions", + "showMiniChat": "Show mini chat", + "autoApplyCode": "Auto-apply results", + "expandCodeBlocks": "Show code while rendering" }, - "code": { - "name": "Code", - "description": "Edit and modify your website's code", - "tooltip": "Switch to code mode" + "miniChat": { + "button": "Chat with AI" }, - "preview": { - "name": "Preview", - "description": "Preview and test your website's functionality", - "tooltip": "Switch to Preview mode" + "openInCode": { + "button": "Open in Code" } - }, - "toolbar": { - "tools": { - "select": { - "name": "Select", - "tooltip": "Select and modify elements" - }, - "pan": { - "name": "Pan", - "tooltip": "Pan and move around the canvas" - }, - "insertDiv": { - "name": "Insert Container", - "tooltip": "Add a new container element" - }, - "insertText": { - "name": "Insert Text", - "tooltip": "Add a new text element" - } - }, - "versionHistory": "Version History" - }, - "panels": { - "edit": { - "tabs": { - "chat": { - "name": "Chat", - "emptyState": "Select an element to chat with AI", - "emptyStateStart": "Start the project to chat", - "input": { - "placeholder": "Type your message...", - "tooltip": "Chat with AI about the selected element" - }, - "mode": { - "tooltip": "Switch between Build and Ask modes" - }, - "controls": { - "newChat": "New Chat", - "history": "Chat History" - }, - "settings": { - "showSuggestions": "Show suggestions", - "showMiniChat": "Show mini chat", - "autoApplyCode": "Auto-apply results", - "expandCodeBlocks": "Show code while rendering" - }, - "miniChat": { - "button": "Chat with AI" - }, - "openInCode": { - "button": "Open in Code" - } - }, - "styles": { - "name": "Styles", - "emptyState": "Select an element to edit its style properties", - "groups": { - "position": "Position & Dimensions", - "layout": "Flexbox & Layout", - "style": "Styles", - "text": "Text" - }, - "tailwind": { - "title": "Tailwind Classes", - "placeholder": "Add tailwind classes here", - "componentClasses": { - "title": "Main Component Classes", - "tooltip": "Changes apply to component code. This is the default." - }, - "instanceClasses": { - "title": "Instance Classes", - "tooltip": "Changes apply to instance code." - } - } - } - } + }, + "styles": { + "name": "Styles", + "emptyState": "Select an element to edit its style properties", + "groups": { + "position": "Position & Dimensions", + "layout": "Flexbox & Layout", + "style": "Styles", + "text": "Text" }, - "layers": { - "name": "Layers", - "tabs": { - "layers": "Layers", - "pages": "Pages", - "components": "Elements", - "images": "Images", - "windows": { - "name": "Windows", - "emptyState": "Select a window to edit its settings" - }, - "brand": "Brand", - "branches": "Branches", - "apps": "Apps" - } + "tailwind": { + "title": "Tailwind Classes", + "placeholder": "Add tailwind classes here", + "componentClasses": { + "title": "Main Component Classes", + "tooltip": "Changes apply to component code. This is the default." + }, + "instanceClasses": { + "title": "Instance Classes", + "tooltip": "Changes apply to instance code." + } } - }, - "settings": { - "preferences": { - "language": "Language", - "theme": "Theme", - "deleteWarning": "Delete Warning", - "analytics": "Analytics", - "editor": { - "ide": "Editor", - "shouldWarnDelete": "Warn when deleting elements", - "enableAnalytics": "Enable analytics" - }, - "shortcuts": "Shortcuts" - } - }, - "frame": { - "startDesigning": { - "prefix": "Press ", - "action": "Play", - "suffix": " to start designing your App" - }, - "playButton": "Play", - "waitingForApp": "Waiting for the App to start..." - }, - "runButton": { - "portInUse": "Port in Use", - "loading": "Loading", - "play": "Play", - "retry": "Retry", - "stop": "Stop" - }, - "zoom": { - "level": "Zoom Level", - "in": "Zoom In", - "out": "Zoom Out", - "fit": "Zoom Fit", - "reset": "Zoom 100%", - "double": "Zoom 200%" + } } - }, - "help": { - "menu": { - "reloadOnlook": "Reload Onlook", - "theme": { - "title": "Theme", - "light": "Light", - "dark": "Dark", - "system": "System" - }, - "language": "Language", - "openSettings": "Open Settings", - "contactUs": { - "title": "Contact Us", - "website": "Website", - "discord": "Discord", - "github": "GitHub", - "email": "Email" - }, - "reportIssue": "Report Issue", - "shortcuts": "Shortcuts" + }, + "layers": { + "name": "Layers", + "tabs": { + "layers": "Layers", + "pages": "Pages", + "components": "Elements", + "images": "Images", + "windows": { + "name": "Windows", + "emptyState": "Select a window to edit its settings" + }, + "brand": "Brand", + "branches": "Branches", + "apps": "Apps" } + } + }, + "settings": { + "preferences": { + "language": "Language", + "theme": "Theme", + "deleteWarning": "Delete Warning", + "analytics": "Analytics", + "editor": { + "ide": "Editor", + "shouldWarnDelete": "Warn when deleting elements", + "enableAnalytics": "Enable analytics" + }, + "shortcuts": "Shortcuts" + } + }, + "frame": { + "startDesigning": { + "prefix": "Press ", + "action": "Play", + "suffix": " to start designing your App" + }, + "playButton": "Play", + "waitingForApp": "Waiting for the App to start..." + }, + "runButton": { + "portInUse": "Port in Use", + "loading": "Loading", + "play": "Play", + "retry": "Retry", + "stop": "Stop" + }, + "zoom": { + "level": "Zoom Level", + "in": "Zoom In", + "out": "Zoom Out", + "fit": "Zoom Fit", + "reset": "Zoom 100%", + "double": "Zoom 200%" + } + }, + "help": { + "menu": { + "reloadOnlook": "Reload Onlook", + "theme": { + "title": "Theme", + "light": "Light", + "dark": "Dark", + "system": "System" + }, + "language": "Language", + "openSettings": "Open Settings", + "contactUs": { + "title": "Contact Us", + "website": "Website", + "discord": "Discord", + "github": "GitHub", + "email": "Email" + }, + "reportIssue": "Report Issue", + "shortcuts": "Shortcuts" } + }, + "ide": { + "noSupportedIDE": "No supported IDE found. Please install VS Code or Cursor." + } } \ No newline at end of file diff --git a/apps/web/client/src/app/project/[id]/_components/left-panel/code-panel/code-tab/hooks/use-code-navigation.ts b/apps/web/client/src/app/project/[id]/_components/left-panel/code-panel/code-tab/hooks/use-code-navigation.ts index 66e9029734..757523569f 100644 --- a/apps/web/client/src/app/project/[id]/_components/left-panel/code-panel/code-tab/hooks/use-code-navigation.ts +++ b/apps/web/client/src/app/project/[id]/_components/left-panel/code-panel/code-tab/hooks/use-code-navigation.ts @@ -5,6 +5,7 @@ import type { CodeNavigationTarget } from '@onlook/models'; import { pathsEqual } from '@onlook/utility'; import { reaction } from 'mobx'; import { useEffect, useRef, useState } from 'react'; +import { useTranslations } from 'next-intl'; const isNavigationTargetEqual = (navigationTarget1: CodeNavigationTarget | null, navigationTarget2: CodeNavigationTarget | null) => { if (!navigationTarget1 || !navigationTarget2) { @@ -19,11 +20,16 @@ const isNavigationTargetEqual = (navigationTarget1: CodeNavigationTarget | null, export function useCodeNavigation() { const editorEngine = useEditorEngine(); + const t = useTranslations(); const savedNavigationTarget = useRef(null); const [navigationTarget, setNavigationTarget] = useState(null); const lastSelected = useRef(editorEngine.elements.selected); const lastOverride = useRef(editorEngine.ide.codeNavigationOverride); + // Inject translation function into IdeManager + const { setIdeTranslation } = await import('@/components/store/editor/ide'); + setIdeTranslation(t); + useEffect(() => { const disposer = reaction( () => ({ diff --git a/apps/web/client/src/components/store/editor/ide/ide-detection.ts b/apps/web/client/src/components/store/editor/ide/ide-detection.ts new file mode 100644 index 0000000000..32f1b69355 --- /dev/null +++ b/apps/web/client/src/components/store/editor/ide/ide-detection.ts @@ -0,0 +1,70 @@ +/** + * Utility to detect if VS Code or Cursor is installed on the user's system. + * Used to warn users when "Open in Code" is clicked but no supported IDE is found. + */ + +const VSCODE_COMMAND = 'code'; +const CURSOR_COMMAND = 'cursor'; + +/** + * Check if a command is available in the system PATH. + * This only works in Node.js environment (server-side or Electron). + * In browser environment, this will always return false. + */ +async function isCommandAvailable(command: string): Promise { + // Only run in Node.js environment + if (typeof window !== 'undefined') { + // Browser environment - cannot detect installed applications + // Return true to avoid false warnings (user might have IDE installed) + return true; + } + + try { + const { execSync } = await import('child_process'); + execSync(`which ${command}`, { stdio: 'ignore' }); + return true; + } catch { + return false; + } +} + +/** + * Detect if VS Code is installed. + */ +export async function isVSCodeInstalled(): Promise { + return isCommandAvailable(VSCODE_COMMAND); +} + +/** + * Detect if Cursor is installed. + */ +export async function isCursorInstalled(): Promise { + return isCommandAvailable(CURSOR_COMMAND); +} + +/** + * Check if any supported IDE (VS Code or Cursor) is installed. + * Returns an object with detection results and a suggestion message if none is found. + */ +export async function detectSupportedIDE(): Promise<{ + vsCodeInstalled: boolean; + cursorInstalled: boolean; + anyInstalled: boolean; + messageKey: string | null; +}> { + const vsCodeInstalled = await isVSCodeInstalled(); + const cursorInstalled = await isCursorInstalled(); + const anyInstalled = vsCodeInstalled || cursorInstalled; + + let messageKey = null; + if (!anyInstalled) { + messageKey = 'ide.noSupportedIDE'; + } + + return { + vsCodeInstalled, + cursorInstalled, + anyInstalled, + messageKey, + }; +} diff --git a/apps/web/client/src/components/store/editor/ide/index.ts b/apps/web/client/src/components/store/editor/ide/index.ts index 41da548c0f..2186aa2fc1 100644 --- a/apps/web/client/src/components/store/editor/ide/index.ts +++ b/apps/web/client/src/components/store/editor/ide/index.ts @@ -1,6 +1,18 @@ import { EditorMode, type CodeNavigationTarget } from "@onlook/models"; import { makeAutoObservable } from "mobx"; import type { EditorEngine } from "../engine"; +import { detectSupportedIDE } from './ide-detection'; +import { toast } from "@onlook/ui/sonner"; + +let tFn: ((key: string) => string) | null = null; + +export function setIdeTranslation(fn: (key: string) => string): void { + tFn = fn; +} + +export function getIdeTranslation(): (((key: string) => string) | null) { + return tFn; +} export class IdeManager { private _codeNavigationOverride: CodeNavigationTarget | null = null; @@ -15,6 +27,15 @@ export class IdeManager { async openCodeBlock(oid: string) { try { + // Check if a supported IDE is installed + const ideDetection = await detectSupportedIDE(); + if (!ideDetection.anyInstalled) { + const msg = tFn ? tFn(ideDetection.messageKey || 'ide.noSupportedIDE') : 'No supported IDE found. Please install VS Code or Cursor.'; + toast.warning(msg || 'No supported IDE found.'); + console.warn('[IdeManager] No supported IDE found'); + // Continue anyway - the code panel will still work + } + // Get the current branch data const activeBranchId = this.editorEngine.branches.activeBranch?.id; if (!activeBranchId) { @@ -67,4 +88,6 @@ export class IdeManager { hasCodeNavigationOverride(): boolean { return this._codeNavigationOverride !== null; } -} \ No newline at end of file +} + +export { setIdeTranslation, getIdeTranslation };