refactor: Flatten directory structure

Move project files from uzdb/ subdirectory to root directory for cleaner project structure.

Changes:
- Move frontend/ to root
- Move internal/ to root
- Move build/ to root
- Move all config files (go.mod, wails.json, etc.) to root
- Remove redundant uzdb/ subdirectory nesting

Project structure is now:
├── frontend/        # React application
├── internal/        # Go backend
├── build/           # Wails build assets
├── doc/             # Design documentation
├── main.go          # Entry point
└── ...

🤖 Generated with Qoder
This commit is contained in:
loveuer
2026-04-04 07:14:00 -07:00
parent 5a83e86bc9
commit 9874561410
83 changed files with 0 additions and 46 deletions

View File

@@ -0,0 +1,134 @@
/**
* AppLayout Component Styles
*/
.app-layout {
display: flex;
flex-direction: column;
height: 100vh;
width: 100vw;
overflow: hidden;
}
/* Menu Bar */
.layout-menubar {
flex-shrink: 0;
height: var(--menubar-height);
}
/* Toolbar */
.layout-toolbar {
flex-shrink: 0;
height: var(--toolbar-height);
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border);
}
/* Main area (sidebar + workspace) */
.layout-main {
display: flex;
flex: 1;
overflow: hidden;
}
/* Sidebar */
.layout-sidebar {
flex-shrink: 0;
height: 100%;
overflow: hidden;
transition: width var(--transition-normal) var(--ease-in-out);
}
.layout-sidebar.collapsed {
width: 0;
overflow: hidden;
}
/* Resize handle */
.layout-resize-handle {
width: 4px;
cursor: col-resize;
background-color: transparent;
transition: background-color var(--transition-fast) var(--ease-in-out);
flex-shrink: 0;
z-index: 10;
}
.layout-resize-handle:hover {
background-color: var(--primary);
}
.layout-resize-handle:active {
background-color: var(--primary-active);
}
/* Sidebar toggle button */
.layout-sidebar-toggle {
width: 48px;
background-color: var(--bg-secondary);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: center;
padding-top: var(--space-2);
gap: var(--space-2);
flex-shrink: 0;
}
.toggle-button {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
}
/* Workspace */
.layout-workspace {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: var(--bg-primary);
}
/* Status Bar */
.layout-statusbar {
flex-shrink: 0;
height: var(--statusbar-height);
background-color: var(--bg-secondary);
border-top: 1px solid var(--border);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.layout-sidebar:not(.collapsed) {
position: absolute;
left: 0;
top: 0;
bottom: 0;
z-index: 100;
box-shadow: var(--shadow-lg);
}
.layout-resize-handle {
display: none;
}
}
/* Animation for sidebar transitions */
@keyframes slideInLeft {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.layout-sidebar:not(.collapsed) {
animation: fadeIn 0.2s ease-out;
}

View File

@@ -0,0 +1,155 @@
/**
* AppLayout Component
*
* Main application layout with menu bar, toolbar, sidebar, main content area, and status bar.
* Based on layout-design.md "整体布局架构"
*/
import React, { useState, useCallback } from 'react';
import './AppLayout.css';
export interface AppLayoutProps {
/** Menu bar component */
menuBar?: React.ReactNode;
/** Toolbar component */
toolbar?: React.ReactNode;
/** Sidebar/connection panel component */
sidebar?: React.ReactNode;
/** Main content area component */
mainContent?: React.ReactNode;
/** Status bar component */
statusBar?: React.ReactNode;
/** Sidebar collapsed state */
sidebarCollapsed?: boolean;
/** Handler when sidebar collapse state changes */
onSidebarToggle?: (collapsed: boolean) => void;
/** Children (alternative to explicit props) */
children?: React.ReactNode;
}
/**
* AppLayout - Main application shell component
*/
export const AppLayout: React.FC<AppLayoutProps> = ({
menuBar,
toolbar,
sidebar,
mainContent,
statusBar,
sidebarCollapsed = false,
onSidebarToggle,
children,
}) => {
const [isResizing, setIsResizing] = useState(false);
const [sidebarWidth, setSidebarWidth] = useState(240);
const [isCollapsed, setIsCollapsed] = useState(sidebarCollapsed);
// Handle sidebar resize start
const handleResizeStart = useCallback((e: React.MouseEvent) => {
e.preventDefault();
setIsResizing(true);
}, []);
// Handle mouse move for resizing
useCallback(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isResizing) return;
const newWidth = Math.max(180, Math.min(400, e.clientX));
setSidebarWidth(newWidth);
};
const handleMouseUp = () => {
setIsResizing(false);
};
if (isResizing) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isResizing]);
// Toggle sidebar collapse
const toggleSidebar = useCallback(() => {
const newCollapsed = !isCollapsed;
setIsCollapsed(newCollapsed);
onSidebarToggle?.(newCollapsed);
}, [isCollapsed, onSidebarToggle]);
// Expose toggle function via custom event for keyboard shortcuts
React.useEffect(() => {
const handleToggleRequest = () => toggleSidebar();
window.addEventListener('toggle-sidebar', handleToggleRequest);
return () => window.removeEventListener('toggle-sidebar', handleToggleRequest);
}, [toggleSidebar]);
return (
<div className="app-layout">
{/* Menu Bar */}
{menuBar && <div className="layout-menubar">{menuBar}</div>}
{/* Toolbar */}
{toolbar && <div className="layout-toolbar">{toolbar}</div>}
{/* Main content area (sidebar + workspace) */}
<div className="layout-main">
{/* Sidebar */}
{sidebar && (
<>
<div
className={`layout-sidebar ${isCollapsed ? 'collapsed' : ''}`}
style={
isCollapsed
? undefined
: { width: sidebarWidth, flex: 'none' }
}
>
{sidebar}
</div>
{/* Resize handle */}
{!isCollapsed && (
<div
className="layout-resize-handle"
onMouseDown={handleResizeStart}
role="separator"
aria-orientation="vertical"
tabIndex={0}
title="Drag to resize sidebar"
/>
)}
{/* Collapse toggle button (when collapsed) */}
{isCollapsed && sidebar && (
<div className="layout-sidebar-toggle">
<button
className="btn-icon toggle-button"
onClick={toggleSidebar}
title="Expand sidebar"
aria-label="Expand sidebar"
>
</button>
</div>
)}
</>
)}
{/* Workspace (main content) */}
<div className="layout-workspace">
{mainContent || children}
</div>
</div>
{/* Status Bar */}
{statusBar && <div className="layout-statusbar">{statusBar}</div>}
</div>
);
};
export default AppLayout;

