aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-07-29 19:07:47 -0700
committerDustin Mierau <dustin@mierau.me>2024-07-29 19:07:47 -0700
commitc6996c3ac262616198e94197d1c6b310d44508ee (patch)
tree6a4a3e98b7a2449a3d200bb5f4cc1c3c8b92b4ad /Hotline
parent5726ab32f161e4b62ee0d65303b76d9894769c92 (diff)
Add drag and drop suppport for files to upload. Tighten up spacing on heirarchical lists. Add some extra HFS type to UTType conversion. Update to latest packages.
Diffstat (limited to 'Hotline')
-rw-r--r--Hotline/Hotline/HotlineExtensions.swift69
-rw-r--r--Hotline/Models/FileInfo.swift1
-rw-r--r--Hotline/Shared/FileIconView.swift26
-rw-r--r--Hotline/macOS/FileDetailsView.swift2
-rw-r--r--Hotline/macOS/FilePreviewImageView.swift2
-rw-r--r--Hotline/macOS/FilePreviewTextView.swift2
-rw-r--r--Hotline/macOS/FilesView.swift196
-rw-r--r--Hotline/macOS/NewsView.swift4
-rw-r--r--Hotline/macOS/ServerView.swift4
9 files changed, 237 insertions, 69 deletions
diff --git a/Hotline/Hotline/HotlineExtensions.swift b/Hotline/Hotline/HotlineExtensions.swift
index fd57971..81387bf 100644
--- a/Hotline/Hotline/HotlineExtensions.swift
+++ b/Hotline/Hotline/HotlineExtensions.swift
@@ -108,6 +108,73 @@ extension FileManager {
"xml": "R*ch".fourCharCode(),
]
+ static var HFSTypeToExtension: [String: String] = [
+ // Documents
+ "text": "txt",
+ "rtf ": "rtf",
+ "wdbn": "doc",
+ "w8bn": "docx",
+ "wprd": "prd",
+ "wp5 ": "wpd",
+ "pdf ": "pdf",
+ "xdoc": "qxd",
+ "indd": "ind",
+
+ // Spreadsheets
+ "xls ": "xls",
+ "xlsx": "xlsx",
+ "nmbr": "numbers",
+
+ // Presentations
+ "ppt3": "ppt",
+ "keyn": "key",
+
+ // Images
+ "jpeg": "jpg",
+ "pngf": "png",
+ "giff": "gif",
+ "tiff": "tiff",
+ "ico ": "ico",
+ "bmpf": "bmp",
+ "epsf": "eps",
+ "8bps": "psd",
+ "pict": "pict",
+ "tpic": "tga",
+ "swfl": "swf",
+
+ // Audio
+ "mp3 ": "mp3",
+ "m4a ": "m4a",
+ "caff": "aac",
+ "wave": "wav",
+ "aiff": "aiff",
+ "midi": "midi",
+ "snd ": "snd",
+
+ // Video
+ "m4v ": "mp4",
+ "mpg ": "mpeg",
+ "mpg2": "mpg2",
+ "moov": "mov",
+ "vfw ": "avi",
+ "wmv ": "wmv",
+
+ // Archives
+ "zip ": "zip",
+ "rar ": "rar",
+ "7zip": "7z",
+ "tar ": "tar",
+ "gzip": "gz",
+ "sitx": "sit",
+ "sit!": "sit",
+ "sit5": "sit",
+ "udif": "dmg",
+ "cdrw": "cdr",
+
+ // Fonts
+ "tfil": "ttf",
+ ]
+
static var extensionToHFSType: [String: UInt32] = [
// Documents
"txt": "TEXT".fourCharCode(),
@@ -115,7 +182,7 @@ extension FileManager {
"doc": "WDBN".fourCharCode(),
"docx": "W8BN".fourCharCode(),
"prd": "WPRD".fourCharCode(),
- "wpd": "WP5".fourCharCode(),
+ "wpd": "WP5 ".fourCharCode(),
"pdf": "PDF ".fourCharCode(),
"qxd": "XDOC".fourCharCode(),
"indd": "inDd".fourCharCode(),
diff --git a/Hotline/Models/FileInfo.swift b/Hotline/Models/FileInfo.swift
index 463f0be..2da7378 100644
--- a/Hotline/Models/FileInfo.swift
+++ b/Hotline/Models/FileInfo.swift
@@ -27,7 +27,6 @@ import UniformTypeIdentifiers
self.isDropboxFolder && (self.name.range(of: "admin", options: [.caseInsensitive]) != nil)
}
-
var expanded: Bool = false
var children: [FileInfo]? = nil
diff --git a/Hotline/Shared/FileIconView.swift b/Hotline/Shared/FileIconView.swift
index ffc9051..d0949fa 100644
--- a/Hotline/Shared/FileIconView.swift
+++ b/Hotline/Shared/FileIconView.swift
@@ -19,10 +19,11 @@ struct FolderIconView: View {
struct FileIconView: View {
let filename: String
+ let fileType: String?
#if os(iOS)
- private func fileIcon(filename: String) -> Image {
- let fileExtension = (filename as NSString).pathExtension
+ private func fileIcon() -> Image {
+ let fileExtension = (self.filename as NSString).pathExtension
if let fileType = UTType(filenameExtension: fileExtension) {
if fileType.isSubtype(of: .movie) {
return Image(systemName: "play.rectangle")
@@ -44,14 +45,29 @@ struct FileIconView: View {
return Image(systemName: "doc")
}
#elseif os(macOS)
- private func fileIcon(filename: String) -> Image {
- Image(nsImage: NSWorkspace.shared.icon(for: UTType(filenameExtension: (filename as NSString).pathExtension) ?? UTType.content))
+ private func fileIcon() -> Image {
+ let fileExtension = (self.filename as NSString).pathExtension
+
+ if !fileExtension.isEmpty,
+ let uttype = UTType(filenameExtension: fileExtension) {
+ return Image(nsImage: NSWorkspace.shared.icon(for: uttype))
+ }
+ else if let fileType = self.fileType,
+ let fileTypeExtension = FileManager.HFSTypeToExtension[fileType.lowercased()],
+ let uttype = UTType(filenameExtension: fileTypeExtension) {
+ return Image(nsImage: NSWorkspace.shared.icon(for: uttype))
+ }
+ else {
+ return Image(nsImage: NSWorkspace.shared.icon(for: UTType.data))
+ }
+
+// Image(nsImage: NSWorkspace.shared.icon(for: UTType(filenameExtension: (filename as NSString).pathExtension) ?? UTType.content))
}
#endif
var body: some View {
- fileIcon(filename: filename)
+ fileIcon()
.resizable()
.scaledToFit()
}
diff --git a/Hotline/macOS/FileDetailsView.swift b/Hotline/macOS/FileDetailsView.swift
index aa7ab09..34e083b 100644
--- a/Hotline/macOS/FileDetailsView.swift
+++ b/Hotline/macOS/FileDetailsView.swift
@@ -14,7 +14,7 @@ struct FileDetailsView: View {
VStack (alignment: .leading){
Form {
HStack(alignment: .center){
- FileIconView(filename: fd.name)
+ FileIconView(filename: fd.name, fileType: nil)
.frame(width: 32, height: 32)
TextField("", text: $filename)
.disabled(!self.canRename())
diff --git a/Hotline/macOS/FilePreviewImageView.swift b/Hotline/macOS/FilePreviewImageView.swift
index b8ad37b..9beeb80 100644
--- a/Hotline/macOS/FilePreviewImageView.swift
+++ b/Hotline/macOS/FilePreviewImageView.swift
@@ -69,7 +69,7 @@ struct FilePreviewImageView: View {
.background(.black)
.toolbar {
ToolbarItem(placement: .navigation) {
- FileIconView(filename: info?.name ?? "")
+ FileIconView(filename: info?.name ?? "", fileType: nil)
.frame(width: 16, height: 16)
.opacity(controlActiveState == .inactive ? 0.5 : 1.0)
}
diff --git a/Hotline/macOS/FilePreviewTextView.swift b/Hotline/macOS/FilePreviewTextView.swift
index 2cb192c..c286381 100644
--- a/Hotline/macOS/FilePreviewTextView.swift
+++ b/Hotline/macOS/FilePreviewTextView.swift
@@ -80,7 +80,7 @@ struct FilePreviewTextView: View {
.navigationTitle(info?.name ?? "File Preview")
.toolbar {
ToolbarItem(placement: .navigation) {
- FileIconView(filename: info?.name ?? "")
+ FileIconView(filename: info?.name ?? "", fileType: nil)
.frame(width: 16, height: 16)
.opacity(controlActiveState == .inactive ? 0.5 : 1.0)
}
diff --git a/Hotline/macOS/FilesView.swift b/Hotline/macOS/FilesView.swift
index e34c7b5..80d89f9 100644
--- a/Hotline/macOS/FilesView.swift
+++ b/Hotline/macOS/FilesView.swift
@@ -1,39 +1,51 @@
import SwiftUI
import UniformTypeIdentifiers
-struct FileView: View {
+struct FolderView: View {
@Environment(Hotline.self) private var model: Hotline
- @State var expanded = false
@State var loading = false
+ @State var dragOver = false
var file: FileInfo
let depth: Int
+ @MainActor private func uploadFile(file fileURL: URL) {
+ var filePath: [String] = [String](self.file.path)
+ if !self.file.isFolder {
+ filePath.removeLast()
+ }
+
+ print("UPLOADING TO PATH: ", filePath)
+
+ model.uploadFile(url: fileURL, path: filePath) { info in
+ Task {
+ // Refresh file listing to display newly uploaded file.
+ let _ = await model.getFileList(path: filePath)
+ }
+ }
+ }
+
var body: some View {
HStack(alignment: .center, spacing: 0) {
- if file.isFolder {
- Button {
- if file.isFolder {
- file.expanded.toggle()
- }
- } label: {
- Text(Image(systemName: file.expanded ? "chevron.down" : "chevron.right"))
- .bold()
- .font(.system(size: 10))
- .opacity(0.5)
+ Spacer()
+ .frame(width: CGFloat(depth * (12 + 2)))
+
+ Button {
+ if file.isFolder {
+ file.expanded.toggle()
}
- .buttonStyle(.plain)
- .frame(width: 10)
- .padding(.leading, 4)
- .padding(.trailing, 8)
- }
- else {
- Spacer()
- .frame(width: 10)
- .padding(.leading, 4)
- .padding(.trailing, 8)
+ } label: {
+ Text(Image(systemName: file.expanded ? "chevron.down" : "chevron.right"))
+ .bold()
+ .font(.system(size: 10))
+ .foregroundStyle(dragOver ? Color.white : Color.primary)
+ .opacity(0.5)
}
+ .buttonStyle(.plain)
+ .frame(width: 10)
+ .padding(.leading, 4)
+ .padding(.trailing, 8)
HStack(alignment: .center) {
if file.isUnavailable {
@@ -53,17 +65,11 @@ struct FileView: View {
.scaledToFit()
.frame(width: 16, height: 16)
}
- else if file.isFolder {
+ else {
Image("Folder")
.resizable()
.scaledToFit()
.frame(width: 16, height: 16)
-// FolderIconView(dropbox: file.isUploadFolder)
-// .frame(width: 16, height: 16)
- }
- else {
- FileIconView(filename: file.name)
- .frame(width: 16, height: 16)
}
}
.frame(width: 16)
@@ -72,47 +78,123 @@ struct FileView: View {
Text(file.name)
.lineLimit(1)
.truncationMode(.tail)
+ .foregroundStyle(dragOver ? Color.white : Color.primary)
.opacity(file.isUnavailable ? 0.5 : 1.0)
- if file.isFolder && loading {
+ if loading {
ProgressView().controlSize(.small).padding([.leading, .trailing], 5)
}
Spacer()
if !file.isUnavailable {
- if file.isFolder {
- Text(file.fileSize == 0 ? "Empty" : "^[\(file.fileSize) \("file")](inflect: true)")
- .foregroundStyle(.secondary)
- .lineLimit(1)
- .padding(.trailing, 6)
- }
- else {
- Text(formattedFileSize(file.fileSize))
- .foregroundStyle(.secondary)
- .lineLimit(1)
- .padding(.trailing, 6)
- }
+ Text(file.fileSize == 0 ? "Empty" : "^[\(file.fileSize) \("file")](inflect: true)")
+ .foregroundStyle(dragOver ? Color.white.opacity(0.75) : Color.secondary)
+ .lineLimit(1)
+ .padding(.trailing, 6)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
- .padding(.leading, CGFloat(depth * (12 + 10)))
+ .background(
+ RoundedRectangle(cornerRadius: 4.0)
+ .fill(dragOver ? Color(nsColor: NSColor.selectedContentBackgroundColor) : Color.clear)
+ .padding(.horizontal, -6)
+ .padding(.vertical, -4)
+ )
.onChange(of: file.expanded) {
loading = false
- if file.isFolder && file.fileSize > 0 {
- if file.expanded {
- Task {
- loading = true
- let _ = await model.getFileList(path: file.path)
- loading = false
+ if file.expanded && file.fileSize > 0 {
+ Task {
+ loading = true
+ let _ = await model.getFileList(path: file.path)
+ loading = false
+ }
+ }
+ }
+ .onDrop(of: [.fileURL], isTargeted: $dragOver) { items in
+ guard let item = items.first,
+ let identifier = item.registeredTypeIdentifiers.first else {
+ return false
+ }
+
+ item.loadItem(forTypeIdentifier: identifier, options: nil) { (urlData, error) in
+ print("WHAT?", Thread.isMainThread)
+ DispatchQueue.main.async {
+ print("WHAT 2?", Thread.isMainThread)
+ if let urlData = urlData as? Data,
+ let fileURL = URL(dataRepresentation: urlData, relativeTo: nil, isAbsolute: true) {
+ // uploadFile(file: fileURL)
}
}
}
+
+ return true
}
if file.expanded {
ForEach(file.children!, id: \.self) { childFile in
- FileView(file: childFile, depth: self.depth + 1).tag(file.id)
-// .environment(self.selectedFile)
-// .frame(height: 34)
+ if childFile.isFolder {
+ FolderView(file: childFile, depth: self.depth + 1).tag(file.id)
+ }
+ else {
+ FileView(file: childFile, depth: self.depth + 1).tag(file.id)
+ }
+ }
+ }
+ }
+}
+
+struct FileView: View {
+ @Environment(Hotline.self) private var model: Hotline
+
+ var file: FileInfo
+ let depth: Int
+
+ var body: some View {
+ HStack(alignment: .center, spacing: 0) {
+ Spacer()
+ .frame(width: CGFloat(depth * (12 + 2)))
+
+ Spacer()
+ .frame(width: 10)
+ .padding(.leading, 4)
+ .padding(.trailing, 8)
+
+ HStack(alignment: .center) {
+ if file.isUnavailable {
+ Image(systemName: "questionmark.app.fill")
+ .frame(width: 16, height: 16)
+ .opacity(0.5)
+ }
+ else {
+ FileIconView(filename: file.name, fileType: file.type)
+ .frame(width: 16, height: 16)
+ }
+ }
+ .frame(width: 16)
+ .padding(.trailing, 6)
+
+ Text(file.name)
+ .lineLimit(1)
+ .truncationMode(.tail)
+ .opacity(file.isUnavailable ? 0.5 : 1.0)
+
+ Spacer()
+ if !file.isUnavailable {
+ Text(formattedFileSize(file.fileSize))
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .padding(.trailing, 6)
+ }
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+
+ if file.expanded {
+ ForEach(file.children!, id: \.self) { childFile in
+ if childFile.isFolder {
+ FolderView(file: childFile, depth: self.depth + 1).tag(file.id)
+ }
+ else {
+ FileView(file: childFile, depth: self.depth + 1).tag(file.id)
+ }
}
}
}
@@ -120,7 +202,6 @@ struct FileView: View {
static let byteFormatter = ByteCountFormatter()
private func formattedFileSize(_ fileSize: UInt) -> String {
- // let bcf = ByteCountFormatter()
FileView.byteFormatter.allowedUnits = [.useAll]
FileView.byteFormatter.countStyle = .file
return FileView.byteFormatter.string(fromByteCount: Int64(fileSize))
@@ -199,9 +280,14 @@ struct FilesView: View {
var body: some View {
NavigationStack {
List(model.files, id: \.self, selection: $selection) { file in
- FileView(file: file, depth: 0).tag(file.id)
+ if file.isFolder {
+ FolderView(file: file, depth: 0).tag(file.id)
+ }
+ else {
+ FileView(file: file, depth: 0).tag(file.id)
+ }
}
- .environment(\.defaultMinListRowHeight, 34)
+ .environment(\.defaultMinListRowHeight, 28)
.listStyle(.inset)
.alternatingRowBackgrounds(.enabled)
.task {
diff --git a/Hotline/macOS/NewsView.swift b/Hotline/macOS/NewsView.swift
index 45cb057..91bf2fe 100644
--- a/Hotline/macOS/NewsView.swift
+++ b/Hotline/macOS/NewsView.swift
@@ -147,7 +147,7 @@ struct NewsView: View {
NewsItemView(news: newsItem, depth: 0).tag(newsItem.id)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
- .environment(\.defaultMinListRowHeight, 34)
+ .environment(\.defaultMinListRowHeight, 28)
.listStyle(.inset)
.alternatingRowBackgrounds(.enabled)
.contextMenu(forSelectionType: NewsInfo.self) { items in
@@ -219,7 +219,7 @@ struct NewsView: View {
VStack {
HStack {
ProgressView {
- Text("Loading News")
+ Text("Loading Newsgroups")
}
.controlSize(.regular)
}
diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift
index 979a233..e9f4a96 100644
--- a/Hotline/macOS/ServerView.swift
+++ b/Hotline/macOS/ServerView.swift
@@ -469,7 +469,7 @@ struct ServerView: View {
case .news:
NewsView()
.navigationTitle(model.serverTitle)
- .navigationSubtitle("News")
+ .navigationSubtitle("Newsgroups")
.navigationSplitViewColumnWidth(min: 250, ideal: 500)
case .board:
MessageBoardView()
@@ -609,7 +609,7 @@ struct TransferItemView: View {
HStack(alignment: .center, spacing: 5) {
HStack(spacing: 0) {
Spacer()
- FileIconView(filename: transfer.title)
+ FileIconView(filename: transfer.title, fileType: nil)
.frame(width: 16, height: 16)
.opacity(controlActiveState == .inactive ? 0.5 : 1.0)
// .padding(.leading, 2)