diff --git a/internal/api/cross_transfer_admin.go b/internal/api/cross_transfer_admin.go index 3ba9c54..c0969d0 100644 --- a/internal/api/cross_transfer_admin.go +++ b/internal/api/cross_transfer_admin.go @@ -18,10 +18,35 @@ func (h *Handler) crossTransferRoutes(w http.ResponseWriter, r *http.Request) { } type crossTransferScanReq struct { - SourceAccountID int64 `json:"source_account_id"` - SourceParentID string `json:"source_parent_id"` - Method string `json:"method"` - SourceDisplayPath string `json:"source_display_path"` + SourceAccountID int64 `json:"source_account_id"` + SourceParentID string `json:"source_parent_id"` + Method string `json:"method"` + SourceDisplayPath string `json:"source_display_path"` + Sources []crossTransferScanSource `json:"sources"` +} + +type crossTransferScanSource struct { + ParentID string `json:"parent_id"` + DisplayPath string `json:"display_path"` + AncestorIDs []string `json:"ancestor_ids"` +} + +func (r crossTransferScanReq) roots() []crosstransfer.ScanRoot { + if len(r.Sources) == 0 { + return []crosstransfer.ScanRoot{{ + ParentID: r.SourceParentID, + DisplayPath: r.SourceDisplayPath, + }} + } + roots := make([]crosstransfer.ScanRoot, 0, len(r.Sources)) + for _, source := range r.Sources { + roots = append(roots, crosstransfer.ScanRoot{ + ParentID: source.ParentID, + DisplayPath: source.DisplayPath, + AncestorIDs: source.AncestorIDs, + }) + } + return roots } func (h *Handler) crossTransferScan(w http.ResponseWriter, r *http.Request) { @@ -33,7 +58,7 @@ func (h *Handler) crossTransferScan(w http.ResponseWriter, r *http.Request) { writeErr(w, err) return } - result, err := h.crossTransfer.ScanSource(r.Context(), req.SourceAccountID, req.SourceParentID, req.Method, req.SourceDisplayPath) + result, err := h.crossTransfer.ScanSources(r.Context(), req.SourceAccountID, req.roots(), req.Method) if err != nil { writeErr(w, err) return @@ -51,12 +76,11 @@ func (h *Handler) crossTransferScanStream(w http.ResponseWriter, r *http.Request return } h.streamCrossTransferNDJSON(w, r, func(emit func(crosstransfer.StreamEvent) error) error { - return h.crossTransfer.ScanSourceStream( + return h.crossTransfer.ScanSourcesStream( r.Context(), req.SourceAccountID, - req.SourceParentID, + req.roots(), req.Method, - req.SourceDisplayPath, emit, ) }) diff --git a/internal/api/web/assets/AccountFolderField-Bj4BEAj9.js.gz b/internal/api/web/assets/AccountFolderField-Bj4BEAj9.js.gz new file mode 100644 index 0000000..a3043fe Binary files /dev/null and b/internal/api/web/assets/AccountFolderField-Bj4BEAj9.js.gz differ diff --git a/internal/api/web/assets/AccountFolderField-DIFNl_XY.js.gz b/internal/api/web/assets/AccountFolderField-DIFNl_XY.js.gz deleted file mode 100644 index 1614da7..0000000 Binary files a/internal/api/web/assets/AccountFolderField-DIFNl_XY.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AccountManagement-CffLSKUK.js.gz b/internal/api/web/assets/AccountManagement-CffLSKUK.js.gz deleted file mode 100644 index f1ddb60..0000000 Binary files a/internal/api/web/assets/AccountManagement-CffLSKUK.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AccountManagement-D2-s7sth.js.gz b/internal/api/web/assets/AccountManagement-D2-s7sth.js.gz new file mode 100644 index 0000000..591b0ff Binary files /dev/null and b/internal/api/web/assets/AccountManagement-D2-s7sth.js.gz differ diff --git a/internal/api/web/assets/AdminRunStatusCell-ClOyARqa.js.gz b/internal/api/web/assets/AdminRunStatusCell-ClOyARqa.js.gz new file mode 100644 index 0000000..aa4d3da Binary files /dev/null and b/internal/api/web/assets/AdminRunStatusCell-ClOyARqa.js.gz differ diff --git a/internal/api/web/assets/AdminRunStatusCell-DJRMCL9P.js.gz b/internal/api/web/assets/AdminRunStatusCell-DJRMCL9P.js.gz deleted file mode 100644 index 45a91c6..0000000 Binary files a/internal/api/web/assets/AdminRunStatusCell-DJRMCL9P.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AdminSettingsDrawer-BcHLAlWo.js.gz b/internal/api/web/assets/AdminSettingsDrawer-BcHLAlWo.js.gz new file mode 100644 index 0000000..2e64873 Binary files /dev/null and b/internal/api/web/assets/AdminSettingsDrawer-BcHLAlWo.js.gz differ diff --git a/internal/api/web/assets/AdminSettingsDrawer-D8QrzazV.js.gz b/internal/api/web/assets/AdminSettingsDrawer-D8QrzazV.js.gz deleted file mode 100644 index e5bcf8b..0000000 Binary files a/internal/api/web/assets/AdminSettingsDrawer-D8QrzazV.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AdminTaskTabHeader-BrfAEq7N.js.gz b/internal/api/web/assets/AdminTaskTabHeader-BrfAEq7N.js.gz deleted file mode 100644 index 19ec28a..0000000 Binary files a/internal/api/web/assets/AdminTaskTabHeader-BrfAEq7N.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AdminTaskTabHeader-Dux3uQAk.js.gz b/internal/api/web/assets/AdminTaskTabHeader-Dux3uQAk.js.gz new file mode 100644 index 0000000..78cbee0 Binary files /dev/null and b/internal/api/web/assets/AdminTaskTabHeader-Dux3uQAk.js.gz differ diff --git a/internal/api/web/assets/AdminView-C_zCJJL6.js.gz b/internal/api/web/assets/AdminView-C_zCJJL6.js.gz new file mode 100644 index 0000000..193ce2d Binary files /dev/null and b/internal/api/web/assets/AdminView-C_zCJJL6.js.gz differ diff --git a/internal/api/web/assets/AdminView-Dv7hPDXn.js.gz b/internal/api/web/assets/AdminView-Dv7hPDXn.js.gz deleted file mode 100644 index ecc09ea..0000000 Binary files a/internal/api/web/assets/AdminView-Dv7hPDXn.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AppBadge-91HmCOI6.js.gz b/internal/api/web/assets/AppBadge-91HmCOI6.js.gz deleted file mode 100644 index 176c19b..0000000 Binary files a/internal/api/web/assets/AppBadge-91HmCOI6.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AppBadge-ClLiSxD1.js.gz b/internal/api/web/assets/AppBadge-ClLiSxD1.js.gz new file mode 100644 index 0000000..2a65b84 Binary files /dev/null and b/internal/api/web/assets/AppBadge-ClLiSxD1.js.gz differ diff --git a/internal/api/web/assets/AppCardActionButton-BJVx3uqo.js.gz b/internal/api/web/assets/AppCardActionButton-BJVx3uqo.js.gz new file mode 100644 index 0000000..08d3625 Binary files /dev/null and b/internal/api/web/assets/AppCardActionButton-BJVx3uqo.js.gz differ diff --git a/internal/api/web/assets/AppCardActionButton-C25YkNPM.js.gz b/internal/api/web/assets/AppCardActionButton-C25YkNPM.js.gz deleted file mode 100644 index 8ce7982..0000000 Binary files a/internal/api/web/assets/AppCardActionButton-C25YkNPM.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AppDropdown-BzmDLBYh.js.gz b/internal/api/web/assets/AppDropdown-BzmDLBYh.js.gz deleted file mode 100644 index 0a317ec..0000000 Binary files a/internal/api/web/assets/AppDropdown-BzmDLBYh.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AppDropdown-apAgYhMQ.js.gz b/internal/api/web/assets/AppDropdown-apAgYhMQ.js.gz new file mode 100644 index 0000000..7386d3d Binary files /dev/null and b/internal/api/web/assets/AppDropdown-apAgYhMQ.js.gz differ diff --git a/internal/api/web/assets/AppIconButton-BKvtKwYy.js.gz b/internal/api/web/assets/AppIconButton-BKvtKwYy.js.gz new file mode 100644 index 0000000..89f681c Binary files /dev/null and b/internal/api/web/assets/AppIconButton-BKvtKwYy.js.gz differ diff --git a/internal/api/web/assets/AppIconButton-BnTsvwgJ.js.gz b/internal/api/web/assets/AppIconButton-BnTsvwgJ.js.gz deleted file mode 100644 index 9900f48..0000000 Binary files a/internal/api/web/assets/AppIconButton-BnTsvwgJ.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AppInput-8xXS7G0N.js.gz b/internal/api/web/assets/AppInput-8xXS7G0N.js.gz deleted file mode 100644 index 6088299..0000000 Binary files a/internal/api/web/assets/AppInput-8xXS7G0N.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AppInput-nGa6beur.js.gz b/internal/api/web/assets/AppInput-nGa6beur.js.gz new file mode 100644 index 0000000..30a6ad2 Binary files /dev/null and b/internal/api/web/assets/AppInput-nGa6beur.js.gz differ diff --git a/internal/api/web/assets/AppSelect-B6dP8bqh.js.gz b/internal/api/web/assets/AppSelect-B6dP8bqh.js.gz new file mode 100644 index 0000000..c6653bc Binary files /dev/null and b/internal/api/web/assets/AppSelect-B6dP8bqh.js.gz differ diff --git a/internal/api/web/assets/AppSelect-pFRULmEx.js.gz b/internal/api/web/assets/AppSelect-pFRULmEx.js.gz deleted file mode 100644 index 773301b..0000000 Binary files a/internal/api/web/assets/AppSelect-pFRULmEx.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AppStateBlock-D9s019UH.js.gz b/internal/api/web/assets/AppStateBlock-D9s019UH.js.gz new file mode 100644 index 0000000..c059af4 Binary files /dev/null and b/internal/api/web/assets/AppStateBlock-D9s019UH.js.gz differ diff --git a/internal/api/web/assets/AppStateBlock-DldHrZXT.js.gz b/internal/api/web/assets/AppStateBlock-DldHrZXT.js.gz deleted file mode 100644 index 30c3bff..0000000 Binary files a/internal/api/web/assets/AppStateBlock-DldHrZXT.js.gz and /dev/null differ diff --git a/internal/api/web/assets/ArchivePreview-B1_YSa3J.js.gz b/internal/api/web/assets/ArchivePreview-B1_YSa3J.js.gz deleted file mode 100644 index e5318fa..0000000 Binary files a/internal/api/web/assets/ArchivePreview-B1_YSa3J.js.gz and /dev/null differ diff --git a/internal/api/web/assets/ArchivePreview-U4fLeuvY.js.gz b/internal/api/web/assets/ArchivePreview-U4fLeuvY.js.gz new file mode 100644 index 0000000..a37407e Binary files /dev/null and b/internal/api/web/assets/ArchivePreview-U4fLeuvY.js.gz differ diff --git a/internal/api/web/assets/AudioPreview-C1R_JaCK.js.gz b/internal/api/web/assets/AudioPreview-C1R_JaCK.js.gz new file mode 100644 index 0000000..5472b86 Binary files /dev/null and b/internal/api/web/assets/AudioPreview-C1R_JaCK.js.gz differ diff --git a/internal/api/web/assets/AudioPreview-DUTvUWIc.js.gz b/internal/api/web/assets/AudioPreview-DUTvUWIc.js.gz deleted file mode 100644 index d1d44b9..0000000 Binary files a/internal/api/web/assets/AudioPreview-DUTvUWIc.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AutomationPanel-0Zm3CmUM.js.gz b/internal/api/web/assets/AutomationPanel-0Zm3CmUM.js.gz new file mode 100644 index 0000000..4653712 Binary files /dev/null and b/internal/api/web/assets/AutomationPanel-0Zm3CmUM.js.gz differ diff --git a/internal/api/web/assets/AutomationPanel-Bo_UWmGn.js.gz b/internal/api/web/assets/AutomationPanel-Bo_UWmGn.js.gz deleted file mode 100644 index 3f51f9f..0000000 Binary files a/internal/api/web/assets/AutomationPanel-Bo_UWmGn.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AuxToolsManagement-B100j6kf.js.gz b/internal/api/web/assets/AuxToolsManagement-B100j6kf.js.gz deleted file mode 100644 index 2cacdfa..0000000 Binary files a/internal/api/web/assets/AuxToolsManagement-B100j6kf.js.gz and /dev/null differ diff --git a/internal/api/web/assets/AuxToolsManagement-BFfAGhdo.js.gz b/internal/api/web/assets/AuxToolsManagement-BFfAGhdo.js.gz new file mode 100644 index 0000000..4861269 Binary files /dev/null and b/internal/api/web/assets/AuxToolsManagement-BFfAGhdo.js.gz differ diff --git a/internal/api/web/assets/BreadcrumbNav-CaYczBic.js.gz b/internal/api/web/assets/BreadcrumbNav-CaYczBic.js.gz deleted file mode 100644 index d279a8f..0000000 Binary files a/internal/api/web/assets/BreadcrumbNav-CaYczBic.js.gz and /dev/null differ diff --git a/internal/api/web/assets/BreadcrumbNav-FdFzeEC4.js.gz b/internal/api/web/assets/BreadcrumbNav-FdFzeEC4.js.gz new file mode 100644 index 0000000..dc03638 Binary files /dev/null and b/internal/api/web/assets/BreadcrumbNav-FdFzeEC4.js.gz differ diff --git a/internal/api/web/assets/BusySpinner-Cxh-J__7.js.gz b/internal/api/web/assets/BusySpinner-Cxh-J__7.js.gz deleted file mode 100644 index a3f97e1..0000000 Binary files a/internal/api/web/assets/BusySpinner-Cxh-J__7.js.gz and /dev/null differ diff --git a/internal/api/web/assets/BusySpinner-CxjxsDPV.js.gz b/internal/api/web/assets/BusySpinner-CxjxsDPV.js.gz new file mode 100644 index 0000000..4e4bd31 Binary files /dev/null and b/internal/api/web/assets/BusySpinner-CxjxsDPV.js.gz differ diff --git a/internal/api/web/assets/CacheRetentionPanel-CVcx0RlA.js.gz b/internal/api/web/assets/CacheRetentionPanel-CVcx0RlA.js.gz new file mode 100644 index 0000000..edaab2d Binary files /dev/null and b/internal/api/web/assets/CacheRetentionPanel-CVcx0RlA.js.gz differ diff --git a/internal/api/web/assets/CacheRetentionPanel-HbcbQ9L4.js.gz b/internal/api/web/assets/CacheRetentionPanel-HbcbQ9L4.js.gz deleted file mode 100644 index 8d1a0df..0000000 Binary files a/internal/api/web/assets/CacheRetentionPanel-HbcbQ9L4.js.gz and /dev/null differ diff --git a/internal/api/web/assets/CacheSettingsPanel-7KDFhruq.js.gz b/internal/api/web/assets/CacheSettingsPanel-7KDFhruq.js.gz deleted file mode 100644 index 536332a..0000000 Binary files a/internal/api/web/assets/CacheSettingsPanel-7KDFhruq.js.gz and /dev/null differ diff --git a/internal/api/web/assets/CacheSettingsPanel-qu3u8SVp.js.gz b/internal/api/web/assets/CacheSettingsPanel-qu3u8SVp.js.gz new file mode 100644 index 0000000..abd742f Binary files /dev/null and b/internal/api/web/assets/CacheSettingsPanel-qu3u8SVp.js.gz differ diff --git a/internal/api/web/assets/CrossDriveTransfer-BwMXgNHB.css.gz b/internal/api/web/assets/CrossDriveTransfer-BwMXgNHB.css.gz new file mode 100644 index 0000000..1f57dfe Binary files /dev/null and b/internal/api/web/assets/CrossDriveTransfer-BwMXgNHB.css.gz differ diff --git a/internal/api/web/assets/CrossDriveTransfer-CJ6qQzeR.css.gz b/internal/api/web/assets/CrossDriveTransfer-CJ6qQzeR.css.gz deleted file mode 100644 index c2b8f02..0000000 Binary files a/internal/api/web/assets/CrossDriveTransfer-CJ6qQzeR.css.gz and /dev/null differ diff --git a/internal/api/web/assets/CrossDriveTransfer-Ch4x_KPL.js.gz b/internal/api/web/assets/CrossDriveTransfer-Ch4x_KPL.js.gz deleted file mode 100644 index 0152115..0000000 Binary files a/internal/api/web/assets/CrossDriveTransfer-Ch4x_KPL.js.gz and /dev/null differ diff --git a/internal/api/web/assets/CrossDriveTransfer-bHsEGuEg.js.gz b/internal/api/web/assets/CrossDriveTransfer-bHsEGuEg.js.gz new file mode 100644 index 0000000..35f5a93 Binary files /dev/null and b/internal/api/web/assets/CrossDriveTransfer-bHsEGuEg.js.gz differ diff --git a/internal/api/web/assets/DashboardManagement-0SsIRc6s.js.gz b/internal/api/web/assets/DashboardManagement-0SsIRc6s.js.gz new file mode 100644 index 0000000..3b6a92f Binary files /dev/null and b/internal/api/web/assets/DashboardManagement-0SsIRc6s.js.gz differ diff --git a/internal/api/web/assets/DashboardManagement-PLFnUjE1.js.gz b/internal/api/web/assets/DashboardManagement-PLFnUjE1.js.gz deleted file mode 100644 index 0335c42..0000000 Binary files a/internal/api/web/assets/DashboardManagement-PLFnUjE1.js.gz and /dev/null differ diff --git a/internal/api/web/assets/DocxPreview-BnKGmGFU.js.gz b/internal/api/web/assets/DocxPreview-BnKGmGFU.js.gz new file mode 100644 index 0000000..c352b85 Binary files /dev/null and b/internal/api/web/assets/DocxPreview-BnKGmGFU.js.gz differ diff --git a/internal/api/web/assets/DocxPreview-CVNFrAYC.js.gz b/internal/api/web/assets/DocxPreview-CVNFrAYC.js.gz deleted file mode 100644 index 143ecbb..0000000 Binary files a/internal/api/web/assets/DocxPreview-CVNFrAYC.js.gz and /dev/null differ diff --git a/internal/api/web/assets/FileShareManagement-CykV2gea.js.gz b/internal/api/web/assets/FileShareManagement-CykV2gea.js.gz new file mode 100644 index 0000000..78f1da8 Binary files /dev/null and b/internal/api/web/assets/FileShareManagement-CykV2gea.js.gz differ diff --git a/internal/api/web/assets/FileShareManagement-DdhM1YAd.js.gz b/internal/api/web/assets/FileShareManagement-DdhM1YAd.js.gz deleted file mode 100644 index 7cef805..0000000 Binary files a/internal/api/web/assets/FileShareManagement-DdhM1YAd.js.gz and /dev/null differ diff --git a/internal/api/web/assets/FolderPickerModal-AtSH6jf4.js.gz b/internal/api/web/assets/FolderPickerModal-AtSH6jf4.js.gz new file mode 100644 index 0000000..de43cce Binary files /dev/null and b/internal/api/web/assets/FolderPickerModal-AtSH6jf4.js.gz differ diff --git a/internal/api/web/assets/FolderPickerModal-BZYCTOdF.css.gz b/internal/api/web/assets/FolderPickerModal-BZYCTOdF.css.gz deleted file mode 100644 index 9191cca..0000000 Binary files a/internal/api/web/assets/FolderPickerModal-BZYCTOdF.css.gz and /dev/null differ diff --git a/internal/api/web/assets/FolderPickerModal-BfMDAHHm.js.gz b/internal/api/web/assets/FolderPickerModal-BfMDAHHm.js.gz deleted file mode 100644 index 0a1936b..0000000 Binary files a/internal/api/web/assets/FolderPickerModal-BfMDAHHm.js.gz and /dev/null differ diff --git a/internal/api/web/assets/FolderPickerModal-VoXCuW38.css.gz b/internal/api/web/assets/FolderPickerModal-VoXCuW38.css.gz new file mode 100644 index 0000000..7d3a042 Binary files /dev/null and b/internal/api/web/assets/FolderPickerModal-VoXCuW38.css.gz differ diff --git a/internal/api/web/assets/FormField-CYjAOp5Z.js.gz b/internal/api/web/assets/FormField-CYjAOp5Z.js.gz new file mode 100644 index 0000000..e09fe8a Binary files /dev/null and b/internal/api/web/assets/FormField-CYjAOp5Z.js.gz differ diff --git a/internal/api/web/assets/FormField-DThvTTx2.js.gz b/internal/api/web/assets/FormField-DThvTTx2.js.gz deleted file mode 100644 index 71b3cd4..0000000 Binary files a/internal/api/web/assets/FormField-DThvTTx2.js.gz and /dev/null differ diff --git a/internal/api/web/assets/FuseManagement-CSsetxcD.js.gz b/internal/api/web/assets/FuseManagement-CSsetxcD.js.gz new file mode 100644 index 0000000..1148399 Binary files /dev/null and b/internal/api/web/assets/FuseManagement-CSsetxcD.js.gz differ diff --git a/internal/api/web/assets/FuseManagement-mQ5ppJG4.js.gz b/internal/api/web/assets/FuseManagement-mQ5ppJG4.js.gz deleted file mode 100644 index 7cc299b..0000000 Binary files a/internal/api/web/assets/FuseManagement-mQ5ppJG4.js.gz and /dev/null differ diff --git a/internal/api/web/assets/ImagePreview-DKVn0Epp.js.gz b/internal/api/web/assets/ImagePreview-DKVn0Epp.js.gz new file mode 100644 index 0000000..fb2765b Binary files /dev/null and b/internal/api/web/assets/ImagePreview-DKVn0Epp.js.gz differ diff --git a/internal/api/web/assets/ImagePreview-KlZp9E3I.js.gz b/internal/api/web/assets/ImagePreview-KlZp9E3I.js.gz deleted file mode 100644 index 2a1d776..0000000 Binary files a/internal/api/web/assets/ImagePreview-KlZp9E3I.js.gz and /dev/null differ diff --git a/internal/api/web/assets/IndexView-D5nZ_ngn.js.gz b/internal/api/web/assets/IndexView-D5nZ_ngn.js.gz deleted file mode 100644 index 6c0608d..0000000 Binary files a/internal/api/web/assets/IndexView-D5nZ_ngn.js.gz and /dev/null differ diff --git a/internal/api/web/assets/IndexView-DBee54zy.js.gz b/internal/api/web/assets/IndexView-DBee54zy.js.gz new file mode 100644 index 0000000..a2ff780 Binary files /dev/null and b/internal/api/web/assets/IndexView-DBee54zy.js.gz differ diff --git a/internal/api/web/assets/LoginView-CTZpn3Is.js.gz b/internal/api/web/assets/LoginView-CTZpn3Is.js.gz deleted file mode 100644 index 22d56a1..0000000 Binary files a/internal/api/web/assets/LoginView-CTZpn3Is.js.gz and /dev/null differ diff --git a/internal/api/web/assets/LoginView-TtGIzSTh.js.gz b/internal/api/web/assets/LoginView-TtGIzSTh.js.gz new file mode 100644 index 0000000..9070d96 Binary files /dev/null and b/internal/api/web/assets/LoginView-TtGIzSTh.js.gz differ diff --git a/internal/api/web/assets/MediaOrganizePanel-DW3y6_xe.js.gz b/internal/api/web/assets/MediaOrganizePanel-DW3y6_xe.js.gz new file mode 100644 index 0000000..3d9ba87 Binary files /dev/null and b/internal/api/web/assets/MediaOrganizePanel-DW3y6_xe.js.gz differ diff --git a/internal/api/web/assets/MediaOrganizePanel-Dz3ymeZK.js.gz b/internal/api/web/assets/MediaOrganizePanel-Dz3ymeZK.js.gz deleted file mode 100644 index fff0809..0000000 Binary files a/internal/api/web/assets/MediaOrganizePanel-Dz3ymeZK.js.gz and /dev/null differ diff --git a/internal/api/web/assets/MediaOrganizeSettings-B2HVCnPD.js.gz b/internal/api/web/assets/MediaOrganizeSettings-B2HVCnPD.js.gz deleted file mode 100644 index c4b4cac..0000000 Binary files a/internal/api/web/assets/MediaOrganizeSettings-B2HVCnPD.js.gz and /dev/null differ diff --git a/internal/api/web/assets/MediaOrganizeSettings-Qdwm1STI.js.gz b/internal/api/web/assets/MediaOrganizeSettings-Qdwm1STI.js.gz new file mode 100644 index 0000000..bbc6ad9 Binary files /dev/null and b/internal/api/web/assets/MediaOrganizeSettings-Qdwm1STI.js.gz differ diff --git a/internal/api/web/assets/PdfPreview-BIcvtt18.js.gz b/internal/api/web/assets/PdfPreview-BIcvtt18.js.gz deleted file mode 100644 index 596078b..0000000 Binary files a/internal/api/web/assets/PdfPreview-BIcvtt18.js.gz and /dev/null differ diff --git a/internal/api/web/assets/PdfPreview-CCVkd-Eb.js.gz b/internal/api/web/assets/PdfPreview-CCVkd-Eb.js.gz new file mode 100644 index 0000000..17621ab Binary files /dev/null and b/internal/api/web/assets/PdfPreview-CCVkd-Eb.js.gz differ diff --git a/internal/api/web/assets/PptxPreview-BWmHTZ2b.js.gz b/internal/api/web/assets/PptxPreview-BWmHTZ2b.js.gz deleted file mode 100644 index af36577..0000000 Binary files a/internal/api/web/assets/PptxPreview-BWmHTZ2b.js.gz and /dev/null differ diff --git a/internal/api/web/assets/PptxPreview-DRRCyDeB.js.gz b/internal/api/web/assets/PptxPreview-DRRCyDeB.js.gz new file mode 100644 index 0000000..e6b17ba Binary files /dev/null and b/internal/api/web/assets/PptxPreview-DRRCyDeB.js.gz differ diff --git a/internal/api/web/assets/PreviewHeader-BQsAcjKh.js.gz b/internal/api/web/assets/PreviewHeader-BQsAcjKh.js.gz new file mode 100644 index 0000000..55d55f1 Binary files /dev/null and b/internal/api/web/assets/PreviewHeader-BQsAcjKh.js.gz differ diff --git a/internal/api/web/assets/PreviewHeader-ntPS0b8e.js.gz b/internal/api/web/assets/PreviewHeader-ntPS0b8e.js.gz deleted file mode 100644 index a51115c..0000000 Binary files a/internal/api/web/assets/PreviewHeader-ntPS0b8e.js.gz and /dev/null differ diff --git a/internal/api/web/assets/PreviewSideNavigation-BdzUW3zn.js.gz b/internal/api/web/assets/PreviewSideNavigation-BdzUW3zn.js.gz new file mode 100644 index 0000000..1268472 Binary files /dev/null and b/internal/api/web/assets/PreviewSideNavigation-BdzUW3zn.js.gz differ diff --git a/internal/api/web/assets/PreviewSideNavigation-CTYIzxLa.js.gz b/internal/api/web/assets/PreviewSideNavigation-CTYIzxLa.js.gz deleted file mode 100644 index bd1c927..0000000 Binary files a/internal/api/web/assets/PreviewSideNavigation-CTYIzxLa.js.gz and /dev/null differ diff --git a/internal/api/web/assets/ProxyToolsPanel-Cyn6Fff5.js.gz b/internal/api/web/assets/ProxyToolsPanel-Cyn6Fff5.js.gz new file mode 100644 index 0000000..dc14eb8 Binary files /dev/null and b/internal/api/web/assets/ProxyToolsPanel-Cyn6Fff5.js.gz differ diff --git a/internal/api/web/assets/ProxyToolsPanel-D0l-iP6R.js.gz b/internal/api/web/assets/ProxyToolsPanel-D0l-iP6R.js.gz deleted file mode 100644 index 4bcb7c3..0000000 Binary files a/internal/api/web/assets/ProxyToolsPanel-D0l-iP6R.js.gz and /dev/null differ diff --git a/internal/api/web/assets/SettingsRow-2s0K_AlY.js.gz b/internal/api/web/assets/SettingsRow-2s0K_AlY.js.gz deleted file mode 100644 index 2c171ae..0000000 Binary files a/internal/api/web/assets/SettingsRow-2s0K_AlY.js.gz and /dev/null differ diff --git a/internal/api/web/assets/SettingsRow-DBnCWn95.js.gz b/internal/api/web/assets/SettingsRow-DBnCWn95.js.gz new file mode 100644 index 0000000..ea4dab8 Binary files /dev/null and b/internal/api/web/assets/SettingsRow-DBnCWn95.js.gz differ diff --git a/internal/api/web/assets/SettingsSegment-BzSxc1R4.js.gz b/internal/api/web/assets/SettingsSegment-BzSxc1R4.js.gz new file mode 100644 index 0000000..feda2d8 Binary files /dev/null and b/internal/api/web/assets/SettingsSegment-BzSxc1R4.js.gz differ diff --git a/internal/api/web/assets/SettingsSegment-D70rgBYK.js.gz b/internal/api/web/assets/SettingsSegment-D70rgBYK.js.gz deleted file mode 100644 index c093802..0000000 Binary files a/internal/api/web/assets/SettingsSegment-D70rgBYK.js.gz and /dev/null differ diff --git a/internal/api/web/assets/SpreadsheetPreview-Cl1vmRLe.js.gz b/internal/api/web/assets/SpreadsheetPreview-Cl1vmRLe.js.gz new file mode 100644 index 0000000..5033445 Binary files /dev/null and b/internal/api/web/assets/SpreadsheetPreview-Cl1vmRLe.js.gz differ diff --git a/internal/api/web/assets/SpreadsheetPreview-Wnx-ZTyp.js.gz b/internal/api/web/assets/SpreadsheetPreview-Wnx-ZTyp.js.gz deleted file mode 100644 index b16b1c7..0000000 Binary files a/internal/api/web/assets/SpreadsheetPreview-Wnx-ZTyp.js.gz and /dev/null differ diff --git a/internal/api/web/assets/StatCard-46CV5pGB.js.gz b/internal/api/web/assets/StatCard-46CV5pGB.js.gz new file mode 100644 index 0000000..8108c17 Binary files /dev/null and b/internal/api/web/assets/StatCard-46CV5pGB.js.gz differ diff --git a/internal/api/web/assets/StatCard-UL9JMXA_.js.gz b/internal/api/web/assets/StatCard-UL9JMXA_.js.gz deleted file mode 100644 index 028bad2..0000000 Binary files a/internal/api/web/assets/StatCard-UL9JMXA_.js.gz and /dev/null differ diff --git a/internal/api/web/assets/StrmScrapePanel-BA7V8Qyq.js.gz b/internal/api/web/assets/StrmScrapePanel-BA7V8Qyq.js.gz new file mode 100644 index 0000000..78e51b6 Binary files /dev/null and b/internal/api/web/assets/StrmScrapePanel-BA7V8Qyq.js.gz differ diff --git a/internal/api/web/assets/StrmScrapePanel-BdRXYblH.js.gz b/internal/api/web/assets/StrmScrapePanel-BdRXYblH.js.gz deleted file mode 100644 index fd34449..0000000 Binary files a/internal/api/web/assets/StrmScrapePanel-BdRXYblH.js.gz and /dev/null differ diff --git a/internal/api/web/assets/StrmScrapeSettings-82UBDSJ1.js.gz b/internal/api/web/assets/StrmScrapeSettings-82UBDSJ1.js.gz deleted file mode 100644 index 1946670..0000000 Binary files a/internal/api/web/assets/StrmScrapeSettings-82UBDSJ1.js.gz and /dev/null differ diff --git a/internal/api/web/assets/StrmScrapeSettings-DB84kvIX.js.gz b/internal/api/web/assets/StrmScrapeSettings-DB84kvIX.js.gz new file mode 100644 index 0000000..2c2189b Binary files /dev/null and b/internal/api/web/assets/StrmScrapeSettings-DB84kvIX.js.gz differ diff --git a/internal/api/web/assets/SystemLogs-DDbXAy8v.js.gz b/internal/api/web/assets/SystemLogs-DDbXAy8v.js.gz new file mode 100644 index 0000000..5c441fa Binary files /dev/null and b/internal/api/web/assets/SystemLogs-DDbXAy8v.js.gz differ diff --git a/internal/api/web/assets/SystemLogs-yIjOk-53.js.gz b/internal/api/web/assets/SystemLogs-yIjOk-53.js.gz deleted file mode 100644 index 7025998..0000000 Binary files a/internal/api/web/assets/SystemLogs-yIjOk-53.js.gz and /dev/null differ diff --git a/internal/api/web/assets/SystemSettings-BOQI2yGp.js.gz b/internal/api/web/assets/SystemSettings-BOQI2yGp.js.gz deleted file mode 100644 index 6d22ec1..0000000 Binary files a/internal/api/web/assets/SystemSettings-BOQI2yGp.js.gz and /dev/null differ diff --git a/internal/api/web/assets/SystemSettings-ror74VFH.js.gz b/internal/api/web/assets/SystemSettings-ror74VFH.js.gz new file mode 100644 index 0000000..4ba5629 Binary files /dev/null and b/internal/api/web/assets/SystemSettings-ror74VFH.js.gz differ diff --git a/internal/api/web/assets/TaskManagement-Ci_no4B_.js.gz b/internal/api/web/assets/TaskManagement-Ci_no4B_.js.gz deleted file mode 100644 index 89dab32..0000000 Binary files a/internal/api/web/assets/TaskManagement-Ci_no4B_.js.gz and /dev/null differ diff --git a/internal/api/web/assets/TaskManagement-xnUL2VDj.js.gz b/internal/api/web/assets/TaskManagement-xnUL2VDj.js.gz new file mode 100644 index 0000000..e9bfb24 Binary files /dev/null and b/internal/api/web/assets/TaskManagement-xnUL2VDj.js.gz differ diff --git a/internal/api/web/assets/TextPreview-B6IppHjx.js.gz b/internal/api/web/assets/TextPreview-B6IppHjx.js.gz deleted file mode 100644 index 3300621..0000000 Binary files a/internal/api/web/assets/TextPreview-B6IppHjx.js.gz and /dev/null differ diff --git a/internal/api/web/assets/TextPreview-Z56u3L3H.js.gz b/internal/api/web/assets/TextPreview-Z56u3L3H.js.gz new file mode 100644 index 0000000..2048c0c Binary files /dev/null and b/internal/api/web/assets/TextPreview-Z56u3L3H.js.gz differ diff --git a/internal/api/web/assets/TimeWheelPicker-DXLi1V1W.js.gz b/internal/api/web/assets/TimeWheelPicker-DXLi1V1W.js.gz new file mode 100644 index 0000000..ac2f9ce Binary files /dev/null and b/internal/api/web/assets/TimeWheelPicker-DXLi1V1W.js.gz differ diff --git a/internal/api/web/assets/TimeWheelPicker-Dpz5a3nY.js.gz b/internal/api/web/assets/TimeWheelPicker-Dpz5a3nY.js.gz deleted file mode 100644 index ee6de4f..0000000 Binary files a/internal/api/web/assets/TimeWheelPicker-Dpz5a3nY.js.gz and /dev/null differ diff --git a/internal/api/web/assets/TmdbHostsHelpTip-BTd0q3lf.js.gz b/internal/api/web/assets/TmdbHostsHelpTip-BTd0q3lf.js.gz deleted file mode 100644 index bf8aa2a..0000000 Binary files a/internal/api/web/assets/TmdbHostsHelpTip-BTd0q3lf.js.gz and /dev/null differ diff --git a/internal/api/web/assets/TmdbHostsHelpTip-B_fctKmT.js.gz b/internal/api/web/assets/TmdbHostsHelpTip-B_fctKmT.js.gz new file mode 100644 index 0000000..e3f1175 Binary files /dev/null and b/internal/api/web/assets/TmdbHostsHelpTip-B_fctKmT.js.gz differ diff --git a/internal/api/web/assets/VideoPreview-C3s75xnw.js.gz b/internal/api/web/assets/VideoPreview-C3s75xnw.js.gz deleted file mode 100644 index f8ba8d2..0000000 Binary files a/internal/api/web/assets/VideoPreview-C3s75xnw.js.gz and /dev/null differ diff --git a/internal/api/web/assets/VideoPreview-Cyyte60w.js.gz b/internal/api/web/assets/VideoPreview-Cyyte60w.js.gz new file mode 100644 index 0000000..b20bed1 Binary files /dev/null and b/internal/api/web/assets/VideoPreview-Cyyte60w.js.gz differ diff --git a/internal/api/web/assets/accounts-Bc4X4_3I.js.gz b/internal/api/web/assets/accounts-Bc4X4_3I.js.gz new file mode 100644 index 0000000..9ea4897 Binary files /dev/null and b/internal/api/web/assets/accounts-Bc4X4_3I.js.gz differ diff --git a/internal/api/web/assets/accounts-BysvblfG.js.gz b/internal/api/web/assets/accounts-BysvblfG.js.gz new file mode 100644 index 0000000..92860d6 Binary files /dev/null and b/internal/api/web/assets/accounts-BysvblfG.js.gz differ diff --git a/internal/api/web/assets/accounts-DASzAw1J.js.gz b/internal/api/web/assets/accounts-DASzAw1J.js.gz deleted file mode 100644 index 3ca9619..0000000 Binary files a/internal/api/web/assets/accounts-DASzAw1J.js.gz and /dev/null differ diff --git a/internal/api/web/assets/accounts-Dgx6lWVt.js.gz b/internal/api/web/assets/accounts-Dgx6lWVt.js.gz deleted file mode 100644 index 3a4377c..0000000 Binary files a/internal/api/web/assets/accounts-Dgx6lWVt.js.gz and /dev/null differ diff --git a/internal/api/web/assets/admin-table-Co9gacdI.js.gz b/internal/api/web/assets/admin-table-Co9gacdI.js.gz new file mode 100644 index 0000000..c2b0219 Binary files /dev/null and b/internal/api/web/assets/admin-table-Co9gacdI.js.gz differ diff --git a/internal/api/web/assets/admin-table-KADlcaAL.js.gz b/internal/api/web/assets/admin-table-KADlcaAL.js.gz deleted file mode 100644 index bb5235d..0000000 Binary files a/internal/api/web/assets/admin-table-KADlcaAL.js.gz and /dev/null differ diff --git a/internal/api/web/assets/cache-BsPfN65I.js.gz b/internal/api/web/assets/cache-BsPfN65I.js.gz deleted file mode 100644 index 1554570..0000000 Binary files a/internal/api/web/assets/cache-BsPfN65I.js.gz and /dev/null differ diff --git a/internal/api/web/assets/cache-WtqL7snR.js.gz b/internal/api/web/assets/cache-WtqL7snR.js.gz new file mode 100644 index 0000000..1f6ac1e Binary files /dev/null and b/internal/api/web/assets/cache-WtqL7snR.js.gz differ diff --git a/internal/api/web/assets/cacheRetention-BdmCyr25.js.gz b/internal/api/web/assets/cacheRetention-BdmCyr25.js.gz deleted file mode 100644 index 1901511..0000000 Binary files a/internal/api/web/assets/cacheRetention-BdmCyr25.js.gz and /dev/null differ diff --git a/internal/api/web/assets/cacheRetention-sRQtcBjT.js.gz b/internal/api/web/assets/cacheRetention-sRQtcBjT.js.gz new file mode 100644 index 0000000..ffbf2e5 Binary files /dev/null and b/internal/api/web/assets/cacheRetention-sRQtcBjT.js.gz differ diff --git a/internal/api/web/assets/cacheSettings-CyIc3gn4.js.gz b/internal/api/web/assets/cacheSettings-CyIc3gn4.js.gz new file mode 100644 index 0000000..78c338d Binary files /dev/null and b/internal/api/web/assets/cacheSettings-CyIc3gn4.js.gz differ diff --git a/internal/api/web/assets/cacheSettings-Oz0uKs9s.js.gz b/internal/api/web/assets/cacheSettings-Oz0uKs9s.js.gz deleted file mode 100644 index 3469e5c..0000000 Binary files a/internal/api/web/assets/cacheSettings-Oz0uKs9s.js.gz and /dev/null differ diff --git a/internal/api/web/assets/crossTransfer-DUpB71Nu.js.gz b/internal/api/web/assets/crossTransfer-DUpB71Nu.js.gz new file mode 100644 index 0000000..7e69b52 Binary files /dev/null and b/internal/api/web/assets/crossTransfer-DUpB71Nu.js.gz differ diff --git a/internal/api/web/assets/crossTransfer-QuXfVCTR.js.gz b/internal/api/web/assets/crossTransfer-QuXfVCTR.js.gz deleted file mode 100644 index 0643271..0000000 Binary files a/internal/api/web/assets/crossTransfer-QuXfVCTR.js.gz and /dev/null differ diff --git a/internal/api/web/assets/dist-B0YeKLwK.js.gz b/internal/api/web/assets/dist-B0YeKLwK.js.gz deleted file mode 100644 index 156258c..0000000 Binary files a/internal/api/web/assets/dist-B0YeKLwK.js.gz and /dev/null differ diff --git a/internal/api/web/assets/dist-BcXAPqe3.js.gz b/internal/api/web/assets/dist-BcXAPqe3.js.gz new file mode 100644 index 0000000..608f002 Binary files /dev/null and b/internal/api/web/assets/dist-BcXAPqe3.js.gz differ diff --git a/internal/api/web/assets/errors-BGjp8ZOs.js.gz b/internal/api/web/assets/errors-BGjp8ZOs.js.gz new file mode 100644 index 0000000..dd76386 Binary files /dev/null and b/internal/api/web/assets/errors-BGjp8ZOs.js.gz differ diff --git a/internal/api/web/assets/errors-BcSvPrsg.js.gz b/internal/api/web/assets/errors-BcSvPrsg.js.gz deleted file mode 100644 index 7832af2..0000000 Binary files a/internal/api/web/assets/errors-BcSvPrsg.js.gz and /dev/null differ diff --git a/internal/api/web/assets/fuse-B2jKcuB4.js.gz b/internal/api/web/assets/fuse-B2jKcuB4.js.gz new file mode 100644 index 0000000..ec8b4cc Binary files /dev/null and b/internal/api/web/assets/fuse-B2jKcuB4.js.gz differ diff --git a/internal/api/web/assets/fuse-B2kRDPbs.js.gz b/internal/api/web/assets/fuse-B2kRDPbs.js.gz deleted file mode 100644 index 57d4c45..0000000 Binary files a/internal/api/web/assets/fuse-B2kRDPbs.js.gz and /dev/null differ diff --git a/internal/api/web/assets/index-BNnYpTY9.js.gz b/internal/api/web/assets/index-BNnYpTY9.js.gz new file mode 100644 index 0000000..1a4c55b Binary files /dev/null and b/internal/api/web/assets/index-BNnYpTY9.js.gz differ diff --git a/internal/api/web/assets/index-CoJrlXwA.js.gz b/internal/api/web/assets/index-CoJrlXwA.js.gz deleted file mode 100644 index 8d3e446..0000000 Binary files a/internal/api/web/assets/index-CoJrlXwA.js.gz and /dev/null differ diff --git a/internal/api/web/assets/logs-CWp5sAC-.js.gz b/internal/api/web/assets/logs-CWp5sAC-.js.gz deleted file mode 100644 index 116148f..0000000 Binary files a/internal/api/web/assets/logs-CWp5sAC-.js.gz and /dev/null differ diff --git a/internal/api/web/assets/logs-akvUt1-d.js.gz b/internal/api/web/assets/logs-akvUt1-d.js.gz new file mode 100644 index 0000000..543b367 Binary files /dev/null and b/internal/api/web/assets/logs-akvUt1-d.js.gz differ diff --git a/internal/api/web/assets/mediaOrganize-CTDqSXKX.js.gz b/internal/api/web/assets/mediaOrganize-CTDqSXKX.js.gz deleted file mode 100644 index 69f67b8..0000000 Binary files a/internal/api/web/assets/mediaOrganize-CTDqSXKX.js.gz and /dev/null differ diff --git a/internal/api/web/assets/mediaOrganize-vHms5zIo.js.gz b/internal/api/web/assets/mediaOrganize-vHms5zIo.js.gz new file mode 100644 index 0000000..18854c5 Binary files /dev/null and b/internal/api/web/assets/mediaOrganize-vHms5zIo.js.gz differ diff --git a/internal/api/web/assets/notifications-Bov2konz.js.gz b/internal/api/web/assets/notifications-Bov2konz.js.gz deleted file mode 100644 index 71db735..0000000 Binary files a/internal/api/web/assets/notifications-Bov2konz.js.gz and /dev/null differ diff --git a/internal/api/web/assets/notifications-CS5IIDPs.js.gz b/internal/api/web/assets/notifications-CS5IIDPs.js.gz new file mode 100644 index 0000000..c1c8166 Binary files /dev/null and b/internal/api/web/assets/notifications-CS5IIDPs.js.gz differ diff --git a/internal/api/web/assets/prod-BMP-Cmm6.js b/internal/api/web/assets/prod-BMP-Cmm6.js new file mode 100644 index 0000000..4762fe4 --- /dev/null +++ b/internal/api/web/assets/prod-BMP-Cmm6.js @@ -0,0 +1 @@ +import{s as e}from"./prod-C0vUTjgG.js";export{e as parseText}; \ No newline at end of file diff --git a/internal/api/web/assets/prod-C0vUTjgG.js.gz b/internal/api/web/assets/prod-C0vUTjgG.js.gz new file mode 100644 index 0000000..c6860fb Binary files /dev/null and b/internal/api/web/assets/prod-C0vUTjgG.js.gz differ diff --git a/internal/api/web/assets/prod-CkW-Soof.js b/internal/api/web/assets/prod-CkW-Soof.js deleted file mode 100644 index 736a9ae..0000000 --- a/internal/api/web/assets/prod-CkW-Soof.js +++ /dev/null @@ -1 +0,0 @@ -import{s as e}from"./prod-DSv8T7Dw.js";export{e as parseText}; \ No newline at end of file diff --git a/internal/api/web/assets/prod-DSv8T7Dw.js.gz b/internal/api/web/assets/prod-DSv8T7Dw.js.gz deleted file mode 100644 index cebbc6e..0000000 Binary files a/internal/api/web/assets/prod-DSv8T7Dw.js.gz and /dev/null differ diff --git a/internal/api/web/assets/srt-parser-BIflpxGb.js.gz b/internal/api/web/assets/srt-parser-BIflpxGb.js.gz deleted file mode 100644 index 253ba4a..0000000 Binary files a/internal/api/web/assets/srt-parser-BIflpxGb.js.gz and /dev/null differ diff --git a/internal/api/web/assets/srt-parser-BPz74Nfq.js.gz b/internal/api/web/assets/srt-parser-BPz74Nfq.js.gz new file mode 100644 index 0000000..04ecd55 Binary files /dev/null and b/internal/api/web/assets/srt-parser-BPz74Nfq.js.gz differ diff --git a/internal/api/web/assets/ssa-parser-BknjNj4t.js.gz b/internal/api/web/assets/ssa-parser-BknjNj4t.js.gz new file mode 100644 index 0000000..d8ab6d6 Binary files /dev/null and b/internal/api/web/assets/ssa-parser-BknjNj4t.js.gz differ diff --git a/internal/api/web/assets/ssa-parser-CkgBnVHE.js.gz b/internal/api/web/assets/ssa-parser-CkgBnVHE.js.gz deleted file mode 100644 index b3eec54..0000000 Binary files a/internal/api/web/assets/ssa-parser-CkgBnVHE.js.gz and /dev/null differ diff --git a/internal/api/web/assets/strm-Cgnpuc3g.js.gz b/internal/api/web/assets/strm-Cgnpuc3g.js.gz deleted file mode 100644 index 40b8b41..0000000 Binary files a/internal/api/web/assets/strm-Cgnpuc3g.js.gz and /dev/null differ diff --git a/internal/api/web/assets/strm-CyzEz8iA.js.gz b/internal/api/web/assets/strm-CyzEz8iA.js.gz new file mode 100644 index 0000000..c024833 Binary files /dev/null and b/internal/api/web/assets/strm-CyzEz8iA.js.gz differ diff --git a/internal/api/web/assets/strmScrape-Bqg9Wzy_.js.gz b/internal/api/web/assets/strmScrape-Bqg9Wzy_.js.gz deleted file mode 100644 index 097fb2a..0000000 Binary files a/internal/api/web/assets/strmScrape-Bqg9Wzy_.js.gz and /dev/null differ diff --git a/internal/api/web/assets/strmScrape-DrZBbcnr.js.gz b/internal/api/web/assets/strmScrape-DrZBbcnr.js.gz new file mode 100644 index 0000000..1928093 Binary files /dev/null and b/internal/api/web/assets/strmScrape-DrZBbcnr.js.gz differ diff --git a/internal/api/web/assets/useSectionTabRoute-D7iQYbF1.js.gz b/internal/api/web/assets/useSectionTabRoute-D7iQYbF1.js.gz deleted file mode 100644 index b864e6c..0000000 Binary files a/internal/api/web/assets/useSectionTabRoute-D7iQYbF1.js.gz and /dev/null differ diff --git a/internal/api/web/assets/useSectionTabRoute-DSgoyQet.js.gz b/internal/api/web/assets/useSectionTabRoute-DSgoyQet.js.gz new file mode 100644 index 0000000..d2de188 Binary files /dev/null and b/internal/api/web/assets/useSectionTabRoute-DSgoyQet.js.gz differ diff --git a/internal/api/web/assets/useSettingsLoad-BriyxNsT.js.gz b/internal/api/web/assets/useSettingsLoad-BriyxNsT.js.gz new file mode 100644 index 0000000..0da0223 Binary files /dev/null and b/internal/api/web/assets/useSettingsLoad-BriyxNsT.js.gz differ diff --git a/internal/api/web/assets/useSettingsLoad-Cpqpwp4y.js.gz b/internal/api/web/assets/useSettingsLoad-Cpqpwp4y.js.gz deleted file mode 100644 index cc021ad..0000000 Binary files a/internal/api/web/assets/useSettingsLoad-Cpqpwp4y.js.gz and /dev/null differ diff --git a/internal/api/web/assets/useSettingsPageDirty-DjCJ_zr4.js.gz b/internal/api/web/assets/useSettingsPageDirty-DjCJ_zr4.js.gz deleted file mode 100644 index 868d536..0000000 Binary files a/internal/api/web/assets/useSettingsPageDirty-DjCJ_zr4.js.gz and /dev/null differ diff --git a/internal/api/web/assets/useSettingsPageDirty-l_4f42zA.js.gz b/internal/api/web/assets/useSettingsPageDirty-l_4f42zA.js.gz new file mode 100644 index 0000000..d7fa710 Binary files /dev/null and b/internal/api/web/assets/useSettingsPageDirty-l_4f42zA.js.gz differ diff --git a/internal/api/web/assets/useStartupCountdown-DXT5LZts.js.gz b/internal/api/web/assets/useStartupCountdown-DXT5LZts.js.gz new file mode 100644 index 0000000..4a851a5 Binary files /dev/null and b/internal/api/web/assets/useStartupCountdown-DXT5LZts.js.gz differ diff --git a/internal/api/web/assets/useStartupCountdown-VhLeimRt.js.gz b/internal/api/web/assets/useStartupCountdown-VhLeimRt.js.gz deleted file mode 100644 index ad4c9e2..0000000 Binary files a/internal/api/web/assets/useStartupCountdown-VhLeimRt.js.gz and /dev/null differ diff --git a/internal/api/web/assets/useUnsavedChanges-BdG9V4zD.js.gz b/internal/api/web/assets/useUnsavedChanges-BdG9V4zD.js.gz new file mode 100644 index 0000000..e649f71 Binary files /dev/null and b/internal/api/web/assets/useUnsavedChanges-BdG9V4zD.js.gz differ diff --git a/internal/api/web/assets/useUnsavedChanges-xcx9Yz0C.js.gz b/internal/api/web/assets/useUnsavedChanges-xcx9Yz0C.js.gz deleted file mode 100644 index 74d0393..0000000 Binary files a/internal/api/web/assets/useUnsavedChanges-xcx9Yz0C.js.gz and /dev/null differ diff --git a/internal/api/web/assets/version-BAvyI_sm.js.gz b/internal/api/web/assets/version-BAvyI_sm.js.gz deleted file mode 100644 index 6f543f8..0000000 Binary files a/internal/api/web/assets/version-BAvyI_sm.js.gz and /dev/null differ diff --git a/internal/api/web/assets/version-Bcxv37jt.js.gz b/internal/api/web/assets/version-Bcxv37jt.js.gz new file mode 100644 index 0000000..1a376a8 Binary files /dev/null and b/internal/api/web/assets/version-Bcxv37jt.js.gz differ diff --git a/internal/api/web/index.html b/internal/api/web/index.html index 54383d8..9515615 100644 --- a/internal/api/web/index.html +++ b/internal/api/web/index.html @@ -5,7 +5,7 @@ LitePan - + diff --git a/internal/crosstransfer/cleanup_test.go b/internal/crosstransfer/cleanup_test.go index acc7228..a260e97 100644 --- a/internal/crosstransfer/cleanup_test.go +++ b/internal/crosstransfer/cleanup_test.go @@ -9,6 +9,7 @@ import ( "time" "litepan/internal/core/driverexec" + "litepan/internal/cache" "litepan/internal/domain" "litepan/internal/driver" "litepan/internal/file" @@ -123,6 +124,44 @@ func TestProbeFallsBackToTemporaryRapidUpload(t *testing.T) { } } +func TestExecuteRapidUploadInvalidatesTargetDirCache(t *testing.T) { + drv := &rapidHitDriver{cleanupDriver: newCleanupDriver()} + exec := driverexec.New(cleanupProvider{drv: drv}, nil) + cacheSvc := cache.NewService(cache.Options{MaxItems: 32}) + t.Cleanup(cacheSvc.Close) + files := file.NewService(exec, cacheSvc, nil, nil, nil, nil) + service := New(Options{Exec: exec, Files: files, DataDir: t.TempDir()}) + + // 先把空目录缓存住,模拟前端进入目标目录后再执行秒传。 + items, err := files.List(context.Background(), 1, "root", false) + if err != nil || len(items) != 0 { + t.Fatalf("预热目标目录缓存失败 items=%#v err=%v", items, err) + } + + err = service.ExecuteStream(context.Background(), ExecuteInput{ + TargetAccountID: 1, + TargetParentID: "root", + MethodID: "md5", + Files: []TransferFile{{ + RelPath: "hit.bin", + Name: "hit.bin", + Size: 1, + Hash: "11111111111111111111111111111111", + }}, + }, func(StreamEvent) error { return nil }) + if err != nil { + t.Fatalf("秒传执行失败: %v", err) + } + + items, err = files.List(context.Background(), 1, "root", false) + if err != nil { + t.Fatalf("秒传后列目录失败: %v", err) + } + if len(items) != 1 || items[0].Name != "hit.bin" { + t.Fatalf("秒传成功后应直接看到新文件,items=%#v", items) + } +} + func TestScanSourceDeepTreeDoesNotDeadlock(t *testing.T) { drv := newCleanupDriver() parentID := "" @@ -203,6 +242,68 @@ func TestScanSourceMarksIncompleteResult(t *testing.T) { } } +func TestScanSourcesMergesRootsAndRemovesNestedSelection(t *testing.T) { + drv := newCleanupDriver() + hash := map[domain.HashType]string{domain.HashMD5: "11111111111111111111111111111111"} + drv.children["movies"] = []domain.FileItem{ + {ID: "movie-file", Name: "movie.mkv", Size: 1, Hash: hash}, + {ID: "season", Name: "第一季", IsDir: true}, + } + drv.children["season"] = []domain.FileItem{ + {ID: "episode-file", Name: "S01E01.mkv", Size: 1, Hash: hash}, + } + drv.children["music"] = []domain.FileItem{ + {ID: "music-file", Name: "song.flac", Size: 1, Hash: hash}, + } + service := newCleanupService(t, drv) + + result, err := service.ScanSources(context.Background(), 1, []ScanRoot{ + {ParentID: "movies", DisplayPath: "/媒体/电影", AncestorIDs: []string{"media"}}, + {ParentID: "season", DisplayPath: "/媒体/电影/第一季"}, + {ParentID: "music", DisplayPath: "/媒体/音乐", AncestorIDs: []string{"media"}}, + }, "md5") + if err != nil { + t.Fatalf("多目录扫描失败: %v", err) + } + if result.Total != 3 || result.Directories != 3 || len(result.Tree) != 2 { + t.Fatalf("多目录合并结果不正确: %#v", result) + } + paths := make(map[string]struct{}, len(result.Files)) + for _, item := range result.Files { + paths[item.RelPath] = struct{}{} + } + for _, path := range []string{"电影/movie.mkv", "电影/第一季/S01E01.mkv", "音乐/song.flac"} { + if _, ok := paths[path]; !ok { + t.Fatalf("缺少合并后的文件路径 %q,得到 %#v", path, result.Files) + } + } +} + +func TestScanSourcesAllowsDriverRootID(t *testing.T) { + drv := newCleanupDriver() + drv.children["-11"] = []domain.FileItem{{ + ID: "file", Name: "root.mkv", Size: 1, + Hash: map[domain.HashType]string{domain.HashMD5: "11111111111111111111111111111111"}, + }} + result, err := newCleanupService(t, drv).ScanSources( + context.Background(), 1, []ScanRoot{{ParentID: "-11", DisplayPath: "/"}}, "md5", + ) + if err != nil || result.Total != 1 || result.Files[0].RelPath != "root.mkv" { + t.Fatalf("驱动根目录扫描不正确: result=%#v err=%v", result, err) + } +} + +func TestScanSourcesRejectsDuplicateRootNames(t *testing.T) { + service := newCleanupService(t, newCleanupDriver()) + _, err := service.ScanSources(context.Background(), 1, []ScanRoot{ + {ParentID: "a", DisplayPath: "/A/电影"}, + {ParentID: "b", DisplayPath: "/B/电影"}, + }, "md5") + if err == nil || !strings.Contains(err.Error(), "同名文件夹") { + t.Fatalf("同名根目录应拒绝合并,得到 %v", err) + } +} + func newCleanupService(t *testing.T, drv driver.Driver) *Service { t.Helper() exec := driverexec.New(cleanupProvider{drv: drv}, nil) @@ -311,3 +412,17 @@ func (d *rapidOnlyDriver) RapidUploadByHash(context.Context, driver.RapidUploadR d.uploadCalls++ return &driver.RapidUploadResult{Reuse: false}, nil } + +type rapidHitDriver struct { + *cleanupDriver + uploadCalls int +} + +func (d *rapidHitDriver) RapidUploadByHash(_ context.Context, req driver.RapidUploadRequest) (*driver.RapidUploadResult, error) { + d.uploadCalls++ + fileID := fmt.Sprintf("file-%d", d.uploadCalls) + item := domain.FileItem{ID: fileID, Name: req.FileName, Size: req.Size} + d.children[req.ParentID] = append(d.children[req.ParentID], item) + d.parents[fileID] = req.ParentID + return &driver.RapidUploadResult{Reuse: true, FileID: fileID}, nil +} diff --git a/internal/crosstransfer/service.go b/internal/crosstransfer/service.go index 9d022c4..8202b51 100644 --- a/internal/crosstransfer/service.go +++ b/internal/crosstransfer/service.go @@ -79,6 +79,12 @@ type ScanResult struct { Files []ScanFile `json:"files"` } +type ScanRoot struct { + ParentID string `json:"parent_id"` + DisplayPath string `json:"display_path"` + AncestorIDs []string `json:"ancestor_ids,omitempty"` +} + type TransferFile struct { SourceFileID string `json:"source_file_id"` RelPath string `json:"rel_path"` @@ -118,7 +124,14 @@ func sourceRootPrefix(displayPath string) string { } func (s *Service) ScanSource(ctx context.Context, sourceAccountID int64, sourceParentID, methodID, sourceDisplayPath string) (*ScanResult, error) { - return s.scanSource(ctx, sourceAccountID, sourceParentID, methodID, sourceDisplayPath, nil) + return s.ScanSources(ctx, sourceAccountID, []ScanRoot{{ + ParentID: sourceParentID, + DisplayPath: sourceDisplayPath, + }}, methodID) +} + +func (s *Service) ScanSources(ctx context.Context, sourceAccountID int64, roots []ScanRoot, methodID string) (*ScanResult, error) { + return s.scanSources(ctx, sourceAccountID, roots, methodID, nil) } func (s *Service) ScanSourceStream( @@ -126,11 +139,24 @@ func (s *Service) ScanSourceStream( sourceAccountID int64, sourceParentID, methodID, sourceDisplayPath string, emit func(StreamEvent) error, +) error { + return s.ScanSourcesStream(ctx, sourceAccountID, []ScanRoot{{ + ParentID: sourceParentID, + DisplayPath: sourceDisplayPath, + }}, methodID, emit) +} + +func (s *Service) ScanSourcesStream( + ctx context.Context, + sourceAccountID int64, + roots []ScanRoot, + methodID string, + emit func(StreamEvent) error, ) error { if err := emit(StreamEvent{"event": "start", "max_files": maxScanFiles}); err != nil { return err } - result, err := s.scanSource(ctx, sourceAccountID, sourceParentID, methodID, sourceDisplayPath, func(p scanProgress) error { + result, err := s.scanSources(ctx, sourceAccountID, roots, methodID, func(p scanProgress) error { return emit(StreamEvent{ "event": "progress", "directories": p.directories, @@ -171,19 +197,105 @@ type scanDirResult struct { err error } -func (s *Service) scanSource( +func normalizeScanRoots(roots []ScanRoot) ([]ScanRoot, error) { + if len(roots) == 0 { + return nil, domain.Errorf(domain.CodeValidation, "请选择源目录") + } + if len(roots) > 100 { + return nil, domain.Errorf(domain.CodeValidation, "一次最多选择 100 个源目录") + } + + normalized := make([]ScanRoot, 0, len(roots)) + seenIDs := make(map[string]struct{}, len(roots)) + seenPaths := make(map[string]struct{}, len(roots)) + for _, root := range roots { + root.ParentID = strings.TrimSpace(root.ParentID) + root.DisplayPath = "/" + strings.Trim(strings.TrimSpace(root.DisplayPath), "/") + for i, ancestorID := range root.AncestorIDs { + root.AncestorIDs[i] = strings.TrimSpace(ancestorID) + } + pathKey := strings.ToLower(root.DisplayPath) + if _, ok := seenPaths[pathKey]; ok { + continue + } + if root.ParentID != "" { + if _, ok := seenIDs[root.ParentID]; ok { + continue + } + seenIDs[root.ParentID] = struct{}{} + } + seenPaths[pathKey] = struct{}{} + normalized = append(normalized, root) + } + + out := make([]ScanRoot, 0, len(normalized)) + for i, root := range normalized { + nested := false + for j, parent := range normalized { + if i == j { + continue + } + for _, ancestorID := range root.AncestorIDs { + if parent.ParentID != "" && ancestorID == parent.ParentID { + nested = true + break + } + } + if nested { + break + } + if parent.DisplayPath == "/" || strings.HasPrefix(root.DisplayPath, parent.DisplayPath+"/") { + nested = true + break + } + } + if !nested { + out = append(out, root) + } + } + if len(out) == 0 { + return nil, domain.Errorf(domain.CodeValidation, "请选择源目录") + } + + names := make(map[string]struct{}, len(out)) + for _, root := range out { + name := strings.TrimSuffix(sourceRootPrefix(root.DisplayPath), "/") + if len(out) > 1 && name == "" { + return nil, domain.Errorf(domain.CodeValidation, "根目录不能与其他目录同时选择") + } + key := strings.ToLower(name) + if _, ok := names[key]; ok { + return nil, domain.Errorf(domain.CodeValidation, "所选源目录存在同名文件夹 %q,请分批传输", name) + } + names[key] = struct{}{} + } + return out, nil +} + +func (s *Service) scanSources( ctx context.Context, sourceAccountID int64, - sourceParentID, methodID, sourceDisplayPath string, + roots []ScanRoot, + methodID string, progress func(scanProgress) error, ) (*ScanResult, error) { if _, ok := GetMethod(methodID); !ok { return nil, domain.Errorf(domain.CodeValidation, "未知的秒传方法: %s", methodID) } - rootPrefix := sourceRootPrefix(sourceDisplayPath) + roots, err := normalizeScanRoots(roots) + if err != nil { + return nil, err + } acc := &scanAccumulator{files: make([]scanFileRec, 0, 256)} - root := &scanDirNode{id: sourceParentID, relPrefix: rootPrefix} - queue := []*scanDirNode{root} + rootNodes := make([]*scanDirNode, 0, len(roots)) + for _, source := range roots { + rootNodes = append(rootNodes, &scanDirNode{ + id: source.ParentID, + name: strings.TrimSuffix(sourceRootPrefix(source.DisplayPath), "/"), + relPrefix: sourceRootPrefix(source.DisplayPath), + }) + } + queue := append([]*scanDirNode(nil), rootNodes...) for len(queue) > 0 && acc.truncatedReason == "" { if err := ctx.Err(); err != nil { @@ -269,7 +381,7 @@ func (s *Service) scanSource( } } - tree := buildScanTree(root) + tree := buildScanRootsTree(rootNodes) outFiles := make([]ScanFile, 0, len(acc.files)) for _, f := range acc.files { outFiles = append(outFiles, ScanFile{ @@ -357,6 +469,22 @@ func buildScanTree(node *scanDirNode) []ScanTreeNode { return out } +func buildScanRootsTree(roots []*scanDirNode) []ScanTreeNode { + if len(roots) == 1 { + return buildScanTree(roots[0]) + } + out := make([]ScanTreeNode, 0, len(roots)) + for _, root := range roots { + out = append(out, ScanTreeNode{ + Type: "dir", + ID: root.id, + Name: root.name, + Children: buildScanTree(root), + }) + } + return out +} + func flattenTreeFilePaths(nodes []ScanTreeNode) []string { out := make([]string, 0, 64) var walk func([]ScanTreeNode) @@ -696,6 +824,9 @@ func (s *Service) executeTransferFile(ctx context.Context, in executeFileInput) return transferItemResult(base, false, "error", "", rapidErr) } if reuse { + if s.files != nil { + s.files.NotifyCreated(ctx, in.targetAccountID, folderID, fileID, f.Name, f.Size, false) + } return transferItemResult(base, true, "rapid", fileID, "") } diff --git a/internal/file/service.go b/internal/file/service.go index 56d97da..6745d9c 100644 --- a/internal/file/service.go +++ b/internal/file/service.go @@ -326,6 +326,20 @@ func (s *Service) UploadLocal(ctx context.Context, accountID int64, req driver.L return result, nil } +// NotifyCreated 用于绕过 file.Service 写路径完成创建时,补发标准写后失效事件。 +// 例如跨盘秒传命中后由驱动直接落文件,需要调用此方法清理目标目录缓存。 +func (s *Service) NotifyCreated(ctx context.Context, accountID int64, parentID, fileID, fileName string, fileSize int64, isDir bool) { + s.publishMutation(ctx, eventbus.FileMutated{ + AccountID: accountID, + Op: "create", + ParentID: parentID, + FileID: fileID, + FileName: fileName, + FileSize: fileSize, + IsDir: isDir, + }) +} + func (s *Service) recordListHit(hit bool) { if s.listHits == nil { return diff --git a/web/src/api/crossTransfer.ts b/web/src/api/crossTransfer.ts index 1e7cd87..ed914b4 100644 --- a/web/src/api/crossTransfer.ts +++ b/web/src/api/crossTransfer.ts @@ -37,6 +37,20 @@ export interface CrossTransferScanResult { files: CrossTransferScanFile[]; } +export interface CrossTransferScanSource { + parent_id: string; + display_path: string; + ancestor_ids?: string[]; +} + +export interface CrossTransferScanRequest { + source_account_id: number; + method: string; + sources?: CrossTransferScanSource[]; + source_parent_id?: string; + source_display_path?: string; +} + export interface CrossTransferRelayTask { task_id: string; source_account_id: number; @@ -72,22 +86,12 @@ export function listCrossTransferRoutes() { return http.get("/cross-transfer/routes"); } -export function scanCrossTransferSource(body: { - source_account_id: number; - source_parent_id: string; - method: string; - source_display_path?: string; -}) { +export function scanCrossTransferSource(body: CrossTransferScanRequest) { return http.post("/cross-transfer/scan", body); } export function scanCrossTransferSourceStream( - body: { - source_account_id: number; - source_parent_id: string; - method: string; - source_display_path?: string; - }, + body: CrossTransferScanRequest, signal?: AbortSignal, ) { return streamCrossTransferNDJSON>("/cross-transfer/scan/stream", body, signal); diff --git a/web/src/components/admin/CrossDriveTransfer.vue b/web/src/components/admin/CrossDriveTransfer.vue index 3ad4026..82b0e26 100644 --- a/web/src/components/admin/CrossDriveTransfer.vue +++ b/web/src/components/admin/CrossDriveTransfer.vue @@ -125,7 +125,7 @@
@@ -330,7 +330,10 @@ :accounts="pickerAccounts" :account-id="pickerInitialAccountId" :initial-path="pickerInitialPath" + :multi-select="pickerMode === 'src'" + :initial-selections="pickerMode === 'src' ? pickerInitialSelections : []" :title="pickerMode === 'src' ? '选择源目录' : '选择目标目录'" + :confirm-text="pickerMode === 'src' ? '确认选择' : '选择当前目录'" show-refresh @close="pickerOpen = false" @resolve="onPickerResolve" @@ -408,6 +411,7 @@ const pickerMode = ref('src') const pickerAccounts = ref([]) const pickerInitialAccountId = ref(null) const pickerInitialPath = ref('') +const pickerInitialSelections = ref([]) const relayTasksHref = computed(() => ( router.resolve({ path: '/', query: { taskPanel: 'relay' } }).href @@ -418,6 +422,15 @@ const showFooterScrollTips = computed(() => !showProgressBar.value && !relayNoti const currentFooterTip = computed(() => footerScrollTips[footerTipIndex.value] || footerScrollTips[0]) const isScanPhase = computed(() => phaseStatus.value.includes('扫描')) const isBaiduMd5Route = computed(() => curRoute.value?.method === 'md5' && srcDriver.value === 'baidu_open') +const sourceRoots = computed(() => Array.isArray(src.value?.sources) ? src.value.sources : []) +const srcPickerText = computed(() => { + if (!src.value) return '选择账号 · 目录' + if (sourceRoots.value.length === 1) return `${src.value.accName} · ${sourceRoots.value[0].path}` + return `${src.value.accName} · 已选 ${sourceRoots.value.length} 个目录` +}) +const srcPickerTitle = computed(() => ( + src.value ? sourceRoots.value.map(item => item.path).join('\n') : '' +)) const accounts = ref([]) const srcTreeRef = ref(null) @@ -687,26 +700,52 @@ async function openPicker(mode) { pickerMode.value = mode; pickerAccounts.value = accs; pickerInitialAccountId.value = Number(cur?.accId || accs[0]?.id || 0) || null; - pickerInitialPath.value = cur?.path || ""; + pickerInitialPath.value = mode === 'src' + ? (cur?.sources?.length === 1 ? cur.sources[0].path : '') + : (cur?.path || ''); + pickerInitialSelections.value = mode === 'src' + ? (cur?.sources || []).map(item => ({ + id: item.parentId, + name: item.name, + path: item.path, + ancestorIds: [...(item.ancestorIds || [])], + })) + : []; pickerOpen.value = true; } function onPickerResolve(payload) { pickerOpen.value = false; - const sel = { - accId: payload.accountId, - accName: payload.accountName, - parentId: payload.parentId, - path: payload.path, - }; if (pickerMode.value === "src") { - src.value = sel; + const selected = Array.isArray(payload.selections) && payload.selections.length + ? payload.selections + : [{ + id: payload.parentId, + name: payload.path.split('/').filter(Boolean).pop() || '根目录', + path: payload.path, + ancestorIds: [], + }] + src.value = { + accId: payload.accountId, + accName: payload.accountName, + sources: selected.map(item => ({ + parentId: item.id, + name: item.name, + path: item.path || '/', + ancestorIds: [...(item.ancestorIds || [])], + })), + }; srcTree.value = null; probeFiles.value = []; scanSummary.value = null; scanLimitReason.value = ''; } else { - dst.value = sel; + dst.value = { + accId: payload.accountId, + accName: payload.accountName, + parentId: payload.parentId, + path: payload.path, + }; } dstTree.value = null; resetMetrics(); @@ -726,8 +765,11 @@ async function scanSource(clearTree = true) { try { for await (const msg of scanCrossTransferSourceStream({ source_account_id: Number(src.value.accId), - source_parent_id: src.value.parentId, - source_display_path: src.value.path || "", + sources: sourceRoots.value.map(item => ({ + parent_id: item.parentId, + display_path: item.path || '/', + ancestor_ids: item.ancestorIds || [], + })), method: curRoute.value.method, }, abortCtrl.value?.signal)) { if (msg.event === 'progress') { diff --git a/web/src/components/file/FolderPickerModal.vue b/web/src/components/file/FolderPickerModal.vue index 678e466..2191d2e 100644 --- a/web/src/components/file/FolderPickerModal.vue +++ b/web/src/components/file/FolderPickerModal.vue @@ -5,6 +5,7 @@ import type { Crumb } from "@/stores/browser"; import AppModal from "@/components/base/AppModal.vue"; import DriverIcon from "@/components/driver/DriverIcon.vue"; import FolderSelector from "./FolderSelector.vue"; +import type { FolderSelection } from "./FolderSelector.vue"; const props = withDefaults( defineProps<{ @@ -21,8 +22,9 @@ const props = withDefaults( // 初始定位面包屑,仅对初始 accountId 生效。 initialBreadcrumb?: Crumb[]; initialPath?: string; - // 锁定浏览根为任务目录,不可回到更上层。 rootAnchor?: { parentId: string; path: string; label?: string }; + multiSelect?: boolean; + initialSelections?: FolderSelection[]; }>(), { title: "选择目录", @@ -36,15 +38,24 @@ const props = withDefaults( initialBreadcrumb: () => [], initialPath: "", rootAnchor: undefined, + multiSelect: false, + initialSelections: () => [], }, ); const emit = defineEmits<{ close: []; - resolve: [payload: { accountId: number; accountName: string; parentId: string; path: string }]; + resolve: [payload: { + accountId: number; + accountName: string; + parentId: string; + path: string; + selections?: FolderSelection[]; + }]; }>(); const selAccount = ref(props.accountId); +const selectedItems = ref([]); function initAccount() { if (props.accountId != null) { @@ -54,10 +65,22 @@ function initAccount() { selAccount.value = props.accounts[0]?.id ?? null; } +function initSelections() { + selectedItems.value = props.multiSelect + ? props.initialSelections.map((item) => ({ + ...item, + ancestorIds: [...(item.ancestorIds ?? [])], + })) + : []; +} + watch( () => props.open, (open) => { - if (open) initAccount(); + if (open) { + initAccount(); + initSelections(); + } }, { immediate: true }, ); @@ -70,7 +93,18 @@ function accountDriverLabel(account: Account) { return account.driver_card_name?.trim() || account.driver_type; } -function onResolve(folder: { accountId: number; parentId: string; path: string }) { +function selectAccount(accountId: number) { + if (selAccount.value === accountId) return; + selAccount.value = accountId; + selectedItems.value = []; +} + +function onResolve(folder: { + accountId: number; + parentId: string; + path: string; + selections?: FolderSelection[]; +}) { emit("resolve", { ...folder, accountName: accountName.value }); } @@ -90,7 +124,7 @@ function onResolve(folder: { accountId: number; parentId: string; path: string } type="button" class="folder-picker__account" :class="{ active: a.id === selAccount }" - @click="selAccount = a.id" + @click="selectAccount(a.id)" > diff --git a/web/src/components/file/FolderSelector.vue b/web/src/components/file/FolderSelector.vue index eba3de2..4ca035f 100644 --- a/web/src/components/file/FolderSelector.vue +++ b/web/src/components/file/FolderSelector.vue @@ -16,7 +16,12 @@ import "@/styles/file-list.css"; type FolderSortKey = Extract; type FolderLoader = (parentId: string, opts?: { forceRefresh?: boolean }) => Promise; -export type FolderSelection = { id: string; name: string }; +export type FolderSelection = { + id: string; + name: string; + path?: string; + ancestorIds?: string[]; +}; const ROOT: Crumb = { id: "", name: "根目录" }; @@ -140,33 +145,56 @@ function normalizeSelectPath(id: string) { return String(id || "").replace(/^\/+|\/+$/g, ""); } -function isAncestorPath(ancestor: string, child: string) { - const a = normalizeSelectPath(ancestor); - const c = normalizeSelectPath(child); - if (!a || !c || a === c) return false; - return c.startsWith(`${a}/`); +function normalizedSelectionPath(path?: string) { + return `/${String(path || "").split("/").filter(Boolean).join("/")}`; } -function selectionState(id: string): SelectState { - const key = normalizeSelectPath(id); - if (!key && !id) { - // 根目录自身一般不出现在行内;无 key 时只看是否有任意已选 - return activeSelections.value.length ? "partial" : "none"; - } +const currentAncestorIds = computed(() => + breadcrumb.value.map((item) => String(item.id || "")).filter(Boolean), +); + +function selectionForDir(dir: FileItem): FolderSelection { + const base = currentPath.value === "/" ? "" : currentPath.value; + return { + id: String(dir.id), + name: dir.name || String(dir.id), + path: normalizedSelectionPath(`${base}/${dir.name || ""}`), + ancestorIds: [...currentAncestorIds.value], + }; +} + +function isAncestorSelection(ancestor: FolderSelection, child: FolderSelection) { + if (child.ancestorIds?.some((id) => String(id) === String(ancestor.id))) return true; + const ancestorPath = normalizedSelectionPath(ancestor.path); + const childPath = normalizedSelectionPath(child.path); + return ancestorPath !== "/" + && childPath !== "/" + && ancestorPath !== childPath + && childPath.startsWith(`${ancestorPath}/`); +} + +function selectionState(dir: FileItem): SelectState { + const item = selectionForDir(dir); const items = activeSelections.value; - if (items.some((item) => normalizeSelectPath(item.id) === key)) return "checked"; - if (items.some((item) => isAncestorPath(item.id, key))) return "covered"; - if (items.some((item) => isAncestorPath(key, item.id))) return "partial"; + if (items.some((selected) => String(selected.id) === item.id)) return "checked"; + if (items.some((selected) => isAncestorSelection(selected, item))) return "covered"; + if (items.some((selected) => isAncestorSelection(item, selected))) return "partial"; return "none"; } -function isSelected(id: string) { - const state = selectionState(id); +function isSelected(dir: FileItem) { + const state = selectionState(dir); return state === "checked" || state === "covered"; } -function isPartialSelected(id: string) { - return selectionState(id) === "partial"; +function isPartialSelected(dir: FileItem) { + return selectionState(dir) === "partial"; +} + +function selectionKey(item: FolderSelection) { + const id = normalizeSelectPath(item.id); + if (id) return id; + return normalizedSelectionPath(item.path); } function commitSelections(next: FolderSelection[]) { @@ -175,42 +203,31 @@ function commitSelections(next: FolderSelection[]) { return; } const map: Record = {}; - for (const item of next) map[item.id] = item; + for (const item of next) map[selectionKey(item)] = item; selectedMap.value = map; } -/** 勾选/取消时父子互斥:选父清子孙,选子清祖先。 */ -function withPathExclusive(current: FolderSelection[], item: FolderSelection, selected: boolean) { - const id = normalizeSelectPath(item.id); +function withHierarchyExclusive(current: FolderSelection[], item: FolderSelection, selected: boolean) { const next = current.filter((entry) => { - const entryId = normalizeSelectPath(entry.id); - if (entryId === id) return false; - if (isAncestorPath(entryId, id) || isAncestorPath(id, entryId)) return false; + if (String(entry.id) === String(item.id)) return false; + if (isAncestorSelection(entry, item) || isAncestorSelection(item, entry)) return false; return true; }); - if (selected) next.push({ id: item.id, name: item.name || item.id }); + if (selected) next.push(item); return next; } function toggleSelect(dir: FileItem, checked?: boolean) { if (!props.multiSelect) return; - const id = String(dir.id); - const state = selectionState(id); - if (state === "covered") { - // 取消覆盖它的上级全选 - const key = normalizeSelectPath(id); - commitSelections( - activeSelections.value.filter((item) => !isAncestorPath(item.id, key)), - ); - return; - } + const state = selectionState(dir); + if (state === "covered") return; + const item = selectionForDir(dir); if (state === "partial") { - // 半选 → 全选当前目录(吸收已选子孙) - commitSelections(withPathExclusive(activeSelections.value, { id, name: dir.name || id }, true)); + commitSelections(withHierarchyExclusive(activeSelections.value, item, true)); return; } const selected = checked ?? state !== "checked"; - commitSelections(withPathExclusive(activeSelections.value, { id, name: dir.name || id }, selected)); + commitSelections(withHierarchyExclusive(activeSelections.value, item, selected)); } async function listDirs(parentId: string, opts?: { forceRefresh?: boolean }): Promise { @@ -290,7 +307,7 @@ const headerSelectState = computed(() => { let checkedOrCovered = 0; let partial = 0; for (const dir of sortedDirs.value) { - const state = selectionState(String(dir.id)); + const state = selectionState(dir); if (state === "checked" || state === "covered") checkedOrCovered += 1; else if (state === "partial") partial += 1; } @@ -304,13 +321,12 @@ function toggleSelectAllVisible() { if (headerSelectState.value === "checked") { let next = activeSelections.value; for (const dir of sortedDirs.value) { - const id = String(dir.id); - const key = normalizeSelectPath(id); + const item = selectionForDir(dir); next = next.filter( - (item) => - normalizeSelectPath(item.id) !== key - && !isAncestorPath(item.id, key) - && !isAncestorPath(key, item.id), + (selected) => + String(selected.id) !== item.id + && !isAncestorSelection(selected, item) + && !isAncestorSelection(item, selected), ); } commitSelections(next); @@ -318,7 +334,7 @@ function toggleSelectAllVisible() { } let next = activeSelections.value; for (const dir of sortedDirs.value) { - next = withPathExclusive(next, { id: String(dir.id), name: dir.name || String(dir.id) }, true); + next = withHierarchyExclusive(next, selectionForDir(dir), true); } commitSelections(next); } @@ -570,24 +586,25 @@ watch( :key="dir.id" class="folder-table-row" :class="{ - selected: multiSelect && (isSelected(String(dir.id)) || isPartialSelected(String(dir.id))), + selected: multiSelect && (isSelected(dir) || isPartialSelected(dir)), }" @click="openDir(dir)" >