aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2025-11-13 20:15:46 -0800
committerDustin Mierau <dustin@mierau.me>2025-11-13 20:15:46 -0800
commit8d4cdce6ec00db6b329a3b9ae71cfc37cd642e9b (patch)
treeffa8affcf5f95fb551378427940102768f473b13 /Hotline
parentda6a1f92caa31c85f9182bff4c29ceb5c72b3742 (diff)
You can now save file details (rename files/folders and comments)
Diffstat (limited to 'Hotline')
-rw-r--r--Hotline/Hotline/HotlineClient.swift23
-rw-r--r--Hotline/State/HotlineState.swift30
-rw-r--r--Hotline/macOS/Files/FileDetailsSheet.swift90
-rw-r--r--Hotline/macOS/Files/FilesView.swift2
-rw-r--r--Hotline/macOS/ServerView.swift16
5 files changed, 107 insertions, 54 deletions
diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift
index 6dbb460..98cf222 100644
--- a/Hotline/Hotline/HotlineClient.swift
+++ b/Hotline/Hotline/HotlineClient.swift
@@ -645,6 +645,29 @@ public actor HotlineClient {
modified: fileModifyDate
)
}
+
+ /// Set a file's information (name/comment)
+ ///
+ /// - Parameters:
+ /// - name: File name
+ /// - path: Directory path containing the file
+ /// - newName: Name to set the file to
+ /// - comment: Comment to set on the file
+ public func setFileInfo(name: String, path: [String], newName: String? = nil, comment: String? = nil, encoding: String.Encoding = .utf8) async throws {
+ var transaction = HotlineTransaction(id: self.generateTransactionID(), type: .setFileInfo)
+ transaction.setFieldString(type: .fileName, val: name)
+ transaction.setFieldPath(type: .filePath, val: path)
+
+ if let newName {
+ transaction.setFieldString(type: .fileNewName, val: newName)
+ }
+
+ if let comment {
+ transaction.setFieldString(type: .fileComment, val: comment)
+ }
+
+ try await sendTransaction(transaction)
+ }
/// Delete a file or folder
///
diff --git a/Hotline/State/HotlineState.swift b/Hotline/State/HotlineState.swift
index c9810ad..c632f91 100644
--- a/Hotline/State/HotlineState.swift
+++ b/Hotline/State/HotlineState.swift
@@ -789,7 +789,6 @@ class HotlineState: Equatable {
// MARK: - Users
- @MainActor
func getUserList() async throws {
guard let client = self.client else {
throw HotlineClientError.notConnected
@@ -801,7 +800,6 @@ class HotlineState: Equatable {
// MARK: - Files
- @MainActor
@discardableResult
func getFileList(path: [String] = [], suppressErrors: Bool = false, preferCache: Bool = false) async throws -> [FileInfo] {
guard let client = self.client else {
@@ -832,7 +830,6 @@ class HotlineState: Equatable {
return newFiles
}
- @MainActor
func getFileDetails(_ fileName: String, path: [String]) async throws -> FileDetails? {
guard let client = self.client else {
throw HotlineClientError.notConnected
@@ -846,7 +843,26 @@ class HotlineState: Equatable {
return try await client.getFileInfo(name: fileName, path: fullPath)
}
- @MainActor
+ @discardableResult
+ func setFileInfo(fileName: String, path filePath: [String], fileNewName: String?, comment: String?) async throws -> Bool {
+ guard let client = self.client else {
+ throw HotlineClientError.notConnected
+ }
+
+ do {
+ try await client.setFileInfo(name: fileName, path: filePath, newName: fileNewName, comment: comment)
+ self.invalidateFileListCache(for: filePath, includingAncestors: true)
+ return true
+ }
+ catch let error as HotlineClientError {
+ self.errorMessage = error.userMessage
+ self.errorDisplayed = true
+ }
+
+ return false
+ }
+
+ @discardableResult
func newFolder(name: String, parentPath: [String]) async throws -> Bool {
guard let client = self.client else {
throw HotlineClientError.notConnected
@@ -1404,12 +1420,6 @@ class HotlineState: Equatable {
}
}
- func setFileInfo(fileName: String, path filePath: [String], fileNewName: String?, comment: String?, encoding: String.Encoding = .utf8) {
- // TODO: Implement setFileInfo in HotlineClient
- // This method updates file metadata (name and/or comment)
- print("setFileInfo not yet implemented in HotlineState/HotlineClient")
- }
-
@MainActor
func previewFile(_ fileName: String, path: [String], complete callback: ((PreviewFileInfo?) -> Void)? = nil) {
guard let client = self.client else {
diff --git a/Hotline/macOS/Files/FileDetailsSheet.swift b/Hotline/macOS/Files/FileDetailsSheet.swift
index 2118518..e86bf28 100644
--- a/Hotline/macOS/Files/FileDetailsSheet.swift
+++ b/Hotline/macOS/Files/FileDetailsSheet.swift
@@ -3,34 +3,35 @@ import SwiftUI
struct FileDetailsSheet: View {
@Environment(HotlineState.self) private var model: HotlineState
- @Environment(\.presentationMode) var presentationMode
+ @Environment(\.dismiss) private var dismiss
- var fd: FileDetails
+ var details: FileDetails
+ @State private var saving: Bool = false
@State private var comment: String = ""
@State private var filename: String = ""
var body: some View {
VStack(alignment: .leading, spacing: 16) {
HStack(alignment: .center, spacing: 16){
- if self.fd.type == "Folder" {
+ if self.details.type == "Folder" {
FolderIconView()
.frame(width: 32, height: 32)
}
else {
- FileIconView(filename: fd.name, fileType: nil)
+ FileIconView(filename: self.details.name, fileType: nil)
.frame(width: 32, height: 32)
}
- TextField("", text: $filename)
- .disabled(!self.canRename())
+ TextField("File Name", text: $filename)
+ .disabled(!self.canRename)
}
let rows: [(String, String)] = [
- ("Type", fd.type),
- ("Creator", fd.creator),
- ("Size", self.formattedSize(byteCount: fd.size)),
- ("Created", Self.dateFormatter.string(from: fd.created)),
- ("Modified", Self.dateFormatter.string(from: fd.modified))
+ ("Type", self.details.type),
+ ("Creator", self.details.creator),
+ ("Size", self.formattedSize(byteCount: self.details.size)),
+ ("Created", Self.dateFormatter.string(from: self.details.created)),
+ ("Modified", Self.dateFormatter.string(from: self.details.modified))
]
Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 8) {
@@ -54,46 +55,65 @@ struct FileDetailsSheet: View {
.font(.body)
.lineLimit(10, reservesSpace: true)
.padding(.leading, 32 + 16)
- .disabled(!self.canSetComment())
+ .disabled(!self.canSetComment)
}
.padding(.vertical, 24)
.padding(.horizontal, 24)
.frame(width: 400)
.toolbar {
+ if self.saving {
+ ToolbarItem {
+ ProgressView()
+ .controlSize(.small)
+ }
+ }
+
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
- presentationMode.wrappedValue.dismiss()
+ self.dismiss()
}
}
ToolbarItem(placement: .primaryAction) {
Button{
var editedFilename: String?
- if filename != fd.name {
- editedFilename = filename
+ if self.filename != self.details.name {
+ editedFilename = self.filename
}
var editedComment: String?
- if comment != fd.comment {
- editedComment = comment
+ if self.comment != self.details.comment {
+ editedComment = self.comment
}
- model.setFileInfo(fileName: fd.name, path: fd.path, fileNewName: editedFilename, comment: editedComment)
- presentationMode.wrappedValue.dismiss()
-
- // TODO: Update the file list if the filename was changed
+ Task {
+ self.saving = true
+ defer { self.saving = false }
+
+ if editedComment != nil || editedFilename != nil {
+ if try await self.model.setFileInfo(fileName: self.details.name, path: self.details.path, fileNewName: editedFilename, comment: editedComment) {
+ try await self.model.getFileList(path: self.details.path)
+ }
+ }
+
+ // We dismiss even if there is an error for now
+ // This is not ideal as we may lose a user's written comment
+ // or new file name, but SwiftUI doesn't show the current
+ // alert above this sheet so we'll need a different way of
+ // handling errors to make this work. Until then...
+ self.dismiss()
+ }
} label: {
Text("Save")
- }.disabled(!isEdited())
+ }
}
}
.onAppear {
- self.filename = fd.name
- self.comment = fd.comment
+ self.filename = self.details.name
+ self.comment = self.details.comment
}
}
-
static var dateFormatter: DateFormatter = {
var dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
@@ -124,24 +144,24 @@ struct FileDetailsSheet: View {
}
private func isEdited() -> Bool {
- return self.filename != fd.name || self.comment != fd.comment
+ return self.filename != self.details.name || self.comment != self.details.comment
}
- private func canRename() -> Bool {
- if self.fd.type == "fldr" {
- return model.access?.contains(.canRenameFolders) == true
+ private var canRename: Bool {
+ if self.details.type == "fldr" || self.details.type == "Folder" {
+ return self.model.access?.contains(.canRenameFolders) == true
}
- return model.access?.contains(.canRenameFiles) == true
+ return self.model.access?.contains(.canRenameFiles) == true
}
- private func canSetComment() -> Bool {
- if self.fd.type == "fldr" {
- return model.access?.contains(.canSetFolderComment) == true
+ private var canSetComment: Bool {
+ if self.details.type == "fldr" || self.details.type == "Folder" {
+ return self.model.access?.contains(.canSetFolderComment) == true
}
- return model.access?.contains(.canSetFileComment) == true
+ return self.model.access?.contains(.canSetFileComment) == true
}
}
//#Preview {
-// FileDetailsView(fd: FileDetails(name: "AppleWorks 6.sit", path: [""], size: 24601664, comment: "test comment", type: "SITD", creator: "SIT!", created: Date.now, modified: Date.now ))
+// FileDetailsView(details: FileDetails(name: "AppleWorks 6.sit", path: [""], size: 24601664, comment: "test comment", type: "SITD", creator: "SIT!", created: Date.now, modified: Date.now ))
//}
diff --git a/Hotline/macOS/Files/FilesView.swift b/Hotline/macOS/Files/FilesView.swift
index 325af8c..1ae99d9 100644
--- a/Hotline/macOS/Files/FilesView.swift
+++ b/Hotline/macOS/Files/FilesView.swift
@@ -235,7 +235,7 @@ struct FilesView: View {
Text("You cannot undo this action.")
})
.sheet(item: self.$fileDetails) { item in
- FileDetailsSheet(fd: item)
+ FileDetailsSheet(details: item)
}
.fileImporter(isPresented: $uploadFileSelectorDisplayed, allowedContentTypes: [.data, .folder], allowsMultipleSelection: false, onCompletion: { results in
switch results {
diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift
index b8297fd..3794018 100644
--- a/Hotline/macOS/ServerView.swift
+++ b/Hotline/macOS/ServerView.swift
@@ -196,14 +196,6 @@ struct ServerView: View {
.onChange(of: self.model.serverTitle) {
self.state.serverName = self.model.serverTitle
}
- .alert("Something Went Wrong", isPresented: self.$model.errorDisplayed) {
- Button("OK") {}
- } message: {
- if let message = self.model.errorMessage,
- !message.isBlank {
- Text(message)
- }
- }
.onAppear {
var address = self.server.address
if self.server.port != HotlinePorts.DefaultServerPort {
@@ -220,6 +212,14 @@ struct ServerView: View {
self.connectionDisplayed = true
}
+ .alert("Something Went Wrong", isPresented: self.$model.errorDisplayed) {
+ Button("OK") {}
+ } message: {
+ if let message = self.model.errorMessage,
+ !message.isBlank {
+ Text(message)
+ }
+ }
.focusedSceneValue(\.activeHotlineModel, model)
.focusedSceneValue(\.activeServerState, state)
}