Files
vibetree/internal/web/tree.go
T
Owen f3a959334f Rename project from inventree to vibetree
- go.mod module path (and every internal/vibetree import) inventree -> vibetree
- cmd/inventree/ -> cmd/vibetree/, deploy/inventree.service -> vibetree.service
- Default site name (seed data + the siteName template func's fallback)
  is now "vibetree", shown in the navbar, <title>, and login page
- Session cookie renamed inventree_session -> vibetree_session
- Default DB path data/inventree.db -> data/vibetree.db, CLI/log text,
  Dockerfile, docker-compose.yml, README, .gitignore/.dockerignore all
  updated to match

Existing installs: this changes the session cookie name (users will be
signed out once) and the default DB filename (point -db at your existing
file, or rename it, to keep your data).
2026-08-17 17:14:52 -05:00

117 lines
3.3 KiB
Go

package web
import (
"context"
"fmt"
"vibetree/internal/assets"
"vibetree/internal/models"
)
// treeAssetLimit bounds how many assets are rendered directly inside a
// single tree node before showing a "N more" placeholder, so a folder with
// thousands of assets doesn't blow up the tree fragment.
const treeAssetLimit = 300
// expandedFolderIDs returns the set of folder IDs the given user has
// persisted as expanded.
func (a *App) expandedFolderIDs(ctx context.Context, userID int64) (map[int64]bool, error) {
rows, err := a.DB.QueryContext(ctx, `SELECT folder_id FROM user_tree_state WHERE user_id = ?`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
out := map[int64]bool{}
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
return nil, err
}
out[id] = true
}
return out, rows.Err()
}
func (a *App) persistExpand(ctx context.Context, userID, folderID int64) error {
_, err := a.DB.ExecContext(ctx,
`INSERT INTO user_tree_state (user_id, folder_id) VALUES (?, ?) ON CONFLICT(user_id, folder_id) DO NOTHING`,
userID, folderID)
return err
}
func (a *App) persistCollapse(ctx context.Context, userID, folderID int64) error {
_, err := a.DB.ExecContext(ctx, `DELETE FROM user_tree_state WHERE user_id = ? AND folder_id = ?`, userID, folderID)
return err
}
func (a *App) persistCollapseAll(ctx context.Context, userID int64) error {
_, err := a.DB.ExecContext(ctx, `DELETE FROM user_tree_state WHERE user_id = ?`, userID)
return err
}
// buildTree returns the ordered list of tree nodes (subfolders then assets)
// directly under parentID. Folders present in the expanded set are
// recursively expanded inline; others are returned collapsed for the client
// to lazy-load on demand.
func (a *App) buildTree(ctx context.Context, parentID *int64, expanded map[int64]bool) ([]models.TreeNode, error) {
childFolders, err := a.Folders.Children(ctx, parentID)
if err != nil {
return nil, err
}
var out []models.TreeNode
for _, f := range childFolders {
node := models.TreeNode{
Type: "folder",
ID: f.ID,
Name: f.Name,
Icon: f.Icon,
ParentID: f.ParentID,
HasChildren: f.FolderCount > 0 || f.AssetCount > 0,
}
if expanded[f.ID] {
node.Expanded = true
children, err := a.buildTree(ctx, &f.ID, expanded)
if err != nil {
return nil, err
}
node.Children = children
}
out = append(out, node)
}
assetList, total, err := a.Assets.List(ctx, assets.ListFilter{
FolderID: parentID, FolderSet: true, Page: 1, PageSize: treeAssetLimit,
})
if err != nil {
return nil, err
}
for _, as := range assetList {
out = append(out, models.TreeNode{
Type: "asset",
ID: as.ID,
Name: as.Name,
Icon: as.ItemTypeIcon,
AssetTag: as.AssetTag,
StatusName: as.StatusName,
StatusColor: as.StatusColor,
})
}
if total > len(assetList) {
out = append(out, models.TreeNode{
Type: "more",
Name: fmt.Sprintf("%d more asset(s) — open this folder to view all", total-len(assetList)),
})
}
return out, nil
}
func fieldValuesForCreate(fields []models.CustomFieldDef) []models.AssetFieldValue {
out := make([]models.AssetFieldValue, len(fields))
for i, f := range fields {
out[i] = models.AssetFieldValue{Def: f, Value: f.DefaultValue}
}
return out
}