├── Resources ├── Icon128.png └── Icon16.png ├── Content ├── WBP_NotionTag.uasset ├── T_Notion_Close.uasset ├── WBP_NewTask_Game.uasset ├── WBP_NotionPaint.uasset ├── BP_Notion_SaveData.uasset ├── E_Notion_InputType.uasset ├── WBP_NewTask_Clean.uasset ├── WBP_NotionPropEdit.uasset ├── WBP_NotionProperty.uasset ├── BP_Notion_SaveManager.uasset ├── UWBP_NewTask_Editor.uasset ├── WBP_NotionPaintColor.uasset ├── BP_Notion_PaintSaveData.uasset ├── E_Notion_SelectionType.uasset ├── WBP_NotionPropEdit_Date.uasset ├── BPI_Notion_EditorAndGame.uasset ├── BP_Notion_GlobalFunctions.uasset ├── WBP_NotionPropEdit_Input.uasset ├── WBP_NotionPropEdit_Checkbox.uasset ├── WBP_NotionPropEdit_RichText.uasset └── WBP_NotionPropEdit_Selection.uasset ├── Source ├── NotionUnrealEditor │ ├── Private │ │ ├── NotionUnrealEditor.cpp │ │ ├── NotionUnrealEditorCommands.cpp │ │ ├── NotionUnrealEditorStyle.cpp │ │ ├── NotionUnrealEditorBase.cpp │ │ └── NotionUnrealEditorModule.cpp │ ├── Public │ │ ├── NotionUnrealEditor.h │ │ ├── NotionUnrealEditorCommands.h │ │ ├── NotionUnrealEditorStyle.h │ │ ├── NotionUnrealEditorModule.h │ │ └── NotionUnrealEditorBase.h │ └── NotionUnrealEditor.Build.cs └── NotionUnreal │ ├── Public │ ├── NotionUnreal.h │ ├── NotionKeyCommands.h │ ├── NotionScreenshotTaker.h │ ├── NotionUnrealBPLibrary.h │ └── NotionSettings.h │ ├── NotionUnreal.Build.cs │ └── Private │ ├── NotionUnreal.cpp │ ├── NotionScreenshotTaker.cpp │ ├── NotionKeyCommands.cpp │ ├── NotionUnrealBPLibrary.cpp │ └── NotionSettings.cpp ├── NotionUnreal.uplugin ├── LICENSE └── README.md /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Resources/Icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Resources/Icon16.png -------------------------------------------------------------------------------- /Content/WBP_NotionTag.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionTag.uasset -------------------------------------------------------------------------------- /Content/T_Notion_Close.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/T_Notion_Close.uasset -------------------------------------------------------------------------------- /Content/WBP_NewTask_Game.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NewTask_Game.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPaint.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPaint.uasset -------------------------------------------------------------------------------- /Content/BP_Notion_SaveData.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/BP_Notion_SaveData.uasset -------------------------------------------------------------------------------- /Content/E_Notion_InputType.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/E_Notion_InputType.uasset -------------------------------------------------------------------------------- /Content/WBP_NewTask_Clean.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NewTask_Clean.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPropEdit.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPropEdit.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionProperty.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionProperty.uasset -------------------------------------------------------------------------------- /Content/BP_Notion_SaveManager.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/BP_Notion_SaveManager.uasset -------------------------------------------------------------------------------- /Content/UWBP_NewTask_Editor.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/UWBP_NewTask_Editor.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPaintColor.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPaintColor.uasset -------------------------------------------------------------------------------- /Content/BP_Notion_PaintSaveData.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/BP_Notion_PaintSaveData.uasset -------------------------------------------------------------------------------- /Content/E_Notion_SelectionType.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/E_Notion_SelectionType.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPropEdit_Date.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPropEdit_Date.uasset -------------------------------------------------------------------------------- /Content/BPI_Notion_EditorAndGame.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/BPI_Notion_EditorAndGame.uasset -------------------------------------------------------------------------------- /Content/BP_Notion_GlobalFunctions.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/BP_Notion_GlobalFunctions.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPropEdit_Input.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPropEdit_Input.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPropEdit_Checkbox.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPropEdit_Checkbox.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPropEdit_RichText.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPropEdit_RichText.uasset -------------------------------------------------------------------------------- /Content/WBP_NotionPropEdit_Selection.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleidoscube/NotionUnreal/HEAD/Content/WBP_NotionPropEdit_Selection.uasset -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Private/NotionUnrealEditor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #include "NotionUnrealEditor.h" 4 | 5 | void UNotionUnrealEditor::Init() 6 | { 7 | // Put initialization code here 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Public/NotionUnrealEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "NotionUnrealEditorBase.h" 6 | #include "NotionUnrealEditor.generated.h" 7 | 8 | /** 9 | * Editor object which handles all of the logic of the Plugin. 10 | */ 11 | 12 | UCLASS() 13 | class NOTIONUNREALEDITOR_API UNotionUnrealEditor : public UNotionUnrealEditorBase 14 | { 15 | 16 | GENERATED_BODY() 17 | 18 | public: 19 | 20 | // UNotionUnrealEditorBase implementation 21 | void Init() override; 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Public/NotionUnreal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleManager.h" 6 | 7 | class FNotionUnrealModule : public IModuleInterface 8 | { 9 | IConsoleCommand* ShowIngameTaskCommand; 10 | class UNotionKeyCommands* commands; 11 | 12 | public: 13 | 14 | /** IModuleInterface implementation */ 15 | virtual void StartupModule() override; 16 | virtual void ShutdownModule() override; 17 | 18 | void OpenNotionTaskMenu(const TArray& Args, UWorld* World); 19 | 20 | void RegisterShortcut(); 21 | }; 22 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Public/NotionKeyCommands.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/PlayerInput.h" 7 | #include "NotionSettings.h" 8 | 9 | /** 10 | * 11 | */ 12 | class NOTIONUNREAL_API UNotionKeyCommands 13 | { 14 | public: 15 | 16 | bool IsBindableKey(const FKey& Key); 17 | void UpdatePlayerInput(UPlayerInput* PlayerInput, const FKeyBind& KeyBind); 18 | FKeyBind CreateUnrealKeyBinding(const FNotionKeyInfo& KeyInfo, const FString& Command); 19 | void SetKeyToCommand(const FNotionKeyInfo& KeyInfo, const TCHAR* CommandName); 20 | }; 21 | -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Public/NotionUnrealEditorCommands.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Framework/Commands/Commands.h" 7 | 8 | /** 9 | * Class handling UICommands of the editor. 10 | * Currently only "Open My Plugin" commands is required. 11 | * It is done via commands, because we want to have a keyboard shortcut for it. 12 | */ 13 | 14 | class NOTIONUNREALEDITOR_API FNotionUnrealEditorCommands : public TCommands 15 | { 16 | 17 | public: 18 | 19 | FNotionUnrealEditorCommands(); 20 | void RegisterCommands() override; 21 | TSharedPtr OpenNotionUnrealWindow; 22 | }; 23 | -------------------------------------------------------------------------------- /NotionUnreal.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "NotionUnreal", 6 | "Description": "Notion Unreal", 7 | "Category": "Other", 8 | "CreatedBy": "kaleidoscube", 9 | "CreatedByURL": "www.kaleidoscube.com", 10 | "DocsURL": "https://kaleidoscube.notion.site/Notion-Unreal-Integration-a55198f0c613465195ab7276f55bee3d?pvs=4", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "EngineVersion": "5.4.0", 14 | "CanContainContent": true, 15 | "Installed": true, 16 | "Modules": [ 17 | { 18 | "Name": "NotionUnreal", 19 | "Type": "DeveloperTool", 20 | "LoadingPhase": "PreLoadingScreen", 21 | "PlatformAllowList": [ 22 | "Win64" 23 | ] 24 | }, 25 | { 26 | "Name": "NotionUnrealEditor", 27 | "Type": "Editor", 28 | "LoadingPhase": "PreDefault", 29 | "TargetAllowList": [ 30 | "Editor" 31 | ] 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Private/NotionUnrealEditorCommands.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #include "NotionUnrealEditorCommands.h" 4 | #include "EditorStyleSet.h" 5 | 6 | FNotionUnrealEditorCommands::FNotionUnrealEditorCommands() : 7 | TCommands( 8 | TEXT("Notion Unreal Editor Commands"), 9 | FText::FromString(TEXT("Commands to control the Notion Integration inside the Editor.")), 10 | NAME_None, 11 | #if ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 1)) 12 | FAppStyle::GetAppStyleSetName() 13 | #else 14 | FEditorStyle::GetStyleSetName() 15 | #endif 16 | ) 17 | {} 18 | 19 | void FNotionUnrealEditorCommands::RegisterCommands() 20 | { 21 | #define LOCTEXT_NAMESPACE "NotionUnrealLoc" 22 | UI_COMMAND(OpenNotionUnrealWindow, "New Notion Task", "Create a new Task in your Notion Database", EUserInterfaceActionType::Check, FInputChord(EModifierKey::Control, EKeys::T)); 23 | #undef LOCTEXT_NAMESPACE 24 | } -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Public/NotionUnrealEditorStyle.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Styling/SlateStyle.h" 7 | 8 | /** 9 | * Handles the styling of the editor. 10 | * Currently used only for menu and tab icon. 11 | */ 12 | 13 | class NOTIONUNREALEDITOR_API FNotionUnrealEditorStyle 14 | { 15 | public: 16 | 17 | /** 18 | * Initialize the style container. 19 | */ 20 | static void Initialize(); 21 | 22 | /** 23 | * Shutdown/cleanup style container. 24 | */ 25 | static void Shutdown(); 26 | 27 | /** 28 | * Reloads textures used by slate renderer. 29 | */ 30 | static void ReloadTextures(); 31 | 32 | /** 33 | * Returns the NotionUnreal style set name. 34 | */ 35 | static FName GetStyleSetName(); 36 | 37 | private: 38 | 39 | /** 40 | * Creates an instance of the style set. 41 | */ 42 | static TSharedRef< class FSlateStyleSet > Create(); 43 | 44 | // The static instance of style set. 45 | static TSharedPtr< class FSlateStyleSet > StyleInstance; 46 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 kaleidoscube GmbH and Dominik Schön. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/NotionUnrealEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class NotionUnrealEditor : ModuleRules 6 | { 7 | public NotionUnrealEditor(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PrivateIncludePaths.Add("NotionUnrealEditor/Private"); 12 | PrivateIncludePaths.Add("NotionUnreal/Private"); 13 | 14 | PublicDependencyModuleNames.AddRange( 15 | new string[] 16 | { 17 | "Core", 18 | "InputCore", 19 | "DeveloperToolSettings", 20 | "UnrealEd" 21 | } 22 | ); 23 | 24 | 25 | PrivateDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Engine", 29 | "CoreUObject", 30 | "Slate", 31 | "SlateCore", 32 | "WorkspaceMenuStructure", 33 | "DesktopPlatform", 34 | "Blutility", 35 | "UMG", 36 | "UMGEditor", 37 | "EditorStyle", 38 | "Projects" 39 | } 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Public/NotionScreenshotTaker.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "HttpModule.h" 6 | #include "Interfaces/IHttpRequest.h" 7 | #include "Interfaces/IHttpResponse.h" 8 | 9 | #include "CoreMinimal.h" 10 | #include "UObject/NoExportTypes.h" 11 | #include "NotionScreenshotTaker.generated.h" 12 | 13 | /** 14 | * 15 | * 16 | */ 17 | 18 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScreenshotJpeg, const TArray&, Jpeg); 19 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FScreenshotResponse, uint8, Status, FString, ResponseString, FString, FileURL); 20 | 21 | UCLASS(BlueprintType, Blueprintable) 22 | class NOTIONUNREAL_API UNotionScreenshotTaker : public UObject 23 | { 24 | GENERATED_BODY() 25 | 26 | public: 27 | UFUNCTION(BlueprintCallable, Category = Screenshot) 28 | virtual void RequestScreenshot(); 29 | 30 | UPROPERTY(BlueprintAssignable, Category = Screenshot) 31 | FScreenshotResponse ScreenshotUploaded; 32 | protected: 33 | virtual void ScreenshotReceived(int32 InSizeX, int32 InSizeY, const TArray& InImageData); 34 | 35 | bool bIsScreenshotRequested; 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Public/NotionUnrealBPLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | #pragma once 4 | #include "HttpModule.h" 5 | #include "Interfaces/IHttpRequest.h" 6 | #include "Interfaces/IHttpResponse.h" 7 | 8 | #include "CoreMinimal.h" 9 | #include "Kismet/BlueprintFunctionLibrary.h" 10 | #include "NotionUnrealBPLibrary.generated.h" 11 | 12 | DECLARE_DYNAMIC_DELEGATE_TwoParams(FResponse, uint8, Status, FString, ResponseString); 13 | DECLARE_DYNAMIC_DELEGATE_ThreeParams(FLogResponse, uint8, Status, FString, ResponseString, FString, FileURL); 14 | 15 | UCLASS() 16 | class UNotionUnrealBPLibrary : public UBlueprintFunctionLibrary 17 | { 18 | GENERATED_UCLASS_BODY() 19 | 20 | public: 21 | UFUNCTION(BlueprintCallable, meta = (DisplayName = "Make a Notion Task", Keywords = "Make a Notion Task"), Category = "Notion Plugin Internal") 22 | static void CreateTaskInNotion(const FString Properties, const FString ContentChildren, const FResponse &OnComplete); 23 | 24 | UFUNCTION(BlueprintCallable, meta = (DisplayName = "Upload Log", Keywords = "Get Game Log"), Category = "Notion Plugin Internal") 25 | static void UploadLog(const FLogResponse& OnComplete); 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /Source/NotionUnreal/NotionUnreal.Build.cs: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | using UnrealBuildTool; 4 | 5 | public class NotionUnreal : ModuleRules 6 | { 7 | public NotionUnreal(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Slate", 40 | "SlateCore", 41 | "HTTP", 42 | "UMG", 43 | "AssetRegistry", 44 | "DeveloperSettings", 45 | "Json", 46 | "JsonUtilities", 47 | "InputCore", 48 | // ... add private dependencies that you statically link with here ... 49 | } 50 | ); 51 | 52 | 53 | DynamicallyLoadedModuleNames.AddRange( 54 | new string[] 55 | { 56 | // ... add any modules that your module loads dynamically here ... 57 | } 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Public/NotionUnrealEditorModule.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/GCObject.h" 7 | #include "Modules/ModuleManager.h" 8 | 9 | /** 10 | * Editor module, which handles Editor object and DockTab creation. 11 | */ 12 | 13 | class NOTIONUNREALEDITOR_API FNotionUnrealEditorModule : public IModuleInterface, public FGCObject 14 | { 15 | 16 | public: 17 | 18 | // IModuleInterface implementation 19 | void StartupModule() override; 20 | void ShutdownModule() override; 21 | 22 | // FGCObject implementation 23 | void AddReferencedObjects(FReferenceCollector& Collector) override; 24 | #if (ENGINE_MAJOR_VERSION == 5) 25 | FString GetReferencerName() const override; 26 | #endif 27 | 28 | protected: 29 | 30 | /** 31 | * Run some initializations after the Engine has been initialized. 32 | */ 33 | void OnPostEngineInit(); 34 | 35 | private: 36 | 37 | /** 38 | * Returns true if the editor can be spawned. 39 | */ 40 | bool CanSpawnEditor(); 41 | 42 | /** 43 | * Spawns editor and returns a ref of the DockTab to which the editor 44 | * has been pinned. 45 | */ 46 | TSharedRef SpawnEditor(const FSpawnTabArgs& Args); 47 | 48 | /** 49 | * Checks if the editor is spawned. 50 | */ 51 | bool IsEditorSpawned(); 52 | 53 | /** 54 | * Invokes spawning editor from the command. 55 | */ 56 | void InvokeEditorSpawn(); 57 | 58 | // Editor object. 59 | class UNotionUnrealEditorBase* Editor; 60 | 61 | // DockTab reference with the editor. 62 | TWeakPtr EditorTab; 63 | 64 | // Handler for an OnPostEngineInit delegate. 65 | FDelegateHandle OnPostEngineInitDelegateHandle; 66 | }; 67 | -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Private/NotionUnrealEditorStyle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #include "NotionUnrealEditorStyle.h" 4 | #include "Framework/Application/SlateApplication.h" 5 | #include "Styling/SlateStyleRegistry.h" 6 | #include "Slate/SlateGameResources.h" 7 | #include "Interfaces/IPluginManager.h" 8 | 9 | // Null declaration of static variable (for linker needs) 10 | TSharedPtr FNotionUnrealEditorStyle::StyleInstance = nullptr; 11 | 12 | void FNotionUnrealEditorStyle::Initialize() 13 | { 14 | if (StyleInstance.IsValid() == false) 15 | { 16 | StyleInstance = Create(); 17 | FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); 18 | } 19 | } 20 | 21 | void FNotionUnrealEditorStyle::Shutdown() 22 | { 23 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); 24 | ensure(StyleInstance.IsUnique()); 25 | StyleInstance.Reset(); 26 | } 27 | 28 | void FNotionUnrealEditorStyle::ReloadTextures() 29 | { 30 | if (FSlateApplication::IsInitialized()) 31 | { 32 | FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); 33 | } 34 | } 35 | 36 | FName FNotionUnrealEditorStyle::GetStyleSetName() 37 | { 38 | static FName StyleSetName(TEXT("NotionUnrealEditorStyle")); 39 | return StyleSetName; 40 | } 41 | 42 | TSharedRef FNotionUnrealEditorStyle::Create() 43 | { 44 | // Create a new Style Set with a content root set to Resources directory of the plugin. 45 | TSharedRef Style = MakeShareable(new FSlateStyleSet(GetStyleSetName())); 46 | Style->SetContentRoot(IPluginManager::Get().FindPlugin("NotionUnreal")->GetBaseDir() / TEXT("Resources")); 47 | 48 | // Create a new Slate Image Brush, which is Icon16.png from Resources directory. 49 | FSlateImageBrush* Brush = new FSlateImageBrush(Style->RootToContentDir(TEXT("Icon16"), TEXT(".png")), { 16.f, 16.f }); 50 | 51 | // Add newly created Brush to the Style Set. 52 | Style->Set("NotionUnrealEditorStyle.MenuIcon", Brush); 53 | 54 | // Result is a Style Set with menu icon in it. 55 | return Style; 56 | } 57 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Private/NotionUnreal.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | #include "NotionUnreal.h" 4 | 5 | // Settings 6 | 7 | #include "NotionSettings.h" 8 | #include "NotionKeyCommands.h" 9 | #include "Blueprint/UserWidget.h" 10 | #include "Blueprint/WidgetBlueprintLibrary.h" 11 | 12 | #define LOCTEXT_NAMESPACE "FNotionUnrealModule" 13 | 14 | void FNotionUnrealModule::StartupModule() 15 | { 16 | UNotionSettings* NotionSettings = GetMutableDefault(); 17 | NotionSettings->RetrieveDatabase(); 18 | NotionSettings->RetrieveUsers(); 19 | 20 | RegisterShortcut(); 21 | ShowIngameTaskCommand = IConsoleManager::Get().RegisterConsoleCommand(TEXT("Notion.OpenTaskUI"), TEXT("test"), FConsoleCommandWithWorldAndArgsDelegate::CreateRaw(this, &FNotionUnrealModule::OpenNotionTaskMenu), ECVF_Default); 22 | 23 | } 24 | 25 | void FNotionUnrealModule::ShutdownModule() 26 | { 27 | if (ShowIngameTaskCommand) 28 | { 29 | IConsoleManager::Get().UnregisterConsoleObject(ShowIngameTaskCommand); 30 | ShowIngameTaskCommand = nullptr; 31 | } 32 | 33 | } 34 | 35 | 36 | void FNotionUnrealModule::OpenNotionTaskMenu(const TArray& Args, UWorld* World) { 37 | 38 | auto* uclass = FSoftClassPath("/NotionUnreal/WBP_NewTask_Game.WBP_NewTask_Game_C").TryLoadClass(); 39 | 40 | FWorldContext* world = GEngine->GetWorldContextFromGameViewport(GEngine->GameViewport); 41 | 42 | 43 | TArray< UUserWidget* > FoundWidgets; 44 | UWidgetBlueprintLibrary::GetAllWidgetsOfClass(world->World(), FoundWidgets, uclass, false); 45 | 46 | if (FoundWidgets.Num() == 1) { 47 | 48 | if (FoundWidgets[0]->IsVisible()) { 49 | FoundWidgets[0]->RemoveFromParent(); 50 | UFunction* MyBPFunc = FoundWidgets[0]->FindFunction(FName(TEXT("CancelNotionWindow"))); 51 | if (MyBPFunc != nullptr) 52 | { 53 | FoundWidgets[0]->ProcessEvent(MyBPFunc, nullptr); 54 | } 55 | } 56 | else { 57 | FoundWidgets[0]->AddToViewport(300); 58 | 59 | } 60 | } 61 | else { 62 | UUserWidget* NewTaskMenu = CreateWidget(world->World(), uclass); 63 | NewTaskMenu->AddToViewport(300); 64 | } 65 | 66 | } 67 | 68 | 69 | void FNotionUnrealModule::RegisterShortcut() { 70 | 71 | UNotionSettings* NotionSettings = GetMutableDefault(); 72 | 73 | commands->SetKeyToCommand(NotionSettings->InGameShortcut, TEXT("Notion.OpenTaskUI")); 74 | } 75 | 76 | 77 | #undef LOCTEXT_NAMESPACE 78 | 79 | IMPLEMENT_MODULE(FNotionUnrealModule, NotionUnreal) -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Public/NotionUnrealEditorBase.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "NotionUnrealEditorBase.generated.h" 7 | 8 | /** 9 | * Editor object handles all of the basic logic of the Plugin. 10 | * It's tasks are to create a widget which is put into the dock. 11 | * Override it to add extra logic the editor should handle. 12 | */ 13 | 14 | UCLASS() 15 | class NOTIONUNREALEDITOR_API UNotionUnrealEditorBase : public UObject 16 | { 17 | 18 | GENERATED_BODY() 19 | 20 | public: 21 | 22 | /** 23 | * Initializes the editor object. Runs right after 24 | * it's creation. 25 | */ 26 | virtual void Init(); 27 | 28 | /** 29 | * Sets up the EditorTab. Used by Editor Module right after a Tab is created. 30 | */ 31 | void SetEditorTab(const TSharedRef& NewEditorTab); 32 | 33 | /** 34 | * Returns true if the Editor UI widget can be created. 35 | */ 36 | bool CanCreateEditorUI(); 37 | 38 | /** 39 | * Creates Editor UI widget and returns a reference to it. 40 | * It is called from the Editor Module in a moment of Tab Creation. 41 | */ 42 | TSharedRef CreateEditorUI(); 43 | 44 | protected: 45 | 46 | /** 47 | * Initialize EditorWidget after it's creation. 48 | * Binds all required delegates and sets up default values to the Widget. 49 | */ 50 | virtual void InitializeTheWidget(); 51 | 52 | private: 53 | 54 | /** 55 | * Returns an Utility Widget Blueprint from Content directory which will 56 | * be used to create an Editor options window. The widget must be located 57 | * in the given in this function location. 58 | */ 59 | class UEditorUtilityWidgetBlueprint* GetUtilityWidgetBlueprint(); 60 | 61 | /** 62 | * Creates the Editor Utility Widget from the Utility Widget Blueprint. 63 | * It can be called from CreateDitorUI() when it is opened by user, 64 | * or it can be called from ChangeTabWorld() in a situation when a map is loaded 65 | * or created and an Editor Tab is valid (moving widget between maps). 66 | */ 67 | TSharedRef CreateEditorWidget(); 68 | 69 | /** 70 | * Called when OnMapChanged event occurs. The EditorTab must be properly 71 | * handled when the world is torn down or when a map is created or loaded. 72 | */ 73 | void ChangeTabWorld(UWorld* World, EMapChangeType MapChangeType); 74 | 75 | 76 | // A pointer to the EditorTab in which the editor widget should be docked. 77 | // It is set by an Editor Module by SetEditorTab() right after the dock is created. 78 | TWeakPtr EditorTab; 79 | }; 80 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Private/NotionScreenshotTaker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | 4 | #include "NotionScreenshotTaker.h" 5 | #include "NotionSettings.h" 6 | 7 | #include "IImageWrapper.h" 8 | #include "IImageWrapperModule.h" 9 | #include "Engine/Engine.h" 10 | #include "Engine/GameViewportClient.h" 11 | #include "UnrealClient.h" 12 | #include "Modules/ModuleManager.h" 13 | #include "Misc/Paths.h" 14 | #include "HAL/FileManager.h" 15 | #include "Misc/FileHelper.h" 16 | 17 | void UNotionScreenshotTaker::RequestScreenshot() 18 | { 19 | if (GEngine == nullptr || GEngine->GameViewport == nullptr || bIsScreenshotRequested) 20 | { 21 | return; 22 | } 23 | 24 | bIsScreenshotRequested = true; 25 | GEngine->GameViewport->OnScreenshotCaptured().AddUObject(this, &UNotionScreenshotTaker::ScreenshotReceived); 26 | 27 | FScreenshotRequest::RequestScreenshot(false); 28 | } 29 | 30 | void UNotionScreenshotTaker::ScreenshotReceived(int32 InSizeX, int32 InSizeY, const TArray& InImageData) 31 | { 32 | IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); 33 | TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG); 34 | 35 | if (!ImageWrapper.IsValid()) 36 | { 37 | bIsScreenshotRequested = false; 38 | return; 39 | } 40 | 41 | if (!ImageWrapper->SetRaw(&InImageData[0], InImageData.Num() * sizeof(FColor), InSizeX, InSizeY, ERGBFormat::BGRA, 8)) 42 | { 43 | bIsScreenshotRequested = false; 44 | return; 45 | } 46 | 47 | 48 | TArray64 CompressedImage = ImageWrapper->GetCompressed(70); 49 | CompressedImage.Shrink(); 50 | TArray CompressedImageDummy = TArray(CompressedImage.GetData(), CompressedImage.GetAllocatedSize()); 51 | 52 | GEngine->GameViewport->OnScreenshotCaptured().RemoveAll(this); 53 | bIsScreenshotRequested = false; 54 | 55 | FString File; 56 | FString time = FDateTime::Now().ToString().Replace(TEXT("."), TEXT("-"), ESearchCase::IgnoreCase); 57 | File.Append("screenshot_" + time + ".png"); 58 | 59 | 60 | FHttpModule& HttpModule = FHttpModule::Get(); 61 | TSharedRef httpRequest = HttpModule.CreateRequest(); 62 | 63 | const UNotionSettings* NotionSettings = GetDefault(); 64 | 65 | httpRequest->SetVerb(TEXT("PUT")); 66 | 67 | httpRequest->SetURL(NotionSettings->fileHostUploadUrl + "/notionScreenshots/" + File); 68 | 69 | for (auto& Elem : NotionSettings->fileHostUploadHeaders) 70 | { 71 | httpRequest->AppendToHeader(Elem.Key, Elem.Value); 72 | } 73 | 74 | httpRequest->SetContent(CompressedImageDummy); 75 | 76 | httpRequest->OnProcessRequestComplete().BindLambda( 77 | [this, File]( 78 | FHttpRequestPtr Request, 79 | FHttpResponsePtr Response, 80 | bool ConnectedSuccessfully) mutable { 81 | ScreenshotUploaded.Broadcast(Response->GetResponseCode(), Response->GetContentAsString(), "/notionScreenshots/" + File); 82 | }); 83 | 84 | // Send the request 85 | httpRequest->ProcessRequest(); 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Private/NotionKeyCommands.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | #include "NotionKeyCommands.h" 3 | 4 | 5 | 6 | bool UNotionKeyCommands::IsBindableKey(const FKey& Key) 7 | { 8 | return Key.IsValid() && Key != EKeys::AnyKey && !Key.IsGamepadKey() && !Key.IsModifierKey() && !Key.IsMouseButton() 9 | && !Key.IsAxis1D() && !Key.IsAxis2D() && !Key.IsAxis3D(); 10 | } 11 | 12 | 13 | void UNotionKeyCommands::UpdatePlayerInput(UPlayerInput* PlayerInputRef, const FKeyBind& NewKeyBind) 14 | { 15 | const int32 Index = PlayerInputRef->DebugExecBindings.IndexOfByPredicate([&](const FKeyBind& PlayerKeyBind) 16 | { 17 | return PlayerKeyBind.Command.Equals(NewKeyBind.Command, ESearchCase::IgnoreCase); 18 | }); 19 | 20 | if (IsBindableKey(NewKeyBind.Key)) 21 | { 22 | if (Index != INDEX_NONE) 23 | { 24 | PlayerInputRef->DebugExecBindings[Index] = NewKeyBind; 25 | } 26 | else 27 | { 28 | PlayerInputRef->DebugExecBindings.Add(NewKeyBind); 29 | } 30 | } 31 | else 32 | { 33 | if (Index != INDEX_NONE) 34 | { 35 | PlayerInputRef->DebugExecBindings.RemoveAt(Index); 36 | } 37 | } 38 | } 39 | 40 | 41 | FKeyBind UNotionKeyCommands::CreateUnrealKeyBinding(const FNotionKeyInfo& KeyInfo, const FString& Command) 42 | { 43 | FKeyBind KeyBind; 44 | KeyBind.Command = Command; 45 | KeyBind.Key = KeyInfo.Key; 46 | KeyBind.bDisabled = false; 47 | 48 | #define FILL_MODIFIER_DATA(KeyInfoProperty, BindProperty, BindIgnoreProperty)\ 49 | if (KeyInfo.KeyInfoProperty == ECheckBoxState::Undetermined)\ 50 | {\ 51 | KeyBind.BindProperty = KeyBind.BindIgnoreProperty = false;\ 52 | }\ 53 | else\ 54 | {\ 55 | KeyBind.BindProperty = (KeyInfo.KeyInfoProperty == ECheckBoxState::Checked);\ 56 | KeyBind.BindIgnoreProperty = !KeyBind.BindProperty;\ 57 | } 58 | 59 | FILL_MODIFIER_DATA(Shift, Shift, bIgnoreShift); 60 | FILL_MODIFIER_DATA(Ctrl, Control, bIgnoreCtrl); 61 | FILL_MODIFIER_DATA(Alt, Alt, bIgnoreAlt); 62 | FILL_MODIFIER_DATA(Cmd, Cmd, bIgnoreCmd); 63 | 64 | #undef FILL_MODIFIER_DATA 65 | 66 | return KeyBind; 67 | } 68 | 69 | 70 | void UNotionKeyCommands::SetKeyToCommand(const FNotionKeyInfo& KeyInfo, const TCHAR* CommandName) 71 | { 72 | const FString& Command = CommandName; 73 | 74 | checkf(!Command.IsEmpty(), TEXT("Command is empty.")); 75 | 76 | const FKeyBind KeyBind = CreateUnrealKeyBinding(KeyInfo, Command); 77 | //UPlayerInput* CopyPlayerInput = &(*GetMutableDefault()); 78 | 79 | if (UPlayerInput* CopyOfPlayerInput = &(*GetMutableDefault())) 80 | { 81 | CopyOfPlayerInput->DebugExecBindings.Empty(); 82 | CopyOfPlayerInput->DebugExecBindings.Add(KeyBind); 83 | CopyOfPlayerInput->InvertedAxis.Empty(); 84 | CopyOfPlayerInput->InvertedAxis.Add(FName("NAME_None")); 85 | 86 | CopyOfPlayerInput->SaveConfig(CPF_Config, *GetMutableDefault()->GetDefaultConfigFilename()); 87 | } 88 | 89 | } 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Private/NotionUnrealEditorBase.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #include "NotionUnrealEditorBase.h" 4 | 5 | #if ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 1)) 6 | #include "AssetRegistry/AssetRegistryModule.h" 7 | #else 8 | #include "AssetRegistryModule.h" 9 | #endif 10 | 11 | #include "EditorUtilityWidget.h" 12 | #include "EditorUtilitySubsystem.h" 13 | #include "EditorUtilityWidgetBlueprint.h" 14 | #include "LevelEditor.h" 15 | 16 | void UNotionUnrealEditorBase::Init() 17 | { 18 | // Empty virtual function - to be overridden 19 | } 20 | 21 | void UNotionUnrealEditorBase::InitializeTheWidget() 22 | { 23 | // Empty virtual function - to be overridden 24 | } 25 | 26 | void UNotionUnrealEditorBase::SetEditorTab(const TSharedRef& NewEditorTab) 27 | { 28 | EditorTab = NewEditorTab; 29 | } 30 | 31 | UEditorUtilityWidgetBlueprint* UNotionUnrealEditorBase::GetUtilityWidgetBlueprint() 32 | { 33 | // Get the Editor Utility Widget Blueprint from the content directory. 34 | FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); 35 | #if ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 1)) 36 | FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath("/NotionUnreal/UWBP_NewTask_Editor.UWBP_NewTask_Editor")); 37 | #else 38 | FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath("/NotionUnreal/UWBP_NewTask_Editor.UWBP_NewTask_Editor"); 39 | #endif 40 | return Cast(AssetData.GetAsset()); 41 | } 42 | 43 | bool UNotionUnrealEditorBase::CanCreateEditorUI() 44 | { 45 | // Editor UI can be created only when we have proper Editor Utility Widget Blueprint available. 46 | return GetUtilityWidgetBlueprint() != nullptr; 47 | } 48 | 49 | TSharedRef UNotionUnrealEditorBase::CreateEditorUI() 50 | { 51 | // Register OnMapChanged event so we can properly handle Tab and Widget when changing levels. 52 | FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); 53 | LevelEditor.OnMapChanged().AddUObject(this, &UNotionUnrealEditorBase::ChangeTabWorld); 54 | 55 | // Create the Widget 56 | return CreateEditorWidget(); 57 | } 58 | 59 | TSharedRef UNotionUnrealEditorBase::CreateEditorWidget() 60 | { 61 | TSharedRef CreatedWidget = SNullWidget::NullWidget; 62 | if (UEditorUtilityWidgetBlueprint* UtilityWidgetBP = GetUtilityWidgetBlueprint()) 63 | { 64 | 65 | 66 | 67 | UtilityWidgetBP->SetRegistrationName(FName("NotionUnreal")); 68 | // Create Widget from the Editor Utility Widget BP. 69 | CreatedWidget = UtilityWidgetBP->CreateUtilityWidget(); 70 | 71 | // Save the pointer to the created Widget and initialize it. 72 | //UEditorUtilityWidget EditorWidget = Cast(UtilityWidgetBP->GetCreatedWidget()); 73 | 74 | } 75 | 76 | // Returned Widget will be docked into the Editor Tab. 77 | return CreatedWidget; 78 | } 79 | 80 | void UNotionUnrealEditorBase::ChangeTabWorld(UWorld* World, EMapChangeType MapChangeType) 81 | { 82 | // Handle the event when editor map changes. 83 | if (MapChangeType == EMapChangeType::TearDownWorld) 84 | { 85 | // If the world is destroyed - set the Tab content to null and null the Widget. 86 | if (EditorTab.IsValid()) 87 | { 88 | EditorTab.Pin()->SetContent(SNullWidget::NullWidget); 89 | } 90 | 91 | } 92 | else if (MapChangeType == EMapChangeType::NewMap || MapChangeType == EMapChangeType::LoadMap) 93 | { 94 | // If the map has been created or loaded and the Tab is valid - put a new Widget into this Tab. 95 | if (EditorTab.IsValid()) 96 | { 97 | EditorTab.Pin()->SetContent(CreateEditorWidget()); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Private/NotionUnrealBPLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | #include "NotionUnrealBPLibrary.h" 4 | #include "NotionSettings.h" 5 | #include "GenericPlatform/GenericPlatformOutputDevices.h" 6 | #include "Misc/OutputDeviceFile.h" 7 | 8 | #include "NotionUnreal.h" 9 | 10 | UNotionUnrealBPLibrary::UNotionUnrealBPLibrary(const FObjectInitializer& ObjectInitializer) 11 | : Super(ObjectInitializer) 12 | { 13 | 14 | } 15 | 16 | 17 | void UNotionUnrealBPLibrary::CreateTaskInNotion(const FString Properties, const FString ContentChildren, const FResponse &OnComplete) 18 | { 19 | const UNotionSettings* NotionSettings = GetDefault(); 20 | 21 | FHttpModule& httpModule = FHttpModule::Get(); 22 | 23 | // Create an asynchronous http request 24 | TSharedRef httpRequest = httpModule.CreateRequest(); 25 | 26 | httpRequest->SetVerb(TEXT("POST")); 27 | 28 | httpRequest->SetURL("https://api.notion.com/v1/pages"); 29 | 30 | httpRequest->AppendToHeader(TEXT("Authorization"), TEXT("Bearer " + NotionSettings->notionAPIKey)); 31 | httpRequest->AppendToHeader(TEXT("Content-Type"), TEXT("application/json")); 32 | httpRequest->AppendToHeader(TEXT("Notion-Version"), TEXT("2021-08-16")); 33 | 34 | FString RequestContent = "{\"parent\": { \"database_id\": \"" + NotionSettings->notionDatabaseID + "\" }, \"properties\": {" + Properties + "}, \"children\": [" + ContentChildren + "]}"; 35 | 36 | httpRequest->SetContentAsString(RequestContent); 37 | 38 | // Callback will execute when the HTTP call is complete 39 | httpRequest->OnProcessRequestComplete().BindLambda( 40 | [&, OnComplete]( 41 | FHttpRequestPtr Request, 42 | FHttpResponsePtr Response, 43 | bool ConnectedSuccessfully) mutable { 44 | OnComplete.ExecuteIfBound(Response->GetResponseCode(), Response->GetContentAsString()); 45 | }); 46 | 47 | // Set Timeout 48 | httpRequest->SetTimeout(20); 49 | 50 | // Submit the request 51 | httpRequest->ProcessRequest(); 52 | } 53 | 54 | void UNotionUnrealBPLibrary::UploadLog(const FLogResponse& OnComplete) { 55 | #if PLATFORM_WINDOWS 56 | FString LogFileName; 57 | FString Log; 58 | 59 | FOutputDevice* OutputDevice = FGenericPlatformOutputDevices::GetLog(); 60 | if (OutputDevice != nullptr) 61 | { 62 | FOutputDeviceFile* OutputDeviceFile = static_cast(OutputDevice); 63 | 64 | LogFileName = OutputDeviceFile->GetFilename(); 65 | int endName = LogFileName.Find("/", ESearchCase::IgnoreCase, ESearchDir::FromEnd); 66 | LogFileName = LogFileName.RightChop(endName); 67 | 68 | FString File = FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()); 69 | File.Append("Logs"); 70 | File.Append(LogFileName); 71 | 72 | if (IFileManager::Get().FileExists(*File)) 73 | { 74 | 75 | UE_LOG(LogTemp, Warning, TEXT("Reading: %s"), *File); 76 | 77 | const UNotionSettings* NotionSettings = GetDefault(); 78 | 79 | FHttpModule& httpModule = FHttpModule::Get(); 80 | 81 | // Create an asynchronous http request 82 | TSharedRef httpRequest = httpModule.CreateRequest(); 83 | 84 | httpRequest->SetVerb(TEXT("PUT")); 85 | 86 | FString FileName; 87 | FString time = FDateTime::Now().ToString().Replace(TEXT("."), TEXT("-"), ESearchCase::IgnoreCase); 88 | FileName.Append("log_" + time + ".log"); 89 | 90 | httpRequest->SetURL(NotionSettings->fileHostUploadUrl + "/notionLogs/" + FileName); 91 | 92 | for (auto& Elem : NotionSettings->fileHostUploadHeaders) 93 | { 94 | httpRequest->AppendToHeader(Elem.Key, Elem.Value); 95 | } 96 | 97 | TArray FileRawData; 98 | 99 | FFileHelper::LoadFileToArray(FileRawData, *File, FILEREAD_AllowWrite); 100 | 101 | httpRequest->SetContent(FileRawData); 102 | 103 | // Callback will execute when the HTTP call is complete 104 | httpRequest->OnProcessRequestComplete().BindLambda( 105 | [&, OnComplete, FileName]( 106 | FHttpRequestPtr Request, 107 | FHttpResponsePtr Response, 108 | bool ConnectedSuccessfully) mutable { 109 | OnComplete.ExecuteIfBound(Response->GetResponseCode(), Response->GetContentAsString(), "/notionLogs/" + FileName); 110 | }); 111 | 112 | //Set Timeout 113 | httpRequest->SetTimeout(20); 114 | 115 | // Submit the request 116 | httpRequest->ProcessRequest(); 117 | 118 | 119 | } 120 | else 121 | { 122 | UE_LOG(LogTemp, Warning, TEXT("File ERROR: Can not read the file because it was not found.")); 123 | UE_LOG(LogTemp, Warning, TEXT("File: Expected file location: %s"), *File); 124 | } 125 | 126 | } 127 | #endif 128 | } 129 | -------------------------------------------------------------------------------- /Source/NotionUnrealEditor/Private/NotionUnrealEditorModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Damian Nowakowski. All rights reserved. 2 | 3 | #include "NotionUnrealEditorModule.h" 4 | #include "NotionUnrealEditor.h" 5 | #include "NotionUnrealEditorCommands.h" 6 | #include "NotionUnrealEditorStyle.h" 7 | 8 | #include "Widgets/Docking/SDockTab.h" 9 | #include "Framework/Docking/TabManager.h" 10 | #include "Interfaces/IMainFrameModule.h" 11 | 12 | #include "Settings/ProjectPackagingSettings.h" 13 | 14 | #include "PropertyEditorModule.h" 15 | #include "LevelEditor.h" 16 | 17 | IMPLEMENT_MODULE(FNotionUnrealEditorModule, FNotionUnrealEditor) 18 | 19 | // Id of the NotionUnreal Tab used to spawn and observe this tab. 20 | const FName NotionUnrealTabId = FName(TEXT("NotionUnreal")); 21 | 22 | void FNotionUnrealEditorModule::StartupModule() 23 | { 24 | // Register Styles. 25 | FNotionUnrealEditorStyle::Initialize(); 26 | FNotionUnrealEditorStyle::ReloadTextures(); 27 | 28 | // Register UICommands. 29 | FNotionUnrealEditorCommands::Register(); 30 | 31 | // Register OnPostEngineInit delegate. 32 | OnPostEngineInitDelegateHandle = FCoreDelegates::OnPostEngineInit.AddRaw(this, &FNotionUnrealEditorModule::OnPostEngineInit); 33 | 34 | // Create and initialize Editor object. 35 | Editor = NewObject(GetTransientPackage(), UNotionUnrealEditor::StaticClass()); 36 | Editor->Init(); 37 | 38 | // Register Tab Spawner. Do not show it in menu, as it will be invoked manually by a UICommand. 39 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner( 40 | NotionUnrealTabId, 41 | FOnSpawnTab::CreateRaw(this, &FNotionUnrealEditorModule::SpawnEditor), 42 | FCanSpawnTab::CreateLambda([this](const FSpawnTabArgs& Args) -> bool { return CanSpawnEditor(); }) 43 | ) 44 | .SetMenuType(ETabSpawnerMenuType::Hidden) 45 | .SetIcon(FSlateIcon(FNotionUnrealEditorStyle::GetStyleSetName(), "NotionUnrealEditorStyle.MenuIcon")); 46 | 47 | } 48 | 49 | void FNotionUnrealEditorModule::ShutdownModule() 50 | { 51 | // Unregister Tab Spawner 52 | FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(NotionUnrealTabId); 53 | 54 | // Cleanup the Editor object. 55 | Editor = nullptr; 56 | 57 | // Remove OnPostEngineInit delegate 58 | FCoreDelegates::OnPostEngineInit.Remove(OnPostEngineInitDelegateHandle); 59 | 60 | // Unregister UICommands. 61 | FNotionUnrealEditorCommands::Unregister(); 62 | 63 | // Unregister Styles. 64 | FNotionUnrealEditorStyle::Shutdown(); 65 | } 66 | 67 | void FNotionUnrealEditorModule::OnPostEngineInit() 68 | { 69 | // This function is for registering UICommand to the engine, so it can be executed via keyboard shortcut. 70 | // This will also add this UICommand to the menu, so it can also be executed from there. 71 | 72 | // This function is valid only if no Commandlet or game is running. It also requires Slate Application to be initialized. 73 | if ((IsRunningCommandlet() == false) && (IsRunningGame() == false) && FSlateApplication::IsInitialized()) 74 | { 75 | if (FLevelEditorModule* LevelEditor = FModuleManager::LoadModulePtr(TEXT("LevelEditor"))) 76 | { 77 | // Create a UICommandList and map editor spawning function to the UICommand of opening Notion Unreal Editor. 78 | TSharedPtr Commands = MakeShareable(new FUICommandList()); 79 | Commands->MapAction( 80 | FNotionUnrealEditorCommands::Get().OpenNotionUnrealWindow, 81 | FExecuteAction::CreateRaw(this, &FNotionUnrealEditorModule::InvokeEditorSpawn), 82 | FCanExecuteAction::CreateRaw(this, &FNotionUnrealEditorModule::CanSpawnEditor), 83 | FIsActionChecked::CreateRaw(this, &FNotionUnrealEditorModule::IsEditorSpawned) 84 | ); 85 | 86 | // Register this UICommandList to the MainFrame. 87 | // Otherwise nothing will handle the input to trigger this command. 88 | IMainFrameModule& MainFrame = FModuleManager::Get().LoadModuleChecked("MainFrame"); 89 | MainFrame.GetMainFrameCommandBindings()->Append(Commands.ToSharedRef()); 90 | 91 | // Create a Menu Extender, which adds a button that executes the UICommandList of opening Notion Unreal Window. 92 | TSharedPtr MainMenuExtender = MakeShareable(new FExtender); 93 | MainMenuExtender->AddMenuExtension( 94 | #if (ENGINE_MAJOR_VERSION == 5) 95 | FName(TEXT("Tools")), 96 | #else 97 | FName(TEXT("General")), 98 | #endif 99 | EExtensionHook::After, 100 | Commands, 101 | FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder) 102 | { 103 | MenuBuilder.AddMenuEntry( 104 | FNotionUnrealEditorCommands::Get().OpenNotionUnrealWindow, 105 | NAME_None, 106 | FText::FromString(TEXT("New Notion Task")), 107 | FText::FromString(TEXT("Create a new Task in your Notion Database")), 108 | FSlateIcon(FNotionUnrealEditorStyle::GetStyleSetName(), "NotionUnrealEditorStyle.MenuIcon") 109 | ); 110 | }) 111 | ); 112 | 113 | // Extend Editors menu with the created Menu Extender. 114 | LevelEditor->GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); 115 | } 116 | } 117 | 118 | if (UProjectPackagingSettings* PackagingSettings = Cast(UProjectPackagingSettings::StaticClass()->GetDefaultObject())) { 119 | bool containsEntry = false; 120 | 121 | for (int i = 0; i < PackagingSettings->DirectoriesToAlwaysCook.Num(); i++) 122 | { 123 | if (PackagingSettings->DirectoriesToAlwaysCook[i].Path.StartsWith("/NotionUnreal")) 124 | { 125 | containsEntry = true; 126 | } 127 | } 128 | 129 | if (!containsEntry) { 130 | // Add correct entry 131 | FDirectoryPath NotionPath; 132 | NotionPath.Path = "/NotionUnreal"; 133 | PackagingSettings->DirectoriesToAlwaysCook.Add(NotionPath); 134 | UE_LOG(LogTemp, Warning, TEXT("Notion Assets missing")); 135 | PackagingSettings->TryUpdateDefaultConfigFile(); 136 | } 137 | } 138 | 139 | } 140 | 141 | void FNotionUnrealEditorModule::AddReferencedObjects(FReferenceCollector& Collector) 142 | { 143 | // Prevent Editor Object from being garbage collected. 144 | if (Editor) 145 | { 146 | Collector.AddReferencedObject(Editor); 147 | } 148 | } 149 | 150 | #if (ENGINE_MAJOR_VERSION == 5) 151 | FString FNotionUnrealEditorModule::GetReferencerName() const 152 | { 153 | return TEXT("NotionUnrealModuleGCObject"); 154 | } 155 | #endif 156 | 157 | bool FNotionUnrealEditorModule::CanSpawnEditor() 158 | { 159 | // Editor can be spawned only when the Editor object say that UI can be created. 160 | if (Editor && Editor->CanCreateEditorUI()) 161 | { 162 | return true; 163 | } 164 | return false; 165 | } 166 | 167 | TSharedRef FNotionUnrealEditorModule::SpawnEditor(const FSpawnTabArgs& Args) 168 | { 169 | // Spawn the Editor only when we can. 170 | if (CanSpawnEditor()) 171 | { 172 | // Spawn new DockTab and fill it with newly created editor UI. 173 | TSharedRef NewTab = SAssignNew(EditorTab, SDockTab) 174 | .TabRole(ETabRole::NomadTab) 175 | [ 176 | Editor->CreateEditorUI() 177 | ]; 178 | 179 | // Tell the Editor Object about newly spawned DockTab, as it will 180 | // need it to handle various editor actions. 181 | Editor->SetEditorTab(NewTab); 182 | 183 | // Return the DockTab to the Global Tab Manager. 184 | return NewTab; 185 | } 186 | 187 | // If editor can't be spawned - create an empty tab. 188 | return SAssignNew(EditorTab, SDockTab).TabRole(ETabRole::NomadTab); 189 | } 190 | 191 | bool FNotionUnrealEditorModule::IsEditorSpawned() 192 | { 193 | // Checks if the editor tab is already existing in the editor 194 | return FGlobalTabmanager::Get()->FindExistingLiveTab(NotionUnrealTabId).IsValid(); 195 | } 196 | 197 | void FNotionUnrealEditorModule::InvokeEditorSpawn() 198 | { 199 | // Tries to invoke opening a plugin tab 200 | FGlobalTabmanager::Get()->TryInvokeTab(NotionUnrealTabId); 201 | } 202 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Public/NotionSettings.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | 3 | #pragma once 4 | #include "Engine/DeveloperSettings.h" 5 | #include "CoreMinimal.h" 6 | #include "HttpModule.h" 7 | #include "InputCoreTypes.h" 8 | #include "Interfaces/IHttpRequest.h" 9 | #include "Interfaces/IHttpResponse.h" 10 | #include "Styling/SlateTypes.h" 11 | #include "Dom/JsonObject.h" 12 | 13 | #include "NotionSettings.generated.h" 14 | 15 | /** 16 | * 17 | */ 18 | 19 | UENUM(BlueprintType) 20 | enum class ENotionPropertyType : uint8{ 21 | Checkbox, 22 | Email, 23 | Multi_Select, 24 | Number, 25 | People, 26 | Phone_Number, 27 | Rich_Text, 28 | Select, 29 | Status, 30 | Url, 31 | Date 32 | }; 33 | 34 | 35 | UENUM(BlueprintType) 36 | enum class ENotionScreenPosition : uint8 { 37 | TopLeft, 38 | Top, 39 | TopRight, 40 | Right, 41 | BottomRight, 42 | Bottom, 43 | BottomLeft, 44 | Left 45 | }; 46 | 47 | 48 | USTRUCT(BlueprintType) 49 | struct FNotionUser 50 | { 51 | GENERATED_BODY() 52 | 53 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 54 | FString Name; 55 | 56 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 57 | FString ID; 58 | 59 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties) 60 | bool isVisibleInUI = true; 61 | 62 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties) 63 | bool isDefault = false; 64 | }; 65 | 66 | USTRUCT(BlueprintType) 67 | struct FNotionSelection 68 | { 69 | GENERATED_BODY() 70 | 71 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 72 | FString Name; 73 | 74 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 75 | FString ID; 76 | 77 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 78 | FColor Color = FColor(0, 0, 0); 79 | }; 80 | 81 | 82 | USTRUCT(BlueprintType) 83 | struct FNotionProperty 84 | { 85 | GENERATED_BODY() 86 | 87 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 88 | FString Name; 89 | 90 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 91 | FString ID; 92 | 93 | UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = DatabaseProperties) 94 | bool isVisibleInUI = true; 95 | 96 | UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = DatabaseProperties, meta = (EditCondition = false)) 97 | ENotionPropertyType PropertyType = ENotionPropertyType::Checkbox; 98 | 99 | UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = DatabaseProperties, meta = (EditCondition = false)) 100 | TArray Options; 101 | 102 | UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = DatabaseProperties, meta = (EditCondition = "PropertyType==ENotionPropertyType::Checkbox", EditConditionHides)) 103 | bool DefaultCheckboxState = false; 104 | 105 | UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = DatabaseProperties, meta = (EditCondition = "PropertyType==ENotionPropertyType::Email || PropertyType==ENotionPropertyType::Phone_Number || PropertyType==ENotionPropertyType::Rich_Text || PropertyType==ENotionPropertyType::Url || PropertyType==ENotionPropertyType::Number", EditConditionHides)) 106 | FString DefaultContent = ""; 107 | 108 | UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = DatabaseProperties, meta = (EditCondition = "PropertyType==ENotionPropertyType::Multi_Select", EditConditionHides)) 109 | TArray DefaultOptionNames; 110 | 111 | UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = DatabaseProperties, meta = (EditCondition = "PropertyType==ENotionPropertyType::Select || PropertyType==ENotionPropertyType::Status", EditConditionHides)) 112 | FString DefaultOptionName = ""; 113 | }; 114 | 115 | 116 | USTRUCT() 117 | struct FNotionKeyInfo 118 | { 119 | GENERATED_BODY() 120 | 121 | UPROPERTY(EditAnywhere, Category = "Input") 122 | FKey Key = FKey("T"); 123 | 124 | UPROPERTY(EditAnywhere, Category = "Input") 125 | ECheckBoxState Shift = ECheckBoxState::Undetermined; 126 | 127 | UPROPERTY(EditAnywhere, Category = "Input") 128 | ECheckBoxState Ctrl = ECheckBoxState::Checked; 129 | 130 | UPROPERTY(EditAnywhere, Category = "Input") 131 | ECheckBoxState Alt = ECheckBoxState::Undetermined; 132 | 133 | UPROPERTY(EditAnywhere, Category = "Input") 134 | ECheckBoxState Cmd = ECheckBoxState::Undetermined; 135 | 136 | friend bool operator==(const FNotionKeyInfo& Lhs, const FNotionKeyInfo& Rhs) 137 | { 138 | return Lhs.Key == Rhs.Key 139 | && Lhs.Shift == Rhs.Shift 140 | && Lhs.Ctrl == Rhs.Ctrl 141 | && Lhs.Alt == Rhs.Alt 142 | && Lhs.Cmd == Rhs.Cmd; 143 | } 144 | 145 | friend bool operator!=(const FNotionKeyInfo& Lhs, const FNotionKeyInfo& Rhs) 146 | { 147 | return !(Lhs == Rhs); 148 | } 149 | }; 150 | 151 | 152 | UCLASS(config = Notion, defaultconfig, meta = (DisplayName = "Notion Integration")) 153 | class NOTIONUNREAL_API UNotionSettings : public UDeveloperSettings 154 | { 155 | GENERATED_BODY() 156 | 157 | public: 158 | UNotionSettings(const FObjectInitializer& ObjectInitializer); 159 | 160 | 161 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = NotionAuthentication) 162 | FString notionAPIKey; 163 | 164 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = NotionAuthentication) 165 | FString notionDatabaseID; 166 | 167 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 168 | FString DatabaseName; 169 | 170 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties, meta = (EditCondition = false)) 171 | FString TaskNameProperty; 172 | 173 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = DatabaseProperties) 174 | TMap DatabaseProperties; 175 | 176 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = Users) 177 | TMap DatabaseUsers; 178 | 179 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = Settings) 180 | bool SendLog; 181 | 182 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = Settings) 183 | ENotionScreenPosition InGameWindowPosition = ENotionScreenPosition::BottomLeft; 184 | 185 | UPROPERTY(Config, EditAnywhere, Category = Settings) 186 | FNotionKeyInfo InGameShortcut; 187 | 188 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = PaintMode) 189 | bool EnablePaintMode = true; 190 | 191 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = PaintMode) 192 | TArray Colors = { FColor(255,255,255,255), FColor(0,0,0,255), FColor(255,0,0,255), FColor(0,0,255,255), FColor(255,255,0,255) }; 193 | 194 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = FileHostingCUrlAuth) 195 | FString fileHostUploadUrl = "https://storage.bunnycdn.com/YOURSTORAGE/"; 196 | 197 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = FileHostingCUrlAuth) 198 | TMap fileHostUploadHeaders; 199 | 200 | UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = FileHostingCUrlAuth) 201 | FString filePublicParentUrl = "https://YOURSTORAGE.b-cdn.net"; 202 | 203 | 204 | void RetrieveDatabase(); 205 | void RetrieveUsers(); 206 | 207 | FNotionProperty PropDataToEnumObject(TSharedPtr PropData); 208 | 209 | FColor NameToNotionColor(FString name); 210 | 211 | void OnDatabaseRetrieved(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 212 | void OnUsersRetrieved(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 213 | }; 214 | 215 | 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NotionUnreal 2 | A Notion Integration for Unreal Engine. Allows you to create tasks or pages for bug tracking and feature requests in standalone development games or the Unreal Editor. 3 | 4 | The integration can be seamlessly added to your existing Unreal project as a plugin. 5 | 6 | It offers the following features: 7 | 8 | - Creating tasks in Notion directly from the Editor 9 | - Creating tasks in Notion from your game (development build) - perfect for early bug reporting 10 | - Setting every task property, such as status, assignee, and tags, right inside the UI 11 | - Option to hide certain task properties from the UI 12 | - Option to set properties in advance via blueprints - perfect for game location tags, weapon tags, etc. 13 | 14 | > **💡 The following features are dependent on a third party (or your own) file hosting setup, as Notion does not allow files to be uploaded:** 15 | 16 | 17 | - Attaching the current game log to the task you are creating 18 | - Attaching a screenshot to the task you are creating 19 | - You can scribble inside of the screenshot you want to upload for additional context or instructions 20 | 21 | ![NotionOverlay](https://github.com/kaleidoscube/NotionUnreal/assets/100562356/a9ba106b-2e6e-40c8-ba27-6d89898b0701) 22 | 23 | 24 | ### Video Demo 25 | [![Youtube Video Demo](https://github.com/kaleidoscube/NotionUnreal/assets/100562356/4535da5d-34e5-462b-9fb5-9b0178c7717d)](http://www.youtube.com/watch?v=kd9rsYIT0A8 "Notion Integration for Unreal Engine - Free plugin")) 26 | 27 | 28 | # Setup 29 | 30 | ### A. Setup in Notion 31 | 32 | You need to create an API access for your Notion database for the plugin to write to it. 33 | 34 | Therefore, go to https://www.notion.so/my-integrations and follow these steps: 35 | 36 | 1. Click `+ New integration`. 37 | 2. Enter the integration name, for example "Unreal". 38 | 3. In the next step, choose `Capabilities` and activate the following: 39 | 1. Read content 40 | 2. Insert content 41 | 3. Read user information without email addresses 42 | 5. Go to `Secrets` and copy the secret key. 43 | 4. Open the Notion database where you want to store your Unreal Tasks and open the small menu on the top right. There, under Connections, connect the database with your newly created Integration. 44 | ![image](https://github.com/user-attachments/assets/79d3e110-305d-436f-b8ed-fadcc489dea5) 45 | 46 | 47 | ### B. Setup in Unreal 48 | 49 | Follow these steps to make the plugin work as intended: 50 | 51 | 1. Copy the "NotionUnreal" folder into your project's "Plugins" directory. 52 | 2. Open your project. If necessary, recompile your project first. 53 | 3. Under `Edit -> Plugins`, check if "NotionUnreal" is enabled. If not, enable it and restart the editor. 54 | 4. Under `Edit -> Project Settings`, you will find a new settings entry called `Notion Integration` in the `Engine` category. 55 | 5. Paste your secret key from step A4 into the field called `Notion APIKey`. 56 | 6. Go to your [notion.so](http://notion.so/) and open the database where you store your project's tasks. We need the ID of your database so that Unreal knows where to create new entries. 57 | The URL should look something like this: 58 | `notion.so/YOURWORKSPACE/3es5asdasda83ca3440fef5a7db313?v=3ef6a086` 59 | 7. Paste the inner ID (in this case 3es5asdasda83ca3440fef5a7db313) from your database URL into the field called `Notion Database ID`. 60 | 8. Restart your editor! 61 | ![Untitled (1)](https://github.com/kaleidoscube/NotionUnreal/assets/100562356/67601682-1764-4c04-ad67-1c22e556b441) 62 | 63 | > **💡 If you want to check if your connection was successful, reopen the Notion Integration settings. The entries “Database Properties” and “Users” should be filled with data from your notion workspace.** 64 | 65 | # Basic Usage 66 | 67 | The usage of the plugin is super easy. 68 | 69 | ### During play mode or in standalone 70 | 71 | 1. Start your game, then press the keyboard shortcut `CTRL + T`. You can change this shortcut inside the Notion Integration settings. Alternatively, open up the console and type in `Notion.OpenTaskUI`. 72 | 2. The task creation window should open up. The plugin pulls your Notion Database properties with every start of the editor, so every property you see in Notion should also be visible here. 73 | 3. In order to create a new task, you need to fill in a new title. Hit enter or "Submit" to create a new task. The newly created task should show up inside of your Notion database instantly. 74 | 75 | ### Inside the editor 76 | 77 | 1. Press `CTRL + T` inside the editor to open up a new editor window with the Notion UI. Please note that you need to close this window on your own after creating a new task. 78 | 2. The editor UI is limited to a certain degree; you will not be able to upload the log or a screenshot from here. 79 | 80 | # Adding Logs and Screenshots 81 | 82 | **Unfortunately, the Notion API does not allow files to be uploaded directly to Notion.** Therefore, if you want full functionality, **you need another third-party file hoster** with REST API access to upload logs and screenshots. 83 | 84 | *We use [bunny.net](https://bunny.net/?ref=b7r15bu3if) for this, as we found the pricing to be quite good. At the time of writing this documentation, you would probably only pay the minimum of $1 per month for file hoster usage.* 85 | 86 | *However, this should work with any other file hoster with REST API access. The plugin is written in a way that allows you to connect to other servers as well. Knowledge of cURL is recommended - you can configure the request headers with your own AccessKeys, etc. inside the Notion Integration settings.* 87 | 88 | ### Setting up file hosting with [bunny.net](https://bunny.net/?ref=b7r15bu3if) 89 | 90 | Sign up for an account at bunny.net and create a new storage zone: 91 | 92 | 1. Select `Storage` in the menu and click `+ Add Storage Zone`. 93 | 2. Give your storage space a name, such as "UnrealNotionStorage". You can configure the storage as you like. We choose the Standard Tier with just one storage region. 94 | 3. After you create your storage zone, you will find your API access data once you open the storage and click on `FTP & API Access`. We will need this information later. 95 | 4. Next, you need to create a so-called Pull Zone so that the content you upload is publicly available and we can embed these files inside your Notion tasks. 96 | 5. Select `CDN` in the menu and click `+ Add Pull Zone`. 97 | 6. Give your pull zone a name, such as "UnrealNotionPublic". Under "Origin Type," choose "Storage Zone" and select your recently created storage. You can configure the rest as you like. We choose the Standard Tier with just one pricing region. 98 | 7. After you create your pull zone, open it and see your linked hostnames. This URL is needed in the next step. 99 | 100 | ### Setting up access inside Unreal 101 | 102 | Now we need to tell the Unreal Notion plugin where it can upload files to. 103 | 104 | 1. Open the `Notion Integration` settings in your `Project Settings`. 105 | 2. Under `File Hosting CUrl Auth` you will see multiple entries that we need to fill. 106 | 1. `File Host Upload Url` would be a combination of your Hostname and your Username that you can find within the bunny storage API access settings. 107 | 108 | `https://storage.bunnycdn.com/username` 109 | 110 | 2. `AccessKey` is the password that you find in the bunny storage API settings. Please note: Don’t use the read-only password, as we want to upload files to this storage! 111 | 3. `File Public Parent Url` is the URL that you see inside of your pullzone. 112 | 113 | ![Untitled (2)](https://github.com/kaleidoscube/NotionUnreal/assets/100562356/c8c7b780-40c6-489e-88dc-2e2bf58ac02e) 114 | This is what the config looks like at our company using bunny as a file hoster. 115 | 116 | ### Sending Logs and Screenshots 117 | 118 | Now you should be good to go! Inside the game, open the Notion window and create a new task. 119 | At the bottom of the window, you’ll see two checkboxes for “Log” and “Screenshot” that you can enable. The files should now be displayed inside the tasks you create! 120 | 121 | ### Paint Overlay 122 | 123 | Sometimes, it's great if you can scribble over a screenshot to show your coworker exactly what change you want or to explain a bug in detail. That's why we integrated a tiny paint overlay within the integration. 124 | 125 | The paint mode opens up as soon as the "Send screenshot" checkbox is enabled. At the top of the screen, you will see a small color selection menu. Simply click anywhere inside the screen to paint. When you start painting, the Notion Task Window will disappear until you are done. If it is in the way nonetheless, you can move this window around via the small top bar. 126 | ![Untitled (3)](https://github.com/kaleidoscube/NotionUnreal/assets/100562356/e3d34b6c-28ed-4a7e-ae80-24386f22632f) 127 | 128 | You can change colors or add new ones inside of the plugins settings. If you don’t want to use this feature, you can disable the paint mode here as well. 129 | 130 | # Other Settings 131 | 132 | There are minor other things that you can tweak inside of the plugin settings: 133 | 134 | - You can hide properties from the UI. You don’t want everyone to be able to set due dates, assignees or the like? Inside of the “Database Properties” array (you can find this in the settings), you can uncheck `IsVisibleInUI` and the property will no longer be shown in Unreal. 135 | - You can set a default value for each property. For selection properties such as tags or the status, please use the actual value name, not the ID. 136 | ![Untitled (4)](https://github.com/kaleidoscube/NotionUnreal/assets/100562356/08b9fd35-bc84-4d1c-84cc-d06767e38823) 137 | With this property config, the property is visible and will always have the “Animation” tag set initially. 138 | 139 | - You can do the same with users inside the “Database Users” array. 140 | - You can choose the default position of the ingame Task Creation UI 141 | - You can make “Send Log” a default 142 | 143 | ### Shortcuts 144 | 145 | The Shortcut for the ingame UI can be changed within the plugin settings. Please restart the editor after you change this, as the shortcut needs to be registered during editor startup. 146 | 147 | The Shortcut for the editor UI can be changed inside of the `Editor Preferences` (not the Project Settings!). Simply search for “Notion”, and you can alter this command as well. 148 | 149 | # Blueprint Features 150 | 151 | There are a few Blueprint functions that you can implement that we find very useful. 152 | 153 | #### Set Notion Task Property 154 | 155 | This will set the initial value of a property - when you open up the task creatio UI, this value is already set. For tags, this will also create new tags if they are not existing yet. 156 | ![Untitled (5)](https://github.com/kaleidoscube/NotionUnreal/assets/100562356/5dc8ece1-be18-4504-a4f7-4ec0230069bd) 157 | We use this in our (linear) game in order to set a location tag at each savepoint. So every task is associated with the right level and game region. A different idea for this would be to set the tag of the current weapon equipped. 158 | 159 | #### Clear Notion Task Property 160 | 161 | This will remove a previously set value again. 162 | 163 | #### Clear Notion Task Properties 164 | 165 | This will remove all values set at all properties. 166 | 167 | #### Save Notion Properties accross sessions 168 | 169 | This will save the initial value that you set via the “Set Notion Task Property” function. Even after restarting the game, the value will still be set initially. 170 | 171 | 172 | If you have any further questions, please reach out to us :) 173 | 174 | Special thanks to Damian Nowakowski for the Editor UI Shortcut Template that we used in this plugin: https://github.com/zompi2/UE4EditorPluginTemplate 175 | -------------------------------------------------------------------------------- /Source/NotionUnreal/Private/NotionSettings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 kaleidoscube GmbH, Germany. All rights reserved. 2 | #include "NotionSettings.h" 3 | #include "Serialization/JsonSerializer.h" 4 | 5 | 6 | UNotionSettings::UNotionSettings(const FObjectInitializer& ObjectInitializer) { 7 | 8 | } 9 | 10 | void UNotionSettings::RetrieveDatabase() 11 | { 12 | const UNotionSettings* NotionSettings = GetDefault(); 13 | 14 | FHttpModule& httpModule = FHttpModule::Get(); 15 | 16 | // Create an asynchronous http request 17 | TSharedRef httpRequest = httpModule.CreateRequest(); 18 | 19 | httpRequest->SetVerb(TEXT("GET")); 20 | 21 | httpRequest->SetURL("https://api.notion.com/v1/databases/" + NotionSettings->notionDatabaseID); 22 | 23 | httpRequest->AppendToHeader(TEXT("Authorization"), TEXT("Bearer " + NotionSettings->notionAPIKey)); 24 | 25 | httpRequest->AppendToHeader(TEXT("Notion-Version"), TEXT("2022-06-28")); 26 | 27 | UE_LOG(LogTemp, Display, TEXT("Retrieving Notion Database...")); 28 | 29 | //UE_LOG(LogTemp, Warning, TEXT("Retrieving Notion Database... with API Key %s"), *NotionSettings->notionAPIKey); 30 | 31 | // Callback will execute when the HTTP call is complete 32 | httpRequest->OnProcessRequestComplete().BindUObject(this, &UNotionSettings::OnDatabaseRetrieved); 33 | 34 | httpRequest->SetTimeout(60); 35 | httpRequest->ProcessRequest(); 36 | } 37 | 38 | void UNotionSettings::OnDatabaseRetrieved(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 39 | { 40 | if (!Response.IsValid()) 41 | { 42 | return; 43 | } 44 | 45 | 46 | TSharedPtr JsonObject; 47 | 48 | // Create a reader pointer to read the json data 49 | TSharedRef> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); 50 | 51 | // Deserialize the json data given Reader and the actual object to deserialize 52 | if (FJsonSerializer::Deserialize(Reader, JsonObject)) { 53 | 54 | UE_LOG(LogTemp, Display, TEXT("Retrieving Notion Database... with content content %s"), *Response->GetContentAsString()); 55 | 56 | if (JsonObject->GetArrayField("title").Num() > 0) { 57 | DatabaseName = JsonObject->GetArrayField("title")[0]->AsObject()->GetStringField("plain_text"); 58 | } 59 | 60 | TMap> PropObject; 61 | PropObject = JsonObject->GetObjectField("properties")->Values; 62 | 63 | TArray KeyArray; 64 | PropObject.GenerateKeyArray(KeyArray); 65 | 66 | TSet DeletionCheck; 67 | 68 | if (KeyArray.Num() == 0) { 69 | UE_LOG(LogTemp, Warning, TEXT("Notion Properties received where 0, therefore not updating properties map...")); 70 | } 71 | else { 72 | 73 | for (int i = 0; i < KeyArray.Num(); i++) { 74 | 75 | TSharedPtr PropData; 76 | PropData = PropObject.Find(KeyArray[i])->Get()->AsObject(); 77 | 78 | FString id = PropData->GetStringField("id"); 79 | 80 | FNotionProperty newProp = PropDataToEnumObject(PropData); 81 | 82 | if (newProp.ID != "") { 83 | if (DatabaseProperties.Contains(id)) { 84 | 85 | FNotionProperty existingProp = *DatabaseProperties.Find(id); 86 | 87 | existingProp.Name = newProp.Name; 88 | existingProp.PropertyType = newProp.PropertyType; 89 | existingProp.Options = newProp.Options; 90 | 91 | DatabaseProperties.Emplace(id, existingProp); 92 | } 93 | else { 94 | DatabaseProperties.Add(id, newProp); 95 | } 96 | } 97 | 98 | if (PropData->GetStringField("type") == "title") { 99 | TaskNameProperty = KeyArray[i]; 100 | } 101 | 102 | DeletionCheck.Add(id); 103 | } 104 | 105 | //DELETE UNUSED Properties from array. Not deleting when iterating over Map itself, but instead on a different list so it's not interfering with the loop 106 | TSet DeletionList; 107 | for (auto& Elem : DatabaseProperties) 108 | { 109 | if (!DeletionCheck.Contains(*Elem.Value.ID)) { 110 | DeletionList.Add(Elem.Value.ID); 111 | } 112 | } 113 | for (auto& Elem : DeletionList) 114 | { 115 | DatabaseProperties.Remove(Elem); 116 | } 117 | 118 | SaveConfig(CPF_Config, *GetDefaultConfigFilename()); 119 | 120 | 121 | } 122 | } 123 | } 124 | 125 | FNotionProperty UNotionSettings::PropDataToEnumObject(TSharedPtr PropData) { 126 | 127 | FNotionProperty newProp = FNotionProperty(); 128 | newProp.Name = PropData->GetStringField("name"); 129 | newProp.ID = PropData->GetStringField("id"); 130 | 131 | if (PropData->GetStringField("type") == "select") { 132 | 133 | TArray options; 134 | 135 | TArray> JsonOptions = PropData->GetObjectField("select")->GetArrayField("options"); 136 | for (int o = 0; o < JsonOptions.Num(); o++) { 137 | FNotionSelection newSelection = FNotionSelection(); 138 | newSelection.Name = JsonOptions[o]->AsObject()->GetStringField("name"); 139 | newSelection.Color = NameToNotionColor(JsonOptions[o]->AsObject()->GetStringField("color")); 140 | 141 | options.Add(newSelection); 142 | } 143 | 144 | newProp.PropertyType = ENotionPropertyType::Select; 145 | newProp.Options = options; 146 | 147 | } 148 | else if (PropData->GetStringField("type") == "multi_select") { 149 | 150 | TArray options; 151 | 152 | TArray> JsonOptions = PropData->GetObjectField("multi_select")->GetArrayField("options"); 153 | for (int o = 0; o < JsonOptions.Num(); o++) { 154 | FNotionSelection newSelection = FNotionSelection(); 155 | newSelection.Name = JsonOptions[o]->AsObject()->GetStringField("name"); 156 | newSelection.Color = NameToNotionColor(JsonOptions[o]->AsObject()->GetStringField("color")); 157 | 158 | options.Add(newSelection); 159 | } 160 | 161 | newProp.PropertyType = ENotionPropertyType::Multi_Select; 162 | newProp.Options = options; 163 | } 164 | else if (PropData->GetStringField("type") == "status") { 165 | 166 | TArray options; 167 | 168 | TArray> JsonOptions = PropData->GetObjectField("status")->GetArrayField("options"); 169 | for (int o = 0; o < JsonOptions.Num(); o++) { 170 | FNotionSelection newSelection = FNotionSelection(); 171 | newSelection.Name = JsonOptions[o]->AsObject()->GetStringField("name"); 172 | newSelection.Color = NameToNotionColor(JsonOptions[o]->AsObject()->GetStringField("color")); 173 | 174 | options.Add(newSelection); 175 | } 176 | 177 | newProp.PropertyType = ENotionPropertyType::Status; 178 | newProp.Options = options; 179 | } 180 | else if (PropData->GetStringField("type") == "title" || 181 | PropData->GetStringField("type") == "relation" || 182 | PropData->GetStringField("type") == "formula" || 183 | PropData->GetStringField("type") == "rollup" || 184 | PropData->GetStringField("type") == "created_by" || 185 | PropData->GetStringField("type") == "created_time" || 186 | PropData->GetStringField("type") == "last_edited_by" || 187 | PropData->GetStringField("type") == "last_edited_time" || 188 | PropData->GetStringField("type") == "unique_id") { 189 | 190 | newProp.ID = ""; 191 | } 192 | else if (PropData->GetStringField("type") == "checkbox"){ 193 | newProp.PropertyType = ENotionPropertyType::Checkbox; 194 | } 195 | else if (PropData->GetStringField("type") == "email") { 196 | newProp.PropertyType = ENotionPropertyType::Email; 197 | } 198 | else if (PropData->GetStringField("type") == "number") { 199 | newProp.PropertyType = ENotionPropertyType::Number; 200 | } 201 | else if (PropData->GetStringField("type") == "people") { 202 | newProp.PropertyType = ENotionPropertyType::People; 203 | } 204 | else if (PropData->GetStringField("type") == "phone_number") { 205 | newProp.PropertyType = ENotionPropertyType::Phone_Number; 206 | } 207 | else if (PropData->GetStringField("type") == "rich_text") { 208 | newProp.PropertyType = ENotionPropertyType::Rich_Text; 209 | } 210 | else if (PropData->GetStringField("type") == "url") { 211 | newProp.PropertyType = ENotionPropertyType::Url; 212 | } 213 | else if (PropData->GetStringField("type") == "date") { 214 | newProp.PropertyType = ENotionPropertyType::Date; 215 | } 216 | 217 | return newProp; 218 | } 219 | 220 | 221 | void UNotionSettings::RetrieveUsers() 222 | { 223 | const UNotionSettings* NotionSettings = GetDefault(); 224 | 225 | FHttpModule& httpModule = FHttpModule::Get(); 226 | 227 | // Create an asynchronous http request 228 | TSharedRef httpRequest = httpModule.CreateRequest(); 229 | 230 | httpRequest->SetVerb(TEXT("GET")); 231 | 232 | httpRequest->SetURL("https://api.notion.com/v1/users?page_size=100"); 233 | 234 | httpRequest->AppendToHeader(TEXT("Authorization"), TEXT("Bearer " + NotionSettings->notionAPIKey)); 235 | 236 | httpRequest->AppendToHeader(TEXT("Notion-Version"), TEXT("2022-06-28")); 237 | 238 | // Callback will execute when the HTTP call is complete 239 | httpRequest->OnProcessRequestComplete().BindUObject(this, &UNotionSettings::OnUsersRetrieved); 240 | 241 | httpRequest->SetTimeout(60); 242 | httpRequest->ProcessRequest(); 243 | } 244 | 245 | void UNotionSettings::OnUsersRetrieved(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 246 | { 247 | if (!Response.IsValid()) 248 | { 249 | return; 250 | } 251 | 252 | TSharedPtr JsonObject; 253 | 254 | // Create a reader pointer to read the json data 255 | TSharedRef> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); 256 | 257 | // Deserialize the json data given Reader and the actual object to deserialize 258 | if (FJsonSerializer::Deserialize(Reader, JsonObject)) { 259 | 260 | TArray> UserList; 261 | UserList = JsonObject->GetArrayField("results"); 262 | 263 | TSet DeletionCheck; 264 | 265 | if (UserList.Num() == 0) { 266 | UE_LOG(LogTemp, Warning, TEXT("Notion Users received where 0, therefore not updating users map...")); 267 | } 268 | else { 269 | 270 | for (int i = 0; i < UserList.Num(); i++) { 271 | FString id = UserList[i]->AsObject()->GetStringField("id"); 272 | 273 | if (DatabaseUsers.Contains(id)) { 274 | FNotionUser existingUser = *DatabaseUsers.Find(id); 275 | existingUser.Name = UserList[i]->AsObject()->GetStringField("name"); 276 | 277 | DatabaseUsers.Emplace(id, existingUser); 278 | } 279 | else { 280 | if (UserList[i]->AsObject()->GetStringField("type") == "person") { 281 | FNotionUser newUser = FNotionUser(); 282 | newUser.Name = UserList[i]->AsObject()->GetStringField("name"); 283 | newUser.ID = UserList[i]->AsObject()->GetStringField("id"); 284 | DatabaseUsers.Add(newUser.ID, newUser); 285 | } 286 | } 287 | 288 | DeletionCheck.Add(id); 289 | } 290 | 291 | //DELETE UNUSED Properties from array. Not deleting when iterating over Map itself, but instead on a different list so it's not interfering with the loop 292 | TSet DeletionList; 293 | for (auto& Elem : DatabaseUsers) 294 | { 295 | if (!DeletionCheck.Contains(*Elem.Value.ID)) { 296 | DeletionList.Add(Elem.Value.ID); 297 | } 298 | } 299 | for (auto& Elem : DeletionList) 300 | { 301 | DatabaseUsers.Remove(Elem); 302 | } 303 | 304 | SaveConfig(CPF_Config, *GetDefaultConfigFilename()); 305 | } 306 | } 307 | } 308 | 309 | FColor UNotionSettings::NameToNotionColor(FString name) { 310 | if (name == "default") { 311 | return FColor(241, 240, 239); 312 | } 313 | if (name == "gray") { 314 | return FColor(227, 226, 224); 315 | } 316 | if (name == "brown") { 317 | return FColor(238, 224, 218); 318 | } 319 | if (name == "orange") { 320 | return FColor(250, 222, 201); 321 | } 322 | if (name == "yellow") { 323 | return FColor(253, 236, 200); 324 | } 325 | if (name == "green") { 326 | return FColor(219, 237, 219); 327 | } 328 | if (name == "blue") { 329 | return FColor(211, 229, 239); 330 | } 331 | if (name == "purple") { 332 | return FColor(232, 222, 238); 333 | } 334 | if (name == "pink") { 335 | return FColor(245, 224, 233); 336 | } 337 | if (name == "red") { 338 | return FColor(255, 226, 221); 339 | } 340 | return FColor(); 341 | } 342 | 343 | --------------------------------------------------------------------------------