View File

@@ -0,0 +1,120 @@
/**
* StatusBar Component Styles
*/
.statusbar {
display: flex;
align-items: center;
justify-content: space-between;
height: var(--statusbar-height);
padding: 0 var(--space-4);
background-color: var(--bg-secondary);
border-top: 1px solid var(--border);
font-size: var(--text-xs);
flex-shrink: 0;
}
/* Status type variations */
.statusbar.status-normal {
background-color: var(--bg-secondary);
color: var(--text-secondary);
}
.statusbar.status-success {
background-color: #d1fae5;
color: #065f46;
}
.statusbar.status-warning {
background-color: #fef3c7;
color: #92400e;
}
.statusbar.status-error {
background-color: #fee2e2;
color: #991b1b;
}
.statusbar.status-info {
background-color: #dbeafe;
color: #1e40af;
}
/* Left section */
.statusbar-left {
display: flex;
align-items: center;
gap: var(--space-4);
overflow: hidden;
}
.status-icon {
font-size: var(--text-sm);
font-weight: bold;
}
.status-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.connection-info {
color: inherit;
}
.query-info {
font-weight: 500;
}
/* Right section */
.statusbar-right {
display: flex;
align-items: center;
gap: var(--space-4);
}
.status-button {
padding: var(--space-1) var(--space-2);
font-size: var(--text-xs);
font-family: var(--font-sans);
color: inherit;
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all var(--transition-fast) var(--ease-in-out);
}
.status-button:hover {
background-color: var(--bg-tertiary);
border-color: var(--border);
}
.editor-mode {
font-family: var(--font-mono);
font-size: var(--text-xs);
color: var(--text-muted);
min-width: 30px;
text-align: center;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.statusbar {
padding: 0 var(--space-2);
font-size: 11px;
}
.statusbar-left {
gap: var(--space-2);
}
.statusbar-right {
gap: var(--space-2);
}
.status-button {
padding: var(--space-1);
}
}

View File

@@ -0,0 +1,126 @@
/**
* StatusBar Component
*
* Bottom status bar showing connection info, encoding, and editor mode.
* Based on layout-design.md section "状态栏设计规范"
*/
import React from 'react';
import './StatusBar.css';
export type StatusType = 'normal' | 'success' | 'warning' | 'error' | 'info';
export interface StatusBarProps {
/** Connection status message */
connectionInfo?: string;
/** Query execution info */
queryInfo?: string;
/** Encoding format */
encoding?: string;
/** Line ending type */
lineEnding?: 'LF' | 'CRLF';
/** Editor mode */
editorMode?: 'ins' | 'ovr';
/** Status type for coloring */
statusType?: StatusType;
/** Handler when encoding is clicked */
onEncodingClick?: () => void;
/** Handler when line ending is clicked */
onLineEndingClick?: () => void;
}
/**
* StatusBar component
*/
export const StatusBar: React.FC<StatusBarProps> = ({
connectionInfo,
queryInfo,
encoding = 'UTF-8',
lineEnding = 'LF',
editorMode = 'ins',
statusType = 'normal',
onEncodingClick,
onLineEndingClick,
}) => {
// Get status class based on type
const getStatusClass = (): string => {
switch (statusType) {
case 'success':
return 'status-success';
case 'warning':
return 'status-warning';
case 'error':
return 'status-error';
case 'info':
return 'status-info';
default:
return 'status-normal';
}
};
// Get status icon
const getStatusIcon = (): string => {
switch (statusType) {
case 'success':
return '✓';
case 'warning':
return '⚠';
case 'error':
return '✕';
case 'info':
return '';
default:
return '';
}
};
return (
<div className={`statusbar ${getStatusClass()}`}>
<div className="statusbar-left">
{/* Status icon and message */}
{getStatusIcon() && (
<span className="status-icon" role="status" aria-label={statusType}>
{getStatusIcon()}
</span>
)}
{/* Connection info */}
{connectionInfo && (
<span className="status-item connection-info">{connectionInfo}</span>
)}
{/* Query info (takes priority over connection info when present) */}
{queryInfo && (
<span className="status-item query-info">{queryInfo}</span>
)}
</div>
<div className="statusbar-right">
{/* Encoding */}
<button
className="status-button"
onClick={onEncodingClick}
title="Change encoding"
>
{encoding}
</button>
{/* Line ending */}
<button
className="status-button"
onClick={onLineEndingClick}
title="Change line ending"
>
{lineEnding}
</button>
{/* Editor mode */}
<span className="status-item editor-mode" title={editorMode === 'ins' ? 'Insert' : 'Overwrite'}>
{editorMode}
</span>
</div>
</div>
);
};
export default StatusBar;

