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/macOS | |
| parent | f198d4d59b922e9cb872fe6831d3475c570fa56f (diff) | |
| parent | 60f90281bf971600733b2d72b747bcb6be9b1c31 (diff) | |
Merge branch 'main' into patch-1
Diffstat (limited to 'Hotline/macOS')
| -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 |
8 files changed, 372 insertions, 245 deletions
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() |