11import React , { useState } from 'react' ;
2- import { Container , Paper , Modal , Box , Button , Typography , Tabs , Tab , Link as MuiLink } from '@mui/material' ;
2+ import { Container , Paper , Modal , Box , Button , Typography , Tabs , Tab , Link as MuiLink , Tooltip , IconButton , Snackbar } from '@mui/material' ;
33import { ThemeProvider } from '@mui/material/styles' ;
44import DescriptionIcon from '@mui/icons-material/Description' ;
55import LibraryBooksIcon from '@mui/icons-material/LibraryBooks' ;
66import BlockIcon from '@mui/icons-material/Block' ;
7+ import ContentCopyIcon from '@mui/icons-material/ContentCopy' ;
8+ import DownloadIcon from '@mui/icons-material/Download' ;
9+ import PreviewIcon from '@mui/icons-material/Visibility' ;
10+ import CloseIcon from '@mui/icons-material/Close' ;
711import { BrowserRouter as Router , Routes , Route , Link , useLocation } from 'react-router-dom' ;
812import theme from './theme' ;
913import ContextForm from './components/ContextForm' ;
1014import ContextDocsForm from './components/ContextDocsForm' ;
1115import ContextIgnoreForm from './components/ContextIgnoreForm' ;
16+ import './App.css' ;
1217
1318// Conditional base path for GitHub Pages
1419const BASE_PATH = process . env . NODE_ENV === 'production' ? '/codebase-context-spec' : '' ;
1520
1621const App : React . FC = ( ) => {
1722 const [ previewOpen , setPreviewOpen ] = useState ( false ) ;
1823 const [ generatedContent , setGeneratedContent ] = useState ( '' ) ;
24+ const [ snackbarOpen , setSnackbarOpen ] = useState ( false ) ;
25+ const [ snackbarMessage , setSnackbarMessage ] = useState ( '' ) ;
1926
2027 const handleFormSubmit = ( content : string ) => {
2128 setGeneratedContent ( content ) ;
29+ } ;
30+
31+ const handleOpenPreview = ( ) => {
2232 setPreviewOpen ( true ) ;
2333 } ;
2434
2535 const handleClosePreview = ( ) => {
2636 setPreviewOpen ( false ) ;
2737 } ;
2838
39+ const handleCopyToClipboard = ( ) => {
40+ navigator . clipboard . writeText ( generatedContent ) . then ( ( ) => {
41+ setSnackbarMessage ( 'Copied to clipboard!' ) ;
42+ setSnackbarOpen ( true ) ;
43+ } , ( err ) => {
44+ console . error ( 'Could not copy text: ' , err ) ;
45+ } ) ;
46+ } ;
47+
48+ const handleDownload = ( ) => {
49+ const element = document . createElement ( 'a' ) ;
50+ const file = new Blob ( [ generatedContent ] , { type : 'text/plain' } ) ;
51+ element . href = URL . createObjectURL ( file ) ;
52+ element . download = 'generated_context.md' ;
53+ document . body . appendChild ( element ) ;
54+ element . click ( ) ;
55+ document . body . removeChild ( element ) ;
56+ setSnackbarMessage ( 'File downloaded!' ) ;
57+ setSnackbarOpen ( true ) ;
58+ } ;
59+
60+ const handleCloseSnackbar = ( event ?: React . SyntheticEvent | Event , reason ?: string ) => {
61+ if ( reason === 'clickaway' ) {
62+ return ;
63+ }
64+ setSnackbarOpen ( false ) ;
65+ } ;
66+
2967 return (
3068 < ThemeProvider theme = { theme } >
3169 < Router basename = { BASE_PATH } >
32- < Container maxWidth = "md" >
33- < Paper elevation = { 3 } sx = { { p : 4 , mt : 4 } } >
34- < Box sx = { { mb : 4 } } >
35- < Typography variant = "h4" component = "h1" gutterBottom >
70+ < Container className = "container" maxWidth = "md" >
71+ < Paper className = "paper" elevation = { 3 } >
72+ < Box className = "header" >
73+ < Typography className = "title" variant = "h4" component = "h1" >
3674 Codebase Context Editor
3775 </ Typography >
38- < Typography variant = "body1" paragraph >
39- This editor helps you create .context.md, .contextdocs.md, and .contextignore files for your project.
40- </ Typography >
41- < Typography variant = "body1" paragraph >
42- GitHub Repository:{ ' ' }
43- < MuiLink href = "https://github.com/Agentic-Insights/codebase-context-spec" target = "_blank" rel = "noopener noreferrer" >
44- https://github.com/Agentic-Insights/codebase-context-spec
45- </ MuiLink >
46- </ Typography >
4776 </ Box >
77+ < Typography variant = "body1" paragraph >
78+ This editor helps you create .context.md, .contextdocs.md, and .contextignore files for your project.
79+ </ Typography >
80+ < Typography variant = "body1" paragraph >
81+ GitHub Repository:{ ' ' }
82+ < MuiLink href = "https://github.com/Agentic-Insights/codebase-context-spec" target = "_blank" rel = "noopener noreferrer" >
83+ https://github.com/Agentic-Insights/codebase-context-spec
84+ </ MuiLink >
85+ </ Typography >
4886 < NavTabs />
49- < Routes >
50- < Route path = "/" element = { < ContextForm onSubmit = { handleFormSubmit } /> } />
51- < Route path = "/contextdocs" element = { < ContextDocsForm onSubmit = { handleFormSubmit } /> } />
52- < Route path = "/contextignore" element = { < ContextIgnoreForm onSubmit = { handleFormSubmit } /> } />
53- </ Routes >
87+ < Box className = "form-section" >
88+ < Routes >
89+ < Route path = "/" element = { < ContextForm onSubmit = { handleFormSubmit } /> } />
90+ < Route path = "/contextdocs" element = { < ContextDocsForm onSubmit = { handleFormSubmit } /> } />
91+ < Route path = "/contextignore" element = { < ContextIgnoreForm onSubmit = { handleFormSubmit } /> } />
92+ </ Routes >
93+ </ Box >
94+ < Box className = "action-buttons" >
95+ < Tooltip title = "Preview generated content" arrow >
96+ < Button
97+ onClick = { handleOpenPreview }
98+ variant = "contained"
99+ startIcon = { < PreviewIcon /> }
100+ disabled = { ! generatedContent }
101+ >
102+ Preview
103+ </ Button >
104+ </ Tooltip >
105+ < Tooltip title = "Copy to clipboard" arrow >
106+ < Button
107+ onClick = { handleCopyToClipboard }
108+ variant = "contained"
109+ startIcon = { < ContentCopyIcon /> }
110+ disabled = { ! generatedContent }
111+ >
112+ Copy
113+ </ Button >
114+ </ Tooltip >
115+ < Tooltip title = "Download file" arrow >
116+ < Button
117+ onClick = { handleDownload }
118+ variant = "contained"
119+ startIcon = { < DownloadIcon /> }
120+ disabled = { ! generatedContent }
121+ >
122+ Download
123+ </ Button >
124+ </ Tooltip >
125+ </ Box >
54126 </ Paper >
55127
56128 < Modal
@@ -59,33 +131,43 @@ const App: React.FC = () => {
59131 aria-labelledby = "preview-modal-title"
60132 aria-describedby = "preview-modal-description"
61133 >
62- < Box sx = { {
63- position : 'absolute' ,
64- top : '50%' ,
65- left : '50%' ,
66- transform : 'translate(-50%, -50%)' ,
67- width : '80%' ,
68- bgcolor : 'background.paper' ,
69- boxShadow : 24 ,
70- p : 4 ,
71- maxHeight : '80vh' ,
72- overflow : 'auto' ,
73- } } >
74- < Typography id = "preview-modal-title" variant = "h6" component = "h2" gutterBottom >
75- Generated File Preview
76- </ Typography >
77- < Paper sx = { { p : 2 , maxHeight : 'calc(80vh - 100px)' , overflow : 'auto' } } >
78- < pre style = { { whiteSpace : 'pre-wrap' , wordBreak : 'break-word' } } >
134+ < Box className = "modal-content" >
135+ < Box display = "flex" justifyContent = "space-between" alignItems = "center" mb = { 2 } >
136+ < Typography id = "preview-modal-title" variant = "h6" component = "h2" >
137+ Preview of generated content
138+ </ Typography >
139+ < IconButton onClick = { handleClosePreview } size = "small" >
140+ < CloseIcon />
141+ </ IconButton >
142+ </ Box >
143+ < Box className = "code-preview" >
144+ < pre >
79145 { generatedContent }
80146 </ pre >
81- </ Paper >
82- < Box sx = { { mt : 2 , display : 'flex' , justifyContent : 'flex-end' } } >
83- < Button onClick = { handleClosePreview } variant = "contained" >
84- Close
85- </ Button >
86147 </ Box >
87148 </ Box >
88149 </ Modal >
150+
151+ < Snackbar
152+ anchorOrigin = { {
153+ vertical : 'bottom' ,
154+ horizontal : 'center' ,
155+ } }
156+ open = { snackbarOpen }
157+ autoHideDuration = { 2000 }
158+ onClose = { handleCloseSnackbar }
159+ message = { snackbarMessage }
160+ action = {
161+ < IconButton
162+ size = "small"
163+ aria-label = "close"
164+ color = "inherit"
165+ onClick = { handleCloseSnackbar }
166+ >
167+ < CloseIcon fontSize = "small" />
168+ </ IconButton >
169+ }
170+ />
89171 </ Container >
90172 </ Router >
91173 </ ThemeProvider >
0 commit comments