import type React from 'react'; import type { SavedConnection } from '../types'; import { isPostgresSchemaDialect as resolveIsPostgresSchemaDialect, normalizeDriverType as normalizeConnectionDriverType, resolveSavedConnectionDriverType as resolveSavedConnectionDriverTypeBase, } from '../utils/connectionDriverType'; const SIDEBAR_CONTEXT_MENU_SAFE_GAP = 8; export const SIDEBAR_CONTEXT_MENU_FALLBACK_WIDTH = 264; export const SIDEBAR_CONTEXT_MENU_FALLBACK_HEIGHT = 420; export type ExternalSQLFileModalMode = 'create' | 'rename' | 'create-directory' | 'rename-directory'; export type SearchScope = 'smart' | 'object' | 'database' | 'host' | 'tag'; type SidebarObjectNodeLike = { title?: string; type?: string; dataRef?: any; }; export const isExternalSQLDirectoryModalMode = (mode: ExternalSQLFileModalMode): boolean => mode === 'create-directory' || mode === 'rename-directory'; export const resolveSidebarContextMenuPosition = ( x: number, y: number, options?: { width?: number; height?: number; viewportWidth?: number; viewportHeight?: number; safeGap?: number; }, ): { x: number; y: number; maxHeight: number } => { const safeGap = options?.safeGap ?? SIDEBAR_CONTEXT_MENU_SAFE_GAP; const viewportWidth = options?.viewportWidth ?? (typeof window === 'undefined' ? 1024 : window.innerWidth); const viewportHeight = options?.viewportHeight ?? (typeof window === 'undefined' ? 768 : window.innerHeight); const width = Math.max(0, options?.width ?? SIDEBAR_CONTEXT_MENU_FALLBACK_WIDTH); const height = Math.max(0, options?.height ?? SIDEBAR_CONTEXT_MENU_FALLBACK_HEIGHT); const maxX = Math.max(safeGap, viewportWidth - width - safeGap); const maxY = Math.max(safeGap, viewportHeight - height - safeGap); const nextX = Math.max(safeGap, Math.min(x, maxX)); const nextY = Math.max(safeGap, Math.min(y, maxY)); return { x: nextX, y: nextY, maxHeight: Math.max(120, viewportHeight - nextY - safeGap), }; }; export const isV2SidebarObjectNode = (node: Pick | null | undefined): boolean => { return node?.type === 'table' || node?.type === 'view' || node?.type === 'materialized-view' || node?.type === 'db-trigger' || node?.type === 'db-event' || node?.type === 'routine'; }; export const resolveSidebarObjectDragText = ( node: Pick | null | undefined, ): string => { const dataRef = node?.dataRef || {}; if (node?.type === 'table') return String(dataRef.tableName || node?.title || '').trim(); if (node?.type === 'view' || node?.type === 'materialized-view') return String(dataRef.viewName || dataRef.tableName || node?.title || '').trim(); if (node?.type === 'db-trigger') return String(dataRef.triggerName || node?.title || '').trim(); if (node?.type === 'routine') return String(dataRef.routineName || node?.title || '').trim(); if (node?.type === 'db-event') return String(dataRef.eventName || node?.title || '').trim(); return ''; }; export const buildConnectionReloadSignature = (conn?: SavedConnection | null): string => { if (!conn) return ''; return JSON.stringify({ config: conn.config || {}, includeDatabases: conn.includeDatabases || [], includeRedisDatabases: conn.includeRedisDatabases || [], }); }; export const isConnectionTreeKey = (key: React.Key, connectionId: string): boolean => { const text = String(key); return text === connectionId || text.startsWith(`${connectionId}-`); }; export const normalizeDriverType = normalizeConnectionDriverType; export const resolveSavedConnectionDriverType = resolveSavedConnectionDriverTypeBase; export const isPostgresSchemaDialect = resolveIsPostgresSchemaDialect; export const SEARCH_SCOPE_OPTIONS: Array<{ value: SearchScope; label: string }> = [ { value: 'smart', label: '智能' }, { value: 'object', label: '表对象' }, { value: 'database', label: '库' }, { value: 'host', label: 'Host' }, { value: 'tag', label: '标签' }, ]; export const SEARCH_SCOPE_LABEL_MAP: Record = SEARCH_SCOPE_OPTIONS.reduce((acc, option) => { acc[option.value] = option.label; return acc; }, {} as Record); export const normalizeMySQLViewDDLForEditing = (viewName: string, rawDefinition: unknown): string => { const text = String(rawDefinition || '').trim(); if (!text) return ''; const normalized = text.replace(/\r\n/g, '\n').trim().replace(/;+\s*$/, ''); const createViewPrefixPattern = /^\s*create\s+(?:algorithm\s*=\s*\w+\s+)?(?:definer\s*=\s*(?:`[^`]+`|\S+)\s*@\s*(?:`[^`]+`|\S+)\s+)?(?:sql\s+security\s+(?:definer|invoker)\s+)?view\s+/i; if (createViewPrefixPattern.test(normalized)) { return `${normalized.replace(createViewPrefixPattern, 'CREATE OR REPLACE VIEW ')};`; } if (/^\s*(select|with)\b/i.test(normalized)) { return `CREATE OR REPLACE VIEW ${viewName} AS\n${normalized};`; } return `${normalized};`; };