Files tab: multi-selection, drag-and-drop, keyboard shortcuts, custom confirm modal, SVG icons
This commit is contained in:
+120
-11
@@ -19,6 +19,8 @@ import (
|
||||
"verstak/internal/core/worklog"
|
||||
)
|
||||
|
||||
|
||||
|
||||
// App is the Wails v2 application adapter. It wraps core services.
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
@@ -32,9 +34,14 @@ type App struct {
|
||||
vault string
|
||||
}
|
||||
|
||||
// startup is called when the app starts. Store context.
|
||||
// startup is called when the app starts. Store context and wire drag-and-drop.
|
||||
func (a *App) startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
wailsruntime.OnFileDrop(ctx, func(x, y int, paths []string) {
|
||||
if len(paths) > 0 {
|
||||
wailsruntime.EventsEmit(ctx, "files-dropped", paths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -75,6 +82,16 @@ type FileDTO struct {
|
||||
Missing bool `json:"missing"`
|
||||
}
|
||||
|
||||
type FileTreeItemDTO struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // "folder" | "file"
|
||||
FileID string `json:"fileId,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Mime string `json:"mime,omitempty"`
|
||||
HasKids bool `json:"hasKids"`
|
||||
}
|
||||
|
||||
type ActionDTO struct {
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"nodeId"`
|
||||
@@ -199,6 +216,7 @@ func (a *App) SaveNote(noteID, content string) error {
|
||||
// Files
|
||||
// ============================================================
|
||||
|
||||
// ListFiles returns file records directly linked to a node (non-recursive).
|
||||
func (a *App) ListFiles(nodeID string) ([]FileDTO, error) {
|
||||
records, err := a.files.ListByNode(nodeID)
|
||||
if err != nil {
|
||||
@@ -206,22 +224,105 @@ func (a *App) ListFiles(nodeID string) ([]FileDTO, error) {
|
||||
}
|
||||
result := make([]FileDTO, len(records))
|
||||
for i := range records {
|
||||
isDir := records[i].MIME == "inode/directory"
|
||||
missing := false
|
||||
rec := &records[i]
|
||||
result[i] = FileDTO{
|
||||
ID: records[i].ID,
|
||||
NodeID: records[i].NodeID,
|
||||
Name: records[i].Filename,
|
||||
Path: records[i].Path,
|
||||
Size: records[i].Size,
|
||||
Mime: records[i].MIME,
|
||||
IsDir: isDir,
|
||||
Missing: missing,
|
||||
ID: rec.ID,
|
||||
NodeID: rec.NodeID,
|
||||
Name: rec.Filename,
|
||||
Path: rec.Path,
|
||||
Size: rec.Size,
|
||||
Mime: rec.MIME,
|
||||
IsDir: rec.MIME == "inode/directory",
|
||||
Missing: rec.Missing,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListItems returns children of a node for the file tree view.
|
||||
// Folders can be expanded; files include their file record info.
|
||||
func (a *App) ListItems(nodeID string) ([]FileTreeItemDTO, error) {
|
||||
children, err := a.nodes.ListChildren(nodeID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]FileTreeItemDTO, 0, len(children))
|
||||
for i := range children {
|
||||
if children[i].Type != nodes.TypeFolder && children[i].Type != nodes.TypeFile {
|
||||
continue
|
||||
}
|
||||
item := FileTreeItemDTO{
|
||||
ID: children[i].ID,
|
||||
Name: children[i].Title,
|
||||
Type: children[i].Type,
|
||||
}
|
||||
if children[i].Type == nodes.TypeFolder {
|
||||
// Check if this folder has children
|
||||
kids, _ := a.nodes.ListChildren(children[i].ID, false)
|
||||
item.HasKids = len(kids) > 0
|
||||
} else if children[i].Type == nodes.TypeFile {
|
||||
records, _ := a.files.ListByNode(children[i].ID)
|
||||
if len(records) > 0 {
|
||||
item.FileID = records[0].ID
|
||||
item.Size = records[0].Size
|
||||
item.Mime = records[0].MIME
|
||||
}
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *App) AddPathCopy(nodeID, sourcePath string) ([]NodeDTO, error) {
|
||||
nodes, err := a.files.AddPathCopy(nodeID, sourcePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toNodeDTOs(nodes), nil
|
||||
}
|
||||
|
||||
func (a *App) AddPathLink(nodeID, sourcePath string) ([]NodeDTO, error) {
|
||||
nodes, err := a.files.AddPathLink(nodeID, sourcePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toNodeDTOs(nodes), nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteFileOrFolder(nodeID string) error {
|
||||
return a.files.DeleteNodeAndChildren(nodeID)
|
||||
}
|
||||
|
||||
func (a *App) CreateEmptyFile(parentID, filename string) (*NodeDTO, error) {
|
||||
node, err := a.files.CreateEmptyFile(parentID, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dto := toNodeDTO(node)
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (a *App) DuplicateNode(nodeID string) (*NodeDTO, error) {
|
||||
node, err := a.files.Duplicate(nodeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dto := toNodeDTO(node)
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (a *App) RenameNode(nodeID, newTitle string) error {
|
||||
return a.nodes.UpdateTitle(nodeID, newTitle)
|
||||
}
|
||||
|
||||
func (a *App) MoveNode(nodeID, newParentID string) error {
|
||||
return a.nodes.Move(nodeID, newParentID, 0)
|
||||
}
|
||||
|
||||
func (a *App) PreviewImport(sourcePath string) (*files.ImportSummary, error) {
|
||||
return a.files.PreviewImport(sourcePath)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Actions
|
||||
// ============================================================
|
||||
@@ -352,6 +453,14 @@ func (a *App) OpenFile(fileID string) error {
|
||||
return a.files.Open(fileID)
|
||||
}
|
||||
|
||||
func (a *App) ReadFileText(fileID string) (string, error) {
|
||||
return a.files.ReadText(fileID)
|
||||
}
|
||||
|
||||
func (a *App) GetFileBase64(fileID string) (string, error) {
|
||||
return a.files.ReadBase64(fileID)
|
||||
}
|
||||
|
||||
func (a *App) OpenFolder(nodeID string) error {
|
||||
n, err := a.nodes.GetActive(nodeID)
|
||||
if err != nil {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,8 +16,8 @@
|
||||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-BqdVWy5o.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-D8LYjC_e.css">
|
||||
<script type="module" crossorigin src="/assets/main-a-M2pafQ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-6cuAgDnH.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -43,7 +43,7 @@ func main() {
|
||||
|
||||
// Init core services
|
||||
nodeRepo := nodes.NewRepository(db)
|
||||
fileSvc := files.NewService(db, abs)
|
||||
fileSvc := files.NewService(db, abs, nodeRepo)
|
||||
noteSvc := notes.NewService(db, abs, nodeRepo, fileSvc)
|
||||
actionSvc := actions.NewService(db)
|
||||
worklogSvc := worklog.NewService(db)
|
||||
@@ -72,7 +72,10 @@ func main() {
|
||||
Assets: assets,
|
||||
},
|
||||
OnStartup: app.startup,
|
||||
Bind: []interface{}{app},
|
||||
DragAndDrop: &options.DragAndDrop{
|
||||
EnableFileDrop: true,
|
||||
},
|
||||
Bind: []interface{}{app},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user