View File

@@ -0,0 +1,124 @@
/**
* ToolBar Component Styles
*/
.toolbar {
display: flex;
align-items: center;
height: var(--toolbar-height);
padding: 0 var(--space-4);
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border);
gap: var(--space-4);
flex-shrink: 0;
}
.toolbar-buttons {
display: flex;
gap: var(--space-1);
}
.toolbar-button {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
font-size: var(--text-sm);
font-family: var(--font-sans);
color: var(--text-primary);
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast) var(--ease-in-out);
white-space: nowrap;
}
.toolbar-button:hover:not(.disabled) {
background-color: var(--bg-tertiary);
border-color: var(--border);
}
.toolbar-button.disabled {
color: var(--text-muted);
cursor: not-allowed;
}
.button-icon {
font-size: var(--text-base);
}
.button-label {
font-weight: 500;
}
.toolbar-content {
display: flex;
align-items: center;
gap: var(--space-2);
}
.toolbar-spacer {
flex: 1;
}
/* Connection indicator */
.toolbar-connection {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
background-color: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast) var(--ease-in-out);
}
.toolbar-connection:hover {
background-color: var(--bg-tertiary);
border-color: var(--text-secondary);
}
.connection-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--text-muted);
}
.connection-status-dot.connected {
background-color: var(--success);
box-shadow: 0 0 4px var(--success);
}
.connection-name {
font-size: var(--text-sm);
font-weight: 500;
color: var(--text-primary);
}
.dropdown-arrow {
font-size: var(--text-xs);
color: var(--text-muted);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.toolbar {
padding: 0 var(--space-2);
gap: var(--space-2);
}
.toolbar-button {
padding: var(--space-2);
}
.button-label {
display: none;
}
.connection-name {
display: none;
}
}

View File

@@ -0,0 +1,104 @@
/**
* ToolBar Component
*
* Quick access toolbar with common actions.
* Based on layout-design.md toolbar specifications
*/
import React from 'react';
import './ToolBar.css';
export interface ToolButton {
id: string;
icon: string;
label: string;
tooltip?: string;
disabled?: boolean;
onClick?: () => void;
}
export interface ToolBarProps {
/** Toolbar buttons */
buttons?: ToolButton[];
/** Additional content (e.g., search box) */
children?: React.ReactNode;
/** Handler when button is clicked */
onButtonClick?: (buttonId: string) => void;
}
/**
* Default toolbar buttons
*/
const defaultButtons: ToolButton[] = [
{
id: 'run',
icon: '▶',
label: 'Run',
tooltip: 'Execute query (Ctrl+Enter)',
},
{
id: 'save',
icon: '💾',
label: 'Save',
tooltip: 'Save query (Ctrl+S)',
},
{
id: 'export',
icon: '📤',
label: 'Export',
tooltip: 'Export results',
},
{
id: 'find',
icon: '🔍',
label: 'Find',
tooltip: 'Find in query (Ctrl+F)',
},
];
/**
* ToolBar component
*/
export const ToolBar: React.FC<ToolBarProps> = ({
buttons = defaultButtons,
children,
onButtonClick,
}) => {
const handleButtonClick = (button: ToolButton) => {
button.onClick?.();
onButtonClick?.(button.id);
};
return (
<div className="toolbar" role="toolbar" aria-label="Main toolbar">
<div className="toolbar-buttons">
{buttons.map((button) => (
<button
key={button.id}
className={`toolbar-button ${button.disabled ? 'disabled' : ''}`}
onClick={() => handleButtonClick(button)}
disabled={button.disabled}
title={button.tooltip}
aria-label={button.label}
>
<span className="button-icon">{button.icon}</span>
<span className="button-label">{button.label}</span>
</button>
))}
</div>
{children && <div className="toolbar-content">{children}</div>}
<div className="toolbar-spacer" />
{/* Connection indicator */}
<div className="toolbar-connection">
<span className="connection-status-dot connected"></span>
<span className="connection-name">🗄 MySQL @ localhost</span>
<span className="dropdown-arrow"></span>
</div>
</div>
);
};
export default ToolBar;