Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a7e277f
Add strings
samwyndham Nov 24, 2025
40cea11
add helpers to error type
samwyndham Nov 24, 2025
87912b0
add rest API method for moving folders.
samwyndham Nov 24, 2025
2355b2e
Create a new version of Fetch Nodes Use Case, renaming the old versio…
samwyndham Nov 24, 2025
12c6cb6
Add convenience method for getting the name of a node
samwyndham Nov 24, 2025
023a80b
create WireCellsMoveNodeUseCase
samwyndham Nov 24, 2025
9618344
move CreateFolderCTA to its own file
samwyndham Nov 24, 2025
9bc7fae
Create Move to Folder feature views.
samwyndham Nov 24, 2025
12b0cd8
Integrate move to folder views
samwyndham Nov 24, 2025
ff43480
Lint & format
samwyndham Nov 24, 2025
fa70f4e
delete Old comment?
samwyndham Nov 24, 2025
b5f37d7
update cells sdk
samwyndham Nov 24, 2025
316c769
Add a method to fetch editor URL to the nodes API.
samwyndham Nov 25, 2025
d2cb5c1
Add WireCellsGetEditingURLUseCase
samwyndham Nov 25, 2025
a5a98f6
Integrate use case
samwyndham Nov 25, 2025
b8c18eb
Add edit button.
samwyndham Nov 25, 2025
694a6e6
Add edit file as a sheet navigation case.
samwyndham Nov 25, 2025
0c7b27b
Add EditFileView
samwyndham Nov 27, 2025
b99cdb8
Refactor View Model
samwyndham Nov 27, 2025
694eb11
conditionally enable editing files
samwyndham Nov 27, 2025
be23b58
Handle errors.
samwyndham Nov 27, 2025
9f783b8
Fix not opening the latest version in the conversation
samwyndham Nov 27, 2025
c422882
Fix not opening the latest version in the Files view.
samwyndham Nov 27, 2025
f01f328
Use items name in navigation bar title.
samwyndham Nov 28, 2025
1be1c41
Reload files after closing the editing view.
samwyndham Nov 28, 2025
3491cea
Tiny refactor.
samwyndham Nov 28, 2025
84a6c02
Fix memory leak.
samwyndham Nov 28, 2025
4009049
add polling
samwyndham Nov 28, 2025
8f3b787
Rename polling methods.
samwyndham Nov 28, 2025
041dc6c
Fix tests
samwyndham Dec 1, 2025
f5bb8a0
Add EditFileViewModelTests
samwyndham Dec 1, 2025
68c221d
lint and format
samwyndham Dec 1, 2025
8090c89
Have the back-end decide if something is editable.
samwyndham Dec 2, 2025
8e7b62f
Add fulu and imai deep links.
samwyndham Dec 2, 2025
39eed30
Add missing divider to menu
samwyndham Dec 2, 2025
1053b05
Merge branch 'develop' into feat/move-file-folder-WPB-21619-v2
samwyndham Dec 2, 2025
ab34a8e
Rename onTap to action.
samwyndham Dec 2, 2025
bd59a78
Rename MoveToFolderContent to MoveToFolderPageViewModel.MainContent.
samwyndham Dec 2, 2025
5b6b520
Rename MoveButtonState to MoveToFolderPageViewModel.MoveButtonState.
samwyndham Dec 2, 2025
ae363f1
Merge branch 'feat/move-file-folder-WPB-21619-v2' into feat/integrate…
samwyndham Dec 2, 2025
c0c7fb7
Merge branch 'develop' into feat/integrate-collabora-WPB-21651
samwyndham Dec 4, 2025
29884d2
Fix editing being available in recycle bin
samwyndham Dec 4, 2025
bcb4695
Remove from menu when it's in the recycle bin.
samwyndham Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion WireMessaging/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
.library(name: "WireMessagingUI", targets: ["WireMessagingUI"])
],
dependencies: [
.package(url: "https://github.com/pydio/cells-sdk-swift.git", from: "0.1.1-alpha15"),
.package(url: "https://github.com/pydio/cells-sdk-swift.git", from: "0.1.1-alpha16"),
.package(url: "https://github.com/awslabs/aws-sdk-swift.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.4"),
.package(name: "WireFoundation", path: "../WireFoundation"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public struct WireMessagingFactory {
private let lastOpenRequest: WireCellsLastOpenRequest
private let nodeCache = WireCellsNodeCache()
private let isFoldersEnabled: Bool
private let isCollaboraEnabled: Bool
private let nodeRenameNotifier: WireCellsNodeRenameNotifier

@MainActor var lastOpenRequestNodeID: UUID?
Expand All @@ -47,7 +48,8 @@ public struct WireMessagingFactory {
accessToken: any AccessTokenProvider,
fileCache: any FileCache,
contextProvider: any ManagedObjectContextProvider,
isFoldersEnabled: Bool
isFoldersEnabled: Bool,
isCollaboraEnabled: Bool
) {
// TODO: [WPB-18798] Remove serverURL temporary override when there exists a method to obtain the correct URL.
let serverURL = switch serverURL.host {
Expand Down Expand Up @@ -75,6 +77,7 @@ public struct WireMessagingFactory {
)
self.lastOpenRequest = WireCellsLastOpenRequest()
self.isFoldersEnabled = isFoldersEnabled
self.isCollaboraEnabled = isCollaboraEnabled
self.nodeRenameNotifier = WireCellsNodeRenameNotifier()
}

Expand Down Expand Up @@ -153,7 +156,8 @@ public extension WireMessagingFactory {
nodeCache: nodeCache,
nodeRenameNotifier: nodeRenameNotifier,
fileCache: fileCache,
isFoldersEnabled: isFoldersEnabled
isFoldersEnabled: isFoldersEnabled,
isCollaboraEnabled: isCollaboraEnabled
)
.environment(\.wireAccentColor, accentColor)
)
Expand All @@ -165,7 +169,7 @@ public extension WireMessagingFactory {
rootView: FilesBrowserView(
viewModel: FilesViewModel(
useCases: .init(
fetchNodes: WireCellsFetchNodesUseCase(
fetchNodes: WireCellsFetchNodesPageUseCase(
configuration: .filesBrowserView,
repository: nodesAPI
),
Expand All @@ -183,11 +187,18 @@ public extension WireMessagingFactory {
updateTags: WireCellsUpdateTagsUseCase(nodesAPI: nodesAPI),
getTagSuggestions: WireCellsGetTagSuggestionsUseCase(nodesAPI: nodesAPI),
createFolder: WireCellsCreateFolderUseCase(nodesRepository: nodesAPI),
getEditingURL: WireCellsGetEditingURLUseCase(editingURLRepository: nodesAPI),
getAssetUseCase: WireCellsGetAssetUseCase(
localAssetRepository: localAssetRepository,
fileCache: fileCache
)
),
isCellsStatePending: false,
localAssetRepository: localAssetRepository,
nodesRepository: nodesAPI,
fileCache: fileCache,
isFoldersEnabled: false
isFoldersEnabled: false,
isCollaboraEnabled: isCollaboraEnabled
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ package import WireMessagingDomain

package enum NodesAPIError: Error {
case failedToCreateWriteStream
case moveFailed
}

package final actor NodesAPI: NodesAPIProtocol, WireCellsNodesRepositoryProtocol {
package final actor NodesAPI: NodesAPIProtocol, WireCellsNodesRepositoryProtocol,
WireCellsEditingURLRepositoryProtocol {
private let awsClient: AWSClient
private let restAPI: RestAPI
private let fileManager: FileManager
Expand Down Expand Up @@ -79,7 +81,13 @@ package final actor NodesAPI: NodesAPIProtocol, WireCellsNodesRepositoryProtocol
}

package func renameNode(nodeID: UUID, targetPath: String) async throws -> Bool {
try await restAPI.renameNode(nodeID: nodeID, targetPath: targetPath)
try await restAPI.renameNode(nodeID: nodeID, targetPath: targetPath, targetIsParent: false)
}

package func moveNode(nodeID: UUID, newContainerPath: String) async throws {
guard try await restAPI.renameNode(nodeID: nodeID, targetPath: newContainerPath, targetIsParent: true) else {
throw NodesAPIError.moveFailed
}
}

package func publishDraft(nodeID: UUID, versionID: UUID) async throws {
Expand Down Expand Up @@ -138,6 +146,10 @@ package final actor NodesAPI: NodesAPIProtocol, WireCellsNodesRepositoryProtocol
}
}

package func getEditorURL(id: UUID) async throws -> (url: URL, date: Date)? {
try await restAPI.getEditorURL(id: id)
}

package func createPublicLink(nodeID: UUID, fileName: String) async throws -> WireCellsPublicLink {
try await restAPI.createPublicLink(uuid: nodeID, fileName: fileName)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,22 @@ final class RestAPI: Sendable {
if let urlError = error as? URLError, urlError.code == .cancelled {
throw CancellationError()
} else {
throw sdkError
throw sdkError.underlyingError
}
}
} catch {
throw error
}
}

func getEditorURL(id: UUID) async throws -> (url: URL, date: Date)? {
do {
let response = try await NodeServiceAPI.getByUuid(
uuid: id.transportString(),
flags: [.withEditorURLs],
apiConfiguration: makeConfiguration()
)
return response.editorURLs?["collabora"]?.info
} catch let error as ErrorResponse {
throw error.underlyingError
}
}

Expand All @@ -83,32 +94,38 @@ final class RestAPI: Sendable {
/// - Parameters:
/// - nodeID: The `UUID`s of the node to rename.
/// - targetPath: The new path for the node.
/// - targetIsParent: Whether the `targetPath` is the parent folder of the node.
/// - Returns: Whether the renaming was successful.

func renameNode(nodeID: UUID, targetPath: String) async throws -> Bool {
func renameNode(nodeID: UUID, targetPath: String, targetIsParent: Bool) async throws -> Bool {
let node = RestNodeLocator(uuid: nodeID.uuidString)

let parameters = RestActionParameters(
awaitStatus: .finished,
awaitTimeout: "5s",
copyMoveOptions: RestActionOptionsCopyMove(
targetIsParent: false,
targetIsParent: targetIsParent,
targetPath: targetPath
),
nodes: [node],
)

let response = try await NodeServiceAPI.performAction(
name: .move,
parameters: parameters,
apiConfiguration: makeConfiguration()
)
guard
let actions = response.backgroundActions,
let renameAction = actions.first(where: { $0.name == Constants.renameBackgroundActionName }) else {
return false
do {
let response = try await NodeServiceAPI.performAction(
name: .move,
parameters: parameters,
apiConfiguration: makeConfiguration()
)

guard
let actions = response.backgroundActions,
let renameAction = actions.first(where: { $0.name == Constants.renameBackgroundActionName }) else {
return false
}
return renameAction.status == .finished
} catch let error as ErrorResponse {
throw error.underlyingError
}
return renameAction.status == .finished
}

/// Deletes nodes by their `UUID`s.
Expand Down Expand Up @@ -381,8 +398,45 @@ private extension WireCellsGetNodesRequest {
)
request.sortDirDesc = true
request.sortField = "mtime"
case let .moveToFolder(root):
request.filters = RestLookupFilter(
status: LookupFilterStatusFilter(
deleted: .not,
isDraft: false
),
type: .collection
)
request.scope = RestLookupScope(
recursive: false,
root: RestNodeLocator(path: root)
)
}
return request
}

}

private extension ErrorResponse {

var underlyingError: any Error {
switch self {
case let .error(_, _, _, error):
error
}
}
}

private extension RestPreSignedURL {

var info: (url: URL, date: Date)? {
guard
let urlString = url,
let url = URL(string: urlString),
let expiresAtString = expiresAt,
let expiresAtTimeInterval = TimeInterval(expiresAtString)
else {
return nil
}
return (url: url, date: Date(timeIntervalSinceNow: expiresAtTimeInterval))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ public enum WireCellsLocalAssetRepositoryError: Error, Equatable {

case missingETag

/// A download for the requested asset is already in progress.

case downloadAlreadyInProgress

/// The requested asset is unknown to the repository.

case unknownAsset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ package protocol WireCellsNodesRepositoryProtocol: Sendable {
/// - Returns: Whether the renaming was successful.
func renameNode(nodeID: UUID, targetPath: String) async throws -> Bool

/// Moves a node to a new container path.
///
/// - Parameters:
/// - nodeID: The `UUID` of the node to move.
/// - newContainerPath: The new container path for the node.
func moveNode(nodeID: UUID, newContainerPath: String) async throws

/// Apply some pre-validation checks on node name before sending an upload
///
/// - Parameters:
Expand All @@ -75,6 +82,9 @@ package struct WireCellsGetNodesRequest: Equatable, Sendable {

/// A `Configuration` suitable for the files browser view.
case filesBrowserView

/// A `Configuration` suitable for moving nodes to a folder.
case moveToFolder(root: String)
}

/// An optional search term to filter nodes by name.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Wire
// Copyright (C) 2025 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

package import Foundation

// sourcery: AutoMockable
package protocol WireCellsEditingURLRepositoryProtocol: Sendable {

/// Returns a URL to an online editor where the document can be edited.
func getEditorURL(id: UUID) async throws -> (url: URL, date: Date)?

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Wire
// Copyright (C) 2025 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation

/// Fetches `WireCellNodes`s for the given parameters.
package struct WireCellsFetchNodesPageUseCase: Sendable {

private let configuration: WireCellsGetNodesRequest.Configuration
private let repository: any WireCellsNodesRepositoryProtocol

/// Initializes the use case with the required parameters.
/// - Parameters:
/// - configuration: The configuration for the use case.
/// - repository: The repository to use for fetching nodes.
package init(
configuration: WireCellsGetNodesRequest.Configuration,
repository: any WireCellsNodesRepositoryProtocol
) {
self.configuration = configuration
self.repository = repository
}

/// Fetches nodes based on the provided search term and pagination token.
///
/// - Parameters:
/// - searchTerm: An optional search term to filter nodes by their name.
/// - token: An optional pagination token. If `nil`, the first page of results will be fetched.
/// - Returns: An array of `WireCellsNode` values and an optional pagination token for the next page of results. If
/// `nil`, there are no more pages to fetch.
package func invoke(
searchTerm: String?,
offset: Int
) async throws -> (nodes: [WireCellsNode], isLastPage: Bool) {
let request = WireCellsGetNodesRequest(
searchTerm: searchTerm,
limit: 30,
offset: offset,
configuration: configuration
)
let (nodes, nextOffset) = try await repository.getNodes(request)

return (nodes, nextOffset == nil)
}

}
Loading
Loading