Improve content saving, label editing
This commit is contained in:
parent
fea06a93b7
commit
1f4f32c9af
@ -204,6 +204,9 @@
|
||||
E2EC1FB42DC0FA8700C41784 /* Insert+Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EC1FB32DC0FA6D00C41784 /* Insert+Route.swift */; };
|
||||
E2F3B3832DC496CB00CFA712 /* GalleryBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3822DC496C800CFA712 /* GalleryBlock.swift */; };
|
||||
E2F3B3852DC49B7A00CFA712 /* Insert+Gallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3842DC49B4400CFA712 /* Insert+Gallery.swift */; };
|
||||
E2F3B3982DC54F9400CFA712 /* ChangeObservingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3972DC54F8600CFA712 /* ChangeObservingItem.swift */; };
|
||||
E2F3B39C2DC5542E00CFA712 /* LabelEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B39B2DC5542E00CFA712 /* LabelEditingView.swift */; };
|
||||
E2F3B39E2DC55B1C00CFA712 /* LabelCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */; };
|
||||
E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */; };
|
||||
E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */; };
|
||||
E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */; };
|
||||
@ -484,6 +487,9 @@
|
||||
E2EC1FB32DC0FA6D00C41784 /* Insert+Route.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Insert+Route.swift"; sourceTree = "<group>"; };
|
||||
E2F3B3822DC496C800CFA712 /* GalleryBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryBlock.swift; sourceTree = "<group>"; };
|
||||
E2F3B3842DC49B4400CFA712 /* Insert+Gallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Insert+Gallery.swift"; sourceTree = "<group>"; };
|
||||
E2F3B3972DC54F8600CFA712 /* ChangeObservingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeObservingItem.swift; sourceTree = "<group>"; };
|
||||
E2F3B39B2DC5542E00CFA712 /* LabelEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelEditingView.swift; sourceTree = "<group>"; };
|
||||
E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCreationView.swift; sourceTree = "<group>"; };
|
||||
E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = "<group>"; };
|
||||
E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingContext.swift; sourceTree = "<group>"; };
|
||||
E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewDetailView.swift; sourceTree = "<group>"; };
|
||||
@ -676,6 +682,7 @@
|
||||
E229901A2D0E3F09009F8D77 /* Item */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2F3B3972DC54F8600CFA712 /* ChangeObservingItem.swift */,
|
||||
E2FD1D1E2D2E9CBE00B48627 /* ItemId.swift */,
|
||||
E2FD1D1C2D2DE31600B48627 /* ItemType.swift */,
|
||||
E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */,
|
||||
@ -995,6 +1002,7 @@
|
||||
E2B85F4B2C4B8B7F0047CD0C /* Posts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2F3B39B2DC5542E00CFA712 /* LabelEditingView.swift */,
|
||||
E2FD1D632D47EF4200B48627 /* DetailListItem.swift */,
|
||||
E2FD1D452D46427B00B48627 /* PageIconView.swift */,
|
||||
E2FD1D3E2D46404900B48627 /* PostLabelsView.swift */,
|
||||
@ -1101,6 +1109,7 @@
|
||||
E2FD1D352D3BBCAF00B48627 /* Commands */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */,
|
||||
E2BF1BC72D6FC87C003089F1 /* Insert+Link.swift */,
|
||||
E2FD1D592D477AB200B48627 /* InsertableItemsView.swift */,
|
||||
E2FD1D572D477A9400B48627 /* InsertableCommand.swift */,
|
||||
@ -1412,10 +1421,12 @@
|
||||
E229901E2D0E4364009F8D77 /* LocalizedItem.swift in Sources */,
|
||||
E29D31262D0370A80051B7F4 /* VideoCommand+Option.swift in Sources */,
|
||||
E2FE0EF82D1D8110002963B7 /* IconCommand.swift in Sources */,
|
||||
E2F3B39E2DC55B1C00CFA712 /* LabelCreationView.swift in Sources */,
|
||||
E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */,
|
||||
E22990242D0EDBD0009F8D77 /* HeaderElement.swift in Sources */,
|
||||
E2BF1BCA2D70EDF8003089F1 /* TagPropertyView.swift in Sources */,
|
||||
E29D31BC2D0DB5120051B7F4 /* CommandProcessor.swift in Sources */,
|
||||
E2F3B39C2DC5542E00CFA712 /* LabelEditingView.swift in Sources */,
|
||||
E2FE0F662D2C3B3A002963B7 /* LabelsBlock.swift in Sources */,
|
||||
E20BCCAF2D53F4A500B8DBEB /* GenerationStringIssuesView.swift in Sources */,
|
||||
E29D312C2D039DB80051B7F4 /* PageDetailView.swift in Sources */,
|
||||
@ -1508,6 +1519,7 @@
|
||||
E2FD1D1F2D2E9CC200B48627 /* ItemId.swift in Sources */,
|
||||
E2FE0EFA2D25AFBA002963B7 /* PageHeader.swift in Sources */,
|
||||
E25DA5252CFF73AB00AEF16D /* NSSize+Scaling.swift in Sources */,
|
||||
E2F3B3982DC54F9400CFA712 /* ChangeObservingItem.swift in Sources */,
|
||||
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */,
|
||||
E2FD1D642D47EF4200B48627 /* DetailListItem.swift in Sources */,
|
||||
E2FE0F0B2D2689FF002963B7 /* FeedGeneratorSource.swift in Sources */,
|
||||
|
@ -184,7 +184,9 @@ final class Content: ObservableObject {
|
||||
private(set) var lastModification: Date = .now
|
||||
|
||||
func update(saveState: SaveState) {
|
||||
self.saveState = saveState
|
||||
DispatchQueue.main.async {
|
||||
self.saveState = saveState
|
||||
}
|
||||
}
|
||||
|
||||
func setModificationTimestamp() {
|
||||
|
@ -1,17 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
final class ContentLabel: ObservableObject {
|
||||
struct ContentLabel {
|
||||
|
||||
@Published
|
||||
var icon: PageIcon
|
||||
|
||||
@Published
|
||||
var value: String
|
||||
|
||||
init(icon: PageIcon, value: String) {
|
||||
self.icon = icon
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentLabel: Equatable {
|
||||
@ -34,7 +27,7 @@ extension ContentLabel {
|
||||
.init(icon: icon.rawValue, value: value)
|
||||
}
|
||||
|
||||
convenience init?(context: LoadingContext, data: Data) {
|
||||
init?(context: LoadingContext, data: Data) {
|
||||
guard let icon = PageIcon(rawValue: data.icon) else {
|
||||
context.error("Unknown label icon '\(data.icon)'")
|
||||
return nil
|
||||
|
27
CHDataManagement/Model/Item/ChangeObservingItem.swift
Normal file
27
CHDataManagement/Model/Item/ChangeObservingItem.swift
Normal file
@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class ChangeObservingItem: ObservableContentItem {
|
||||
|
||||
unowned let content: Content
|
||||
|
||||
/// A dummy property to force views to update when properties change
|
||||
@Published
|
||||
private var changeToggle = false
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(content: Content) {
|
||||
self.content = content
|
||||
|
||||
observeChanges()
|
||||
}
|
||||
|
||||
// MARK: Change observation
|
||||
|
||||
func didChange() {
|
||||
DispatchQueue.main.async {
|
||||
self.changeToggle.toggle()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class Item: ObservableContentItem, Identifiable {
|
||||
|
||||
unowned let content: Content
|
||||
class Item: ChangeObservingItem, Identifiable {
|
||||
|
||||
/// A dummy property to force views to update when properties change
|
||||
@Published
|
||||
@ -12,23 +10,13 @@ class Item: ObservableContentItem, Identifiable {
|
||||
@Published
|
||||
var id: String
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(content: Content, id: String) {
|
||||
self.content = content
|
||||
self.id = id
|
||||
super.init(content: content)
|
||||
|
||||
observeChanges()
|
||||
}
|
||||
|
||||
// MARK: Change observation
|
||||
|
||||
func didChange() {
|
||||
DispatchQueue.main.async {
|
||||
self.changeToggle.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Paths
|
||||
|
||||
func makeCleanAbsolutePath(_ path: String) -> String {
|
||||
|
@ -6,9 +6,7 @@ import SwiftUI
|
||||
including the title, url path and required resources
|
||||
|
||||
*/
|
||||
final class LocalizedPage: ObservableObject {
|
||||
|
||||
unowned let content: Content
|
||||
final class LocalizedPage: ChangeObservingItem {
|
||||
|
||||
/**
|
||||
The string to use when creating the url for the page.
|
||||
@ -50,13 +48,13 @@ final class LocalizedPage: ObservableObject {
|
||||
originalUrl: String? = nil,
|
||||
linkPreview: LinkPreview = .init(),
|
||||
hideTitle: Bool = false) {
|
||||
self.content = content
|
||||
self.urlString = urlString
|
||||
self.title = title
|
||||
self.lastModified = lastModified
|
||||
self.originalUrl = originalUrl
|
||||
self.linkPreview = linkPreview
|
||||
self.hideTitle = hideTitle
|
||||
super.init(content: content)
|
||||
}
|
||||
|
||||
func isValid(urlComponent: String) -> Bool {
|
||||
|
@ -1,9 +1,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class LocalizedPost: ObservableObject {
|
||||
|
||||
unowned let content: Content
|
||||
final class LocalizedPost: ChangeObservingItem {
|
||||
|
||||
@Published
|
||||
var title: String?
|
||||
@ -36,7 +34,6 @@ final class LocalizedPost: ObservableObject {
|
||||
labels: [ContentLabel] = [],
|
||||
pageLinkText: String? = nil,
|
||||
linkPreview: LinkPreview = .init()) {
|
||||
self.content = content
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.lastModified = lastModified
|
||||
@ -44,6 +41,7 @@ final class LocalizedPost: ObservableObject {
|
||||
self.labels = labels
|
||||
self.pageLinkText = pageLinkText
|
||||
self.linkPreview = linkPreview
|
||||
super.init(content: content)
|
||||
}
|
||||
|
||||
func contains(_ string: String) -> Bool {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
final class LocalizedTag: ObservableObject {
|
||||
|
||||
unowned let content: Content
|
||||
final class LocalizedTag: ChangeObservingItem {
|
||||
|
||||
@Published
|
||||
var urlComponent: String
|
||||
@ -22,11 +20,11 @@ final class LocalizedTag: ObservableObject {
|
||||
name: String,
|
||||
linkPreview: LinkPreview = .init(),
|
||||
originalUrl: String? = nil) {
|
||||
self.content = content
|
||||
self.urlComponent = urlComponent
|
||||
self.name = name
|
||||
self.linkPreview = linkPreview
|
||||
self.originalUrl = originalUrl
|
||||
super.init(content: content)
|
||||
}
|
||||
|
||||
func isValid(urlComponent: String) -> Bool {
|
||||
|
@ -37,7 +37,7 @@ enum RouteStatisticType: String, CaseIterable {
|
||||
case .speed: "km/h"
|
||||
case .pace: "min/km"
|
||||
case .heartRate: "bpm"
|
||||
case .energy: "kcal"
|
||||
case .energy: "kcal/min"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,14 @@ struct UploadSheet: View {
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
private let lineLimit = 4
|
||||
|
||||
@State
|
||||
private var output: [String] = ["Ready to upload"]
|
||||
private var output: [String]
|
||||
|
||||
init(output: [String] = ["Ready to upload", "", "", ""]) {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
private var uploadSymbol: SFSymbol {
|
||||
if upload.isTransmittingToRemote {
|
||||
@ -34,7 +40,7 @@ struct UploadSheet: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Button("Upload", action: startUpload)
|
||||
.disabled(upload.isTransmittingToRemote)
|
||||
@ -42,12 +48,26 @@ struct UploadSheet: View {
|
||||
Spacer()
|
||||
Button("Close", action: { dismiss() })
|
||||
}
|
||||
ScrollView {
|
||||
Text(output.joined(separator: "\n"))
|
||||
.font(.body.monospaced())
|
||||
.foregroundStyle(.primary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
VStack(alignment: .leading) {
|
||||
Text(output[0])
|
||||
Text(output[1])
|
||||
Text(output[2])
|
||||
Text(output[3])
|
||||
}
|
||||
.font(.body.monospaced())
|
||||
.lineLimit(1)
|
||||
// TextField("", text: .constant(output.joined(separator: "\n")))
|
||||
// .font(.body.monospaced())
|
||||
// .textFieldStyle(.plain)
|
||||
// .lineLimit(lineLimit)
|
||||
// .disabled(true)
|
||||
// .frame(minHeight: 150)
|
||||
// ScrollView {
|
||||
// Text(output.joined(separator: "\n"))
|
||||
// .font(.body.monospaced())
|
||||
// .foregroundStyle(.primary)
|
||||
// .frame(maxWidth: .infinity, alignment: .leading)
|
||||
// }
|
||||
}
|
||||
.padding()
|
||||
.frame(minWidth: 500, idealWidth: 600)
|
||||
@ -55,18 +75,28 @@ struct UploadSheet: View {
|
||||
|
||||
private func startUpload() {
|
||||
guard let folder = content.storage.outputScope?.url.path() else {
|
||||
output = ["No output folder to start upload"]
|
||||
output = ["No output folder to start upload", "", "", ""]
|
||||
return
|
||||
}
|
||||
output = ["Starting upload..."]
|
||||
output = ["Starting upload...", "", "", ""]
|
||||
|
||||
upload.transmitToRemote(
|
||||
settings: content.settings.general,
|
||||
outputFolder: folder) { newContent in
|
||||
DispatchQueue.main.async {
|
||||
let newLines = newContent.components(separatedBy: "\n").suffix(4)
|
||||
self.output = (self.output + newLines).suffix(4)
|
||||
let newLines = newContent.components(separatedBy: "\n").suffix(lineLimit)
|
||||
if newLines.count >= lineLimit {
|
||||
self.output = newLines.suffix(lineLimit)
|
||||
} else {
|
||||
self.output = (self.output + newLines).suffix(lineLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
UploadSheet(output: [
|
||||
"Some very long text that should cause the view to scroll", "More", "Some", "Yes"
|
||||
])
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ private struct FileButtonView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
LabelEditingView(label: content.label)
|
||||
LabelEditingView(label: $content.label)
|
||||
Button("\(content.file?.id ?? "Select file")", action: { showFileSelectionSheet = true })
|
||||
OptionalTextField("", text: $content.downloadedFileName, prompt: "Downloaded file name")
|
||||
.textFieldStyle(.roundedBorder)
|
||||
@ -178,7 +178,7 @@ private struct UrlButtonView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
LabelEditingView(label: content.label)
|
||||
LabelEditingView(label: $content.label)
|
||||
TextField("", text: $content.url, prompt: Text("URL"))
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
@ -192,7 +192,7 @@ private struct EventButtonView: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
LabelEditingView(label: content.label)
|
||||
LabelEditingView(label: $content.label)
|
||||
TextField("", text: $content.event, prompt: Text("Javascript"))
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
|
@ -35,47 +35,15 @@ struct InsertableLabels: View, InsertableCommandView {
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(\.colorScheme)
|
||||
private var colorScheme
|
||||
|
||||
@ObservedObject
|
||||
private var model: Model
|
||||
|
||||
|
||||
|
||||
init(model: Model) {
|
||||
self.model = model
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 2) {
|
||||
ForEach(model.labels, id: \.icon) { label in
|
||||
HStack {
|
||||
Button(action: { remove(label) }) {
|
||||
Image(systemSymbol: .minusCircleFill)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
LabelEditingView(label: label)
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 8)
|
||||
.background(colorScheme == .light ? Color.white : Color.black)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
Button("Add", action: addLabel)
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
LabelCreationView(labels: $model.labels)
|
||||
}
|
||||
|
||||
private func addLabel() {
|
||||
model.labels.append(.init(icon: .clockFill, value: "Value"))
|
||||
}
|
||||
|
||||
private func remove(_ label: ContentLabel) {
|
||||
guard let index = model.labels.firstIndex(of: label) else {
|
||||
return
|
||||
}
|
||||
model.labels.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LabelCreationView: View {
|
||||
|
||||
@Environment(\.colorScheme)
|
||||
private var colorScheme
|
||||
|
||||
@Binding
|
||||
var labels: [ContentLabel]
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach($labels) { label in
|
||||
HStack {
|
||||
Button(action: { remove(label.wrappedValue) }) {
|
||||
Image(systemSymbol: .minusCircleFill)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
LabelEditingView(label: label)
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 8)
|
||||
.background(colorScheme == .light ? Color.white : Color.black)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.onMove(perform: moveLabel)
|
||||
Button("Add new label", action: addLabel)
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
.frame(minHeight: 250)
|
||||
}
|
||||
|
||||
private func addLabel() {
|
||||
var label = ContentLabel(icon: .statisticsTime, value: "Value")
|
||||
var number = 0
|
||||
while labels.contains(label) {
|
||||
number += 1
|
||||
label.value = "Value \(number)"
|
||||
}
|
||||
labels.append(label)
|
||||
}
|
||||
|
||||
private func remove(_ label: ContentLabel) {
|
||||
guard let index = labels.firstIndex(of: label) else {
|
||||
return
|
||||
}
|
||||
labels.remove(at: index)
|
||||
}
|
||||
|
||||
private func moveLabel(from source: IndexSet, to destination: Int) {
|
||||
labels.move(fromOffsets: source, toOffset: destination)
|
||||
}
|
||||
}
|
65
CHDataManagement/Views/Posts/LabelEditingView.swift
Normal file
65
CHDataManagement/Views/Posts/LabelEditingView.swift
Normal file
@ -0,0 +1,65 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LabelEditingView: View {
|
||||
|
||||
@Binding
|
||||
var label: ContentLabel
|
||||
|
||||
@State
|
||||
private var showIconPicker: Bool = false
|
||||
|
||||
let scale: CGFloat
|
||||
|
||||
init(label: Binding<ContentLabel>, scale: CGFloat = 1.0) {
|
||||
self._label = label
|
||||
self.scale = scale
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Button(action: { showIconPicker = true }) {
|
||||
PageIconView(icon: label.icon)
|
||||
.frame(maxWidth: 16, maxHeight: 16)
|
||||
.scaleEffect(scale)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
TextField("", text: $label.value)
|
||||
.textFieldStyle(.plain)
|
||||
}
|
||||
.sheet(isPresented: $showIconPicker) {
|
||||
LabelIconSelectionView(selected: $label.icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct LabelIconSelectionView: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
var dismiss
|
||||
|
||||
@Binding
|
||||
var selected: PageIcon
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List(PageIcon.allCases, id: \.rawValue) { icon in
|
||||
HStack {
|
||||
Image(systemSymbol: selected == icon ? .checkmarkCircleFill : .circle)
|
||||
PageIconView(icon: icon)
|
||||
.frame(maxWidth: 20, maxHeight: 20)
|
||||
Text(icon.name)
|
||||
Spacer()
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
selected = icon
|
||||
dismiss()
|
||||
}
|
||||
}.frame(minHeight: 300)
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
@ -1,61 +1,5 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LabelEditingView: View {
|
||||
|
||||
@ObservedObject
|
||||
var label: ContentLabel
|
||||
|
||||
@State
|
||||
private var showIconPicker: Bool = false
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Button(action: { showIconPicker = true }) {
|
||||
PageIconView(icon: label.icon)
|
||||
.frame(maxWidth: 20, maxHeight: 20)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
TextField("", text: $label.value)
|
||||
.textFieldStyle(.plain)
|
||||
}
|
||||
.sheet(isPresented: $showIconPicker) {
|
||||
LabelIconSelectionView(selected: $label.icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct LabelIconSelectionView: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
var dismiss
|
||||
|
||||
@Binding
|
||||
var selected: PageIcon
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List(PageIcon.allCases, id: \.rawValue) { icon in
|
||||
HStack {
|
||||
Image(systemSymbol: selected == icon ? .checkmarkCircleFill : .circle)
|
||||
PageIconView(icon: icon)
|
||||
.frame(maxWidth: 20, maxHeight: 20)
|
||||
Text(icon.name)
|
||||
Spacer()
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
selected = icon
|
||||
dismiss()
|
||||
}
|
||||
}.frame(minHeight: 300)
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct PostLabelsView: View {
|
||||
|
||||
@ObservedObject
|
||||
@ -67,26 +11,38 @@ struct PostLabelsView: View {
|
||||
@Environment(\.colorScheme)
|
||||
var colorScheme
|
||||
|
||||
@State
|
||||
private var showLabelEditor: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.horizontal) {
|
||||
HStack(spacing: 5) {
|
||||
Text("Labels")
|
||||
.font(.headline)
|
||||
ForEach(post.labels, id: \.icon) { label in
|
||||
if post.labels.isEmpty {
|
||||
Text("Labels")
|
||||
.font(.headline)
|
||||
}
|
||||
ForEach(post.labels) { label in
|
||||
HStack {
|
||||
Button(action: { remove(label) }) {
|
||||
Image(systemSymbol: .minusCircleFill)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
LabelEditingView(label: label)
|
||||
PageIconView(icon: label.icon)
|
||||
.frame(maxWidth: 16, maxHeight: 16)
|
||||
.scaleEffect(25/16)
|
||||
Text(label.value)
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 8)
|
||||
.background(colorScheme == .light ? Color.white : Color.black)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
Button("Add", action: addLabel)
|
||||
Button(action: { showLabelEditor = true }) {
|
||||
Image(systemSymbol: .squareAndPencilCircleFill)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 22)
|
||||
.foregroundColor(Color.gray)
|
||||
.background(Circle()
|
||||
.fill(Color.white)
|
||||
.padding(1))
|
||||
}.buttonStyle(.plain)
|
||||
if !other.labels.isEmpty {
|
||||
Button("Transfer") {
|
||||
post.labels = other.labels.map {
|
||||
@ -95,9 +51,24 @@ struct PostLabelsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !post.labels.isEmpty {
|
||||
Button("Copy") {
|
||||
var command = "```labels"
|
||||
for label in post.labels {
|
||||
command += "\n\(label.icon.rawValue): \(label.value)"
|
||||
}
|
||||
command += "\n```"
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
pasteboard.setString(command, forType: .string)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
.sheet(isPresented: $showLabelEditor) {
|
||||
LabelModificationView(labels: $post.labels)
|
||||
}
|
||||
}
|
||||
|
||||
func addLabel() {
|
||||
@ -111,3 +82,23 @@ struct PostLabelsView: View {
|
||||
post.labels.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
private struct LabelModificationView: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
@Binding
|
||||
var labels: [ContentLabel]
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Labels")
|
||||
.font(.title)
|
||||
LabelCreationView(labels: $labels)
|
||||
Button("Save") {
|
||||
dismiss()
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user