package web import ( "context" "fmt" "inventree/internal/assets" "inventree/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 }