mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
Connect FE with BE api
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
@@ -10,28 +9,52 @@ type FileSystem struct {
|
||||
RootDir string
|
||||
}
|
||||
|
||||
type FileNode struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Files []FileNode `json:"files,omitempty"`
|
||||
}
|
||||
|
||||
func New(rootDir string) *FileSystem {
|
||||
return &FileSystem{RootDir: rootDir}
|
||||
}
|
||||
|
||||
func (fs *FileSystem) ListFilesRecursively() ([]string, error) {
|
||||
var files []string
|
||||
func (fs *FileSystem) ListFilesRecursively() ([]FileNode, error) {
|
||||
return fs.walkDirectory(fs.RootDir)
|
||||
}
|
||||
|
||||
err := filepath.Walk(fs.RootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
relPath, _ := filepath.Rel(fs.RootDir, path)
|
||||
files = append(files, relPath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
func (fs *FileSystem) walkDirectory(dir string) ([]FileNode, error) {
|
||||
var nodes []FileNode
|
||||
|
||||
return files, err
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
subdir := filepath.Join(dir, entry.Name())
|
||||
subFiles, err := fs.walkDirectory(subdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, FileNode{
|
||||
Type: "directory",
|
||||
Name: entry.Name(),
|
||||
Files: subFiles,
|
||||
})
|
||||
} else {
|
||||
nodes = append(nodes, FileNode{
|
||||
Type: "file",
|
||||
Name: entry.Name(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (fs *FileSystem) GetFileContent(filePath string) ([]byte, error) {
|
||||
fullPath := filepath.Join(fs.RootDir, filePath)
|
||||
return ioutil.ReadFile(fullPath)
|
||||
return os.ReadFile(fullPath)
|
||||
}
|
||||
|
||||
27
frontend/package-lock.json
generated
27
frontend/package-lock.json
generated
@@ -13,6 +13,8 @@
|
||||
"@codemirror/lang-markdown": "^6.2.5",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.34.0",
|
||||
"@geist-ui/core": "^2.3.8",
|
||||
"@geist-ui/icons": "^1.0.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
@@ -1857,7 +1859,6 @@
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
|
||||
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
@@ -2111,6 +2112,29 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@geist-ui/core": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@geist-ui/core/-/core-2.3.8.tgz",
|
||||
"integrity": "sha512-OKwGgTA4+fBM41eQbqDoUj4XBycZbYH7Ynrn6LPO5yKX7zeWPu/R7HN3vB4/oHt34VTDQI5sDNb1SirHvNyB5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@geist-ui/icons": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@geist-ui/icons/-/icons-1.0.2.tgz",
|
||||
"integrity": "sha512-Npfa0NW6fQ31qw/+iMPWbs1hAcJ/3FqBjSLYgEfITDqy/3TJFpFKeVyK04AC/hTmYTsdNruVYczqPNcham5FOQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@geist-ui/core": ">=1.0.0",
|
||||
"react": ">=16.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
@@ -5941,7 +5965,6 @@
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
"@codemirror/lang-markdown": "^6.2.5",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.34.0",
|
||||
"@geist-ui/core": "^2.3.8",
|
||||
"@geist-ui/icons": "^1.0.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
|
||||
@@ -1,23 +1,67 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { GeistProvider, CssBaseline } from '@geist-ui/core';
|
||||
import Editor from './components/Editor';
|
||||
import FileTree from './components/FileTree';
|
||||
import { fetchFileList, fetchFileContent } from './services/api';
|
||||
import './App.scss';
|
||||
|
||||
function App() {
|
||||
const [content, setContent] = useState('# Welcome to NovaMD\n\nStart editing here!');
|
||||
const [files, setFiles] = useState(['README.md', 'chapter1.md', 'chapter2.md']);
|
||||
const [files, setFiles] = useState([]);
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadFileList = async () => {
|
||||
try {
|
||||
const fileList = await fetchFileList();
|
||||
if (Array.isArray(fileList)) {
|
||||
setFiles(fileList);
|
||||
} else {
|
||||
throw new Error('File list is not an array');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load file list:', error);
|
||||
setError('Failed to load file list. Please try again later.');
|
||||
}
|
||||
};
|
||||
|
||||
loadFileList();
|
||||
}, []);
|
||||
|
||||
const handleFileSelect = async (filePath) => {
|
||||
try {
|
||||
const fileContent = await fetchFileContent(filePath);
|
||||
setContent(fileContent);
|
||||
setSelectedFile(filePath);
|
||||
} catch (error) {
|
||||
console.error('Failed to load file content:', error);
|
||||
setError('Failed to load file content. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="sidebar">
|
||||
<FileTree files={files} />
|
||||
<GeistProvider>
|
||||
<CssBaseline />
|
||||
<div className="App">
|
||||
<div className="sidebar">
|
||||
{error ? (
|
||||
<div className="error">{error}</div>
|
||||
) : (
|
||||
<FileTree
|
||||
files={files}
|
||||
onFileSelect={handleFileSelect}
|
||||
selectedFile={selectedFile}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="main-content">
|
||||
<h1>NovaMD</h1>
|
||||
<Editor content={content} onChange={setContent} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="main-content">
|
||||
<h1>NovaMD</h1>
|
||||
<Editor content={content} onChange={setContent} />
|
||||
</div>
|
||||
</div>
|
||||
</GeistProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
@@ -1,33 +1,49 @@
|
||||
// Variables
|
||||
$sidebar-width: 250px;
|
||||
$border-color: #eaeaea;
|
||||
$padding: 20px;
|
||||
|
||||
.App {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background-color: #f0f0f0;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex-grow: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
.sidebar {
|
||||
width: $sidebar-width;
|
||||
padding: $padding;
|
||||
border-right: 1px solid $border-color;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex-grow: 1;
|
||||
padding: $padding;
|
||||
overflow-y: auto;
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Geist UI Tree component customization
|
||||
:global {
|
||||
.file-tree {
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding-left: 20px;
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: #0070f3;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Tree } from '@geist-ui/core';
|
||||
import { File, Folder } from '@geist-ui/icons';
|
||||
|
||||
const FileTree = ({
|
||||
files = [],
|
||||
onFileSelect = () => {},
|
||||
selectedFile = null
|
||||
}) => {
|
||||
if (files.length === 0) {
|
||||
return <div>No files to display</div>;
|
||||
}
|
||||
|
||||
const handleSelect = (filePath) => {
|
||||
onFileSelect(filePath);
|
||||
};
|
||||
|
||||
const renderLabel = (node) => {
|
||||
const path = getFilePath(node);
|
||||
return (
|
||||
<span style={{ color: path === selectedFile ? '#0070f3' : 'inherit' }}>
|
||||
{node.name}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderIcon = ({ type }) => type === 'directory' ? <Folder /> : <File />;
|
||||
|
||||
const FileTree = ({ files }) => {
|
||||
return (
|
||||
<div className="file-tree">
|
||||
<h3>Files</h3>
|
||||
<ul>
|
||||
{files.map((file, index) => (
|
||||
<li key={index}>{file}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<Tree
|
||||
value={files}
|
||||
onClick={handleSelect}
|
||||
renderIcon={renderIcon}
|
||||
renderLabel={renderLabel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
27
frontend/src/services/api.js
Normal file
27
frontend/src/services/api.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const API_BASE_URL = 'http://localhost:8080/api/v1';
|
||||
|
||||
export const fetchFileList = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/files`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch file list');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching file list:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchFileContent = async (filePath) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/files/${filePath}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch file content');
|
||||
}
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
console.error('Error fetching file content:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user