Skip to content

Commit 528b79c

Browse files
feat: implement project file system scaffolding and base UI components for document management
1 parent 9bed9e1 commit 528b79c

12 files changed

Lines changed: 486 additions & 58 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
**Postman Power + Enterprise Control + 100% Data Ownership.**
88

99
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10-
[![Version](https://img.shields.io/badge/version-1.0.24-emerald.svg)](package.json)
10+
[![Version](https://img.shields.io/badge/version-1.0.25-emerald.svg)](package.json)
1111
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
1212
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](#)
1313
</div>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "api-documenter",
3-
"version": "1.0.24",
3+
"version": "1.0.25",
44
"description": "Self-hosted Postman alternative with folder-level RBAC and offline-first documentation",
55
"main": "./out/main/index.js",
66
"author": "Praneeth Kulukuri",

src/main/fileSystemManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export function initProjectDirectory(
7878
// .gitignore
7979
const gitignoreContent = [
8080
'project.secrets.json',
81+
'.sync-meta.json',
8182
''
8283
].join('\n')
8384
fs.writeFileSync(path.join(dirPath, '.gitignore'), gitignoreContent, 'utf-8')

src/renderer/src/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Sidebar } from './components/Sidebar'
99
import { GitSidebar } from './components/GitSidebar'
1010
import { RequestEditor } from './components/RequestEditor'
1111
import { EmptyState } from './components/EmptyState'
12+
import { TabsBar } from './components/TabsBar'
1213
import { CreateProjectDialog } from './components/CreateProjectDialog'
1314
import { CreateFolderDialog } from './components/CreateFolderDialog'
1415
import { CreateApiDialog } from './components/CreateApiDialog'
@@ -83,7 +84,8 @@ export function App() {
8384
{activeSidebarTab === 'explorer' ? <Sidebar /> : <GitSidebar />}
8485

8586
{/* Editor area */}
86-
<main style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column', position: 'relative' }}>
87+
<main style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column', position: 'relative', background: '#0A0A0A' }}>
88+
<TabsBar />
8789
{showApiDocumentation ? (
8890
<ApiDocumentationPage />
8991
) : currentApiId ? (

src/renderer/src/components/ActivityBar.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function ActivityBar() {
1010
const qc = useQueryClient()
1111
const {
1212
activeSidebarTab, setActiveSidebarTab,
13-
setShowDatabaseSettings, setShowRbacSettings, setShowDeploySettings,
13+
setShowDatabaseSettings, setShowRbacSettings, setShowDeploySettings, setShowGeneralSettings, openDocsTab,
1414
currentProjectId, activeBranch, currentSyncBranch, isSyncing, databaseUrl,
1515
isTeamWorkspace
1616
} = useAppStore()
@@ -62,23 +62,45 @@ export function ActivityBar() {
6262
{currentProjectId && !isTeamWorkspace && (
6363
<>
6464
<ActivityItem
65-
icon="database"
65+
icon="document"
6666
isActive={false}
67-
onClick={() => setShowDatabaseSettings(true)}
68-
title="Database Settings"
67+
onClick={() => openDocsTab()}
68+
title="Generate API Docs"
6969
/>
7070
<ActivityItem
71-
icon="users"
71+
icon="database"
7272
isActive={false}
73-
onClick={() => setShowRbacSettings(true)}
74-
title="Team & Permissions (RBAC)"
73+
onClick={() => setShowDatabaseSettings(true)}
74+
title="Database Settings"
7575
/>
76+
{activeBranch === currentSyncBranch && (
77+
<ActivityItem
78+
icon="users"
79+
isActive={false}
80+
onClick={() => setShowRbacSettings(true)}
81+
title="Team & Permissions (RBAC)"
82+
/>
83+
)}
7684
<ActivityItem
7785
icon="deploy"
7886
isActive={false}
7987
onClick={() => setShowDeploySettings(true)}
8088
title="Deploy Proxy"
8189
/>
90+
<ActivityItem
91+
icon="settings"
92+
isActive={false}
93+
onClick={() => setShowGeneralSettings(true)}
94+
title="Project Settings"
95+
/>
96+
{project?.localPath && (
97+
<ActivityItem
98+
icon="folderOpen"
99+
isActive={false}
100+
onClick={() => (window as any).electronAPI.openInExplorer(project.localPath)}
101+
title="Open in Explorer"
102+
/>
103+
)}
82104
</>
83105
)}
84106
</div>
@@ -213,6 +235,18 @@ function ActivityItem({ icon, isActive, onClick, title }: { icon: string, isActi
213235
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
214236
<line x1="12" y1="22.08" x2="12" y2="12" />
215237
</svg>
238+
) : icon === 'document' ? (
239+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
240+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
241+
</svg>
242+
) : icon === 'settings' ? (
243+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
244+
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.1a2 2 0 0 1-1-1.72v-.51a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path><circle cx="12" cy="12" r="3"></circle>
245+
</svg>
246+
) : icon === 'folderOpen' ? (
247+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
248+
<path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"></path><path d="M2 10h20"></path>
249+
</svg>
216250
) : (
217251
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
218252
<ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M3 5V19A9 3 0 0 0 21 19V5" /><path d="M3 12A9 3 0 0 0 21 12" />

src/renderer/src/components/KeyValueEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export function KeyValueEditor({ pairs, onChange, keyPlaceholder = 'Key', valueP
189189
style={{
190190
width: '100%', fontFamily: 'monospace', fontSize: '11px',
191191
height: '32px', borderRadius: '8px',
192-
background: '#0A0A0A', border: '1px solid #2A2A2A',
192+
background: '#0A0A0A', borderWidth: '1px', borderStyle: 'solid', borderColor: '#2A2A2A',
193193
transition: '150ms ease',
194194
cursor: readOnly ? 'default' : 'text'
195195
}}

src/renderer/src/components/RequestEditor.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,25 @@ export function RequestEditor({ apiId }: Props) {
8181

8282
useEffect(() => {
8383
if (!api) return
84-
setName(api.name || ''); setDescription(api.description || ''); setMethod(api.method || 'GET')
85-
setPath(api.path || ''); setUrlParams(api.urlParams || []); setHeaders(api.headers || [])
86-
setBodyType(api.bodyType || 'none'); setRequestBody(api.requestBody || '')
87-
setRawType(api.rawType || 'json')
88-
setFormData(api.formData || [])
89-
setUrlencoded(api.urlencoded || [])
90-
setResponses(api.responseExamples || []); setSaved(true)
84+
85+
const draft = useAppStore.getState().apiDrafts[api.id]
86+
if (draft) {
87+
setName(draft.name); setDescription(draft.description); setMethod(draft.method)
88+
setPath(draft.path); setUrlParams(draft.urlParams); setHeaders(draft.headers)
89+
setBodyType(draft.bodyType); setRequestBody(draft.requestBody)
90+
setRawType(draft.rawType)
91+
setFormData(draft.formData)
92+
setUrlencoded(draft.urlencoded)
93+
setResponses(draft.responses); setSaved(draft.saved)
94+
} else {
95+
setName(api.name || ''); setDescription(api.description || ''); setMethod(api.method || 'GET')
96+
setPath(api.path || ''); setUrlParams(api.urlParams || []); setHeaders(api.headers || [])
97+
setBodyType(api.bodyType || 'none'); setRequestBody(api.requestBody || '')
98+
setRawType(api.rawType || 'json')
99+
setFormData(api.formData || [])
100+
setUrlencoded(api.urlencoded || [])
101+
setResponses(api.responseExamples || []); setSaved(true)
102+
}
91103
setLiveResponse(null)
92104
}, [api])
93105

@@ -104,6 +116,11 @@ export function RequestEditor({ apiId }: Props) {
104116
JSON.stringify(responses) !== JSON.stringify(api.responseExamples)
105117

106118
setSaved(!dirty)
119+
if (dirty) {
120+
useAppStore.getState().setApiDraft(api.id, { name, description, method, path, urlParams, headers, bodyType, rawType, formData, urlencoded, requestBody, responses, saved: false, version: api.version })
121+
} else {
122+
useAppStore.getState().clearApiDraft(api.id)
123+
}
107124
}, 500) // Debounce for 500ms
108125

109126
return () => clearTimeout(timeout)
@@ -117,6 +134,7 @@ export function RequestEditor({ apiId }: Props) {
117134
responseExamples: responses, version: (api.version || 0) + 1
118135
})
119136
setSaved(true)
137+
useAppStore.getState().clearApiDraft(api.id)
120138
}, [api, name, description, method, path, urlParams, headers, bodyType, rawType, formData, urlencoded, requestBody, responses, updateApi])
121139

122140
const updateContentType = useCallback((type: BodyType, rType: RawType) => {
@@ -452,7 +470,7 @@ export function RequestEditor({ apiId }: Props) {
452470
variables={variablesMap}
453471
placeholder="https://api.example.com/endpoint"
454472
onKeyDown={e => e.key === 'Enter' && sendRequest()}
455-
style={{ flex: 1, fontFamily: 'monospace', fontSize: '13px', background: '#0F0F0F', border: '1px solid #2A2A2A', borderRadius: '10px', height: '40px', transition: '150ms ease' }}
473+
style={{ flex: 1, fontFamily: 'monospace', fontSize: '13px', background: '#0F0F0F', borderWidth: '1px', borderStyle: 'solid', borderColor: '#2A2A2A', borderRadius: '10px', height: '40px', transition: '150ms ease' }}
456474
/>
457475

458476
{/* Send */}

0 commit comments

Comments
 (0)