diff options
| author | Dustin Mierau <mierau@users.noreply.github.com> | 2024-01-09 14:44:14 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-09 14:44:14 -0800 |
| commit | a2b7946804f5ab17ef1aebc4b5bb72e7cf14e83b (patch) | |
| tree | 218012a1565033fa89a862a34a0683a9fad3d39b /Hotline | |
| parent | f198d4d59b922e9cb872fe6831d3475c570fa56f (diff) | |
| parent | 60f90281bf971600733b2d72b747bcb6be9b1c31 (diff) | |
Merge branch 'main' into patch-1
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Application.swift | 10 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineClient.swift | 116 | ||||
| -rw-r--r-- | Hotline/Models/Hotline.swift | 8 | ||||
| -rw-r--r-- | Hotline/Models/NewsArticle.swift | 21 | ||||
| -rw-r--r-- | Hotline/Shared/URLAdditions.swift | 16 | ||||
| -rw-r--r-- | Hotline/macOS/ChatView.swift | 1 | ||||
| -rw-r--r-- | Hotline/macOS/FilePreviewImageView.swift | 1 | ||||
| -rw-r--r-- | Hotline/macOS/FilePreviewTextView.swift | 3 | ||||
| -rw-r--r-- | Hotline/macOS/FilesView.swift | 1 | ||||
| -rw-r--r-- | Hotline/macOS/MessageBoardView.swift | 2 | ||||
| -rw-r--r-- | Hotline/macOS/NewsEditorView.swift | 100 | ||||
| -rw-r--r-- | Hotline/macOS/NewsView.swift | 358 | ||||
| -rw-r--r-- | Hotline/macOS/ServerView.swift | 151 |
13 files changed, 463 insertions, 325 deletions
diff --git a/Hotline/Application.swift b/Hotline/Application.swift index 4541ea3..e7445dc 100644 --- a/Hotline/Application.swift +++ b/Hotline/Application.swift @@ -55,6 +55,16 @@ struct Application: App { .environment(preferences) } + // MARK: News Editor Window +// WindowGroup(id: "news-editor", for: NewsArticle.self) { $article in +// NewsEditorView(article: $article) +// } +// .windowResizability(.contentSize) +// .windowStyle(.titleBar) +// .windowToolbarStyle(.unifiedCompact(showsTitle: true)) +// .defaultSize(width: 450, height: 550) +// .defaultPosition(.center) + // MARK: Image Preview Window WindowGroup(id: "preview-image", for: PreviewFileInfo.self) { $info in FilePreviewImageView(info: $info) diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift index c33cb75..ccb0c4c 100644 --- a/Hotline/Hotline/HotlineClient.swift +++ b/Hotline/Hotline/HotlineClient.swift @@ -131,82 +131,46 @@ class HotlineClient { } switch newState { - case .preparing: - print("HotlineClient: connection preparing...") - case .setup: - print("HotlineClient: connection setup") - case .waiting(let err): - print("HotlineClient: connection waiting \(err)...") - switch err { - case .posix(let errCode): - print("HotlineClient: posix error code \(errCode)") - self.disconnect() -// switch errCode { -// case .ETIMEDOUT: -// self.disconnect() -// case .ECONNREFUSED: -// self.disconnect() -// default: -// self.disconnect() -// break -// } - case .tls(let errStatus): - print("HotlineClient: tls error code \(errStatus)") - self.disconnect() - case .dns(let errType): - print("HotlineClient: DNS Error code \(errType)") - self.disconnect() - default: - print("HotlineClient: error code \(err)") - } case .ready: print("HotlineClient: connection ready!") self.updateConnectionStatus(.connected) self.connectCallback?(true) self.connectCallback = nil -// if self.connectionContinuation != nil { -// let continuation = self.connectionContinuation! -// self.connectionContinuation = nil -// callback?(true) -// continuation.resume(returning: true) +// case .waiting(let err): +// print("HotlineClient: connection waiting \(err)...") +// switch err { +// case .posix(let errCode): +// print("HotlineClient: posix error code \(errCode)") +// self.disconnect() +// case .tls(let errStatus): +// print("HotlineClient: tls error code \(errStatus)") +// self.disconnect() +// case .dns(let errType): +// print("HotlineClient: DNS Error code \(errType)") +// self.disconnect() +// default: +// print("HotlineClient: error code \(err)") // } -// callback?(true) - case .failed(let err): - print("HotlineClient: connection error \(err)") + case .cancelled: + print("HotlineClient: connection cancelled") self.updateConnectionStatus(.disconnected) self.reset() self.connectCallback?(false) self.connectCallback = nil -// callback?(false) -// if self.connectionContinuation != nil { -// let continuation = self.connectionContinuation! -// self.connectionContinuation = nil -// continuation.resume(returning: false) -// } - case .cancelled: - print("HotlineClient: connection cancelled") + case .failed(let err): + print("HotlineClient: connection error \(err)") self.updateConnectionStatus(.disconnected) self.reset() self.connectCallback?(false) self.connectCallback = nil -// callback?(false) -// if self.connectionContinuation != nil { -// let continuation = self.connectionContinuation! -// self.connectionContinuation = nil -// continuation.resume(returning: false) -// } default: + print("HotlineClient: hmm", newState) break } } self.updateConnectionStatus(.connecting) self.connection?.start(queue: .global()) - -// return await withCheckedContinuation { [weak self] continuation in -// self?.connectionContinuation = continuation -// self?.connection?.start(queue: .global()) -// } } private func reset() { @@ -228,7 +192,6 @@ class HotlineClient { private func sendTransaction(_ t: HotlineTransaction, sent sentCallback: ((Bool) -> Void)? = nil, reply replyCallback: ((HotlineTransaction, HotlineTransactionError?) -> Void)? = nil) { guard let c = connection else { - print("NO CONNECTION?????") sentCallback?(false) return } @@ -590,6 +553,47 @@ class HotlineClient { }) } + func postNewsArticle(title: String, text: String, path: [String] = [], parentID: UInt32? = nil, sent: ((Bool) -> Void)? = nil, reply: (([HotlineNewsArticle]) -> Void)? = nil) { + var t = HotlineTransaction(type: .postNewsArticle) + if !path.isEmpty { + t.setFieldPath(type: .newsPath, val: path) + } + if let parentID = parentID { + t.setFieldUInt32(type: .newsArticleID, val: parentID) + } + t.setFieldString(type: .newsArticleTitle, val: title) + t.setFieldString(type: .newsArticleDataFlavor, val: "text/plain") + t.setFieldUInt32(type: .newsArticleFlags, val: 0) + t.setFieldString(type: .newsArticleData, val: text) + + self.sendTransaction(t, sent: sent, reply: { r, err in + if err != nil { + reply?([]) + return + } + + var articles: [HotlineNewsArticle] = [] +// let articleData = r.getField(type: .newsArticleListData) + +// print("ARTICLE DATA?", articleData) + + if let articleData = r.getField(type: .newsArticleListData) { + let newsList = articleData.getNewsList() + for art in newsList.articles { + var blah = art + blah.path = path + articles.append(blah) + + print(blah.title) + } + } + + DispatchQueue.main.async { + reply?(articles) + } + }) + } + func sendGetNewsArticles(path: [String] = [], sent: ((Bool) -> Void)? = nil, reply: (([HotlineNewsArticle]) -> Void)? = nil) { var t = HotlineTransaction(type: .getNewsArticleNameList) if !path.isEmpty { diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift index e88c267..e62ac15 100644 --- a/Hotline/Models/Hotline.swift +++ b/Hotline/Models/Hotline.swift @@ -246,8 +246,6 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate { self.username = username self.iconID = iconID - print("CLIENT LOGIN: '\(server.login)' '\(server.password == nil)'") - self.client.login(server.address, port: UInt16(server.port), login: server.login, password: server.password, username: username, iconID: UInt16(iconID)) { [weak self] err, serverName, serverVersion in self?.serverVersion = serverVersion if serverName != nil { @@ -335,8 +333,6 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate { continuation.resume(returning: nil) return } - - print("GET NEWS CATS FROM \(path)") }, reply: { articleText in // let parentNews = self?.findNews(in: self?.news ?? [], at: path) @@ -376,8 +372,6 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate { continuation.resume(returning: []) return } - - print("GET NEWS CATS FROM \(path)") }, reply: { [weak self] categories in // let parentNews = self?.findNews(in: self?.news ?? [], at: path) @@ -445,8 +439,6 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate { } return } - - print("GET NEWS CATS FROM \(path)") }, reply: { [weak self] categories in let parentNews = self?.findNews(in: self?.news ?? [], at: path) diff --git a/Hotline/Models/NewsArticle.swift b/Hotline/Models/NewsArticle.swift new file mode 100644 index 0000000..067d6b6 --- /dev/null +++ b/Hotline/Models/NewsArticle.swift @@ -0,0 +1,21 @@ +import Foundation + +struct NewsArticle: Identifiable, Codable { + var id: UUID = UUID() + var parentID: UInt32? + var path: [String] + var title: String + var body: String +} + +extension NewsArticle: Equatable { + static func == (lhs: NewsArticle, rhs: NewsArticle) -> Bool { + return lhs.id == rhs.id && lhs.parentID == rhs.parentID + } +} + +extension NewsArticle: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } +} diff --git a/Hotline/Shared/URLAdditions.swift b/Hotline/Shared/URLAdditions.swift index 7eebb46..1f25541 100644 --- a/Hotline/Shared/URLAdditions.swift +++ b/Hotline/Shared/URLAdditions.swift @@ -23,20 +23,4 @@ extension URL { return filePath } - -// private func prepareDownloadFile(name: String) -> Bool { -// let folderURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] -// let filePath = findUniqueFilePath(base: name, at: folderURL) -// -// if FileManager.default.createFile(atPath: filePath, contents: nil) { -// if let h = FileHandle(forWritingAtPath: filePath) { -// self.filePath = filePath -// self.fileHandle = h -// self.fileProgress?.fileURL = URL(filePath: filePath).resolvingSymlinksInPath() -// return true -// } -// } -// -// return false -// } } diff --git a/Hotline/macOS/ChatView.swift b/Hotline/macOS/ChatView.swift index 2c154d3..7f7d677 100644 --- a/Hotline/macOS/ChatView.swift +++ b/Hotline/macOS/ChatView.swift @@ -118,7 +118,6 @@ struct ChatView: View { // withAnimation { reader.scrollTo(bottomID, anchor: .bottom) // } - print("SCROLLED TO BOTTOM") } .onAppear { reader.scrollTo(bottomID, anchor: .bottom) diff --git a/Hotline/macOS/FilePreviewImageView.swift b/Hotline/macOS/FilePreviewImageView.swift index 1ee4e9b..461f5a4 100644 --- a/Hotline/macOS/FilePreviewImageView.swift +++ b/Hotline/macOS/FilePreviewImageView.swift @@ -13,7 +13,6 @@ struct FilePreviewImageView: View { @Binding var info: PreviewFileInfo? @State var preview: FilePreview? = nil - @Namespace var mainNamespace @FocusState private var focusField: FilePreviewFocus? var body: some View { diff --git a/Hotline/macOS/FilePreviewTextView.swift b/Hotline/macOS/FilePreviewTextView.swift index 4c81e6f..2cb192c 100644 --- a/Hotline/macOS/FilePreviewTextView.swift +++ b/Hotline/macOS/FilePreviewTextView.swift @@ -12,7 +12,6 @@ struct FilePreviewTextView: View { @Binding var info: PreviewFileInfo? @State var preview: FilePreview? = nil - @Namespace var mainNamespace @FocusState private var focusField: FilePreviewFocus? var body: some View { @@ -43,8 +42,8 @@ struct FilePreviewTextView: View { .contentMargins(.top, -16.0, for: .scrollIndicators) .contentMargins(.bottom, -16.0, for: .scrollIndicators) .contentMargins(.trailing, -16.0, for: .scrollIndicators) - .frame(maxWidth: .infinity, maxHeight: .infinity) .scrollClipDisabled() + .frame(maxWidth: .infinity, maxHeight: .infinity) } else { VStack(alignment: .center, spacing: 0) { diff --git a/Hotline/macOS/FilesView.swift b/Hotline/macOS/FilesView.swift index 1eab0cf..b9f15db 100644 --- a/Hotline/macOS/FilesView.swift +++ b/Hotline/macOS/FilesView.swift @@ -218,7 +218,6 @@ struct FilesView: View { ToolbarItem(placement: .primaryAction) { Button { if let s = selection, !s.isFolder { - print("DOWNLOAD FILE", s.name, s.path) model.downloadFile(s.name, path: s.path) } } label: { diff --git a/Hotline/macOS/MessageBoardView.swift b/Hotline/macOS/MessageBoardView.swift index d72b122..65a947f 100644 --- a/Hotline/macOS/MessageBoardView.swift +++ b/Hotline/macOS/MessageBoardView.swift @@ -22,8 +22,6 @@ struct MessageBoardView: View { .task { if !model.messageBoardLoaded { let _ = await model.getMessageBoard() -// self.initialLoadComplete = true - print("INITIAL LOAD?") } } .overlay { diff --git a/Hotline/macOS/NewsEditorView.swift b/Hotline/macOS/NewsEditorView.swift new file mode 100644 index 0000000..18c4a0d --- /dev/null +++ b/Hotline/macOS/NewsEditorView.swift @@ -0,0 +1,100 @@ +import SwiftUI +import UniformTypeIdentifiers + +struct NewsEditorView: View { + @Environment(\.controlActiveState) private var controlActiveState + @Environment(\.colorScheme) private var colorScheme + @Environment(\.dismiss) private var dismiss + @Environment(Hotline.self) private var model: Hotline + +// @Binding var article: NewsArticle? + @State var title: String = "" + @State var text: String = "" + + var body: some View { + NavigationStack { + VStack(alignment: .leading, spacing: 0) { + HStack { + Button { + dismiss() + } label: { + Image(systemName: "xmark.circle.fill") + .resizable() + .scaledToFit() + } + .buttonStyle(.plain) + .frame(width: 16, height: 16) + .padding() + + Spacer() + + Button { + } label: { + Image(systemName: "paperplane") + .resizable() + .scaledToFit() + } + .buttonStyle(.plain) + .frame(width: 16, height: 16) + .padding() + } + .frame(maxWidth: .infinity) + TextField("Title", text: $title, axis: .vertical) + .textFieldStyle(.plain) + .padding() + .focusEffectDisabled() + .font(.title) + .frame(maxWidth: .infinity) + .border(Color.pink, width: 0) + Divider() + TextEditor(text: $text) + .textEditorStyle(.plain) + .font(.system(size: 14)) + .lineSpacing(3) + .padding(16) + .contentMargins(.top, -16.0, for: .scrollIndicators) + .contentMargins(.bottom, -16.0, for: .scrollIndicators) + .contentMargins(.trailing, -16.0, for: .scrollIndicators) + .scrollClipDisabled() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .frame(minWidth: 300, idealWidth: 450, maxWidth: .infinity, minHeight: 300, idealHeight: 500, maxHeight: .infinity) + .background(Color(nsColor: .textBackgroundColor)) + .presentationCompactAdaptation(.sheet) + .toolbarTitleDisplayMode(.inlineLarge) +// .toolbar { +// ToolbarItem(placement: .navigation) { +// Button("Post", action: {}) +// } +// ToolbarItem(placement: .automatic) { +// Button("Delete", action: { +// dismiss() +// }) +// } +// } + .task { +// if let info = info { +// preview = FilePreview(info: info) +// preview?.download() +// } + } + .onAppear { +// if info == nil { +// Task { +// dismiss() +// } +// return +// } + } + .onDisappear { +// preview?.cancel() + dismiss() + } +// .onChange(of: preview?.state) { +// if preview?.state == .failed { +// dismiss() +// } +// } + } +} diff --git a/Hotline/macOS/NewsView.swift b/Hotline/macOS/NewsView.swift index daeb45b..d7e40f2 100644 --- a/Hotline/macOS/NewsView.swift +++ b/Hotline/macOS/NewsView.swift @@ -1,5 +1,190 @@ import SwiftUI import MarkdownUI +import SplitView + +struct NewsView: View { + @Environment(Hotline.self) private var model: Hotline + @Environment(\.openWindow) private var openWindow + + @State private var selection: NewsInfo? + @State private var articleText: String? + @State private var splitHidden = SideHolder(.bottom) + @State private var splitFraction = FractionHolder.usingUserDefaults(0.25, key: "News Split Fraction") + @State private var editorOpen: Bool = false + + var body: some View { + NavigationStack { + VSplit( + top: { + newsBrowser + }, + bottom: { + articleViewer + } + ) + .fraction(splitFraction) + .constraints(minPFraction: 0.1, minSFraction: 0.3) + .hide(splitHidden) + .styling(inset: 0, visibleThickness: 0.5, invisibleThickness: 5, hideSplitter: true) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(nsColor: .textBackgroundColor)) + } +// .sheet(isPresented: $editorOpen) { +// print("Sheet dismissed!") +// } content: { +// NewsEditorView() +// } + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button { + + } label: { + Image(systemName: "trash") + } + } + + ToolbarItem(placement: .primaryAction) { + Button { +// if let selection = selection { +// editorOpen = true +//// openWindow(id: "news-editor", value: NewsArticle(parentID: nil, path: selection.path, title: "", body: "")) +// } + } label: { + Image(systemName: "square.and.pencil") + } + } + + ToolbarItem(placement: .primaryAction) { + Button { + + } label: { + Image(systemName: "arrowshape.turn.up.left") + } + } + } + } + + var newsBrowser: some View { + List(model.news, id: \.self, selection: $selection) { newsItem in + NewsItemView(news: newsItem, depth: 0).tag(newsItem.id) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .environment(\.defaultMinListRowHeight, 34) + .listStyle(.inset) + .alternatingRowBackgrounds(.enabled) + .task { + if !model.newsLoaded { + let _ = await model.getNewsList() + } + } + .contextMenu(forSelectionType: NewsInfo.self) { items in + // ... + } primaryAction: { items in + guard let clickedNews = items.first else { + return + } + + self.selection = clickedNews + if clickedNews.type == .bundle || clickedNews.type == .category { + clickedNews.expanded.toggle() + } + } + .onChange(of: selection) { + self.articleText = nil + if let article = selection, article.type == .article { + if let articleFlavor = article.articleFlavors?.first, + let articleID = article.articleID { + Task { + if let articleText = await self.model.getNewsArticle(id: articleID, at: article.path, flavor: articleFlavor) { + self.articleText = articleText + } + } + if self.splitHidden.side != nil { + withAnimation(.easeOut(duration: 0.15)) { + self.splitHidden.side = nil + } + } + + } + } + else { + if self.splitHidden.side != .bottom { + withAnimation(.easeOut(duration: 0.25)) { + self.splitHidden.side = .bottom + } + } + } + } + .onKeyPress(.rightArrow) { + if let s = selection, s.type == .bundle || s.type == .category { + s.expanded = true + return .handled + } + return .ignored + } + .onKeyPress(.leftArrow) { + if let s = selection, s.type == .bundle || s.type == .category { + s.expanded = false + return .handled + } + return .ignored + } + .overlay { + if !model.newsLoaded { + VStack { + ProgressView() + .controlSize(.regular) + } + .frame(maxWidth: .infinity) + } + } + } + + var articleViewer: some View { + ScrollView { + if let selection = selection { + VStack(alignment: .leading, spacing: 0) { + if let poster = selection.articleUsername, let postDate = selection.articleDate { + HStack(alignment: .firstTextBaseline) { + Text(poster) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.tail) + .textSelection(.enabled) + .padding(.bottom, 16) + Spacer() + Text("\(NewsItemView.dateFormatter.string(from: postDate))") + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.tail) + .textSelection(.enabled) + .padding(.bottom, 16) + } + } + + Divider() + + Text(selection.name).font(.title) + .textSelection(.enabled) + .padding(.bottom, 8) + .padding(.top, 16) + + if let newsText = self.articleText { + Markdown(newsText) + .markdownTheme(.basic) + .textSelection(.enabled) + .lineSpacing(6) + .padding(.top, 16) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .padding() + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .transition(.move(edge: .bottom)) + } +} struct NewsItemView: View { @Environment(Hotline.self) private var model: Hotline @@ -47,6 +232,7 @@ struct NewsItemView: View { .scaledToFit() .frame(width: 10) .foregroundStyle(.secondary) + .opacity(0.5) } } Text(news.name) @@ -93,178 +279,6 @@ struct NewsItemView: View { } } -struct NewsView: View { - @Environment(Hotline.self) private var model: Hotline - - @State private var selection: NewsInfo? - @State private var articleText: String? - - var body: some View { - NavigationStack { - VSplitView { - - // MARK: News Browser - List(model.news, id: \.self, selection: $selection) { newsItem in - NewsItemView(news: newsItem, depth: 0).tag(newsItem.id) - } - - .frame(maxWidth: .infinity, minHeight: 100, idealHeight: 100) - .environment(\.defaultMinListRowHeight, 34) - .listStyle(.inset) - .alternatingRowBackgrounds(.enabled) - .task { - if !model.newsLoaded { - let _ = await model.getNewsList() - } - } - .contextMenu(forSelectionType: NewsInfo.self) { items in - // ... - } primaryAction: { items in - print("ITEMS?", items) - guard let clickedNews = items.first else { - return - } - - self.selection = clickedNews - if clickedNews.type == .bundle || clickedNews.type == .category { - clickedNews.expanded.toggle() - } - } - .onChange(of: selection) { - if - let article = selection, - article.type == .article { - self.articleText = nil - if -// let article = self.articleSelection.selectedArticle, - let articleFlavor = article.articleFlavors?.first, - let articleID = article.articleID { - Task { - if let articleText = await self.model.getNewsArticle(id: articleID, at: article.path, flavor: articleFlavor) { - self.articleText = articleText - } - } - } - } - } - .onKeyPress(.rightArrow) { - if let s = selection, s.type == .bundle || s.type == .category { - s.expanded = true - return .handled - } - return .ignored - } - .onKeyPress(.leftArrow) { - if let s = selection, s.type == .bundle || s.type == .category { - s.expanded = false - return .handled - } - return .ignored - } - .overlay { - if !model.newsLoaded { - VStack { - ProgressView() - .controlSize(.large) - } - .frame(maxWidth: .infinity) - } - } - - // MARK: Article Viewer - ScrollView { - VStack(alignment: .leading, spacing: 0) { - if let news = selection { - if news.type == .article { - -// Text(news.name).font(.title) -// .textSelection(.enabled) -// .padding(.bottom, 8) - - if let poster = news.articleUsername, let postDate = news.articleDate { - HStack(alignment: .firstTextBaseline) { - Text(poster) - .foregroundStyle(.secondary) - .lineLimit(1) - .truncationMode(.tail) - .textSelection(.enabled) - .padding(.bottom, 16) - Spacer() - Text("\(NewsItemView.dateFormatter.string(from: postDate))") - .foregroundStyle(.secondary) - .lineLimit(1) - .truncationMode(.tail) - .textSelection(.enabled) - .padding(.bottom, 16) - } - } - - Divider() - - if let newsText = self.articleText { - Markdown(newsText) -// Text(newsText) - .textSelection(.enabled) - .lineSpacing(4) - .padding(.top, 16) - } - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding() - } - .frame(maxWidth: .infinity, minHeight: 200) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(nsColor: .textBackgroundColor)) - } - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button { - - } label: { - Image(systemName: "square.and.pencil") - } - } - - ToolbarItem(placement: .primaryAction) { - Button { - - } label: { - Image(systemName: "arrowshape.turn.up.left") - } - } - -// if let bannerImage = model.bannerImage { -// ToolbarItem(placement: .primaryAction) { -//// HStack { -// bannerImage -// .resizable() -// .aspectRatio(contentMode: .fit) -// .clipped() -// .frame(minHeight: 60, alignment: .topLeading -// ) -// .clipped() -// } -// .frame(maxWidth: .infinity) -// .tag(MenuItem(name: "title", image: "", type: .banner)) -// .padding(.bottom, 16) - - // VStack(spacing: 0) { - // bannerImage - // .resizable() - // .aspectRatio(contentMode: .fit) - // .frame(maxWidth: .infinity, minHeight: 60, alignment: .topLeading) - // .clipped() - // } - // .frame(maxWidth: .infinity) -// } -// } - } - } -} - #Preview { NewsView() .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient())) diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift index 366fff2..b21e4fb 100644 --- a/Hotline/macOS/ServerView.swift +++ b/Hotline/macOS/ServerView.swift @@ -171,88 +171,107 @@ struct ServerView: View { MenuItem(name: "Files", image: "folder", type: .files), ] + enum FocusFields { + case address + case login + case password + } + + @FocusState private var focusedField: FocusFields? + var connectForm: some View { - GroupBox { - Form { - Group { - TextField(text: $connectAddress) { - Text("Address:") - } - TextField(text: $connectLogin, prompt: Text("optional")) { - Text("Login:") + VStack(alignment: .center) { + GroupBox { + Form { + Group { + TextField(text: $connectAddress) { + Text("Address:") + } + .focused($focusedField, equals: .address) + + Text("Type the address of the Hotline server you would like to connect to. If you have an account on that server, type your login and password too.") + .font(.caption) + .foregroundStyle(.secondary) + + TextField(text: $connectLogin, prompt: Text("optional")) { + Text("Login:") + } + .focused($focusedField, equals: .login) + SecureField(text: $connectPassword, prompt: Text("optional")) { + Text("Password:") + } + .focused($focusedField, equals: .password) } - SecureField(text: $connectPassword, prompt: Text("optional")) { - Text("Password:") + .textFieldStyle(.roundedBorder) + .controlSize(.regular) + + HStack { + Button { + print("SAVE BOOKMARK... SOMEHOW") + } label: { + Text("Save...") + } + .controlSize(.regular) + .buttonStyle(.automatic) + .help("Save server as bookmark") + + Spacer() + + Button { + dismiss() + } label: { + Text("Cancel") + } + .controlSize(.regular) + .buttonStyle(.automatic) + .keyboardShortcut(.cancelAction) + + Button { + + // if var s = server { + // print("CHANGING EXISTING SERVER") + // s.name = newServer.name + // s.description = newServer.description + // s.users = newServer.users + // s.address = newServer.address + // s.port = newServer.port + // s.login = newServer.login + // s.password = newServer.password + // } + // else { + // server = newServer + // } + + Task { + await connectToServer() + } + } label: { + Text("Connect") + } + .controlSize(.regular) + .buttonStyle(.automatic) + .keyboardShortcut(.defaultAction) } + .padding(.top, 8) + } - .textFieldStyle(.roundedBorder) - .controlSize(.regular) + .padding() .onChange(of: connectAddress) { let (a, p) = Server.parseServerAddressAndPort(connectAddress) server.address = a server.port = p - print("ADDRESS CHANGED: '\(connectAddress)' \(a) \(p)") } .onChange(of: connectLogin) { server.login = connectLogin.trimmingCharacters(in: .whitespacesAndNewlines) - print("LOGIN CHANGED: '\(connectLogin)'" + $server.wrappedValue.login) } .onChange(of: connectPassword) { server.password = connectPassword - print("PASS CHANGED: '\(connectPassword)'" + server.password) - } - - HStack { - Button { - print("SAVE BOOKMARK... SOMEHOW") - } label: { - Text("Save...") - } - .controlSize(.regular) - .buttonStyle(.automatic) - .help("Save server as bookmark") - - Spacer() - - Button { - dismiss() - } label: { - Text("Cancel") - } - .controlSize(.regular) - .buttonStyle(.automatic) - .keyboardShortcut(.cancelAction) - - Button { - -// if var s = server { -// print("CHANGING EXISTING SERVER") -// s.name = newServer.name -// s.description = newServer.description -// s.users = newServer.users -// s.address = newServer.address -// s.port = newServer.port -// s.login = newServer.login -// s.password = newServer.password -// } -// else { -// server = newServer -// } - - Task { - await connectToServer() - } - } label: { - Text("Connect") - } - .controlSize(.regular) - .buttonStyle(.automatic) - .keyboardShortcut(.defaultAction) } - .padding(.top, 8) - } - .padding() +// .padding(.top, 8) + .onAppear { + focusedField = .address + } } .frame(maxWidth: 350) .padding() |