aboutsummaryrefslogtreecommitdiff
path: root/Hotline/macOS
diff options
context:
space:
mode:
authorDustin Mierau <mierau@users.noreply.github.com>2024-01-09 14:44:14 -0800
committerGitHub <noreply@github.com>2024-01-09 14:44:14 -0800
commita2b7946804f5ab17ef1aebc4b5bb72e7cf14e83b (patch)
tree218012a1565033fa89a862a34a0683a9fad3d39b /Hotline/macOS
parentf198d4d59b922e9cb872fe6831d3475c570fa56f (diff)
parent60f90281bf971600733b2d72b747bcb6be9b1c31 (diff)
Merge branch 'main' into patch-1
Diffstat (limited to 'Hotline/macOS')
-rw-r--r--Hotline/macOS/ChatView.swift1
-rw-r--r--Hotline/macOS/FilePreviewImageView.swift1
-rw-r--r--Hotline/macOS/FilePreviewTextView.swift3
-rw-r--r--Hotline/macOS/FilesView.swift1
-rw-r--r--Hotline/macOS/MessageBoardView.swift2
-rw-r--r--Hotline/macOS/NewsEditorView.swift100
-rw-r--r--Hotline/macOS/NewsView.swift358
-rw-r--r--Hotline/macOS/ServerView.swift151
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()