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