diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-01-09 10:30:44 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-01-09 10:30:44 -0800 |
| commit | 382121de2e79303845331d699adcdd777f8f062a (patch) | |
| tree | 83bab877069d3a0daddcb6c2a031400b99c60756 /Hotline/macOS | |
| parent | 0910d3380755d38a93e5b87a31ecf31f17d0bc58 (diff) | |
Split view in News saves position and hiding when an article isn't selected. Some code cleanup. Some plumbing for news posting though it's not hooked up yet.
Diffstat (limited to 'Hotline/macOS')
| -rw-r--r-- | Hotline/macOS/FilePreviewTextView.swift | 2 | ||||
| -rw-r--r-- | Hotline/macOS/NewsEditorView.swift | 100 | ||||
| -rw-r--r-- | Hotline/macOS/NewsView.swift | 358 | ||||
| -rw-r--r-- | Hotline/macOS/TrackerView.swift | 2 |
4 files changed, 288 insertions, 174 deletions
diff --git a/Hotline/macOS/FilePreviewTextView.swift b/Hotline/macOS/FilePreviewTextView.swift index 4c81e6f..8b5b4f3 100644 --- a/Hotline/macOS/FilePreviewTextView.swift +++ b/Hotline/macOS/FilePreviewTextView.swift @@ -43,8 +43,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/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/TrackerView.swift b/Hotline/macOS/TrackerView.swift index 268cd3e..23e6da2 100644 --- a/Hotline/macOS/TrackerView.swift +++ b/Hotline/macOS/TrackerView.swift @@ -237,7 +237,7 @@ struct TrackerView: View { var bookmarks: [TrackerBookmark] = [ TrackerBookmark(type: .server, name: "RetroMac", address: "82.32.130.173"), - TrackerBookmark(type: .server, name: "System 7 Today", address: "158.174.146.146"), + TrackerBookmark(type: .server, name: "System 7 Today", address: "62.116.228.147"), TrackerBookmark(type: .tracker, name: "Featured Servers", address: "hltracker.com"), TrackerBookmark(type: .tracker, name: "Agent79", address: "tracked.agent79.org"), TrackerBookmark(type: .tracker, name: "Preterhuman", address: "tracker.preterhuman.net"), |