Add save file option for profile sharing
Add "Save File" and "Save Content JSON" options to profile share menu, allowing users to save profiles directly to a chosen location using fileExporter instead of the system share sheet.
This commit is contained in:
@@ -533,8 +533,19 @@ private struct ProfilePickerRow: View {
|
||||
@State private var showQRCode = false
|
||||
#if os(macOS)
|
||||
@State private var shareItemType: ShareItemType?
|
||||
@State private var exportItemType: ExportItemType?
|
||||
@State private var profileExportDocument: ProfileExportDocument?
|
||||
@State private var showProfileExporter = false
|
||||
@State private var profileJSONExportDocument: ProfileJSONExportDocument?
|
||||
@State private var showJSONExporter = false
|
||||
@State private var menuAnchorView: NSView?
|
||||
#endif
|
||||
#if os(iOS)
|
||||
@State private var profileExportDocument: ProfileExportDocument?
|
||||
@State private var showProfileExporter = false
|
||||
@State private var profileJSONExportDocument: ProfileJSONExportDocument?
|
||||
@State private var showJSONExporter = false
|
||||
#endif
|
||||
|
||||
var body: some View {
|
||||
#if os(tvOS)
|
||||
@@ -697,6 +708,28 @@ private struct ProfilePickerRow: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $showProfileExporter,
|
||||
document: profileExportDocument,
|
||||
contentType: .profile,
|
||||
defaultFilename: profileExportDocument?.filename
|
||||
) { result in
|
||||
profileExportDocument = nil
|
||||
if case let .failure(error) = result {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $showJSONExporter,
|
||||
document: profileJSONExportDocument,
|
||||
contentType: .json,
|
||||
defaultFilename: profileJSONExportDocument?.filename
|
||||
) { result in
|
||||
profileJSONExportDocument = nil
|
||||
if case let .failure(error) = result {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var macOSEditingBody: some View {
|
||||
@@ -720,6 +753,28 @@ private struct ProfilePickerRow: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $showProfileExporter,
|
||||
document: profileExportDocument,
|
||||
contentType: .profile,
|
||||
defaultFilename: profileExportDocument?.filename
|
||||
) { result in
|
||||
profileExportDocument = nil
|
||||
if case let .failure(error) = result {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $showJSONExporter,
|
||||
document: profileJSONExportDocument,
|
||||
contentType: .json,
|
||||
defaultFilename: profileJSONExportDocument?.filename
|
||||
) { result in
|
||||
profileJSONExportDocument = nil
|
||||
if case let .failure(error) = result {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -828,6 +883,11 @@ private struct ProfilePickerRow: View {
|
||||
self.shareItemType = nil
|
||||
shareProfile(type: shareItemType)
|
||||
}
|
||||
.onChange(of: exportItemType) { exportItemType in
|
||||
guard let exportItemType else { return }
|
||||
self.exportItemType = nil
|
||||
exportProfileMacOS(type: exportItemType)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -836,17 +896,53 @@ private struct ProfilePickerRow: View {
|
||||
private var shareMenu: some View {
|
||||
Menu {
|
||||
#if os(macOS)
|
||||
Button {
|
||||
exportItemType = .file
|
||||
} label: {
|
||||
Label("Save File", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
|
||||
Button {
|
||||
shareItemType = .file
|
||||
} label: {
|
||||
Label("Share File", systemImage: "doc")
|
||||
}
|
||||
|
||||
Button {
|
||||
exportItemType = .json
|
||||
} label: {
|
||||
Label("Save Content JSON", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
|
||||
Button {
|
||||
shareItemType = .json
|
||||
} label: {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
}
|
||||
#else
|
||||
Button {
|
||||
exportProfile(type: .file)
|
||||
} label: {
|
||||
Label("Save File", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share File", systemImage: "doc")
|
||||
} itemURL: {
|
||||
try profile.origin.toContent().generateShareFile()
|
||||
}
|
||||
|
||||
Button {
|
||||
exportProfile(type: .json)
|
||||
} label: {
|
||||
Label("Save Content JSON", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
} itemURL: {
|
||||
try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
}
|
||||
#endif
|
||||
|
||||
if profile.type == .remote {
|
||||
@@ -856,24 +952,25 @@ private struct ProfilePickerRow: View {
|
||||
Label("Share URL as QR Code", systemImage: "qrcode")
|
||||
}
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
Button {
|
||||
shareItemType = .json
|
||||
} label: {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
}
|
||||
#else
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
} itemURL: {
|
||||
try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
}
|
||||
#endif
|
||||
} label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
|
||||
private func exportProfile(type: ExportItemType) {
|
||||
do {
|
||||
switch type {
|
||||
case .file:
|
||||
profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
|
||||
showProfileExporter = true
|
||||
case .json:
|
||||
profileJSONExportDocument = ProfileJSONExportDocument(jsonContent: try profile.origin.read(), name: profile.name)
|
||||
showJSONExporter = true
|
||||
}
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private var profileInfo: some View {
|
||||
@@ -921,6 +1018,21 @@ private struct ProfilePickerRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func exportProfileMacOS(type: ExportItemType) {
|
||||
do {
|
||||
switch type {
|
||||
case .file:
|
||||
profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
|
||||
showProfileExporter = true
|
||||
case .json:
|
||||
profileJSONExportDocument = ProfileJSONExportDocument(jsonContent: try profile.origin.read(), name: profile.name)
|
||||
showJSONExporter = true
|
||||
}
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
static func previewContent(profile: ProfilePreview, width: CGFloat) -> some View {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: "line.3.horizontal")
|
||||
@@ -965,6 +1077,15 @@ private struct ProfilePickerRow: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Export Helpers
|
||||
|
||||
#if !os(tvOS)
|
||||
private enum ExportItemType {
|
||||
case file
|
||||
case json
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - macOS Helpers
|
||||
|
||||
#if os(macOS)
|
||||
@@ -1015,6 +1136,10 @@ private struct ProfilePickerRow: View {
|
||||
|
||||
@State private var isUpdating = false
|
||||
@State private var showQRCode = false
|
||||
@State private var profileExportDocument: ProfileExportDocument?
|
||||
@State private var showProfileExporter = false
|
||||
@State private var profileJSONExportDocument: ProfileJSONExportDocument?
|
||||
@State private var showJSONExporter = false
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -1072,6 +1197,28 @@ private struct ProfilePickerRow: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $showProfileExporter,
|
||||
document: profileExportDocument,
|
||||
contentType: .profile,
|
||||
defaultFilename: profileExportDocument?.filename
|
||||
) { result in
|
||||
profileExportDocument = nil
|
||||
if case let .failure(error) = result {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $showJSONExporter,
|
||||
document: profileJSONExportDocument,
|
||||
contentType: .json,
|
||||
defaultFilename: profileJSONExportDocument?.filename
|
||||
) { result in
|
||||
profileJSONExportDocument = nil
|
||||
if case let .failure(error) = result {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var rowMenu: some View {
|
||||
@@ -1115,12 +1262,30 @@ private struct ProfilePickerRow: View {
|
||||
@ViewBuilder
|
||||
private var shareMenu: some View {
|
||||
Menu {
|
||||
Button {
|
||||
exportProfile(type: .file)
|
||||
} label: {
|
||||
Label("Save File", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share File", systemImage: "doc")
|
||||
} itemURL: {
|
||||
try profile.origin.toContent().generateShareFile()
|
||||
}
|
||||
|
||||
Button {
|
||||
exportProfile(type: .json)
|
||||
} label: {
|
||||
Label("Save Content JSON", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
} itemURL: {
|
||||
try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
}
|
||||
|
||||
if profile.type == .remote {
|
||||
Button {
|
||||
showQRCode = true
|
||||
@@ -1128,17 +1293,26 @@ private struct ProfilePickerRow: View {
|
||||
Label("Share URL as QR Code", systemImage: "qrcode")
|
||||
}
|
||||
}
|
||||
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
} itemURL: {
|
||||
try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
}
|
||||
} label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
|
||||
private func exportProfile(type: ExportItemType) {
|
||||
do {
|
||||
switch type {
|
||||
case .file:
|
||||
profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
|
||||
showProfileExporter = true
|
||||
case .json:
|
||||
profileJSONExportDocument = ProfileJSONExportDocument(jsonContent: try profile.origin.read(), name: profile.name)
|
||||
showJSONExporter = true
|
||||
}
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private var profileInfo: some View {
|
||||
HStack(spacing: 8) {
|
||||
HStack(spacing: 4) {
|
||||
|
||||
Reference in New Issue
Block a user