From 2911f42db7fa4581a263d49ae792ff4f95b8c046 Mon Sep 17 00:00:00 2001 From: bsorrentino Date: Sat, 1 Apr 2023 21:05:08 +0200 Subject: [PATCH] feat: add clipboard and orientation management save each prompt to the clipboard and disable openAI input in landscape #18 --- PROMPT.md | 4 + PlantUML/PlantUML+OpenAI.swift | 47 +++--- PlantUML/PlantUMLContentView.swift | 136 +++++++++--------- PlantUML/SwiftUI+Rotate.swift | 30 ++++ PlantUML/View+Clipboard.swift | 52 +++++++ .../contents.xcworkspacedata | 3 + PlantUMLApp.xcodeproj/project.pbxproj | 8 ++ 7 files changed, 190 insertions(+), 90 deletions(-) create mode 100644 PROMPT.md create mode 100644 PlantUML/View+Clipboard.swift diff --git a/PROMPT.md b/PROMPT.md new file mode 100644 index 0000000..63f2406 --- /dev/null +++ b/PROMPT.md @@ -0,0 +1,4 @@ +# OpenAI Prompt + +* `make a new sequence that represent an OAuth2 classic flow` + diff --git a/PlantUML/PlantUML+OpenAI.swift b/PlantUML/PlantUML+OpenAI.swift index 55b108d..ebb3c07 100644 --- a/PlantUML/PlantUML+OpenAI.swift +++ b/PlantUML/PlantUML+OpenAI.swift @@ -9,17 +9,18 @@ import SwiftUI import OpenAIKit extension PlantUMLContentView { - func ToggleOpenAIButton() -> some View { + + var ToggleOpenAIButton: some View { Button { viewState.isOpenAIVisible.toggle() } - label: { - Label( "OpenAI Editor", systemImage: "brain" ) - .labelStyle(.iconOnly) - .foregroundColor( viewState.isEditorVisible ? .blue : .gray) - - } + label: { + Label( "OpenAI Editor", systemImage: "brain" ) + .environment(\.symbolVariants, .fill) + .labelStyle(.iconOnly) + .foregroundColor( viewState.isOpenAIVisible ? .blue : .gray) + } } } @@ -73,16 +74,15 @@ class OpenAIService : ObservableObject { }() + @MainActor func generateEdit( input: String, instruction: String ) async -> String? { guard let openAI, case .Ready = status else { return nil } - Task { @MainActor in - self.status = .Editing - } - + self.status = .Editing + do { let editParameter = EditParameters( model: "text-davinci-edit-001", @@ -99,7 +99,9 @@ class OpenAIService : ObservableObject { return result } catch { + status = .Error( error.localizedDescription ) + return nil } } @@ -135,19 +137,19 @@ struct OpenAIView : View { var body: some View { - VStack { + VStack(spacing:0) { HStack(spacing: 10) { Button( action: { tabs = .Input } ) { Label( "OpenAI", systemImage: "") } Divider().frame(height: 20 ) - Button( action: { tabs = .Result } ) { - Label( "Result", systemImage: "") - } - Divider().frame(height: 20 ) Button( action: { tabs = .Prompt } ) { Label( "Prompt", systemImage: "") } + Divider().frame(height: 20 ) + Button( action: { tabs = .Result } ) { + Label( "Result", systemImage: "") + } } if case .Input = tabs { Input_Fragment @@ -160,7 +162,7 @@ struct OpenAIView : View { Prompt_Fragment } } - .padding() + .padding( EdgeInsets(top: 0, leading: 5, bottom: 5, trailing: 0)) } } @@ -170,7 +172,7 @@ extension OpenAIView { var Input_Fragment: some View { - VStack(spacing:5) { + ZStack(alignment: .bottomTrailing ) { TextEditor(text: $instruction) .font(.title3.monospaced() ) .lineSpacing(15) @@ -207,7 +209,7 @@ extension OpenAIView { }, label: { if isEditing { - ProgressView("AI editing....") + ProgressView() } else { Label( "Submit", systemImage: "arrow.right") @@ -216,6 +218,7 @@ extension OpenAIView { .disabled( isEditing ) } + .padding() } .padding() } @@ -228,7 +231,10 @@ extension OpenAIView { var Prompt_Fragment: some View { List( service.prompt.elements, id: \.self ) { prompt in - Text( prompt ) + HStack { + Text( prompt ) + CopyToClipboardButton( value: prompt ) + } } } @@ -262,5 +268,6 @@ struct OpenAIView_Previews: PreviewProvider { OpenAIView( service: OpenAIService(), result: Binding.constant(""), onUndo: { } ) + .frame(height: 200) } } diff --git a/PlantUML/PlantUMLContentView.swift b/PlantUML/PlantUMLContentView.swift index 2740935..0258aa4 100644 --- a/PlantUML/PlantUMLContentView.swift +++ b/PlantUML/PlantUMLContentView.swift @@ -36,46 +36,22 @@ struct PlantUMLContentView: View { } } - + @Environment(\.scenePhase) var scene + @Environment(\.interfaceOrientation) var interfaceOrientation: InterfaceOrientationHolder @Environment(\.editMode) private var editMode @Environment(\.openURL) private var openURL - @State var keyboardTab: String = "general" - + @StateObject var document: PlantUMLDocumentProxy - @StateObject var viewState = ViewState() - - @State private var isScaleToFit = true - @State private var fontSize = CGFloat(12) - @State private var showLine:Bool = false - - @State private var diagramImage:UIImage? - + @StateObject private var service = OpenAIService() + + @State var keyboardTab: String = "general" + @State private var isScaleToFit = true + @State private var fontSize = CGFloat(12) + @State private var showLine:Bool = false @State private var saving = false - @State private var openAIResult:String = "" - - @StateObject private var service = OpenAIService() - - var PlantUMLDiagramViewFit: some View { - PlantUMLDiagramView( url: document.buildURL(), contentMode: .fit ) - } - - - var EditorView_Fragment: some View { - - PlantUMLLineEditorView( items: $document.items, - fontSize: $fontSize, - showLine: $showLine) { onHide, onPressSymbol in - PlantUMLKeyboardView( selectedTab: $keyboardTab, - onHide: onHide, - onPressSymbol: onPressSymbol) - } - .onChange(of: document.items ) { _ in - saving = true - document.updateRequest.send() - } - } + @State private var diagramImage:UIImage? var OpenAIView_Fragment: some View { @@ -96,24 +72,6 @@ struct PlantUMLContentView: View { } - func DiagramView_Fragment( size: CGSize ) -> some View { - - Group { - if isScaleToFit { - PlantUMLDiagramViewFit - .frame( width: size.width, height: size.height ) - } - else { - ScrollView([.horizontal, .vertical], showsIndicators: true) { - PlantUMLDiagramView( url: document.buildURL(), contentMode: .fill ) - .frame( minWidth: size.width) - } - .frame( minWidth: size.width, minHeight: size.height ) - } - } - - } - var body: some View { VStack { @@ -126,10 +84,10 @@ struct PlantUMLContentView: View { DiagramView_Fragment( size: geometry.size ) } } - if viewState.isOpenAIVisible { - Divider() + if viewState.isOpenAIVisible && interfaceOrientation.value.isPortrait { +// Divider() OpenAIView_Fragment - .frame( height: 300 ) + .frame( height: 200 ) } } @@ -141,7 +99,6 @@ struct PlantUMLContentView: View { viewState.forceUpdate() } } - //.navigationBarTitleDisplayMode(.inline) .onRotate(perform: { orientation in if (orientation.isPortrait && viewState.isDiagramVisible) || (orientation.isLandscape && viewState.isEditorVisible) @@ -149,6 +106,7 @@ struct PlantUMLContentView: View { viewState.isEditorVisible.toggle() } }) + //.navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { } @@ -156,11 +114,15 @@ struct PlantUMLContentView: View { HStack( spacing: 0 ) { SavingStateView( saving: saving ) - HStack(alignment: .center, spacing: 5) { - ToggleOpenAIButton() - Divider().background(Color.blue) + if interfaceOrientation.value.isPortrait { + HStack(alignment: .center, spacing: 5) { + ToggleOpenAIButton + Divider().background(Color.blue) + } + .frame(height:20) } - .frame(height:20) + + ToggleEditorButton() if viewState.isEditorVisible { HStack { @@ -182,10 +144,26 @@ struct PlantUMLContentView: View { } // -// MARK: - Editor actions - +// MARK: - Editor extension - // extension PlantUMLContentView { + var EditorView_Fragment: some View { + + PlantUMLLineEditorView( items: $document.items, + fontSize: $fontSize, + showLine: $showLine) { onHide, onPressSymbol in + PlantUMLKeyboardView( selectedTab: $keyboardTab, + onHide: onHide, + onPressSymbol: onPressSymbol) + } + .onChange(of: document.items ) { _ in + saving = true + document.updateRequest.send() + } + } + + // [SwiftUI Let View disappear automatically](https://stackoverflow.com/a/60820491/521197) struct SavedStateView: View { @Binding var visible: Bool @@ -240,18 +218,14 @@ extension PlantUMLContentView { Button( action: { fontSize += 1 } ) { Image( systemName: "textformat.size.larger") } - .padding( EdgeInsets(top:0, leading: 5,bottom: 0, trailing: 0)) - Divider().background(Color.blue).frame(height:20) + Divider() + .background(Color.blue) + .frame(height:20) + .padding( .leading, 5) Button( action: { fontSize -= 1} ) { Image( systemName: "textformat.size.smaller") } - .padding( EdgeInsets(top:0, leading: 5,bottom: 0, trailing: 0)) } - // .overlay { - // RoundedRectangle(cornerRadius: 16) - // .stroke(.blue, lineWidth: 1) - // } - //.padding() } func ToggleEditorButton() -> some View { @@ -278,7 +252,7 @@ extension PlantUMLContentView { Button( action: { document.save() }, - label: { + label: { Label( "Save", systemImage: "arrow.down.doc.fill" ) .labelStyle(.titleOnly) }) @@ -291,6 +265,28 @@ extension PlantUMLContentView { // extension PlantUMLContentView { + var PlantUMLDiagramViewFit: some View { + PlantUMLDiagramView( url: document.buildURL(), contentMode: .fit ) + } + + func DiagramView_Fragment( size: CGSize ) -> some View { + + Group { + if isScaleToFit { + PlantUMLDiagramViewFit + .frame( width: size.width, height: size.height ) + } + else { + ScrollView([.horizontal, .vertical], showsIndicators: true) { + PlantUMLDiagramView( url: document.buildURL(), contentMode: .fill ) + .frame( minWidth: size.width) + } + .frame( minWidth: size.width, minHeight: size.height ) + } + } + + } + func ShareDiagramButton() -> some View { Button(action: { diff --git a/PlantUML/SwiftUI+Rotate.swift b/PlantUML/SwiftUI+Rotate.swift index 0985a57..e1c611f 100644 --- a/PlantUML/SwiftUI+Rotate.swift +++ b/PlantUML/SwiftUI+Rotate.swift @@ -7,15 +7,45 @@ // Inspired by [How to detect device rotation](https://www.hackingwithswift.com/quick-start/swiftui/how-to-detect-device-rotation) import SwiftUI +import Combine +import OSLog + +struct InterfaceOrientationHolder { + + var value:UIInterfaceOrientation { + + let scenes = UIApplication.shared.connectedScenes + if let windowScene = scenes.first as? UIWindowScene { + return windowScene.interfaceOrientation + } + else { + return .unknown + } + + } +} + +private struct InterfaceOrientationKey: EnvironmentKey { + static let defaultValue = InterfaceOrientationHolder() +} + +extension EnvironmentValues { + var interfaceOrientation: InterfaceOrientationHolder { + get { self[InterfaceOrientationKey.self] } + set { os_log( "InterfaceOrientationKey is read only!", type: .info) } + } +} // Our custom view modifier to track rotation and // call our action struct DeviceRotationViewModifier: ViewModifier { + let action: (UIDeviceOrientation) -> Void func body(content: Content) -> some View { content .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + action(UIDevice.current.orientation) } } diff --git a/PlantUML/View+Clipboard.swift b/PlantUML/View+Clipboard.swift new file mode 100644 index 0000000..24a56a7 --- /dev/null +++ b/PlantUML/View+Clipboard.swift @@ -0,0 +1,52 @@ +// +// View+Clipboard.swift +// PlantUMLApp +// +// Created by Bartolomeo Sorrentino on 01/04/23. +// + +import SwiftUI +import OSLog + + +// https://www.simpleswiftguide.com/advanced-swiftui-button-styling-and-animation/ +struct ScaleButtonStyle: ButtonStyle { + @Environment(\.colorScheme) var colorScheme: ColorScheme + + func makeBody(configuration: Self.Configuration) -> some View { + configuration.label + .scaleEffect(configuration.isPressed ? 1.3 : 1.0) + .foregroundColor(colorScheme == .dark ? Color.white : Color.black) + } +} + +struct CopyToClipboardButton : View { + + var value:String + + init( value:String ) { + self.value = value + } + + var body: some View { + Button( action: { + #if os(iOS) + UIPasteboard.general.string = self.value + #elseif os(macOS) + NSPasteboard.general.declareTypes([ NSPasteboard.PasteboardType.string ], owner: nil) + NSPasteboard.general.setString(self.value, forType: .string) + #endif + os_log("copied to clipboard!", type: .debug) + }) { + Image( systemName: "doc.on.clipboard") + } + .buttonStyle(ScaleButtonStyle()) + + } +} + +struct View_Clipboard_Previews: PreviewProvider { + static var previews: some View { + CopyToClipboardButton(value: "") + } +} diff --git a/PlantUML4iPad.xcworkspace/contents.xcworkspacedata b/PlantUML4iPad.xcworkspace/contents.xcworkspacedata index 3974518..381c7a7 100644 --- a/PlantUML4iPad.xcworkspace/contents.xcworkspacedata +++ b/PlantUML4iPad.xcworkspace/contents.xcworkspacedata @@ -16,6 +16,9 @@ + + diff --git a/PlantUMLApp.xcodeproj/project.pbxproj b/PlantUMLApp.xcodeproj/project.pbxproj index 9473014..5cf2ce3 100644 --- a/PlantUMLApp.xcodeproj/project.pbxproj +++ b/PlantUMLApp.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ A038DB6029D489BA0032E312 /* PlantUML+OpenAI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A038DB5F29D489BA0032E312 /* PlantUML+OpenAI.swift */; }; A047206F29549ACC007E061F /* SwiftUI+Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = A047206E29549ACC007E061F /* SwiftUI+Share.swift */; }; A047207029549ACC007E061F /* SwiftUI+Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = A047206E29549ACC007E061F /* SwiftUI+Share.swift */; }; + A068572C29D88D7300E82C2F /* PROMPT.md in Resources */ = {isa = PBXBuildFile; fileRef = A068572B29D88D7300E82C2F /* PROMPT.md */; }; + A068572E29D8B31100E82C2F /* View+Clipboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A068572D29D8B31100E82C2F /* View+Clipboard.swift */; }; A08AA78429561170004DE329 /* View+UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08AA78329561170004DE329 /* View+UIImage.swift */; }; A0943A6F2944A44900342426 /* ScaleToFit+ToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0943A6E2944A44900342426 /* ScaleToFit+ToggleStyle.swift */; }; A0943A702944A44900342426 /* ScaleToFit+ToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0943A6E2944A44900342426 /* ScaleToFit+ToggleStyle.swift */; }; @@ -61,6 +63,8 @@ A038DB5B29D452880032E312 /* OpenAI.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = OpenAI.xcconfig; sourceTree = ""; }; A038DB5F29D489BA0032E312 /* PlantUML+OpenAI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PlantUML+OpenAI.swift"; sourceTree = ""; }; A047206E29549ACC007E061F /* SwiftUI+Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftUI+Share.swift"; sourceTree = ""; }; + A068572B29D88D7300E82C2F /* PROMPT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = PROMPT.md; sourceTree = ""; }; + A068572D29D8B31100E82C2F /* View+Clipboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Clipboard.swift"; sourceTree = ""; }; A08AA78329561170004DE329 /* View+UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+UIImage.swift"; sourceTree = ""; }; A0943A6D29448A1700342426 /* AppStore.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = AppStore.xcassets; sourceTree = ""; }; A0943A6E2944A44900342426 /* ScaleToFit+ToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScaleToFit+ToggleStyle.swift"; sourceTree = ""; }; @@ -125,6 +129,7 @@ A0D3C63D28984A0E000838D7 = { isa = PBXGroup; children = ( + A068572B29D88D7300E82C2F /* PROMPT.md */, A038DB5B29D452880032E312 /* OpenAI.xcconfig */, A01907EF2951CD5C0059CCBE /* privacy_policy.md */, A0943A6D29448A1700342426 /* AppStore.xcassets */, @@ -151,6 +156,7 @@ A0D3C64828984A0E000838D7 /* PlantUML */ = { isa = PBXGroup; children = ( + A068572D29D8B31100E82C2F /* View+Clipboard.swift */, A08AA78329561170004DE329 /* View+UIImage.swift */, A047206E29549ACC007E061F /* SwiftUI+Share.swift */, A0BADC7029D4C2740056A098 /* SwiftUI+Conditional.swift */, @@ -313,6 +319,7 @@ files = ( A0D3C65328984A10000838D7 /* Preview Assets.xcassets in Resources */, A0BFBA9A290D551F008340E3 /* Localizable.strings in Resources */, + A068572C29D88D7300E82C2F /* PROMPT.md in Resources */, A038DB5C29D452880032E312 /* OpenAI.xcconfig in Resources */, A0D3C65028984A10000838D7 /* Assets.xcassets in Resources */, ); @@ -349,6 +356,7 @@ A0D3C64C28984A0E000838D7 /* PlantUMLDocument.swift in Sources */, A0D3C64E28984A0E000838D7 /* PlantUMLContentView.swift in Sources */, A047206F29549ACC007E061F /* SwiftUI+Share.swift in Sources */, + A068572E29D8B31100E82C2F /* View+Clipboard.swift in Sources */, A0F2B14229353C2D00A44481 /* SwiftUI+Rotate.swift in Sources */, A0A42A76289ABC2D00E929EB /* PlantUMLDiagramView.swift in Sources */, );