-
-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move data format menu into format namespace
- Loading branch information
1 parent
ce84a69
commit c575a95
Showing
16 changed files
with
187 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// | ||
// RichTextDataFormat+Menu.swift | ||
// RichTextKit | ||
// | ||
// Created by Daniel Saidi on 2022-12-19. | ||
// Copyright © 2022-2024 Daniel Saidi. All rights reserved. | ||
// | ||
|
||
#if iOS || macOS || os(visionOS) | ||
import SwiftUI | ||
|
||
public extension RichTextDataFormat { | ||
|
||
/** | ||
This menu can be used to trigger custom actions for any | ||
list of ``RichTextDataFormat`` values. | ||
|
||
The menu uses customizable actions, which means that it | ||
can be used in toolbars, menu bar commands etc. It also | ||
has an optional `pdf` action, which for instance can be | ||
used when exporting or sharing rich text. | ||
*/ | ||
struct Menu: View { | ||
|
||
public init( | ||
title: String, | ||
icon: Image, | ||
formats: [Format] = Format.libraryFormats, | ||
formatAction: @escaping (Format) -> Void, | ||
pdfAction: (() -> Void)? = nil | ||
) { | ||
self.title = title | ||
self.icon = icon | ||
self.formats = formats | ||
self.formatAction = formatAction | ||
self.pdfAction = pdfAction | ||
} | ||
|
||
public typealias Format = RichTextDataFormat | ||
|
||
private let title: String | ||
private let icon: Image | ||
private let formats: [Format] | ||
private let formatAction: (Format) -> Void | ||
private let pdfAction: (() -> Void)? | ||
|
||
public var body: some View { | ||
SwiftUI.Menu { | ||
ForEach(formats) { format in | ||
Button { | ||
formatAction(format) | ||
} label: { | ||
Label(format.fileFormatText, icon) | ||
} | ||
} | ||
if let action = pdfAction { | ||
Button(action: action) { | ||
Label(RTKL10n.fileFormatPdf.text, icon) | ||
} | ||
} | ||
} label: { | ||
Label(title, icon) | ||
} | ||
} | ||
} | ||
} | ||
|
||
struct RichTextData_FormatMenu_Previews: PreviewProvider { | ||
|
||
static var previews: some View { | ||
VStack { | ||
RichTextDataFormat.Menu( | ||
title: "Export...", | ||
icon: .richTextActionExport, | ||
formatAction: { _ in }, | ||
pdfAction: {} | ||
) | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,140 +1,139 @@ | ||
// | ||
// RichTextDataFormat.swift | ||
// RichTextData+Format.swift | ||
// RichTextKit | ||
// | ||
// Created by Daniel Saidi on 2022-06-02. | ||
// Copyright © 2022-2023 Daniel Saidi. All rights reserved. | ||
// Copyright © 2022-2024 Daniel Saidi. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import UniformTypeIdentifiers | ||
|
||
/** | ||
This enum specifies rich text data formats. | ||
|
||
Different formats handle rich text in different ways. | ||
|
||
For instance, ``rtf`` supports rich text attributes, styles, | ||
formatting etc. while ``plainText`` only handles plain text. | ||
``archivedData`` lets you archive text and attachments into | ||
a binary archive, which is convenient if you stick to Apple | ||
platforms, but restricts how the data can be used elsewhere. | ||
|
||
The ``archivedData`` format uses `rtk` a file extension, as | ||
well as a `UTType.archivedData` uniform type. You can use a | ||
``RichTextDataFormat/vendorArchivedData(id:fileExtension:fileFormatText:uniformType:)`` | ||
value to specify custom formats. | ||
|
||
|
||
Different formats have different capabilities. For instance, | ||
``rtf`` supports rich text, styles, etc., while ``plainText`` | ||
only handles text. ``archivedData`` can archive text, image | ||
data and attachments in binary archives. This is convenient | ||
when only targeting Apple platforms, but restricts how data | ||
can be used elsewhere. | ||
|
||
``archivedData`` uses an `rtk` file extension, as well as a | ||
`UTType.archivedData` uniform type. You can create a custom ``vendorArchivedData(id:fileExtension:fileFormatText:uniformType:)`` | ||
value to specify a custom data format. | ||
|
||
Remember to configure your app for handling the UTTypes you | ||
want to support, as well as the file extensions you want to | ||
open with the app. Have a look at the demo app for examples. | ||
open with the app. Take a look at the demo app for examples. | ||
*/ | ||
public enum RichTextDataFormat: Equatable, Identifiable { | ||
|
||
/// Archived data that's persisted with a keyed archiver. | ||
case archivedData | ||
|
||
/// Plain data is persisted as plain text. | ||
case plainText | ||
|
||
/// RTF data is persisted as formatted text. | ||
case rtf | ||
|
||
/// A vendor-specific archived data format. | ||
case vendorArchivedData( | ||
id: String, | ||
fileExtension: String, | ||
fileFormatText: String, | ||
uniformType: UTType) | ||
uniformType: UTType | ||
) | ||
} | ||
|
||
public extension Collection where Element == RichTextDataFormat { | ||
|
||
/// Get all library supported data formats. | ||
static var libraryFormats: [RichTextDataFormat] { | ||
RichTextDataFormat.libraryFormats | ||
static var libraryFormats: [Element] { | ||
Element.libraryFormats | ||
} | ||
} | ||
|
||
public extension RichTextDataFormat { | ||
|
||
/// Get all library supported data formats. | ||
static var libraryFormats: [RichTextDataFormat] { | ||
static var libraryFormats: [Self] { | ||
[.archivedData, .plainText, .rtf] | ||
} | ||
|
||
/// The format's unique identifier. | ||
var id: String { | ||
switch self { | ||
case .archivedData: return "archivedData" | ||
case .plainText: return "plainText" | ||
case .rtf: return "rtf" | ||
case .vendorArchivedData(let id, _, _, _): return id | ||
case .archivedData: "archivedData" | ||
case .plainText: "plainText" | ||
case .rtf: "rtf" | ||
case .vendorArchivedData(let id, _, _, _): id | ||
} | ||
} | ||
|
||
/// The formats that a format can be converted to. | ||
var convertibleFormats: [RichTextDataFormat] { | ||
var convertibleFormats: [Self] { | ||
switch self { | ||
case .vendorArchivedData: return Self.libraryFormats.removing(.archivedData) | ||
default: return Self.libraryFormats.removing(self) | ||
case .vendorArchivedData: Self.libraryFormats.removing(.archivedData) | ||
default: Self.libraryFormats.removing(self) | ||
} | ||
} | ||
|
||
/// The format's file format display text. | ||
var fileFormatText: String { | ||
switch self { | ||
case .archivedData: return RTKL10n.fileFormatRtk.text | ||
case .plainText: return RTKL10n.fileFormatTxt.text | ||
case .rtf: return RTKL10n.fileFormatRtf.text | ||
case .vendorArchivedData(_, _, let text, _): return text | ||
case .archivedData: RTKL10n.fileFormatRtk.text | ||
case .plainText: RTKL10n.fileFormatTxt.text | ||
case .rtf: RTKL10n.fileFormatRtf.text | ||
case .vendorArchivedData(_, _, let text, _): text | ||
} | ||
} | ||
|
||
/// Whether or not the format is an archived data type. | ||
var isArchivedDataFormat: Bool { | ||
switch self { | ||
case .archivedData: return true | ||
case .plainText: return false | ||
case .rtf: return false | ||
case .vendorArchivedData: return true | ||
case .archivedData: true | ||
case .plainText: false | ||
case .rtf: false | ||
case .vendorArchivedData: true | ||
} | ||
} | ||
|
||
/// The format's standard file extension. | ||
var standardFileExtension: String { | ||
switch self { | ||
case .archivedData: return "rtk" | ||
case .plainText: return "txt" | ||
case .rtf: return "rtf" | ||
case .vendorArchivedData(_, let ext, _, _): return ext | ||
case .archivedData: "rtk" | ||
case .plainText: "txt" | ||
case .rtf: "rtf" | ||
case .vendorArchivedData(_, let ext, _, _): ext | ||
} | ||
} | ||
|
||
/// Whether or not the format supports images. | ||
var supportsImages: Bool { | ||
switch self { | ||
case .archivedData: return true | ||
case .plainText: return false | ||
case .rtf: return false | ||
case .vendorArchivedData: return true | ||
case .archivedData: true | ||
case .plainText: false | ||
case .rtf: false | ||
case .vendorArchivedData: true | ||
} | ||
} | ||
|
||
/// The format's uniform type. | ||
var uniformType: UTType { | ||
switch self { | ||
case .archivedData: return .archivedData | ||
case .plainText: return .plainText | ||
case .rtf: return .rtf | ||
case .vendorArchivedData(_, _, _, let type): return type | ||
case .archivedData: .archivedData | ||
case .plainText: .plainText | ||
case .rtf: .rtf | ||
case .vendorArchivedData(_, _, _, let type): type | ||
} | ||
} | ||
} | ||
|
||
private extension Collection where Element == RichTextDataFormat { | ||
|
||
func removing(_ format: RichTextDataFormat) -> [RichTextDataFormat] { | ||
func removing(_ format: Element) -> [Element] { | ||
filter { $0 != format } | ||
} | ||
} |
Oops, something went wrong.