feat: gitea-workspace now has file tree browser like local-workspace
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- Main content area now shows file/folder tree with breadcrumb navigation
- Click folders to drill down, click files to open
- Breadcrumb shows current path with clickable segments
- Sorted tree: folders first, then files alphabetically
- refreshTree() now reloads without full page reload
- Empty state with 'New file' button when folder is empty
This commit is contained in:
2026-07-16 12:42:48 -04:00
parent b5dd37a652
commit 043dd4871c
+90 -13
View File
@@ -88,20 +88,51 @@
</div>
</template>
<!-- Empty state -->
<!-- File tree browser (like local-workspace) -->
<template x-if="!showFile && !showEditor && !showPrivate">
<div class="gw-content">
<div class="empty-state">
<h2>📁 <span x-text="owner + '/' + repo"></span></h2>
<p x-show="!showNewFile">Select a file from the sidebar or create a new one</p>
<!-- New file form -->
<div x-show="showNewFile" style="margin-top:16px;text-align:left;max-width:400px;margin-left:auto;margin-right:auto;">
<input type="text" x-model="newFilePath" placeholder="path/to/file.md" class="settings-input" style="margin-bottom:8px;">
<textarea x-model="newFileContent" placeholder="File content..." class="settings-input" style="height:120px;margin-bottom:8px;"></textarea>
<input type="text" x-model="newFileMsg" placeholder="Commit message" class="settings-input" style="margin-bottom:8px;">
<button class="btn btn-primary" @click="createNewFile()">💾 Create file</button>
<!-- Breadcrumb -->
<div class="gw-breadcrumb" style="display:flex;align-items:center;gap:4px;margin-bottom:16px;font-size:14px;color:var(--text-secondary);">
<a href="#" @click.prevent="navigateToRoot()" style="color:var(--text-secondary);text-decoration:none;" x-text="owner + '/' + repo"></a>
<template x-for="(b, i) in folderStack" :key="i">
<span style="display:contents;">
<span style="color:var(--text-tertiary);">/</span>
<a href="#" @click.prevent="navigateToFolder(i)" style="color:var(--text-secondary);text-decoration:none;" x-text="b"></a>
</span>
</template>
<span x-show="treeLoading" style="color:var(--text-tertiary);">Loading...</span>
</div>
<!-- Tree items -->
<div x-show="!treeLoading">
<template x-if="treeItems.length === 0">
<div class="empty-state">
<h2>📁 Empty</h2>
<p>No files in this folder</p>
<button class="btn btn-primary" @click="showNewFile=!showNewFile">📄+ New file</button>
</div>
</template>
<div class="ws-tree">
<template x-for="item in sortedTreeItems" :key="item.path">
<div class="ws-tree-item"
@click="item.type==='folder' ? drillDown(item.path) : openFile(item.path, item.sha)"
style="display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:pointer;">
<span x-text="item.type==='folder' ? '📁' : '📄'" style="font-size:16px;"></span>
<span x-text="item.name" style="flex:1;font-size:14px;" :style="{ fontWeight: item.type==='folder' ? '500' : '400' }"></span>
<span x-text="item.type==='folder' ? '' : formatSize(item.size)" style="font-size:12px;color:var(--text-tertiary);"></span>
</div>
</template>
</div>
</div>
<!-- New file form -->
<div x-show="showNewFile" style="margin-top:16px;max-width:400px;">
<input type="text" x-model="newFilePath" placeholder="path/to/file.md" class="settings-input" style="margin-bottom:8px;">
<textarea x-model="newFileContent" placeholder="File content..." class="settings-input" style="height:120px;margin-bottom:8px;"></textarea>
<input type="text" x-model="newFileMsg" placeholder="Commit message" class="settings-input" style="margin-bottom:8px;">
<button class="btn btn-primary" @click="createNewFile()">💾 Create file</button>
</div>
</div>
</template>
@@ -171,16 +202,61 @@ function giteaWorkspace() {
editContent: '',
commitMessage: 'Update via FlowDeck',
commitHistory: JSON.parse(localStorage.getItem('fd_commit_msgs') || '[]'),
// File tree browser
treeItems: [],
treeLoading: false,
folderStack: [],
currentPath: '',
get sortedTreeItems() {
return [...this.treeItems].sort(function(a, b) {
if (a.type === b.type) return a.name.localeCompare(b.name);
return a.type === 'folder' ? -1 : 1;
});
},
init() {
// Set cookie for sidebar
document.cookie = 'flowdeck_workspace=gitea:' + this.owner + ':' + this.repo + ';path=/;SameSite=Lax';
// Load root tree into sidebar
// Load sidebar tree
this.loadSidebarTree();
// Load main content tree
this.loadMainTree('');
// Listen for sidebar clicks on Gitea items
this.setupSidebarClicks();
},
async loadMainTree(path) {
this.treeLoading = true;
this.currentPath = path;
try {
var url = '/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree';
if (path) url += '?path=' + encodeURIComponent(path);
var r = await fetch(url);
if (!r.ok) { this.treeItems = []; return; }
var d = await r.json();
this.treeItems = d.tree || [];
} catch(e) { this.treeItems = []; }
finally { this.treeLoading = false; }
},
drillDown(path) {
this.folderStack.push(path.split('/').pop());
this.loadMainTree(path);
},
navigateToFolder(idx) {
// Truncate stack and rebuild path
this.folderStack = this.folderStack.slice(0, idx + 1);
var path = this.folderStack.join('/');
this.loadMainTree(path);
},
navigateToRoot() {
this.folderStack = [];
this.loadMainTree('');
},
async loadSidebarTree() {
try {
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree');
@@ -444,8 +520,9 @@ function giteaWorkspace() {
},
async refreshTree() {
// Reload page to refresh sidebar tree
window.location.reload();
// Reload main tree and sidebar tree without full page reload
this.loadMainTree(this.currentPath);
this.loadSidebarTree();
},
formatSize(bytes) {