diff --git a/.gitignore b/.gitignore index 93a061ece..f45070bdf 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ octo*.db .eslintcache .vscode/settings.json .prettierrc.json +mac/temp +mac/dist diff --git a/Makefile b/Makefile index ad97e081a..3dd1b7655 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: prebuild clean cleanall server server-linux generate watch-server +.PHONY: prebuild clean cleanall server server-linux generate watch-server mac all: server @@ -36,10 +36,22 @@ server-doc: watch-server: cd server; modd +mac: + rm -rf mac/resources/bin + rm -rf mac/resources/pack + mkdir -p mac/resources + cp -R bin mac/resources/bin + cp -R webapp/pack mac/resources/pack + mkdir -p mac/temp + xcodebuild archive -workspace mac/Tasks.xcworkspace -scheme Tasks -archivePath mac/temp/tasks.xcarchive + xcodebuild -exportArchive -archivePath mac/temp/tasks.xcarchive -exportPath mac/dist -exportOptionsPlist mac/export.plist + clean: rm -rf bin rm -rf dist rm -rf webapp/pack + rm -rf mac/temp + rm -rf mac/dist cleanall: clean rm -rf webapp/node_modules diff --git a/README.md b/README.md index 5d6c28aef..a9573449f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # mattermost-octo-tasks -## Build instructions +## Building the server ``` cd webapp @@ -14,6 +14,7 @@ make Currently tested with: * Go 1.15.2 * MacOS Catalina (10.15.6) +* Ubuntu 18.04 The server defaults to using sqlite as the store, but can be configured to use Postgres: * In config.json @@ -22,7 +23,7 @@ The server defaults to using sqlite as the store, but can be configured to use P * Create a new "octo" database with psql * Restart the server -## Running and testing +## Running and testing the server To start the server: ``` @@ -32,3 +33,13 @@ To start the server: Server settings are in config.json. Open a browser to [http://localhost:8000](http://localhost:8000) to start. + +## Building and running the macOS app +You can build the Mac app on a Mac running macOS Catalina (10.15.6+) and with Xcode 12.0+. A valid development signing certificate must be available. + +First build the server using the steps above, then run: +``` +make mac +``` + +To run, launch mac/dist/Tasks.app diff --git a/mac/Tasks.xcodeproj/project.pbxproj b/mac/Tasks.xcodeproj/project.pbxproj new file mode 100644 index 000000000..cab2f3ffc --- /dev/null +++ b/mac/Tasks.xcodeproj/project.pbxproj @@ -0,0 +1,618 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 80D6DEBB252E13CB00AEED9E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */; }; + 80D6DEBD252E13CB00AEED9E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DEBC252E13CB00AEED9E /* ViewController.swift */; }; + 80D6DEBF252E13CD00AEED9E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 80D6DEBE252E13CD00AEED9E /* Assets.xcassets */; }; + 80D6DEC2252E13CD00AEED9E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 80D6DEC0252E13CD00AEED9E /* Main.storyboard */; }; + 80D6DECE252E13CD00AEED9E /* TasksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DECD252E13CD00AEED9E /* TasksTests.swift */; }; + 80D6DED9252E13CD00AEED9E /* TasksUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DED8252E13CD00AEED9E /* TasksUITests.swift */; }; + 80D6DEEA252E15D100AEED9E /* resources in Resources */ = {isa = PBXBuildFile; fileRef = 80D6DEE9252E15D100AEED9E /* resources */; }; + 80D6DF18252F9BDE00AEED9E /* AutoSaveWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DF17252F9BDE00AEED9E /* AutoSaveWindowController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 80D6DECA252E13CD00AEED9E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 80D6DEAF252E13CB00AEED9E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 80D6DEB6252E13CB00AEED9E; + remoteInfo = Tasks; + }; + 80D6DED5252E13CD00AEED9E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 80D6DEAF252E13CB00AEED9E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 80D6DEB6252E13CB00AEED9E; + remoteInfo = Tasks; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 80D6DEB7252E13CB00AEED9E /* Tasks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tasks.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 80D6DEBC252E13CB00AEED9E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 80D6DEBE252E13CD00AEED9E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 80D6DEC1252E13CD00AEED9E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 80D6DEC3252E13CD00AEED9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 80D6DEC4252E13CD00AEED9E /* Tasks.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tasks.entitlements; sourceTree = ""; }; + 80D6DEC9252E13CD00AEED9E /* TasksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TasksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 80D6DECD252E13CD00AEED9E /* TasksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksTests.swift; sourceTree = ""; }; + 80D6DECF252E13CD00AEED9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 80D6DED4252E13CD00AEED9E /* TasksUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TasksUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 80D6DED8252E13CD00AEED9E /* TasksUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksUITests.swift; sourceTree = ""; }; + 80D6DEDA252E13CD00AEED9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 80D6DEE9252E15D100AEED9E /* resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = resources; sourceTree = ""; }; + 80D6DF17252F9BDE00AEED9E /* AutoSaveWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSaveWindowController.swift; sourceTree = ""; }; + 80D6DF1C25324A4F00AEED9E /* Inherit.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Inherit.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 80D6DEB4252E13CB00AEED9E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80D6DEC6252E13CD00AEED9E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80D6DED1252E13CD00AEED9E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 80D6DEAE252E13CB00AEED9E = { + isa = PBXGroup; + children = ( + 80D6DEB9252E13CB00AEED9E /* Tasks */, + 80D6DECC252E13CD00AEED9E /* TasksTests */, + 80D6DEE9252E15D100AEED9E /* resources */, + 80D6DED7252E13CD00AEED9E /* TasksUITests */, + 80D6DEB8252E13CB00AEED9E /* Products */, + ); + sourceTree = ""; + }; + 80D6DEB8252E13CB00AEED9E /* Products */ = { + isa = PBXGroup; + children = ( + 80D6DEB7252E13CB00AEED9E /* Tasks.app */, + 80D6DEC9252E13CD00AEED9E /* TasksTests.xctest */, + 80D6DED4252E13CD00AEED9E /* TasksUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 80D6DEB9252E13CB00AEED9E /* Tasks */ = { + isa = PBXGroup; + children = ( + 80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */, + 80D6DEBC252E13CB00AEED9E /* ViewController.swift */, + 80D6DF17252F9BDE00AEED9E /* AutoSaveWindowController.swift */, + 80D6DEBE252E13CD00AEED9E /* Assets.xcassets */, + 80D6DEC0252E13CD00AEED9E /* Main.storyboard */, + 80D6DEC3252E13CD00AEED9E /* Info.plist */, + 80D6DEC4252E13CD00AEED9E /* Tasks.entitlements */, + 80D6DF1C25324A4F00AEED9E /* Inherit.entitlements */, + ); + path = Tasks; + sourceTree = ""; + }; + 80D6DECC252E13CD00AEED9E /* TasksTests */ = { + isa = PBXGroup; + children = ( + 80D6DECD252E13CD00AEED9E /* TasksTests.swift */, + 80D6DECF252E13CD00AEED9E /* Info.plist */, + ); + path = TasksTests; + sourceTree = ""; + }; + 80D6DED7252E13CD00AEED9E /* TasksUITests */ = { + isa = PBXGroup; + children = ( + 80D6DED8252E13CD00AEED9E /* TasksUITests.swift */, + 80D6DEDA252E13CD00AEED9E /* Info.plist */, + ); + path = TasksUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 80D6DEB6252E13CB00AEED9E /* Tasks */ = { + isa = PBXNativeTarget; + buildConfigurationList = 80D6DEDD252E13CD00AEED9E /* Build configuration list for PBXNativeTarget "Tasks" */; + buildPhases = ( + 80D6DEB3252E13CB00AEED9E /* Sources */, + 80D6DEB4252E13CB00AEED9E /* Frameworks */, + 80D6DEB5252E13CB00AEED9E /* Resources */, + 80D6DF1D25324A8100AEED9E /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Tasks; + productName = Tasks; + productReference = 80D6DEB7252E13CB00AEED9E /* Tasks.app */; + productType = "com.apple.product-type.application"; + }; + 80D6DEC8252E13CD00AEED9E /* TasksTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 80D6DEE0252E13CD00AEED9E /* Build configuration list for PBXNativeTarget "TasksTests" */; + buildPhases = ( + 80D6DEC5252E13CD00AEED9E /* Sources */, + 80D6DEC6252E13CD00AEED9E /* Frameworks */, + 80D6DEC7252E13CD00AEED9E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 80D6DECB252E13CD00AEED9E /* PBXTargetDependency */, + ); + name = TasksTests; + productName = TasksTests; + productReference = 80D6DEC9252E13CD00AEED9E /* TasksTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 80D6DED3252E13CD00AEED9E /* TasksUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 80D6DEE3252E13CD00AEED9E /* Build configuration list for PBXNativeTarget "TasksUITests" */; + buildPhases = ( + 80D6DED0252E13CD00AEED9E /* Sources */, + 80D6DED1252E13CD00AEED9E /* Frameworks */, + 80D6DED2252E13CD00AEED9E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 80D6DED6252E13CD00AEED9E /* PBXTargetDependency */, + ); + name = TasksUITests; + productName = TasksUITests; + productReference = 80D6DED4252E13CD00AEED9E /* TasksUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 80D6DEAF252E13CB00AEED9E /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1200; + LastUpgradeCheck = 1200; + TargetAttributes = { + 80D6DEB6252E13CB00AEED9E = { + CreatedOnToolsVersion = 12.0; + }; + 80D6DEC8252E13CD00AEED9E = { + CreatedOnToolsVersion = 12.0; + TestTargetID = 80D6DEB6252E13CB00AEED9E; + }; + 80D6DED3252E13CD00AEED9E = { + CreatedOnToolsVersion = 12.0; + TestTargetID = 80D6DEB6252E13CB00AEED9E; + }; + }; + }; + buildConfigurationList = 80D6DEB2252E13CB00AEED9E /* Build configuration list for PBXProject "Tasks" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 80D6DEAE252E13CB00AEED9E; + productRefGroup = 80D6DEB8252E13CB00AEED9E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 80D6DEB6252E13CB00AEED9E /* Tasks */, + 80D6DEC8252E13CD00AEED9E /* TasksTests */, + 80D6DED3252E13CD00AEED9E /* TasksUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 80D6DEB5252E13CB00AEED9E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 80D6DEBF252E13CD00AEED9E /* Assets.xcassets in Resources */, + 80D6DEEA252E15D100AEED9E /* resources in Resources */, + 80D6DEC2252E13CD00AEED9E /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80D6DEC7252E13CD00AEED9E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80D6DED2252E13CD00AEED9E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 80D6DF1D25324A8100AEED9E /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/bin/codesign --force --sign \"$CODE_SIGN_IDENTITY\" -i \"com.qrayon.octoserver\" --entitlement \"$PROJECT_DIR/Tasks/Inherit.entitlements\" \"$BUILD_DIR/$CONFIGURATION/$EXECUTABLE_FOLDER_PATH/../Resources/resources/bin/octoserver\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 80D6DEB3252E13CB00AEED9E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 80D6DF18252F9BDE00AEED9E /* AutoSaveWindowController.swift in Sources */, + 80D6DEBD252E13CB00AEED9E /* ViewController.swift in Sources */, + 80D6DEBB252E13CB00AEED9E /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80D6DEC5252E13CD00AEED9E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 80D6DECE252E13CD00AEED9E /* TasksTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80D6DED0252E13CD00AEED9E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 80D6DED9252E13CD00AEED9E /* TasksUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 80D6DECB252E13CD00AEED9E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 80D6DEB6252E13CB00AEED9E /* Tasks */; + targetProxy = 80D6DECA252E13CD00AEED9E /* PBXContainerItemProxy */; + }; + 80D6DED6252E13CD00AEED9E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 80D6DEB6252E13CB00AEED9E /* Tasks */; + targetProxy = 80D6DED5252E13CD00AEED9E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 80D6DEC0252E13CD00AEED9E /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 80D6DEC1252E13CD00AEED9E /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 80D6DEDB252E13CD00AEED9E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 80D6DEDC252E13CD00AEED9E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 80D6DEDE252E13CD00AEED9E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Tasks/Tasks.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HFP57A3MYB; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = Tasks/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.Tasks; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 80D6DEDF252E13CD00AEED9E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Tasks/Tasks.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HFP57A3MYB; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = Tasks/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.Tasks; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 80D6DEE1252E13CD00AEED9E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HFP57A3MYB; + INFOPLIST_FILE = TasksTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.TasksTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tasks.app/Contents/MacOS/Tasks"; + }; + name = Debug; + }; + 80D6DEE2252E13CD00AEED9E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HFP57A3MYB; + INFOPLIST_FILE = TasksTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.TasksTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tasks.app/Contents/MacOS/Tasks"; + }; + name = Release; + }; + 80D6DEE4252E13CD00AEED9E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HFP57A3MYB; + INFOPLIST_FILE = TasksUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.TasksUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = Tasks; + }; + name = Debug; + }; + 80D6DEE5252E13CD00AEED9E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HFP57A3MYB; + INFOPLIST_FILE = TasksUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.TasksUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = Tasks; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 80D6DEB2252E13CB00AEED9E /* Build configuration list for PBXProject "Tasks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80D6DEDB252E13CD00AEED9E /* Debug */, + 80D6DEDC252E13CD00AEED9E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 80D6DEDD252E13CD00AEED9E /* Build configuration list for PBXNativeTarget "Tasks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80D6DEDE252E13CD00AEED9E /* Debug */, + 80D6DEDF252E13CD00AEED9E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 80D6DEE0252E13CD00AEED9E /* Build configuration list for PBXNativeTarget "TasksTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80D6DEE1252E13CD00AEED9E /* Debug */, + 80D6DEE2252E13CD00AEED9E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 80D6DEE3252E13CD00AEED9E /* Build configuration list for PBXNativeTarget "TasksUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80D6DEE4252E13CD00AEED9E /* Debug */, + 80D6DEE5252E13CD00AEED9E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 80D6DEAF252E13CB00AEED9E /* Project object */; +} diff --git a/mac/Tasks.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mac/Tasks.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/mac/Tasks.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mac/Tasks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mac/Tasks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/mac/Tasks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mac/Tasks.xcworkspace/contents.xcworkspacedata b/mac/Tasks.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..3c31341cd --- /dev/null +++ b/mac/Tasks.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mac/Tasks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mac/Tasks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/mac/Tasks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mac/Tasks/AppDelegate.swift b/mac/Tasks/AppDelegate.swift new file mode 100644 index 000000000..41b0e1022 --- /dev/null +++ b/mac/Tasks/AppDelegate.swift @@ -0,0 +1,99 @@ +// +// AppDelegate.swift +// Tasks +// +// Created by Chen-I Lim on 10/7/20. +// + +import Cocoa + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + static let serverStartedNotification = NSNotification.Name("serverStarted") + + private var serverProcess: Process? + var serverPort = 8088 + + func applicationDidFinishLaunching(_ aNotification: Notification) { + copyResources() + startServer() + + NotificationCenter.default.post(name: AppDelegate.serverStartedNotification, object: nil) + } + + func applicationWillTerminate(_ aNotification: Notification) { + stopServer() + } + + @IBAction func openNewWindow(_ sender: Any?) { + let mainStoryBoard = NSStoryboard(name: "Main", bundle: nil) + let tabViewController = mainStoryBoard.instantiateController(withIdentifier: "ViewController") as? ViewController + let windowController = mainStoryBoard.instantiateController(withIdentifier: "WindowController") as! NSWindowController + windowController.showWindow(self) + windowController.contentViewController = tabViewController + } + + private func webFolder() -> URL { + let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! + return url.appendingPathComponent("server") + } + + private func copyResources() { + let destBaseUrl = webFolder() + do { + try FileManager.default.createDirectory(atPath: destBaseUrl.path, withIntermediateDirectories: true, attributes: nil) + } catch { + NSLog("copyResources createDirectory ERROR") + } + copyResource(destBaseUrl: destBaseUrl, resourceRelativePath: "pack") + copyResource(destBaseUrl: destBaseUrl, resourceRelativePath: "config.json") + + NSLog("copyResources OK") + } + + private func copyResource(destBaseUrl: URL, resourceRelativePath: String, fileExtension: String = "") { + let srcUrl = Bundle.main.url(forResource: "resources/" + resourceRelativePath, withExtension: fileExtension)! + let destUrl = destBaseUrl.appendingPathComponent(resourceRelativePath) + + let fileManager = FileManager.default + if fileManager.fileExists(atPath: destUrl.path) { + do { + try fileManager.removeItem(at: destUrl) + } catch { + NSLog("copyResource removeItem ERROR") + } + } + + do { + try fileManager.copyItem(at: srcUrl, to: destUrl) + } catch { + NSLog("copyResource copyItem ERROR") + return + } + } + + private func startServer() { + let cwdUrl = webFolder() + let executablePath = Bundle.main.path(forResource: "resources/bin/octoserver", ofType: "") + + let pid = ProcessInfo.processInfo.processIdentifier + NSLog("pid: \(pid)") + let serverProcess = Process() + serverProcess.currentDirectoryPath = cwdUrl.path + serverProcess.arguments = ["-monitorpid", "\(pid)", "-port", "\(serverPort)"] + serverProcess.launchPath = executablePath + serverProcess.launch() + self.serverProcess = serverProcess + + NSLog("startServer OK") + NSLog("cwd: \(cwdUrl)") + } + + private func stopServer() { + guard let serverProcess = self.serverProcess else { return } + + serverProcess.terminate() + self.serverProcess = nil + NSLog("stopServer OK") + } +} diff --git a/mac/Tasks/Assets.xcassets/AccentColor.colorset/Contents.json b/mac/Tasks/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/mac/Tasks/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Tasks/Assets.xcassets/AppIcon.appiconset/Contents.json b/mac/Tasks/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..35318b5cf --- /dev/null +++ b/mac/Tasks/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,60 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "tasks app icon-512x512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "tasks app icon.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Tasks/Assets.xcassets/AppIcon.appiconset/tasks app icon-512x512.png b/mac/Tasks/Assets.xcassets/AppIcon.appiconset/tasks app icon-512x512.png new file mode 100644 index 000000000..6479c6e69 Binary files /dev/null and b/mac/Tasks/Assets.xcassets/AppIcon.appiconset/tasks app icon-512x512.png differ diff --git a/mac/Tasks/Assets.xcassets/AppIcon.appiconset/tasks app icon.png b/mac/Tasks/Assets.xcassets/AppIcon.appiconset/tasks app icon.png new file mode 100644 index 000000000..678a5cf7b Binary files /dev/null and b/mac/Tasks/Assets.xcassets/AppIcon.appiconset/tasks app icon.png differ diff --git a/mac/Tasks/Assets.xcassets/Contents.json b/mac/Tasks/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/mac/Tasks/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Tasks/AutoSaveWindowController.swift b/mac/Tasks/AutoSaveWindowController.swift new file mode 100644 index 000000000..f0280590c --- /dev/null +++ b/mac/Tasks/AutoSaveWindowController.swift @@ -0,0 +1,18 @@ +// +// AutoSaveWindowController.swift +// Tasks +// +// Created by Chen-I Lim on 10/8/20. +// + +import Cocoa + +class AutoSaveWindowController: NSWindowController { + + override func windowDidLoad() { + super.windowDidLoad() + + // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. + self.windowFrameAutosaveName = NSWindow.FrameAutosaveName("AutoSaveWindow") + } +} diff --git a/mac/Tasks/Base.lproj/Main.storyboard b/mac/Tasks/Base.lproj/Main.storyboard new file mode 100644 index 000000000..c6bf61936 --- /dev/null +++ b/mac/Tasks/Base.lproj/Main.storyboard @@ -0,0 +1,744 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mac/Tasks/Info.plist b/mac/Tasks/Info.plist new file mode 100644 index 000000000..82c9eefd1 --- /dev/null +++ b/mac/Tasks/Info.plist @@ -0,0 +1,35 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/mac/Tasks/Inherit.entitlements b/mac/Tasks/Inherit.entitlements new file mode 100644 index 000000000..794eada1c --- /dev/null +++ b/mac/Tasks/Inherit.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + diff --git a/mac/Tasks/Tasks.entitlements b/mac/Tasks/Tasks.entitlements new file mode 100644 index 000000000..85c03d7b4 --- /dev/null +++ b/mac/Tasks/Tasks.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/mac/Tasks/ViewController.swift b/mac/Tasks/ViewController.swift new file mode 100644 index 000000000..b8c227f13 --- /dev/null +++ b/mac/Tasks/ViewController.swift @@ -0,0 +1,126 @@ +// +// ViewController.swift +// Tasks +// +// Created by Chen-I Lim on 10/7/20. +// + +import Cocoa +import WebKit + +class ViewController: + NSViewController, + WKUIDelegate, + WKNavigationDelegate { + @IBOutlet var webView: WKWebView! + + override func viewDidLoad() { + super.viewDidLoad() + + NSLog("viewDidLoad") + + webView.navigationDelegate = self + webView.uiDelegate = self + + clearWebViewCache() + loadHomepage() + + // Do any additional setup after loading the view. + NotificationCenter.default.addObserver(self, selector: #selector(onServerStarted), name: AppDelegate.serverStartedNotification, object: nil) + } + + override var representedObject: Any? { + didSet { + // Update the view, if already loaded. + } + } + + private func clearWebViewCache() { + let websiteDataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache]) + let date = Date(timeIntervalSince1970: 0) + WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes as! Set, modifiedSince: date, completionHandler:{ }) + } + + @objc func onServerStarted() { + NSLog("onServerStarted") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.loadHomepage() + } + } + + private func loadHomepage() { + let appDelegate = NSApplication.shared.delegate as! AppDelegate + let port = appDelegate.serverPort + let url = URL(string: "http://localhost:\(port)/")! + let request = URLRequest(url: url) + webView.load(request) + } + + private func downloadJsonUrl(_ url: URL) { + NSLog("downloadJsonUrl") + let prefix = "data:text/json," + let urlString = url.absoluteString + let encodedJson = String(urlString[urlString.index(urlString.startIndex, offsetBy: prefix.lengthOfBytes(using: .utf8))...]) + guard let json = encodedJson.removingPercentEncoding else { + return + } + + // Form file name + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-M-d" + let dateString = dateFormatter.string(from: Date()) + let filename = "archive-\(dateString).octo" + + // Save file + let savePanel = NSSavePanel() + savePanel.canCreateDirectories = true + savePanel.nameFieldStringValue = filename + // BUGBUG: Specifying the allowedFileTypes causes Catalina to hang / error out + //savePanel.allowedFileTypes = [".octo"] + savePanel.begin { (result) in + if result.rawValue == NSApplication.ModalResponse.OK.rawValue, + let fileUrl = savePanel.url { + try? json.write(to: fileUrl, atomically: true, encoding: .utf8) + } + } + } + + func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { + NSLog("webView runOpenPanel") + let openPanel = NSOpenPanel() + openPanel.canChooseFiles = true + openPanel.begin { (result) in + if result == NSApplication.ModalResponse.OK { + if let url = openPanel.url { + completionHandler([url]) + } + } else if result == NSApplication.ModalResponse.cancel { + completionHandler(nil) + } + } + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let url = navigationAction.request.url { + // Intercept archive downloads, and present native UI + if (url.absoluteString.hasPrefix("data:text/json,")) { + decisionHandler(.cancel) + downloadJsonUrl(url) + return + } + NSLog("decidePolicyFor navigationAction: \(url.absoluteString)") + } + decisionHandler(.allow) + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + NSLog("webView didFinish navigation: \(webView.url?.absoluteString ?? "")") + // Disable right-click menu + webView.evaluateJavaScript("document.body.setAttribute('oncontextmenu', 'event.preventDefault();');", completionHandler: nil) + } + + @IBAction func navigateToHome(_ sender: NSObject) { + loadHomepage() + } +} + diff --git a/mac/TasksTests/Info.plist b/mac/TasksTests/Info.plist new file mode 100644 index 000000000..bd32b84cf --- /dev/null +++ b/mac/TasksTests/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + 127.0.0.1 + + NSExceptionAllowsInsecureHTTPLoads + + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + + diff --git a/mac/TasksTests/TasksTests.swift b/mac/TasksTests/TasksTests.swift new file mode 100644 index 000000000..3163bce49 --- /dev/null +++ b/mac/TasksTests/TasksTests.swift @@ -0,0 +1,33 @@ +// +// TasksTests.swift +// TasksTests +// +// Created by Chen-I Lim on 10/7/20. +// + +import XCTest +@testable import Tasks + +class TasksTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/mac/TasksUITests/Info.plist b/mac/TasksUITests/Info.plist new file mode 100644 index 000000000..64d65ca49 --- /dev/null +++ b/mac/TasksUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/mac/TasksUITests/TasksUITests.swift b/mac/TasksUITests/TasksUITests.swift new file mode 100644 index 000000000..e84d81aff --- /dev/null +++ b/mac/TasksUITests/TasksUITests.swift @@ -0,0 +1,42 @@ +// +// TasksUITests.swift +// TasksUITests +// +// Created by Chen-I Lim on 10/7/20. +// + +import XCTest + +class TasksUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/mac/export.plist b/mac/export.plist new file mode 100644 index 000000000..90cf3947e --- /dev/null +++ b/mac/export.plist @@ -0,0 +1,16 @@ + + + + + compileBitcode + + method + development + signingStyle + automatic + stripSwiftSymbols + + thinning + <none> + + diff --git a/mac/resources/config.json b/mac/resources/config.json new file mode 100644 index 000000000..5a2fa7509 --- /dev/null +++ b/mac/resources/config.json @@ -0,0 +1,9 @@ +{ + "port": 8088, + "dbtype": "sqlite3", + "dbconfig": "./octo.db", + "postgres_dbconfig": "dbname=octo sslmode=disable", + "useSSL": false, + "webpath": "./pack", + "filespath": "./files" +}