diff --git a/.DS_Store b/.DS_Store index d4de974..0ad4526 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.version b/.version index be13f15..4023a10 100644 --- a/.version +++ b/.version @@ -1,6 +1,6 @@ { - "latestVersionNum": 22, - "latestVersion": "2.5.2", + "latestVersionNum": 30, + "latestVersion": "3.0.0", "updateType": "hint", - "releaseNotes": "1. 增加字符串长度查询设置,对于超长字符通守getrange改善性能。\n2. 优化线程池关闭方式。" + "releaseNotes": "1. 升级TCA,使用Reducer重写store 部分。\n2. 增加快速分页模式, 在查询结果数据量较大情况时,优化查询性能。" } diff --git a/Q&A.md b/Q&A.md index b14c648..2990bea 100644 --- a/Q&A.md +++ b/Q&A.md @@ -1,4 +1,13 @@ # 问题记录 -1. tca 监听不存在的属值时, 会造成编译卡死, swift-frontend 内存泄漏 - +## TCA +1. tca 监听不存在的属性值时, 会造成编译卡死, swift-frontend 内存泄漏 +2. 同一个reducer被注入多次时, action方法会被多次调用,例如: +``` + Scope(state: \.redisKeysState, action: /Action.redisKeysAction) { + RedisKeysStore(redisInstanceModel: redisInstanceModel) + } + Scope(state: \.redisKeysState, action: /Action.redisKeysAction) { + RedisKeysStore(redisInstanceModel: redisInstanceModel) + } +``` diff --git a/Tests/RedisBaseTests/RedisClientBaseTest.swift b/Tests/RedisBaseTests/RedisClientBaseTest.swift index b94b614..c125b6f 100644 --- a/Tests/RedisBaseTests/RedisClientBaseTest.swift +++ b/Tests/RedisBaseTests/RedisClientBaseTest.swift @@ -13,11 +13,13 @@ import Logging open class RedisClientBaseTest: RedisBaseTest { let logger = Logger(label: "redis-client-test") + var redisModel: RedisModel! var redisClient: RediStackClient! open override func setUp() { logger.info("redis client base test setUp...") - redisClient = .init(RedisModel(host: redisHostname, port: redisPort, username: redisUsername, password: redisPassword)) + self.redisModel = RedisModel(host: redisHostname, port: redisPort, username: redisUsername, password: redisPassword) + redisClient = .init(redisModel) // let conn = try await redisClient.initConn(host: redisHostname, port: redisPort, username: redisUsername ?? "", pass: redisPassword ?? "", database: 0) } diff --git a/Tests/Store/AppContextStoreTests.swift b/Tests/Store/AppContextStoreTests.swift new file mode 100644 index 0000000..00dd194 --- /dev/null +++ b/Tests/Store/AppContextStoreTests.swift @@ -0,0 +1,28 @@ +// +// AppContextStoreTests.swift +// Tests +// +// Created by chengpan on 2023/8/5. +// + +@testable import redis_pro +import Foundation +import XCTest +import ComposableArchitecture + +@MainActor +class AppContextStoreTests: StoreBaseTests { + func testShow() async { + let store = TestStore(initialState: AppContextStore.State()) { + AppContextStore() + } withDependencies: { + $0.redisInstance = redisInstance + $0.redisClient = redisClient + } + + await store.send(.show) { + $0.loading = true + $0.loadingCount = 1 + } + } +} diff --git a/Tests/Store/KeysDelStoreTests.swift b/Tests/Store/KeysDelStoreTests.swift new file mode 100644 index 0000000..cc18964 --- /dev/null +++ b/Tests/Store/KeysDelStoreTests.swift @@ -0,0 +1,27 @@ +// +// AppStoreTest.swift +// Tests +// +// Created by chengpan on 2023/8/5. +// + +@testable import redis_pro +import Foundation +import XCTest +import ComposableArchitecture + +@MainActor +class KeysDelStoreTests: StoreBaseTests { + func testBasics() async { + let store = TestStore(initialState: KeysDelStore.State()) { + KeysDelStore() + } withDependencies: { + $0.redisInstance = redisInstance + $0.redisClient = redisClient + } + +// await store.send(.favoriteAction(.connectSuccess(self.redisModel))) { +// $0.isConnect = true +// } + } +} diff --git a/Tests/Store/StoreBaseTests.swift b/Tests/Store/StoreBaseTests.swift new file mode 100644 index 0000000..1aa01be --- /dev/null +++ b/Tests/Store/StoreBaseTests.swift @@ -0,0 +1,28 @@ +// +// StoreBaseTests.swift +// Tests +// +// Created by chengpan on 2023/8/5. +// + +@testable import redis_pro +import Foundation +import Logging +import XCTest +import ComposableArchitecture + + +class StoreBaseTests: RedisClientBaseTest { + var redisInstance: RedisInstanceModel! + + override func setUp() { + super.setUp() + self.redisInstance = RedisInstanceModel(redisModel: redisModel) + logger.info("StoreBaseTests setup...") + } + + + func testExample() { + logger.info("test example ...") + } +} diff --git a/redis-pro.xcodeproj/project.pbxproj b/redis-pro.xcodeproj/project.pbxproj index 775b654..54aa81c 100644 --- a/redis-pro.xcodeproj/project.pbxproj +++ b/redis-pro.xcodeproj/project.pbxproj @@ -44,7 +44,6 @@ 432A6AE526A7C4D900F6DB64 /* RedisConfigItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432A6AE426A7C4D900F6DB64 /* RedisConfigItemModel.swift */; }; 434047FA2611C828001519F9 /* RedisFavoriteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434047F92611C828001519F9 /* RedisFavoriteModel.swift */; }; 4340E2CA2666494300F51F19 /* ScanModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4340E2C92666494300F51F19 /* ScanModel.swift */; }; - 4340E2CC266649D000F51F19 /* ScanBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4340E2CB266649D000F51F19 /* ScanBar.swift */; }; 436BB6932644D126008B4866 /* SetEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436BB6922644D126008B4866 /* SetEditorView.swift */; }; 436BB6952644E8D6008B4866 /* ZSetEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436BB6942644E8D6008B4866 /* ZSetEditorView.swift */; }; 436BB6972644EED8008B4866 /* FormItemDouble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436BB6962644EED8008B4866 /* FormItemDouble.swift */; }; @@ -109,28 +108,30 @@ 6237D032275C954A000ACD6A /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 6237D031275C954A000ACD6A /* Logging */; }; 6237D034275CFF1F000ACD6A /* NIntField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6237D033275CFF1F000ACD6A /* NIntField.swift */; }; 6240E0AB280303890005E793 /* RedisDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6240E0AA280303890005E793 /* RedisDefaults.swift */; }; + 624770212A51450000A4AF74 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 624770202A51450000A4AF74 /* ComposableArchitecture */; }; + 624770232A515D9700A4AF74 /* DependencyKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624770222A515D9700A4AF74 /* DependencyKeys.swift */; }; 624F940427FADDD400DF3D3E /* TableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624F940327FADDD400DF3D3E /* TableCellView.swift */; }; 6252A4B627F0450200C9536A /* NTableColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6252A4B527F0450200C9536A /* NTableColumn.swift */; }; 6252A4B827F04ACC00C9536A /* TableColumnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6252A4B727F04ACC00C9536A /* TableColumnType.swift */; }; 6252A4BA27F0542E00C9536A /* Assert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6252A4B927F0542E00C9536A /* Assert.swift */; }; - 625B41B9293AEBD700C7766D /* RediStack in Frameworks */ = {isa = PBXBuildFile; productRef = 625B41B8293AEBD700C7766D /* RediStack */; }; - 625B41BB293AEBD700C7766D /* RedisTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 625B41BA293AEBD700C7766D /* RedisTypes */; }; 625B41BE293AF81100C7766D /* AppCenterAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 625B41BD293AF81100C7766D /* AppCenterAnalytics */; }; 625B41C0293AF81100C7766D /* AppCenterCrashes in Frameworks */ = {isa = PBXBuildFile; productRef = 625B41BF293AF81100C7766D /* AppCenterCrashes */; }; + 6264BAE72A760CC90051FB61 /* KeyObjectBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6264BAE62A760CC90051FB61 /* KeyObjectBar.swift */; }; + 6264BAE92A7659D40051FB61 /* KeysDelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6264BAE82A7659D40051FB61 /* KeysDelStore.swift */; }; + 6264BAEB2A765E870051FB61 /* KeyDelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6264BAEA2A765E870051FB61 /* KeyDelModel.swift */; }; 626C4C10281D331D00B7A542 /* FavoriteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C0F281D331D00B7A542 /* FavoriteStore.swift */; }; 626C4C14281D485A00B7A542 /* AppStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C13281D485A00B7A542 /* AppStore.swift */; }; 626C4C16281D4B6500B7A542 /* TableStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C15281D4B6500B7A542 /* TableStore.swift */; }; 626C4C18281E6BC600B7A542 /* LoginStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C17281E6BC600B7A542 /* LoginStore.swift */; }; 626C4C60281FEEC100B7A542 /* SettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C5F281FEEC100B7A542 /* SettingsStore.swift */; }; 626C4C622820376700B7A542 /* RedisFavoriteDefaultSelectTypeEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C612820376700B7A542 /* RedisFavoriteDefaultSelectTypeEnum.swift */; }; - 626C4C642821053A00B7A542 /* AlertStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C632821053A00B7A542 /* AlertStore.swift */; }; - 626C4C6628210EED00B7A542 /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C6528210EED00B7A542 /* AlertView.swift */; }; 626C4C68282111BB00B7A542 /* LoadingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C67282111BB00B7A542 /* LoadingStore.swift */; }; 626C4C6A2821126100B7A542 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C4C692821126100B7A542 /* LoadingView.swift */; }; 626E10ED27B794E7007ED968 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626E10EC27B794E7007ED968 /* AboutView.swift */; }; + 627B2DD32A6CBA8900ADB5DA /* RedisClientConn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627B2DD22A6CBA8900ADB5DA /* RedisClientConn.swift */; }; + 627B2DD72A6D33FF00ADB5DA /* KeyObjectStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627B2DD62A6D33FF00ADB5DA /* KeyObjectStore.swift */; }; 627E1BC4282F462C00163D6B /* RenameStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627E1BC3282F462C00163D6B /* RenameStore.swift */; }; 627E1BC6282FC72B00163D6B /* HashValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627E1BC5282FC72B00163D6B /* HashValueStore.swift */; }; - 627E1BC8282FCEE300163D6B /* ScanStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627E1BC7282FCEE300163D6B /* ScanStore.swift */; }; 6280596828B218D800126E81 /* RedisClientString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6280596728B218D800126E81 /* RedisClientString.swift */; }; 6280596A28B23DBB00126E81 /* RediStackClientStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6280596928B23DBB00126E81 /* RediStackClientStream.swift */; }; 628F9AA02896A3FB0003B6C0 /* RedisCommandExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 628F9A9F2896A3FB0003B6C0 /* RedisCommandExt.swift */; }; @@ -152,11 +153,9 @@ 62CB3D7D2619AF990061E8C3 /* RedisKeyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3D7C2619AF990061E8C3 /* RedisKeyModel.swift */; }; 62CB3D802619B09B0061E8C3 /* RedisKeyTypeEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3D7F2619B09B0061E8C3 /* RedisKeyTypeEnum.swift */; }; 62D1BA93284257AA00F41CAD /* ZSetValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1BA92284257AA00F41CAD /* ZSetValueStore.swift */; }; - 62D1BA9628437AF700F41CAD /* MessageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1BA9528437AF700F41CAD /* MessageStore.swift */; }; 62D1BA9828437F5900F41CAD /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1BA9728437F5900F41CAD /* Messages.swift */; }; 62D1BA9A2847990F00F41CAD /* Loadings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1BA992847990F00F41CAD /* Loadings.swift */; }; - 62D1BA9C2848F1F800F41CAD /* GlobalStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1BA9B2848F1F800F41CAD /* GlobalStore.swift */; }; - 62D23AAF2818DCA000B2AA3F /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 62D23AAE2818DCA000B2AA3F /* ComposableArchitecture */; }; + 62D1BA9C2848F1F800F41CAD /* AppContextStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1BA9B2848F1F800F41CAD /* AppContextStore.swift */; }; 62D741B3284B536A0049AB3C /* RedisInfoStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D741B2284B536A0049AB3C /* RedisInfoStore.swift */; }; 62D741B5284B67B00049AB3C /* RedisSystemStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D741B4284B67B00049AB3C /* RedisSystemStore.swift */; }; 62D741B7284B69040049AB3C /* RedisSystemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D741B6284B69040049AB3C /* RedisSystemView.swift */; }; @@ -172,6 +171,12 @@ 62E8F9D62765EC97006A5326 /* NSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E8F9D52765EC97006A5326 /* NSecureField.swift */; }; 62E8F9D92765F197006A5326 /* MPasswordField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E8F9D82765F197006A5326 /* MPasswordField.swift */; }; 62E9F13F2849DA9F00F4FABF /* SystemEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9F13E2849DA9F00F4FABF /* SystemEnvironment.swift */; }; + 62F44FE42A7E752D00714D98 /* AppContextStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F44FE32A7E752D00714D98 /* AppContextStoreTests.swift */; }; + 62F44FE62AADAD2000714D98 /* FormText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F44FE52AADAD2000714D98 /* FormText.swift */; }; + 62FD85D62A7E6020005DE558 /* RediStack in Frameworks */ = {isa = PBXBuildFile; productRef = 62FD85D52A7E6020005DE558 /* RediStack */; }; + 62FD85D82A7E6020005DE558 /* RedisTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 62FD85D72A7E6020005DE558 /* RedisTypes */; }; + 62FD85DB2A7E680E005DE558 /* KeysDelStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD85DA2A7E680E005DE558 /* KeysDelStoreTests.swift */; }; + 62FD85DD2A7E6A46005DE558 /* StoreBaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD85DC2A7E6A46005DE558 /* StoreBaseTests.swift */; }; CE0290CE2786B75A0058442B /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = CE0290CD2786B75A0058442B /* Puppy */; }; CE3A187D282525AB00988515 /* KeyStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3A187C282525AB00988515 /* KeyStore.swift */; }; CE3A18812825367000988515 /* ValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3A18802825367000988515 /* ValueStore.swift */; }; @@ -245,7 +250,6 @@ 432A6AE426A7C4D900F6DB64 /* RedisConfigItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisConfigItemModel.swift; sourceTree = ""; }; 434047F92611C828001519F9 /* RedisFavoriteModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisFavoriteModel.swift; sourceTree = ""; }; 4340E2C92666494300F51F19 /* ScanModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanModel.swift; sourceTree = ""; }; - 4340E2CB266649D000F51F19 /* ScanBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanBar.swift; sourceTree = ""; }; 436BB6922644D126008B4866 /* SetEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetEditorView.swift; sourceTree = ""; }; 436BB6942644E8D6008B4866 /* ZSetEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSetEditorView.swift; sourceTree = ""; }; 436BB6962644EED8008B4866 /* FormItemDouble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormItemDouble.swift; sourceTree = ""; }; @@ -313,24 +317,27 @@ 621EFD8D276CC52E0079D1E3 /* NPasswordField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NPasswordField.swift; sourceTree = ""; }; 6237D033275CFF1F000ACD6A /* NIntField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIntField.swift; sourceTree = ""; }; 6240E0AA280303890005E793 /* RedisDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisDefaults.swift; sourceTree = ""; }; + 624770222A515D9700A4AF74 /* DependencyKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyKeys.swift; sourceTree = ""; }; 624F940327FADDD400DF3D3E /* TableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellView.swift; sourceTree = ""; }; 6252A4B527F0450200C9536A /* NTableColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NTableColumn.swift; sourceTree = ""; }; 6252A4B727F04ACC00C9536A /* TableColumnType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableColumnType.swift; sourceTree = ""; }; 6252A4B927F0542E00C9536A /* Assert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assert.swift; sourceTree = ""; }; + 6264BAE62A760CC90051FB61 /* KeyObjectBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyObjectBar.swift; sourceTree = ""; }; + 6264BAE82A7659D40051FB61 /* KeysDelStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeysDelStore.swift; sourceTree = ""; }; + 6264BAEA2A765E870051FB61 /* KeyDelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDelModel.swift; sourceTree = ""; }; 626C4C0F281D331D00B7A542 /* FavoriteStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteStore.swift; sourceTree = ""; }; 626C4C13281D485A00B7A542 /* AppStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStore.swift; sourceTree = ""; }; 626C4C15281D4B6500B7A542 /* TableStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableStore.swift; sourceTree = ""; }; 626C4C17281E6BC600B7A542 /* LoginStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginStore.swift; sourceTree = ""; }; 626C4C5F281FEEC100B7A542 /* SettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStore.swift; sourceTree = ""; }; 626C4C612820376700B7A542 /* RedisFavoriteDefaultSelectTypeEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisFavoriteDefaultSelectTypeEnum.swift; sourceTree = ""; }; - 626C4C632821053A00B7A542 /* AlertStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertStore.swift; sourceTree = ""; }; - 626C4C6528210EED00B7A542 /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = ""; }; 626C4C67282111BB00B7A542 /* LoadingStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingStore.swift; sourceTree = ""; }; 626C4C692821126100B7A542 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; 626E10EC27B794E7007ED968 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + 627B2DD22A6CBA8900ADB5DA /* RedisClientConn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisClientConn.swift; sourceTree = ""; }; + 627B2DD62A6D33FF00ADB5DA /* KeyObjectStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyObjectStore.swift; sourceTree = ""; }; 627E1BC3282F462C00163D6B /* RenameStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameStore.swift; sourceTree = ""; }; 627E1BC5282FC72B00163D6B /* HashValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashValueStore.swift; sourceTree = ""; }; - 627E1BC7282FCEE300163D6B /* ScanStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanStore.swift; sourceTree = ""; }; 6280596728B218D800126E81 /* RedisClientString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisClientString.swift; sourceTree = ""; }; 6280596928B23DBB00126E81 /* RediStackClientStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RediStackClientStream.swift; sourceTree = ""; }; 628F9A9F2896A3FB0003B6C0 /* RedisCommandExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisCommandExt.swift; sourceTree = ""; }; @@ -353,10 +360,9 @@ 62CB3D7C2619AF990061E8C3 /* RedisKeyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisKeyModel.swift; sourceTree = ""; }; 62CB3D7F2619B09B0061E8C3 /* RedisKeyTypeEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisKeyTypeEnum.swift; sourceTree = ""; }; 62D1BA92284257AA00F41CAD /* ZSetValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSetValueStore.swift; sourceTree = ""; }; - 62D1BA9528437AF700F41CAD /* MessageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStore.swift; sourceTree = ""; }; 62D1BA9728437F5900F41CAD /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; 62D1BA992847990F00F41CAD /* Loadings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loadings.swift; sourceTree = ""; }; - 62D1BA9B2848F1F800F41CAD /* GlobalStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalStore.swift; sourceTree = ""; }; + 62D1BA9B2848F1F800F41CAD /* AppContextStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContextStore.swift; sourceTree = ""; }; 62D741B1284B51600049AB3C /* Q&A.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Q&A.md"; sourceTree = ""; }; 62D741B2284B536A0049AB3C /* RedisInfoStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisInfoStore.swift; sourceTree = ""; }; 62D741B4284B67B00049AB3C /* RedisSystemStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedisSystemStore.swift; sourceTree = ""; }; @@ -373,6 +379,10 @@ 62E8F9D52765EC97006A5326 /* NSecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSecureField.swift; sourceTree = ""; }; 62E8F9D82765F197006A5326 /* MPasswordField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPasswordField.swift; sourceTree = ""; }; 62E9F13E2849DA9F00F4FABF /* SystemEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemEnvironment.swift; sourceTree = ""; }; + 62F44FE32A7E752D00714D98 /* AppContextStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContextStoreTests.swift; sourceTree = ""; }; + 62F44FE52AADAD2000714D98 /* FormText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormText.swift; sourceTree = ""; }; + 62FD85DA2A7E680E005DE558 /* KeysDelStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeysDelStoreTests.swift; sourceTree = ""; }; + 62FD85DC2A7E6A46005DE558 /* StoreBaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreBaseTests.swift; sourceTree = ""; }; CE3A187C282525AB00988515 /* KeyStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyStore.swift; sourceTree = ""; }; CE3A18802825367000988515 /* ValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueStore.swift; sourceTree = ""; }; CE3A1882282536BB00988515 /* StringValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringValueStore.swift; sourceTree = ""; }; @@ -391,8 +401,8 @@ files = ( CEA1EF4D277C56DD00D300E9 /* NIO in Frameworks */, CEA1EF55277C56DD00D300E9 /* NIOFoundationCompat in Frameworks */, - 62D23AAF2818DCA000B2AA3F /* ComposableArchitecture in Frameworks */, - 625B41B9293AEBD700C7766D /* RediStack in Frameworks */, + 624770212A51450000A4AF74 /* ComposableArchitecture in Frameworks */, + 62FD85D62A7E6020005DE558 /* RediStack in Frameworks */, CE0290CE2786B75A0058442B /* Puppy in Frameworks */, CEA1EF53277C56DD00D300E9 /* NIOEmbedded in Frameworks */, CEA1EF5E277C57BC00D300E9 /* SwiftyJSON in Frameworks */, @@ -400,8 +410,8 @@ 625B41C0293AF81100C7766D /* AppCenterCrashes in Frameworks */, CEA1EF4A277C56B100D300E9 /* NIOSSH in Frameworks */, CEA1EF57277C56DD00D300E9 /* NIOPosix in Frameworks */, - 625B41BB293AEBD700C7766D /* RedisTypes in Frameworks */, 6237D032275C954A000ACD6A /* Logging in Frameworks */, + 62FD85D82A7E6020005DE558 /* RedisTypes in Frameworks */, 625B41BE293AF81100C7766D /* AppCenterAnalytics in Frameworks */, CEA1EF51277C56DD00D300E9 /* NIOCore in Frameworks */, CEA1EF4F277C56DD00D300E9 /* NIOConcurrencyHelpers in Frameworks */, @@ -443,7 +453,7 @@ 431266ED261D517500FB6B69 /* RedisKeyTypePicker.swift */, 4314A33D2624370E00053FEE /* SearchBar.swift */, 4314A34026243ED500053FEE /* PageBar.swift */, - 4340E2CB266649D000F51F19 /* ScanBar.swift */, + 6264BAE62A760CC90051FB61 /* KeyObjectBar.swift */, 43BB012226390BC20039565E /* ModalView.swift */, 437C0936264952B300F1904C /* MTabView.swift */, 4382203D265668CC00DA7F9E /* MLoading.swift */, @@ -453,7 +463,6 @@ 43F22EE4269C2E0900A00F97 /* TextViewController.swift */, 43F22EE2269C2D6600A00F97 /* TextViewController.xib */, 62D1BA9728437F5900F41CAD /* Messages.swift */, - 626C4C6528210EED00B7A542 /* AlertView.swift */, 626C4C692821126100B7A542 /* LoadingView.swift */, ); path = Components; @@ -533,6 +542,7 @@ 4373C50825C3F218002B700E /* RedisModel.swift */, 434047F92611C828001519F9 /* RedisFavoriteModel.swift */, 62CB3D7C2619AF990061E8C3 /* RedisKeyModel.swift */, + 6264BAEA2A765E870051FB61 /* KeyDelModel.swift */, 431266EA261D4EED00FB6B69 /* RedisKeyValueModel.swift */, 4314A34B26258A4300053FEE /* Page.swift */, 4340E2C92666494300F51F19 /* ScanModel.swift */, @@ -652,6 +662,7 @@ 62D741C2288416470049AB3C /* RedisClientLua.swift */, 628F9A9F2896A3FB0003B6C0 /* RedisCommandExt.swift */, 628F9AA3289EAEE90003B6C0 /* RedisClientSSH.swift */, + 627B2DD22A6CBA8900ADB5DA /* RedisClientConn.swift */, ); path = RedisClient; sourceTree = ""; @@ -682,18 +693,16 @@ 626C4C13281D485A00B7A542 /* AppStore.swift */, 62E9F13E2849DA9F00F4FABF /* SystemEnvironment.swift */, 626C4C0F281D331D00B7A542 /* FavoriteStore.swift */, - 62D1BA9B2848F1F800F41CAD /* GlobalStore.swift */, + 62D1BA9B2848F1F800F41CAD /* AppContextStore.swift */, 626C4C5F281FEEC100B7A542 /* SettingsStore.swift */, CEEC2561282508C0000463F5 /* RedisKeysStore.swift */, CE3A187C282525AB00988515 /* KeyStore.swift */, + 627B2DD62A6D33FF00ADB5DA /* KeyObjectStore.swift */, CE3A18842825499A00988515 /* DatabaseStore.swift */, CE3A18862825577300988515 /* PageStore.swift */, - 627E1BC7282FCEE300163D6B /* ScanStore.swift */, 626C4C15281D4B6500B7A542 /* TableStore.swift */, 627E1BC3282F462C00163D6B /* RenameStore.swift */, 626C4C17281E6BC600B7A542 /* LoginStore.swift */, - 626C4C632821053A00B7A542 /* AlertStore.swift */, - 62D1BA9528437AF700F41CAD /* MessageStore.swift */, 626C4C67282111BB00B7A542 /* LoadingStore.swift */, CE3A18802825367000988515 /* ValueStore.swift */, CE3A1882282536BB00988515 /* StringValueStore.swift */, @@ -703,10 +712,12 @@ 62D1BA92284257AA00F41CAD /* ZSetValueStore.swift */, 62D741B4284B67B00049AB3C /* RedisSystemStore.swift */, 62D741B2284B536A0049AB3C /* RedisInfoStore.swift */, + 6264BAE82A7659D40051FB61 /* KeysDelStore.swift */, 62D741B8284B776D0049AB3C /* RedisConfigStore.swift */, 62D741BA284B82790049AB3C /* SlowLogStore.swift */, 62D741BC284C66560049AB3C /* ClientListStore.swift */, 62D741C0288415180049AB3C /* LuaStore.swift */, + 624770222A515D9700A4AF74 /* DependencyKeys.swift */, ); path = Store; sourceTree = ""; @@ -723,6 +734,7 @@ 62BDAB2C293B040C00ADA406 /* Tests */ = { isa = PBXGroup; children = ( + 62FD85D92A7E67BE005DE558 /* Store */, 62BDAB34293B451E00ADA406 /* RedisBaseTests */, 62BDAB39293B73B200ADA406 /* RedisCommandTests */, 62BDAB2D293B040C00ADA406 /* Tests.swift */, @@ -813,10 +825,21 @@ 43BCCD3426A919B8000BE45F /* MSecureField.swift */, 437BC2482645413000E2C84D /* MDoubleField.swift */, 4313576C263AB6A20077EE46 /* MTextEditor.swift */, + 62F44FE52AADAD2000714D98 /* FormText.swift */, ); path = Form; sourceTree = ""; }; + 62FD85D92A7E67BE005DE558 /* Store */ = { + isa = PBXGroup; + children = ( + 62FD85DC2A7E6A46005DE558 /* StoreBaseTests.swift */, + 62FD85DA2A7E680E005DE558 /* KeysDelStoreTests.swift */, + 62F44FE32A7E752D00714D98 /* AppContextStoreTests.swift */, + ); + path = Store; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -846,11 +869,11 @@ CEA1EF5A277C56DD00D300E9 /* _NIOConcurrency */, CEA1EF5D277C57BC00D300E9 /* SwiftyJSON */, CE0290CD2786B75A0058442B /* Puppy */, - 62D23AAE2818DCA000B2AA3F /* ComposableArchitecture */, - 625B41B8293AEBD700C7766D /* RediStack */, - 625B41BA293AEBD700C7766D /* RedisTypes */, 625B41BD293AF81100C7766D /* AppCenterAnalytics */, 625B41BF293AF81100C7766D /* AppCenterCrashes */, + 624770202A51450000A4AF74 /* ComposableArchitecture */, + 62FD85D52A7E6020005DE558 /* RediStack */, + 62FD85D72A7E6020005DE558 /* RedisTypes */, ); productName = "redis-pro"; productReference = 4320AAF025B6740900A8E214 /* redis-pro.app */; @@ -908,9 +931,9 @@ CEA1EF4B277C56DD00D300E9 /* XCRemoteSwiftPackageReference "swift-nio" */, CEA1EF5C277C57BC00D300E9 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, CE0290CC2786B75A0058442B /* XCRemoteSwiftPackageReference "Puppy" */, - 62D23AAD2818DCA000B2AA3F /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, - 625B41B7293AEBD700C7766D /* XCRemoteSwiftPackageReference "RediStack" */, 625B41BC293AF81100C7766D /* XCRemoteSwiftPackageReference "appcenter-sdk-apple" */, + 6247701F2A51450000A4AF74 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, + 62FD85D42A7E6020005DE558 /* XCRemoteSwiftPackageReference "RediStack" */, ); productRefGroup = 4320AAF125B6740900A8E214 /* Products */; projectDirPath = ""; @@ -957,12 +980,12 @@ 4313576F263ABB1C0077EE46 /* FormWrapper.swift in Sources */, 621EFD8C276CB7510079D1E3 /* NTable.swift in Sources */, 6240E0AB280303890005E793 /* RedisDefaults.swift in Sources */, + 6264BAEB2A765E870051FB61 /* KeyDelModel.swift in Sources */, 626C4C14281D485A00B7A542 /* AppStore.swift in Sources */, 620AF6B827D486F5002D6895 /* RedisClientScan.swift in Sources */, 626C4C68282111BB00B7A542 /* LoadingStore.swift in Sources */, 626C4C16281D4B6500B7A542 /* TableStore.swift in Sources */, 6252A4B827F04ACC00C9536A /* TableColumnType.swift in Sources */, - 4340E2CC266649D000F51F19 /* ScanBar.swift in Sources */, 4313576B263AB6400077EE46 /* FormItemTextArea.swift in Sources */, CEEC2562282508C0000463F5 /* RedisKeysStore.swift in Sources */, CE916155276B60650026C625 /* NSearchField.swift in Sources */, @@ -985,6 +1008,7 @@ CE3A187D282525AB00988515 /* KeyStore.swift in Sources */, 62CB3D722619AE050061E8C3 /* HomeView.swift in Sources */, CE3A1883282536BB00988515 /* StringValueStore.swift in Sources */, + 62F44FE62AADAD2000714D98 /* FormText.swift in Sources */, 431266E8261D4E4100FB6B69 /* RedisValueHeaderView.swift in Sources */, 43F22F0B26A159B900A00F97 /* RedisZSetItemModel.swift in Sources */, 620AF6B627D485EB002D6895 /* RedisClientKeys.swift in Sources */, @@ -998,17 +1022,16 @@ 6280596828B218D800126E81 /* RedisClientString.swift in Sources */, 62D741B9284B776D0049AB3C /* RedisConfigStore.swift in Sources */, 62E9F13F2849DA9F00F4FABF /* SystemEnvironment.swift in Sources */, - 627E1BC8282FCEE300163D6B /* ScanStore.swift in Sources */, - 62D1BA9C2848F1F800F41CAD /* GlobalStore.swift in Sources */, + 62D1BA9C2848F1F800F41CAD /* AppContextStore.swift in Sources */, 431266D5261C5C9000FB6B69 /* ButtonTypeEnum.swift in Sources */, 43CD287A267209D900E11876 /* RedisInfoView.swift in Sources */, 62C262E02839D4380036A282 /* ListValueStore.swift in Sources */, 627E1BC4282F462C00163D6B /* RenameStore.swift in Sources */, - 62D1BA9628437AF700F41CAD /* MessageStore.swift in Sources */, 4370534E262812EE00D8F1B7 /* GlobalContext.swift in Sources */, 431266EB261D4EED00FB6B69 /* RedisKeyValueModel.swift in Sources */, 626C4C10281D331D00B7A542 /* FavoriteStore.swift in Sources */, 620AF6BE27D488C4002D6895 /* RedisClientSet.swift in Sources */, + 624770232A515D9700A4AF74 /* DependencyKeys.swift in Sources */, 431266F2261D89E600FB6B69 /* RedisValueEditView.swift in Sources */, 62C262E22839DD0B0036A282 /* RedisListItemModel.swift in Sources */, 62D1BA9828437F5900F41CAD /* Messages.swift in Sources */, @@ -1030,6 +1053,7 @@ 62D1BA9A2847990F00F41CAD /* Loadings.swift in Sources */, 43CD286E2670A03800E11876 /* VersionManager.swift in Sources */, 6252A4B627F0450200C9536A /* NTableColumn.swift in Sources */, + 6264BAE72A760CC90051FB61 /* KeyObjectBar.swift in Sources */, 430A09DC25BEAD2700B60DFC /* RedisInsanceModel.swift in Sources */, 627E1BC6282FC72B00163D6B /* HashValueStore.swift in Sources */, 437BC24E26461E2100E2C84D /* NumberHelper.swift in Sources */, @@ -1056,16 +1080,15 @@ 43DFEF3725C29288006EF535 /* FormItemInt.swift in Sources */, 4313576D263AB6A20077EE46 /* MTextEditor.swift in Sources */, 626C4C18281E6BC600B7A542 /* LoginStore.swift in Sources */, - 626C4C6628210EED00B7A542 /* AlertView.swift in Sources */, 62C262E6283A327B0036A282 /* SetValueStore.swift in Sources */, 620AF6C427D48973002D6895 /* RedisClientConfig.swift in Sources */, 620AF6C027D488D0002D6895 /* RedisClientZSet.swift in Sources */, 4314A33E2624370E00053FEE /* SearchBar.swift in Sources */, 62E8F9D22765019D006A5326 /* MIntField.swift in Sources */, 431266DE261D45C500FB6B69 /* MIcon.swift in Sources */, + 627B2DD72A6D33FF00ADB5DA /* KeyObjectStore.swift in Sources */, 62D741BB284B827A0049AB3C /* SlowLogStore.swift in Sources */, 437C09352649039000F1904C /* DatabasePicker.swift in Sources */, - 626C4C642821053A00B7A542 /* AlertStore.swift in Sources */, 6252A4BA27F0542E00C9536A /* Assert.swift in Sources */, 4301C84526BBE68400C08E19 /* SSHAuthentication.swift in Sources */, 430A09E225C1559400B60DFC /* FormLabel.swift in Sources */, @@ -1096,7 +1119,9 @@ 43CD28782671FA2A00E11876 /* RedisInfoModel.swift in Sources */, 437BC2492645413000E2C84D /* MDoubleField.swift in Sources */, 43CD28B4267C6F1100E11876 /* ClientModel.swift in Sources */, + 6264BAE92A7659D40051FB61 /* KeysDelStore.swift in Sources */, CE3A18812825367000988515 /* ValueStore.swift in Sources */, + 627B2DD32A6CBA8900ADB5DA /* RedisClientConn.swift in Sources */, 620AF6C227D48923002D6895 /* RedisClientSystem.swift in Sources */, 62CB3D802619B09B0061E8C3 /* RedisKeyTypeEnum.swift in Sources */, 437BC2452645272E00E2C84D /* DoubleFormatter.swift in Sources */, @@ -1116,8 +1141,11 @@ buildActionMask = 2147483647; files = ( 62BDAB3B293B73E300ADA406 /* RedisClentStringTest.swift in Sources */, + 62FD85DB2A7E680E005DE558 /* KeysDelStoreTests.swift in Sources */, + 62FD85DD2A7E6A46005DE558 /* StoreBaseTests.swift in Sources */, 62BDAB2E293B040C00ADA406 /* Tests.swift in Sources */, 62BDAB38293B4C5200ADA406 /* RedisClientBaseTest.swift in Sources */, + 62F44FE42A7E752D00714D98 /* AppContextStoreTests.swift in Sources */, 62BDAB36293B462600ADA406 /* RedisBaseTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1273,7 +1301,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 30; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"redis-pro/Preview Content\""; ENABLE_PREVIEWS = YES; @@ -1283,7 +1311,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.cmushroom.redis-pro"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -1299,7 +1327,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 30; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"redis-pro/Preview Content\""; DEVELOPMENT_TEAM = ""; @@ -1310,7 +1338,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 2.5.1; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.cmushroom.redis-pro"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1332,10 +1360,11 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.redis-pro.Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTED_PLATFORMS = macosx; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/redis-pro.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/redis-pro"; }; name = Debug; @@ -1354,10 +1383,11 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.redis-pro.Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTED_PLATFORMS = macosx; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/redis-pro.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/redis-pro"; }; name = Release; @@ -1403,12 +1433,12 @@ minimumVersion = 1.5.2; }; }; - 625B41B7293AEBD700C7766D /* XCRemoteSwiftPackageReference "RediStack" */ = { + 6247701F2A51450000A4AF74 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/chengpan168/RediStack"; + repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; requirement = { - branch = feat/user; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 1.2.0; }; }; 625B41BC293AF81100C7766D /* XCRemoteSwiftPackageReference "appcenter-sdk-apple" */ = { @@ -1419,11 +1449,11 @@ minimumVersion = 5.0.0; }; }; - 62D23AAD2818DCA000B2AA3F /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { + 62FD85D42A7E6020005DE558 /* XCRemoteSwiftPackageReference "RediStack" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; + repositoryURL = "https://github.com/chengpan168/RediStack"; requirement = { - branch = main; + branch = feat/user; kind = branch; }; }; @@ -1440,7 +1470,7 @@ repositoryURL = "https://github.com/apple/swift-nio-ssh"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.6.1; + minimumVersion = 0.8.0; }; }; CEA1EF4B277C56DD00D300E9 /* XCRemoteSwiftPackageReference "swift-nio" */ = { @@ -1448,7 +1478,7 @@ repositoryURL = "https://github.com/apple/swift-nio.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.0.0; + minimumVersion = 2.57.0; }; }; CEA1EF5C277C57BC00D300E9 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { @@ -1467,15 +1497,10 @@ package = 6237D030275C954A000ACD6A /* XCRemoteSwiftPackageReference "swift-log" */; productName = Logging; }; - 625B41B8293AEBD700C7766D /* RediStack */ = { - isa = XCSwiftPackageProductDependency; - package = 625B41B7293AEBD700C7766D /* XCRemoteSwiftPackageReference "RediStack" */; - productName = RediStack; - }; - 625B41BA293AEBD700C7766D /* RedisTypes */ = { + 624770202A51450000A4AF74 /* ComposableArchitecture */ = { isa = XCSwiftPackageProductDependency; - package = 625B41B7293AEBD700C7766D /* XCRemoteSwiftPackageReference "RediStack" */; - productName = RedisTypes; + package = 6247701F2A51450000A4AF74 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; + productName = ComposableArchitecture; }; 625B41BD293AF81100C7766D /* AppCenterAnalytics */ = { isa = XCSwiftPackageProductDependency; @@ -1487,10 +1512,15 @@ package = 625B41BC293AF81100C7766D /* XCRemoteSwiftPackageReference "appcenter-sdk-apple" */; productName = AppCenterCrashes; }; - 62D23AAE2818DCA000B2AA3F /* ComposableArchitecture */ = { + 62FD85D52A7E6020005DE558 /* RediStack */ = { isa = XCSwiftPackageProductDependency; - package = 62D23AAD2818DCA000B2AA3F /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; - productName = ComposableArchitecture; + package = 62FD85D42A7E6020005DE558 /* XCRemoteSwiftPackageReference "RediStack" */; + productName = RediStack; + }; + 62FD85D72A7E6020005DE558 /* RedisTypes */ = { + isa = XCSwiftPackageProductDependency; + package = 62FD85D42A7E6020005DE558 /* XCRemoteSwiftPackageReference "RediStack" */; + productName = RedisTypes; }; CE0290CD2786B75A0058442B /* Puppy */ = { isa = XCSwiftPackageProductDependency; diff --git a/redis-pro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/redis-pro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7fec572..eb7df10 100644 --- a/redis-pro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/redis-pro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/microsoft/appcenter-sdk-apple.git", "state" : { - "revision" : "88e65475ffd3a7cf2bbde07df9e62159a1fd60a8", - "version" : "5.0.0" + "revision" : "5756ddb0f09041e91bdb3b73c17296ac005ad11a", + "version" : "5.0.3" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "4cf088c29a20f52be0f2ca54992b492c54e0076b", - "version" : "0.5.3" + "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", + "version" : "1.0.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics", "state" : { - "revision" : "919eb1d83e02121cdb434c7bfc1f0c66ef17febe", - "version" : "1.0.2" + "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", + "version" : "1.1.0" } }, { @@ -59,8 +59,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "ce9c0d897db8a840c39de64caaa9b60119cf4be8", - "version" : "0.8.1" + "revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb", + "version" : "1.0.0" } }, { @@ -77,8 +86,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "branch" : "main", - "revision" : "007c857bde0410b9d214d7f1e7659551553e337a" + "revision" : "a7c1f799b55ecb418f85094b142565834f7ee7c7", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "ea631ce892687f5432a833312292b80db238186a", + "version" : "1.0.0" } }, { @@ -95,8 +113,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "51698ece74ecf31959d3fa81733f0a5363ef1b4e", - "version" : "0.3.0" + "revision" : "edd66cace818e1b1c6f1b3349bb1d8e00d6f8b01", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "4e1eb6e28afe723286d8cc60611237ffbddba7c5", + "version" : "1.0.0" } }, { @@ -104,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-identified-collections", "state" : { - "revision" : "680bf440178a78a627b1c2c64c0855f6523ad5b9", - "version" : "0.3.2" + "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", + "version" : "1.0.0" } }, { @@ -122,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-metrics.git", "state" : { - "revision" : "53be78637ecd165d1ddedc4e20de69b8f43ec3b7", - "version" : "2.3.2" + "revision" : "971ba26378ab69c43737ee7ba967a896cb74c0d1", + "version" : "2.4.1" } }, { @@ -131,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "e855380cb5234e96b760d93e0bfdc403e381e928", - "version" : "2.45.0" + "revision" : "a2e487b77f17edbce9a65f2b7415f2f479dc8e48", + "version" : "2.57.0" } }, { @@ -140,8 +167,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssh", "state" : { - "revision" : "baa05dc6ff3de89c589a9d582659f0f6699f62ab", - "version" : "0.6.1" + "revision" : "ded5e5ce4ef1b2c29933088651fa66c89e2175d2", + "version" : "0.8.0" } }, { @@ -149,8 +176,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-service-discovery", "state" : { - "revision" : "c83afedb1c95ef0111907cd6e2fd03d7175cc0d0", - "version" : "1.2.0" + "revision" : "5c88b88dd40446740e86ba809453bc39f7904fa7", + "version" : "1.2.1" + } + }, + { + "identity" : "swiftui-navigation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swiftui-navigation", + "state" : { + "revision" : "905274b2bd98be556d2cfcf31b94d5979b924755", + "version" : "1.0.1" } }, { @@ -167,8 +203,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "50a70a9d3583fe228ce672e8923010c8df2deddd", - "version" : "0.2.1" + "revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631", + "version" : "1.0.2" } } ], diff --git a/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/UserInterfaceState.xcuserstate b/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/UserInterfaceState.xcuserstate index a3531ce..15667bd 100644 Binary files a/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/UserInterfaceState.xcuserstate and b/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/redis-pro.xcodeproj/xcuserdata/chengpan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/redis-pro.xcodeproj/xcuserdata/chengpan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index b74826b..7a8538f 100644 --- a/redis-pro.xcodeproj/xcuserdata/chengpan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/redis-pro.xcodeproj/xcuserdata/chengpan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,118 +3,4 @@ uuid = "1F6FC20B-7A60-4E4D-9CFD-509EE3AA4D81" type = "1" version = "2.0"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/redis-pro/Common/RedisClient/RediStackClient.swift b/redis-pro/Common/RedisClient/RediStackClient.swift index 843d903..45f8c2c 100644 --- a/redis-pro/Common/RedisClient/RediStackClient.swift +++ b/redis-pro/Common/RedisClient/RediStackClient.swift @@ -10,15 +10,15 @@ import NIO import RediStack import Logging import NIOSSH -import Swift import ComposableArchitecture +import Cocoa class RediStackClient { let logger = Logger(label: "redis-client") var redisModel:RedisModel // conn - private let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) var connection:RedisConnection? var connPool:RedisConnectionPool? @@ -31,26 +31,47 @@ class RediStackClient { // 递归查询每页大小 let dataScanCount:Int = 2000 - var dataCountScanCount:Int = 4000 + var dataCountScanCount:Int = 2000 var recursionSize:Int = 2000 var recursionCountSize:Int = 5000 - var viewStore:ViewStore? + private var observers = [NSObjectProtocol]() + + var appContextViewStore:ViewStore? + var settingViewStore: ViewStoreOf? + + convenience init(_ redisModel:RedisModel, settingViewStore: ViewStoreOf?) { + self.init(redisModel) + self.settingViewStore = settingViewStore + } init(_ redisModel:RedisModel) { self.redisModel = redisModel + + // 监听app退出 + observers.append( + NotificationCenter.default.addObserver(forName: NSApplication.willTerminateNotification, object: nil, queue: .main) { [self] _ in + logger.info("redis pro will exit...") + + shutdown() + } + ) } - func setGlobalStore(_ globalStore: ViewStore?) { - self.viewStore = globalStore + deinit { + observers.forEach(NotificationCenter.default.removeObserver) + } + + func setAppContextStore(_ globalStore: ViewStore?) { + self.appContextViewStore = globalStore } func loading(_ bool: Bool) { DispatchQueue.main.async { if bool { - self.viewStore?.send(.show) + self.appContextViewStore?.send(.show) } else { - self.viewStore?.send(.hide) + self.appContextViewStore?.send(.hide) } } } @@ -77,25 +98,6 @@ class RediStackClient { Messages.show(error) } - /* - * 初始化redis 连接 - */ - func initConnection() async -> Bool { - begin() - defer { - complete() - } - - do { - let _ = try await getConn() - return true - - } catch { - handleError(error) - } - - return false - } func assertExist(_ key:String) async throws { let exist = await exist(key) @@ -122,9 +124,20 @@ class RediStackClient { // 公共底层请求redis 数据方法, 不处理任何异常, 使用者需要自己行处理异常信息 - func _send(_ command: RedisCommand) async throws -> R { - let conn = try await getConn() - return try await _send(conn, command) + func _send(_ command: RedisCommand) async -> R? { + do { + let conn = try await getConn() + return try await _send(conn, command) + } catch { + handleError(error) + } + + return nil + } + + // 公共底层请求redis 数据方法, 不处理任何异常, 使用者需要自己行处理异常信息 + func _send(_ command: RedisCommand, _ defaultValue: R) async -> R { + return await _send(command) ?? defaultValue } func send(_ command: RedisCommand, _ defaultValue: R) async -> R { @@ -134,12 +147,7 @@ class RediStackClient { complete() } - do { - return try await _send(command) - } catch { - handleError(error) - } - return defaultValue + return await _send(command) ?? defaultValue } func send(_ command: RedisCommand) async -> R? { @@ -149,12 +157,7 @@ class RediStackClient { complete() } - do { - return try await _send(command) - } catch { - handleError(error) - } - return nil + return await _send(command) } func ttlSecond(_ lifetime: RedisKey.Lifetime) -> Int { @@ -169,122 +172,6 @@ class RediStackClient { } - /// test redis connection - /// - func testConn() async -> Bool { - do { - var conn:RedisConnection - - if self.redisModel.connectionType == RedisConnectionTypeEnum.SSH.rawValue { - conn = try await initSSHConn() - } else { - conn = try await initConn(host: self.redisModel.host, port: self.redisModel.port, username: self.redisModel.username, pass: self.redisModel.password, database: self.redisModel.database) - } - - defer { - conn.close() - } - - return try await _send(conn, .ping) == "PONG" - } catch { - Messages.show(error) - return false - } - } - - func getConn() async throws -> RedisClient { - if self.connPool != nil { - return self.connPool! - } - return try await getConnPool() - } - - func getConnPool() async throws -> RedisClient { - if self.connPool != nil { - return self.connPool! - } else { - self.logger.info("get redis connection, but connection is not available...") - self.close() - } - - if self.redisModel.connectionType == RedisConnectionTypeEnum.SSH.rawValue { - self.connPool = try await initSSHPool() - } else { - self.connPool = try initPool(host: self.redisModel.host, port: self.redisModel.port, username: self.redisModel.username, pass: self.redisModel.password, database: self.redisModel.database) - } - - return self.connPool! - } - - func initConn(host:String, port:Int, - username: String? = nil, - pass:String, - database:Int - ) async throws -> RedisConnection { - logger.info("redis client- init new redis connection, host: \(host), port: \(port), pass: \(pass), database: \(database)") - return try await withCheckedThrowingContinuation { continuation in - do { - let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 4).next() - var configuration: RedisConnection.Configuration - if (pass.isEmpty) { - configuration = try RedisConnection.Configuration(hostname: host, port: port, initialDatabase: database, defaultLogger: logger) - } else { - let _username = username?.isEmpty ?? false ? nil : username - configuration = try RedisConnection.Configuration(hostname: host, port: port, username: _username, password: pass, initialDatabase: database, defaultLogger: logger) - } - - let future = RedisConnection.make( - configuration: configuration - , boundEventLoop: eventLoop - ) - - future.whenSuccess({ redisConnection in - self.logger.info("init redis connection success, connection id: \(redisConnection.id)") - continuation.resume(returning: redisConnection) - }) - future.whenFailure({ error in - self.logger.info("init redis connection error: \(error)") - continuation.resume(throwing: error) - }) - } catch { - self.logger.info("init redis connection error: \(error)") - continuation.resume(throwing: error) - } - } - } - - public func initPool(host:String, port:Int, username:String?, pass:String, database:Int) throws -> RedisConnectionPool { - let eventLoop = eventLoopGroup.next() - - let addresses = try [SocketAddress.makeAddressResolvingHost(host, port: port)] - - let _username = username?.isEmpty ?? false ? nil : username - let _password = pass.isEmpty ? nil : pass - - let config: RedisConnectionPool.PoolConnectionConfiguration = .init( - initialDatabase: database - , username: _username - , password: _password - , defaultLogger: self.logger, tcpClient: nil) - - let pool = RedisConnectionPool( - configuration: .init( - initialServerConnectionAddresses: addresses - , connectionCountBehavior: .elastic(maximumConnectionCount: 3, minimumConnectionCount: 2) - , connectionConfiguration: config -// , retryStrategy: .none - , retryStrategy: .exponentialBackoff(initialDelay: .milliseconds(100), backoffFactor: 3, timeout: .seconds(3)) - , poolDefaultLogger: self.logger - ) - , boundEventLoop: eventLoop - ) - - pool.activate() -// _keepalive() - self.logger.info("init redis connection pool complete...") - return pool - } - private func _keepalive() { let eventLoop = eventLoopGroup.next() self.keepaliveTask = eventLoop.scheduleRepeatedAsyncTask(initialDelay: .seconds(10), delay: .seconds(5)) {_ in @@ -319,6 +206,8 @@ class RediStackClient { func shutdown() { do { + close() + logger.info("gracefully shutdown event loop group start...") try self.eventLoopGroup.syncShutdownGracefully() } catch { diff --git a/redis-pro/Common/RedisClient/RedisClientConn.swift b/redis-pro/Common/RedisClient/RedisClientConn.swift new file mode 100644 index 0000000..9655f7c --- /dev/null +++ b/redis-pro/Common/RedisClient/RedisClientConn.swift @@ -0,0 +1,151 @@ +// +// RedisClientConn.swift +// redis-pro +// +// Created by chengpan on 2023/7/23. +// + +import Foundation +import RediStack +import NIO + +// MARK: - conn operator +extension RediStackClient { + + /* + * 初始化redis 连接 + */ + func initConnection() async -> Bool { + begin() + defer { + complete() + } + + do { + let _ = try await getConn() + return true + + } catch { + handleError(error) + } + + return false + } + + /// test redis connection + func testConn() async -> Bool { + do { + var conn:RedisConnection + + if self.redisModel.connectionType == RedisConnectionTypeEnum.SSH.rawValue { + conn = try await initSSHConn() + } else { + conn = try await initConn(host: self.redisModel.host, port: self.redisModel.port, username: self.redisModel.username, pass: self.redisModel.password, database: self.redisModel.database) + } + + defer { + conn.close() + } + + return try await _send(conn, .ping) == "PONG" + } catch { + Messages.show(error) + return false + } + } + + func getConn() async throws -> RedisClient { + if self.connPool != nil { + return self.connPool! + } + return try await getConnPool() + } + + func getConnPool() async throws -> RedisClient { + if self.connPool != nil { + return self.connPool! + } else { + self.logger.info("get redis connection, but connection is not available...") + self.close() + } + + if self.redisModel.connectionType == RedisConnectionTypeEnum.SSH.rawValue { + self.connPool = try await initSSHPool() + } else { + self.connPool = try initPool(host: self.redisModel.host, port: self.redisModel.port, username: self.redisModel.username, pass: self.redisModel.password, database: self.redisModel.database) + } + + return self.connPool! + } + + func initConn(host:String, port:Int, + username: String? = nil, + pass:String, + database:Int + ) async throws -> RedisConnection { + logger.info("redis client- init new redis connection, host: \(host), port: \(port), pass: \(pass), database: \(database)") + return try await withCheckedThrowingContinuation { continuation in + do { + let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 4).next() + var configuration: RedisConnection.Configuration + if (pass.isEmpty) { + configuration = try RedisConnection.Configuration(hostname: host, port: port, initialDatabase: database, defaultLogger: logger) + } else { + let _username = username?.isEmpty ?? false ? nil : username + configuration = try RedisConnection.Configuration(hostname: host, port: port, username: _username, password: pass, initialDatabase: database, defaultLogger: logger) + } + + let future = RedisConnection.make( + configuration: configuration + , boundEventLoop: eventLoop + ) + + future.whenSuccess({ redisConnection in + self.logger.info("init redis connection success, connection id: \(redisConnection.id)") + continuation.resume(returning: redisConnection) + }) + future.whenFailure({ error in + self.logger.info("init redis connection error: \(error)") + continuation.resume(throwing: error) + }) + } catch { + self.logger.info("init redis connection error: \(error)") + continuation.resume(throwing: error) + } + } + } + + public func initPool(host:String, port:Int, username:String?, pass:String, database:Int) throws -> RedisConnectionPool { + let eventLoop = eventLoopGroup.next() + + let addresses = try [SocketAddress.makeAddressResolvingHost(host, port: port)] + + let _username = username?.isEmpty ?? false ? nil : username + let _password = pass.isEmpty ? nil : pass + + let config: RedisConnectionPool.PoolConnectionConfiguration = .init( + initialDatabase: database + , username: _username + , password: _password + , defaultLogger: self.logger, tcpClient: nil) + + let pool = RedisConnectionPool( + configuration: .init( + initialServerConnectionAddresses: addresses + , connectionCountBehavior: .elastic(maximumConnectionCount: 3, minimumConnectionCount: 2) + , connectionConfiguration: config +// , retryStrategy: .none + , retryStrategy: .exponentialBackoff(initialDelay: .milliseconds(100), backoffFactor: 3, timeout: .seconds(3)) + , poolDefaultLogger: self.logger + ) + , boundEventLoop: eventLoop + ) + + pool.activate() +// _keepalive() + self.logger.info("init redis connection pool complete...") + return pool + } + +} + diff --git a/redis-pro/Common/RedisClient/RedisClientHash.swift b/redis-pro/Common/RedisClient/RedisClientHash.swift index 3e0741b..90bce6d 100644 --- a/redis-pro/Common/RedisClient/RedisClientHash.swift +++ b/redis-pro/Common/RedisClient/RedisClientHash.swift @@ -75,7 +75,7 @@ extension RediStackClient { var count:Int = 0 while true { - let res = try await _hscanCount(key, keywords: keywords, cursor: cursor, count: dataCountScanCount) + let res = await _hscanCount(key, keywords: keywords, cursor: cursor, count: dataCountScanCount) logger.info("loop scan page, current cursor: \(cursor), total count: \(count)") cursor = res.0 count = count + res.1 @@ -119,27 +119,26 @@ extension RediStackClient { private func _hlen(_ key:String) async throws -> Int { let command: RedisCommand = .hlen(of: RedisKey(key)) - return try await _send(command) + return await _send(command, -1) } - private func _hscanCount(_ key:String, keywords:String?, cursor:Int, count:Int = 100) async throws -> (Int, Int) { + private func _hscanCount(_ key:String, keywords:String?, cursor:Int, count:Int = 100) async -> (Int, Int) { logger.debug("redis hash scan, key: \(key) cursor: \(cursor), keywords: \(String(describing: keywords)), count:\(String(describing: count))") - let r = try await _hscan(key, keywords: keywords, cursor: cursor, count: count) + let r = await _hscan(key, keywords: keywords, cursor: cursor, count: count) return (r.0, r.1.count) } - private func _hscan(_ key:String, keywords:String?, cursor:Int, count:Int = 100) async throws -> (Int, [(String, String?)]) { + private func _hscan(_ key:String, keywords:String?, cursor:Int, count:Int = 100) async -> (Int, [(String, String?)]) { logger.debug("redis hash scan, key: \(key) cursor: \(cursor), keywords: \(String(describing: keywords)), count:\(String(describing: count))") let command: RedisCommand<(Int, [(String, String?)])> = ._hscan(key, keywords: keywords, cursor: cursor, count: count) - return try await _send(command) + return await _send(command, (0, [])) } - private func _hget(_ key:String, field:String) async throws -> String? { + private func _hget(_ key:String, field:String) async -> String? { let command: RedisCommand = .hget(key, field: field) - let r = try await _send(command) - return r + return await _send(command, "") } } diff --git a/redis-pro/Common/RedisClient/RedisClientKeys.swift b/redis-pro/Common/RedisClient/RedisClientKeys.swift index 8f06b22..f2b1fca 100644 --- a/redis-pro/Common/RedisClient/RedisClientKeys.swift +++ b/redis-pro/Common/RedisClient/RedisClientKeys.swift @@ -16,7 +16,7 @@ extension RediStackClient { logger.debug("redis keys scan, cursor: \(cursor), keywords: \(String(describing: keywords)), count:\(String(describing: count))") let command:RedisCommand<(Int, [RedisKey])> = .scan(startingFrom: cursor, matching: keywords, count: count) - let r = try await _send(command) + let r = await _send(command)! return (r.0, r.1.map { $0.rawValue }) } @@ -53,6 +53,10 @@ extension RediStackClient { return count } + /// 分页查询 key + /// - Parameters: + /// - page: 分页参数 + /// - Returns: keys array private func keysPageScan(_ page: Page) async throws -> [String] { let keywords = page.keywords.isEmpty ? nil : page.keywords var end:Int = page.end @@ -89,7 +93,6 @@ extension RediStackClient { logger.info("redis keys page scan, page: \(page)") let isScan = isScan(page.keywords) -// let match = page.keywords.isEmpty ? nil : page.keywords defer { self.logger.info("keys scan complete, spend: \(stopwatch.elapsedMillis()) ms") @@ -131,10 +134,18 @@ extension RediStackClient { let match = page.keywords.isEmpty ? nil : page.keywords do { + // 是否走scan扫描key if isScan { + let res = try await countScan(cursor: cursor, keywords: match, count: dataCountScanCount) logger.info("count scan keys, current cursor: \(cursor), r: \(res)") + // 检查fast page + if settingViewStore?.fastPage ?? true && ((res.1 + page.total) > ((settingViewStore?.fastPageMax ?? 99) * page.size)) { + logger.info("count scan keys, fast page switch is open, stop scan") + return (0, res.1) + } + return res } else { let count = await self.exist(page.keywords) ? 1 : 0 diff --git a/redis-pro/Common/RedisClient/RedisClientList.swift b/redis-pro/Common/RedisClient/RedisClientList.swift index f0e897c..335556b 100644 --- a/redis-pro/Common/RedisClient/RedisClientList.swift +++ b/redis-pro/Common/RedisClient/RedisClientList.swift @@ -43,8 +43,8 @@ extension RediStackClient { logger.debug("redis list range, key: \(key)") let command: RedisCommand<[RESPValue]> = .lrange(from: RedisKey(key), firstIndex: start, lastIndex: stop) - let r = try await _send(command) - return r.map { $0.string } + let r = await _send(command) + return r!.map { $0.description } } func ldel(_ key:String, index:Int, value:String) async -> Int { @@ -56,14 +56,14 @@ extension RediStackClient { } do { - let existValue = try await _lindex(key, index: index) + let existValue = await _lindex(key, index: index) guard existValue == value else { throw BizError("list value: \(value), index: \(index) have changed, please check!") } - try await _lset(key, index: index, value: Const.LIST_VALUE_DELETE_MARK) + await _lset(key, index: index, value: Const.LIST_VALUE_DELETE_MARK) - return try await _lrem(key,value: Const.LIST_VALUE_DELETE_MARK) + return await _lrem(key,value: Const.LIST_VALUE_DELETE_MARK) } catch { handleError(error) } @@ -71,10 +71,10 @@ extension RediStackClient { } - private func _lrem(_ key:String, value:String) async throws -> Int { + private func _lrem(_ key:String, value:String) async -> Int { let command: RedisCommand = .lrem(value, from: RedisKey(key), count: 0) - return try await _send(command) + return await _send(command, 0) } func lset(_ key:String, index:Int, value:String) async -> Void { @@ -82,16 +82,12 @@ extension RediStackClient { defer { complete() } - do { - try await _lset(key, index: index, value: value) - } catch { - handleError(error) - } + await _lset(key, index: index, value: value) } - private func _lset(_ key:String, index:Int, value:String) async throws -> Void { + private func _lset(_ key:String, index:Int, value:String) async -> Void { let command: RedisCommand = .lset(index: index, to: value, in: RedisKey(key)) - try await _send(command) + await _send(command) } func lpush(_ key:String, value:String) async -> Int { @@ -105,14 +101,14 @@ extension RediStackClient { return await send(command, 0) } - private func _lindex(_ key:String, index:Int) async throws -> String? { + private func _lindex(_ key:String, index:Int) async -> String? { let command: RedisCommand = .lindex(index, from: RedisKey(key)) - return try await _send(command)?.string + return await _send(command)??.description } - private func llen(_ key:String) async throws -> Int { + private func llen(_ key:String) async -> Int { logger.debug("redis list length, key: \(key)") let command: RedisCommand = .llen(of: RedisKey(key)) - return try await _send(command) + return await _send(command, 0) } } diff --git a/redis-pro/Common/RedisClient/RedisClientSet.swift b/redis-pro/Common/RedisClient/RedisClientSet.swift index ede7e0d..dcb593d 100644 --- a/redis-pro/Common/RedisClient/RedisClientSet.swift +++ b/redis-pro/Common/RedisClient/RedisClientSet.swift @@ -107,13 +107,13 @@ extension RediStackClient { logger.debug("redis set scan, key: \(key) cursor: \(cursor), keywords: \(String(describing: keywords)), count:\(String(describing: count))") let command: RedisCommand<(Int, [RESPValue])> = .sscan(RedisKey(key), startingFrom: cursor, matching: keywords, count: count) - let r = try await _send(command) - return (r.0, r.1.map { $0.string }) + let r = await _send(command)! + return (r.0, r.1.map { $0.description }) } - private func _sexist(_ key:String, ele:String?) async throws -> Bool{ + private func _sexist(_ key:String, ele:String?) async -> Bool{ let command: RedisCommand = .sismember(ele, of: RedisKey(key)) - return try await _send(command) + return await _send(command, false) } func supdate(_ key:String, from:String, to:String) async -> Int { @@ -124,10 +124,10 @@ extension RediStackClient { logger.info("redis set update, key: \(key), from: \(from), to: \(to)") do { - let r = try await _srem(key, ele: from) + let r = await _srem(key, ele: from) try Assert.isTrue(r > 0, message: "set element: `\(from)` is not exist!") - return try await _sadd(key, ele: to) + return await _sadd(key, ele: to) } catch { handleError(error) } @@ -141,12 +141,7 @@ extension RediStackClient { complete() } - do { - return try await _srem(key, ele: ele) - } catch { - handleError(error) - } - return 0 + return await _srem(key, ele: ele) } func sadd(_ key:String, ele:String) async -> Int { @@ -154,28 +149,23 @@ extension RediStackClient { defer { complete() } - do { - return try await _sadd(key, ele: ele) - } catch { - handleError(error) - } - return 0 + return await _sadd(key, ele: ele) } - private func _scard(_ key:String) async throws -> Int { + private func _scard(_ key:String) async -> Int { let command: RedisCommand = .scard(of: RedisKey(key)) - return try await _send(command) + return await _send(command, 0) } - private func _srem(_ key:String, ele:String) async throws -> Int { + private func _srem(_ key:String, ele:String) async -> Int { let command: RedisCommand = .srem(ele, from: RedisKey(key)) - return try await _send(command) + return await _send(command, 0) } - private func _sadd(_ key:String, ele:String) async throws -> Int { + private func _sadd(_ key:String, ele:String) async -> Int { let command: RedisCommand = .sadd(ele, to: RedisKey(key)) - return try await _send(command) + return await _send(command, 0) } diff --git a/redis-pro/Common/RedisClient/RedisClientString.swift b/redis-pro/Common/RedisClient/RedisClientString.swift index d1a6139..e6540dc 100644 --- a/redis-pro/Common/RedisClient/RedisClientString.swift +++ b/redis-pro/Common/RedisClient/RedisClientString.swift @@ -48,8 +48,7 @@ extension RediStackClient { logger.info("get value length, key:\(key)") let command:RedisCommand = .strln(RedisKey(key)) - let r = await send(command) - return r ?? 0 + return await _send(command, 0) } func del(_ key:String) async -> Int { @@ -89,7 +88,13 @@ extension RediStackClient { func ttl(_ key:String) async -> Int { logger.info("get ttl key: \(key)") let command:RedisCommand = .ttl(RedisKey(key)) - return ttlSecond(await send(command, RedisKey.Lifetime.keyDoesNotExist)) + return ttlSecond(await _send(command, RedisKey.Lifetime.keyDoesNotExist)) + } + + func objectEncoding(_ key:String) async -> String { + logger.info("get object encoding, key: \(key)") + let command:RedisCommand = .objectEncoding(key) + return await _send(command, "") } func getTypes(_ keys:[String]) async -> [String:String] { @@ -113,13 +118,8 @@ extension RediStackClient { } private func type(_ key:String) async -> String { - do { - let command:RedisCommand = .type(key) - return try await _send(command) - } catch { - self.logger.error("get type error: \(error)") - } - return RedisKeyTypeEnum.NONE.rawValue + let command:RedisCommand = .type(key) + return await _send(command, RedisKeyTypeEnum.NONE.rawValue) } diff --git a/redis-pro/Common/RedisClient/RedisClientZSet.swift b/redis-pro/Common/RedisClient/RedisClientZSet.swift index ae3a4f5..304efce 100644 --- a/redis-pro/Common/RedisClient/RedisClientZSet.swift +++ b/redis-pro/Common/RedisClient/RedisClientZSet.swift @@ -31,7 +31,7 @@ extension RediStackClient { // 查询所有时使用 ZRANGEBYSCORE 按顺序返回 if isMatchAll(page.keywords) { r = try await _zrangeByScore(key, page: page) - page.total = try await _zcard(key) + page.total = await _zcard(key) } else if isScan { let match = page.keywords.isEmpty ? nil : page.keywords @@ -60,7 +60,7 @@ extension RediStackClient { private func zsetCountScan(_ key:String, keywords:String?) async throws -> Int { if isMatchAll(keywords ?? "") { logger.info("keywords is match all, use scard...") - return try await _zcard(key) + return await _zcard(key) } var cursor:Int = 0 @@ -127,7 +127,7 @@ extension RediStackClient { logger.debug("redis set scan, key: \(key) cursor: \(cursor), keywords: \(String(describing: keywords)), count:\(String(describing: count))") let command: RedisCommand<(Int, [(RESPValue, Double)])> = .zscan(RedisKey(key), startingFrom: cursor, matching: keywords, count: count) - let r = try await _send(command) + let r = await _send(command)! return (r.0, r.1.map { ($0.0.string ?? Const.EMPTY_STRING, $0.1) }) } @@ -142,7 +142,7 @@ extension RediStackClient { let r = try await _zrem(key, ele: from) try Assert.isTrue(r > 0, message: "set zset element: `\(from)` is not exist!") - return try await _zadd(key, score: score, ele: to) + return await _zadd(key, score: score, ele: to) } catch { handleError(error) @@ -155,24 +155,19 @@ extension RediStackClient { defer { complete() } - do { - return try await _zadd(key, score: score, ele: ele) - } catch { - handleError(error) - } - return false + return await _zadd(key, score: score, ele: ele) } - private func _zadd(_ key:String, score:Double, ele:String) async throws -> Bool { + private func _zadd(_ key:String, score:Double, ele:String) async -> Bool { let command: RedisCommand = .zadd((ele, score), to: RedisKey(key)) - return try await _send(command) > 0 + return await _send(command, 0) > 0 } - private func _zcard(_ key:String) async throws -> Int { + private func _zcard(_ key:String) async -> Int { let command: RedisCommand = .zcard(of: RedisKey(key)) - return try await _send(command) + return await _send(command, 0) } func zrem(_ key:String, ele:String) async -> Int { @@ -191,18 +186,18 @@ extension RediStackClient { private func _zrem(_ key:String, ele:String) async throws -> Int { let command: RedisCommand = .zrem(ele, from: RedisKey(key)) - return try await _send(command) + return await _send(command, 0) } private func _zscore(_ key:String, ele:String) async throws -> Double? { let command: RedisCommand = .zscore(of: ele, in: RedisKey(key)) - return try await _send(command) + return await _send(command)! } private func _zrangeByScore(_ key:String, page:Page) async throws -> [(String, String)] { let command: RedisCommand<[(RESPValue, Double)]> = .zrangebyscore(from: RedisKey(key), withMinimumScoreOf: .inclusive(Double.min), limitBy: (offset: page.start, count: page.size), returning: .valuesAndScores) - let r:[(RESPValue, Double)] = try await _send(command) + let r:[(RESPValue, Double)] = await _send(command)! return r.map { ($0.string ?? Const.EMPTY_STRING, "\($1)") } } diff --git a/redis-pro/Common/RedisClient/RedisCommandExt.swift b/redis-pro/Common/RedisClient/RedisCommandExt.swift index 66c3ab9..35d0b15 100644 --- a/redis-pro/Common/RedisClient/RedisCommandExt.swift +++ b/redis-pro/Common/RedisClient/RedisCommandExt.swift @@ -101,6 +101,10 @@ extension RedisCommand where ResultType == String { public static func getConfig(_ key: String) -> RedisCommand { return .init(keyword: "CONFIG", arguments: [.init(from: "GET"), .init(from: key)]) } + + public static func objectEncoding(_ key: String) -> RedisCommand { + return .init(keyword: "OBJECT", arguments: [.init(from: "ENCODING"), .init(from: key)]) + } } // MARK: - Int diff --git a/redis-pro/Common/UserDefaults/UserDefaultsKeysEnum.swift b/redis-pro/Common/UserDefaults/UserDefaultsKeysEnum.swift index c0802d7..aae8dda 100644 --- a/redis-pro/Common/UserDefaults/UserDefaultsKeysEnum.swift +++ b/redis-pro/Common/UserDefaults/UserDefaultsKeysEnum.swift @@ -19,5 +19,7 @@ enum UserDefaulsKeysEnum: String { case AppStringMaxLength = "App.StringMaxLength" // keepalive second case AppKeepalive = "App.Keepalive" + // fast page + case AppFastPage = "App.FastPage" } diff --git a/redis-pro/Model/KeyDelModel.swift b/redis-pro/Model/KeyDelModel.swift new file mode 100644 index 0000000..b5e2f7a --- /dev/null +++ b/redis-pro/Model/KeyDelModel.swift @@ -0,0 +1,21 @@ +// +// KeyDelModel.swift +// redis-pro +// +// Created by chengpan on 2023/7/30. +// + +import Foundation + +class KeyDelModel: RedisKeyModel { + var status: Int = 0 + + var statusText: String { + status == 0 ? "Ready" : ( status == 1 ? "Deleted" : "Delete Fail!") + } + + convenience init(_ keyModel: RedisKeyModel) { + self.init(keyModel.key, type: keyModel.type) + self.status = 0 + } +} diff --git a/redis-pro/Model/Page.swift b/redis-pro/Model/Page.swift index 9a820f4..300ecfb 100644 --- a/redis-pro/Model/Page.swift +++ b/redis-pro/Model/Page.swift @@ -7,15 +7,15 @@ import Foundation -class Page:ObservableObject, CustomStringConvertible, Equatable { +class Page: CustomStringConvertible, Equatable { static func == (lhs: Page, rhs: Page) -> Bool { return lhs.current == rhs.current && lhs.size == rhs.size && lhs.keywords == rhs.keywords } - @Published var current:Int = 1 - @Published var size:Int = 50 - @Published var total:Int = 0 - @Published var keywords:String = "" + var current:Int = 1 + var size:Int = 50 + var total:Int = 0 + var keywords:String = "" var totalPage:Int { get { diff --git a/redis-pro/Model/RedisInsanceModel.swift b/redis-pro/Model/RedisInsanceModel.swift index 0e4d8f8..0224fa5 100644 --- a/redis-pro/Model/RedisInsanceModel.swift +++ b/redis-pro/Model/RedisInsanceModel.swift @@ -12,32 +12,28 @@ import RediStack import Logging import ComposableArchitecture -class RedisInstanceModel:ObservableObject, Identifiable { - @Published var redisModel:RedisModel +class RedisInstanceModel: Identifiable { + var redisModel:RedisModel private var rediStackClient:RediStackClient? - private var viewStore:ViewStore? + private var appContextviewStore:ViewStoreOf? + private var settingViewStore:ViewStoreOf? let logger = Logger(label: "redis-instance") - private var observers = [NSObjectProtocol]() + + convenience init(_ redisModel:RedisModel, settingViewStore: ViewStoreOf?) { + self.init(redisModel: redisModel) + self.settingViewStore = settingViewStore + } init(redisModel: RedisModel) { self.redisModel = redisModel logger.info("redis instance model init") - - observers.append( - NotificationCenter.default.addObserver(forName: NSApplication.willTerminateNotification, object: nil, queue: .main) { [self] _ in - logger.info("redis pro will exit...") - close() - - shutdown() - } - ) } - func setAppStore(_ appStore: Store) { - let globalStore = appStore.scope(state: \.globalState, action: AppAction.globalAction) - self.viewStore = ViewStore(globalStore) + func setAppStore(_ appStore: StoreOf) { + let globalStore = appStore.scope(state: \.globalState, action: AppStore.Action.globalAction) + self.appContextviewStore = ViewStore(globalStore, observe: { $0 }) } // get client @@ -54,8 +50,8 @@ class RedisInstanceModel:ObservableObject, Identifiable { logger.info("init new redis client, redisModel: \(redisModel)") self.redisModel = redisModel - let client = RediStackClient(redisModel) - client.setGlobalStore(self.viewStore) + let client = RediStackClient(redisModel, settingViewStore: settingViewStore) + client.setAppContextStore(self.appContextviewStore) self.rediStackClient = client return client diff --git a/redis-pro/Model/RedisKeyModel.swift b/redis-pro/Model/RedisKeyModel.swift index d59dea2..ebf5d27 100644 --- a/redis-pro/Model/RedisKeyModel.swift +++ b/redis-pro/Model/RedisKeyModel.swift @@ -8,11 +8,11 @@ import Foundation import Cocoa -class RedisKeyModel:NSObject, ObservableObject, Identifiable { - @objc @Published var key: String = "" - @objc @Published var type: String = RedisKeyTypeEnum.STRING.rawValue - @Published var ttl: Int = -1 - @Published var isNew: Bool = false +class RedisKeyModel: NSObject, Identifiable { + @objc var key: String = "" + @objc var type: String = RedisKeyTypeEnum.STRING.rawValue + var ttl: Int = -1 + var isNew: Bool = false private var _id:String = "" var id:String { @@ -22,7 +22,6 @@ class RedisKeyModel:NSObject, ObservableObject, Identifiable { return key } - override init() {} convenience init(_ key:String, type:String) { self.init() diff --git a/redis-pro/Model/RedisModel.swift b/redis-pro/Model/RedisModel.swift index 2d77eba..756481f 100644 --- a/redis-pro/Model/RedisModel.swift +++ b/redis-pro/Model/RedisModel.swift @@ -34,6 +34,7 @@ class RedisModel: NSObject, Identifiable { "host": host, "port": port, "database": database, + "username": username, "password": password, "connectionType": connectionType, "sshHost": sshHost, @@ -73,6 +74,7 @@ class RedisModel: NSObject, Identifiable { self.host = dictionary["host"] as! String self.port = dictionary["port"] as! Int self.database = dictionary["database"] as! Int + self.username = (dictionary["username"] ?? "") as! String self.password = dictionary["password"] as! String // ssh let connectionType:String = dictionary["connectionType"] as? String ?? RedisConnectionTypeEnum.TCP.rawValue diff --git a/redis-pro/Store/AlertStore.swift b/redis-pro/Store/AlertStore.swift deleted file mode 100644 index 79cde94..0000000 --- a/redis-pro/Store/AlertStore.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// AlertStore.swift -// redis-pro -// -// Created by chengpan on 2022/5/3. -// - - -import Logging -import Foundation -import ComposableArchitecture - -private let logger = Logger(label: "alert-store") - -struct AppAlertState: Equatable { - - var alert: AlertState? - var action: (() -> Void)? - - init() { - logger.info("alert state init ...") - } - - static func == (lhs: AppAlertState, rhs: AppAlertState) -> Bool { - lhs.alert == rhs.alert - } -} - -enum AlertAction: Equatable { - static func == (lhs: AlertAction, rhs: AlertAction) -> Bool { - lhs.value == rhs.value - } - - var value: String? { - return String(describing: self).components(separatedBy: "(").first - } - - case alert - case error(String) - case confirm(String, String, String, (() -> Void)) - case doAction - case clearAlert - case ok - case cancel - case none -} - -struct AlertEnvironment { -} - - -let alertReducer = Reducer.combine( - Reducer { - state, action, _ in - switch action { - case .alert: - state.alert = .init( - title: TextState("Delete"), - message: TextState("Are you sure you want to delete this? It cannot be undone."), - primaryButton: .default(TextState("Confirm"), action: .send(.none)), - secondaryButton: .cancel(TextState("Cancel")) - ) - return .none - case let .error(message): - state.alert = .init( - title: TextState("Error!"), - message: TextState(message), - dismissButton: .default(TextState("Ok")) - ) - return .none - case let .confirm(title, message, primaryButton, action): - state.action = action - state.alert = .init( - title: TextState(title), - message: TextState(message), - primaryButton: .default(TextState(primaryButton), action: .send(.doAction)), - secondaryButton: .cancel(TextState("Cancel")) - ) - return .none - case .doAction: - state.action?() - state.action = nil - return .none - case .clearAlert: - state.alert = nil - return .none - case .ok: - print("ok") - return .none - case .cancel: - print("cancel") - return .none - case .none: - return .none - } - }.debug() -) diff --git a/redis-pro/Store/AppContextStore.swift b/redis-pro/Store/AppContextStore.swift new file mode 100644 index 0000000..405b1a3 --- /dev/null +++ b/redis-pro/Store/AppContextStore.swift @@ -0,0 +1,53 @@ +// +// GlobalStore.swift +// redis-pro +// +// Created by chengpan on 2022/6/2. +// + + +import Logging +import Foundation +import ComposableArchitecture + +private let logger = Logger(label: "app-context-store") + + +struct AppContextStore: Reducer { + struct State: Equatable { + var loading:Bool = false + var loadingCount:Int = 0 + + init() { + logger.info("app context state init ...") + } + } + + enum Action: Equatable { + case show + case hide + } + + var body: some Reducer { + + Reduce { state, action in + switch action { + case .show: + if state.loadingCount <= 0 { + state.loading = true + } + + state.loadingCount += 1 + return .none + case .hide: + state.loadingCount -= 1 + if state.loadingCount <= 0 { + state.loading = false + state.loadingCount = 0 + } + + return .none + } + } + } +} diff --git a/redis-pro/Store/AppStore.swift b/redis-pro/Store/AppStore.swift index 9fcfd6e..254286f 100644 --- a/redis-pro/Store/AppStore.swift +++ b/redis-pro/Store/AppStore.swift @@ -12,126 +12,98 @@ import ComposableArchitecture private let logger = Logger(label: "app-store") -struct AppState: Equatable { - var id:String = UUID().uuidString - // app title - var title:String = "" - // 是否已经连接 redis server - var isConnect: Bool = false - var globalState: GlobalState = GlobalState() -// var appAlertState: AppAlertState = AppAlertState() - var loadingState: LoadingState = LoadingState() - private var _favoriteState: FavoriteState = FavoriteState() - var favoriteState: FavoriteState { - get { - var state = _favoriteState - state.globalState = globalState - return state + +struct AppStore: Reducer { + + struct State: Equatable { + var id:String = UUID().uuidString + // app title + var title:String = "" + // 是否已经连接 redis server + var isConnect: Bool = false + var globalState: AppContextStore.State = AppContextStore.State() + var loadingState: LoadingStore.State = LoadingStore.State() + private var _favoriteState: FavoriteStore.State = FavoriteStore.State() + var favoriteState: FavoriteStore.State { + get { + var state = _favoriteState + state.globalState = globalState + return state + } + set { + _favoriteState = newValue + globalState = newValue.globalState! + } } - set { - _favoriteState = newValue - globalState = newValue.globalState! + var settingsState: SettingsStore.State = SettingsStore.State() + var redisKeysState: RedisKeysStore.State = RedisKeysStore.State() + + init() { + logger.info("app state init ...") + } } - var settingsState: SettingsState = SettingsState() - var redisKeysState: RedisKeysState = RedisKeysState() - init() { - logger.info("app state init ...") - + enum Action:Equatable { + case initial + case onStart + case onClose + case globalAction(AppContextStore.Action) + case loadingAction(LoadingStore.Action) + case favoriteAction(FavoriteStore.Action) + case settingsAction(SettingsStore.Action) + case redisKeysAction(RedisKeysStore.Action) } -} - -extension Store { - -} - -enum AppAction:Equatable { - case initContext - case onStart - case onClose - case globalAction(GlobalAction) - case alertAction(AlertAction) - case loadingAction(LoadingAction) - case favoriteAction(FavoriteAction) - case settingsAction(SettingsAction) - case redisKeysAction(RedisKeysAction) -} -struct AppEnvironment { - var redisInstanceModel:RedisInstanceModel = RedisInstanceModel(redisModel: RedisModel()) + @Dependency(\.redisInstance) var redisInstanceModel: RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} - - -let appReducer = Reducer>.combine( - globalReducer.pullback( - state: \.globalState, - action: /AppAction.globalAction, - environment: { env in GlobalEnvironment() } - ), - favoriteReducer.pullback( - state: \.favoriteState, - action: /AppAction.favoriteAction, - environment: { env in FavoriteEnvironment(redisInstanceModel: env.redisInstanceModel) } - ), - settingsReducer.pullback( - state: \.settingsState, - action: /AppAction.settingsAction, - environment: { _ in SettingsEnvironment() } - ), -// alertReducer.pullback( -// state: \.appAlertState, -// action: /AppAction.alertAction, -// environment: { _ in AlertEnvironment() } -// ), - loadingReducer.pullback( - state: \.loadingState, - action: /AppAction.loadingAction, - environment: { _ in LoadingEnvironment() } - ), - redisKeysReducer.pullback( - state: \.redisKeysState, - action: /AppAction.redisKeysAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - Reducer> { - state, action, env in - switch action { - case .initContext: - logger.info("init app context complete...") - return .none - case .onStart: - return .none + + + var body: some Reducer { - case .onClose: - env.redisInstanceModel.close() - state.isConnect = false - return .none - case .globalAction: - return .none - case .alertAction: - return .none - case .loadingAction: - return .none - case let .favoriteAction(.connectSuccess(redisModel)): - state.isConnect = true - state.title = redisModel.name - return .none - case .favoriteAction: - return .none - case .settingsAction: - return .none - case .redisKeysAction: - return .none + Scope(state: \.globalState, action: /Action.globalAction) { + AppContextStore() + } + Scope(state: \.loadingState, action: /Action.loadingAction) { + LoadingStore() + } + Scope(state: \.settingsState, action: /Action.settingsAction) { + SettingsStore() + } + Scope(state: \.favoriteState, action: /Action.favoriteAction) { + FavoriteStore() + } + Scope(state: \.redisKeysState, action: /Action.redisKeysAction) { + RedisKeysStore() + } + + Reduce { state, action in + switch action { + case .initial: + logger.info("init app context complete...") + return .send(.redisKeysAction(.initial)) + case .onStart: + return .none + + case .onClose: + redisInstanceModel.close() + state.isConnect = false + return .none + case .globalAction: + return .none + case .loadingAction: + return .none + case let .favoriteAction(.connectSuccess(redisModel)): + state.isConnect = true + state.title = redisModel.name + return .none + case .favoriteAction: + return .none + case .settingsAction: + return .none + case .redisKeysAction: + return .none + } } - }.debug() -) - - -struct Test { - var a:String = UUID().uuidString - init() { - logger.info("app state init ...") } } diff --git a/redis-pro/Store/ClientListStore.swift b/redis-pro/Store/ClientListStore.swift index 9ffe39a..4578595 100644 --- a/redis-pro/Store/ClientListStore.swift +++ b/redis-pro/Store/ClientListStore.swift @@ -11,128 +11,125 @@ import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "client-list-store") -struct ClientListState: Equatable { - - var tableState: TableState = TableState( - columns: [ - .init(title: "id", key: "id", width: 60), - .init(title: "name", key: "name", width: 60), - .init(title: "addr", key: "addr", width: 140), - .init(title: "laddr", key: "laddr", width: 140), - .init(title: "fd", key: "fd", width: 60), - .init(title: "age", key: "age", width: 60), - .init(title: "idle", key: "idle", width: 60), - .init(title: "flags", key: "flags", width: 60), - .init(title: "db", key: "db", width: 60), - .init(title: "sub", key: "sub", width: 60), - .init(title: "psub", key: "psub", width: 60), - .init(title: "multi", key: "multi", width: 60), - .init(title: "qbuf", key: "qbuf", width: 60), - .init(title: "qbuf_free", key: "qbuf_free", width: 60), - .init(title: "obl", key: "obl", width: 60), - .init(title: "oll", key: "oll", width: 60), - .init(title: "omem", key: "omem", width: 60), - .init(title: "events", key: "events", width: 60), - .init(title: "cmd", key: "cmd", width: 100), - .init(title: "argv_mem", key: "argv_mem", width: 60), - .init(title: "tot_mem", key: "tot_mem", width: 60), - .init(title: "redir", key: "redir", width: 60), - .init(title: "user", key: "user", width: 60) - ] - , datasource: [] - , contextMenus: [.KILL] - , selectIndex: -1) - - init() { - logger.info("client list state init ...") - } -} -enum ClientListAction: Equatable { - case initial - case getValue - case setValue([ClientModel]) - case refresh - case killConfirm(Int) - case kill(Int) - case tableAction(TableAction) -} -struct ClientListEnvironment { - var redisInstanceModel:RedisInstanceModel -} - -let clientListReducer = Reducer>.combine( - tableReducer.pullback( - state: \.tableState, - action: /ClientListAction.tableAction, - environment: { _ in .init() } - ), - Reducer> { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: +struct ClientListStore: Reducer { + struct State: Equatable { - logger.info("client list store initial...") - return .result { - .success(.getValue) - } + var tableState: TableStore.State = TableStore.State( + columns: [ + .init(title: "id", key: "id", width: 60), + .init(title: "name", key: "name", width: 60), + .init(title: "addr", key: "addr", width: 140), + .init(title: "laddr", key: "laddr", width: 140), + .init(title: "fd", key: "fd", width: 60), + .init(title: "age", key: "age", width: 60), + .init(title: "idle", key: "idle", width: 60), + .init(title: "flags", key: "flags", width: 60), + .init(title: "db", key: "db", width: 60), + .init(title: "sub", key: "sub", width: 60), + .init(title: "psub", key: "psub", width: 60), + .init(title: "multi", key: "multi", width: 60), + .init(title: "qbuf", key: "qbuf", width: 60), + .init(title: "qbuf_free", key: "qbuf_free", width: 60), + .init(title: "obl", key: "obl", width: 60), + .init(title: "oll", key: "oll", width: 60), + .init(title: "omem", key: "omem", width: 60), + .init(title: "events", key: "events", width: 60), + .init(title: "cmd", key: "cmd", width: 100), + .init(title: "argv_mem", key: "argv_mem", width: 60), + .init(title: "tot_mem", key: "tot_mem", width: 60), + .init(title: "redir", key: "redir", width: 60), + .init(title: "user", key: "user", width: 60) + ] + , datasource: [] + , contextMenus: [.KILL] + , selectIndex: -1) - case .getValue: - return .task { - let r = await env.redisInstanceModel.getClient().clientList() - return .setValue(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() + init() { + logger.info("client list state init ...") + } + } + + enum Action: Equatable { + case initial + case getValue + case setValue([ClientModel]) + case refresh + case killConfirm(Int) + case kill(Int) + case tableAction(TableStore.Action) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + let mainQueue: AnySchedulerOf = .main + + var body: some Reducer { - case let .setValue(clientLists): - state.tableState.datasource = clientLists - - return .none + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: - case let .killConfirm(index): - - let item = state.tableState.datasource[index] as! ClientModel - return .future { callback in - Messages.confirm("Kill Client?" - , message: "Are you sure you want to kill client:\(item.addr)? This operation cannot be undone." - , primaryButton: "Kill" - , action: { - callback(.success(.kill(index))) - }) - } + logger.info("client list store initial...") + return .run { send in + await send(.getValue) + } - case let .kill(index): - let client = state.tableState.datasource[index] as! ClientModel - logger.info("kill client, addr: \(client.addr)") + case .getValue: + return .run { send in + let r = await redisInstanceModel.getClient().clientList() + await send(.setValue(r)) + } - return .task { - let r = await env.redisInstanceModel.getClient().clientKill(client) - logger.info("do kill client, addr: \(client.addr), r:\(r)") + case let .setValue(clientLists): + state.tableState.datasource = clientLists - return .refresh - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .refresh: - return .result { - .success(.getValue) - } + return .none + + case let .killConfirm(index): + + let item = state.tableState.datasource[index] as! ClientModel + return .run { send in + Messages.confirm("Kill Client?" + , message: "Are you sure you want to kill client:\(item.addr)? This operation cannot be undone." + , primaryButton: "Kill" + , action: { + await send(.kill(index)) + }) + } + + case let .kill(index): + let client = state.tableState.datasource[index] as! ClientModel + logger.info("kill client, addr: \(client.addr)") + + return .run { send in + let r = await redisInstanceModel.getClient().clientKill(client) + logger.info("do kill client, addr: \(client.addr), r:\(r)") + + await send(.refresh) + } - // table action - case let .tableAction(.contextMenu(title, index)): - if title == "Kill" { - return .result { - .success(.killConfirm(index)) + case .refresh: + return .run { send in + await send(.getValue) + } + + // table action + case let .tableAction(.contextMenu(title, index)): + if title == "Kill" { + return .run { send in + await send(.killConfirm(index)) + } } + + return .none + case .tableAction: + return .none } - - return .none - case .tableAction: - return .none } } -).debug() +} diff --git a/redis-pro/Store/DatabaseStore.swift b/redis-pro/Store/DatabaseStore.swift index 83793e6..60a17cc 100644 --- a/redis-pro/Store/DatabaseStore.swift +++ b/redis-pro/Store/DatabaseStore.swift @@ -11,71 +11,70 @@ import ComposableArchitecture private let logger = Logger(label: "database-store") -struct DatabaseState: Equatable { - var database: Int = 0 - var databases:Int = 16 - init() { - logger.info("database state init ...") - } -} +struct DatabaseStore: Reducer { + struct State: Equatable { + var database: Int = 0 + var databases:Int = 16 + init() { + logger.info("database state init ...") + } + } -enum DatabaseAction:BindableAction, Equatable { - case initial - case getDatabases - case setDB(Int) - case selectDB(Int) - case onDBChange(Int) - case none - case binding(BindingAction) -} -struct DatabaseEnvironment { - var redisInstanceModel:RedisInstanceModel + enum Action:BindableAction, Equatable { + case initial + case getDatabases + case setDB(Int) + case selectDB(Int) + case onDBChange(Int) + case none + case binding(BindingAction) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} - -let databaseReducer = Reducer.combine( - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - logger.info("database store initial...") - state.database = env.redisInstanceModel.redisModel.database - return .result { - .success(.getDatabases) - } - case .getDatabases: - return .task { - let r = await env.redisInstanceModel.getClient().databases() - return .setDB(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() - case let .setDB(databases): - state.databases = databases - return .none - case let .selectDB(database): - state.database = database - - return .task { - let r = await env.redisInstanceModel.getClient().selectDB(database) - if r { - return .onDBChange(database) + + var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + logger.info("database store initial...") + state.database = redisInstanceModel.redisModel.database + return .run { send in + await send(.getDatabases) + } + case .getDatabases: + return .run { send in + let r = await redisInstanceModel.getClient().databases() + await send(.setDB(r)) } + case let .setDB(databases): + state.databases = databases + return .none + case let .selectDB(database): + state.database = database + + return .run { send in + let r = await redisInstanceModel.getClient().selectDB(database) + if r { + await send(.onDBChange(database)) + } + } + + case .onDBChange: + return .none + case .none: + return .none + case .binding: return .none } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .onDBChange: - return .none - case .none: - return .none - case .binding: - return .none } } -).binding().debug() + + +} + diff --git a/redis-pro/Store/DependencyKeys.swift b/redis-pro/Store/DependencyKeys.swift new file mode 100644 index 0000000..246a644 --- /dev/null +++ b/redis-pro/Store/DependencyKeys.swift @@ -0,0 +1,40 @@ +// +// DependencyKeys.swift +// redis-pro +// +// Created by chengpan on 2023/7/2. +// + +import Foundation +import Dependencies + +private enum RedisInstanceKey: DependencyKey { + static let liveValue = RedisInstanceModel(redisModel: RedisModel()) +} + +private enum RedisClientKey: DependencyKey { + static let liveValue = RediStackClient(RedisModel()) +} + + +//private enum AppContextKey: DependencyKey { +// static let liveValue = AppContext() +//} + + +extension DependencyValues { + var redisInstance: RedisInstanceModel { + get { self[RedisInstanceKey.self] } + set { self[RedisInstanceKey.self] = newValue } + } + + var redisClient: RediStackClient { + get { self[RedisClientKey.self] } + set { self[RedisClientKey.self] = newValue } + } + + // var appContext: AppContext { + // get { self[AppContextKey.self] } + // set { self[AppContextKey.self] = newValue } + // } +} diff --git a/redis-pro/Store/FavoriteStore.swift b/redis-pro/Store/FavoriteStore.swift index 2151b67..d862e73 100644 --- a/redis-pro/Store/FavoriteStore.swift +++ b/redis-pro/Store/FavoriteStore.swift @@ -8,181 +8,174 @@ import Logging import Foundation import ComposableArchitecture -struct FavoriteState: Equatable { - var globalState: GlobalState? - var tableState: TableState = TableState(columns: [NTableColumn(title: "FAVORITES", key: "name", width: 50, icon: .APP)], datasource: [], selectIndex: -1, dragable: true) - var loginState: LoginState = LoginState() - -// init(globalState: GlobalState) { -// self.globalState = globalState -// self.tableState = TableState(columns: [NTableColumn(title: "FAVORITES", key: "name", width: 50, icon: .APP)], datasource: [], selectIndex: -1) -// self.loginState = LoginState() -// } -} +private let logger = Logger(label: "favorite-store") -enum FavoriteAction:Equatable { - case getAll - case addNew - case save(RedisModel) - case deleteConfirm(Int) - case delete(Int) - case connect(Int) - case connectSuccess(RedisModel) - case initDefaultSelection - case tableAction(TableAction) - case loginAction(LoginAction) - case loadingAction(LoadingAction) - case none -} +struct FavoriteStore: Reducer { + + struct State: Equatable { + var globalState: AppContextStore.State? + var tableState: TableStore.State = TableStore.State(columns: [NTableColumn(title: "FAVORITES", key: "name", width: 50, icon: .APP)], datasource: [], selectIndex: -1, dragable: true) + var loginState: LoginStore.State = LoginStore.State() + } -struct FavoriteEnvironment { - var redisInstanceModel:RedisInstanceModel + enum Action:Equatable { + case getAll + case addNew + case save(RedisModel) + case deleteConfirm(Int) + case delete(Int) + case connect(Int) + case connectSuccess(RedisModel) + case initDefaultSelection + case tableAction(TableStore.Action) + case loginAction(LoginStore.Action) + case loadingAction(LoadingStore.Action) + case none + } + + + @Dependency(\.redisInstance) var redisInstanceModel: RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} - -private let logger = Logger(label: "redis-favorite-store") -let favoriteReducer = Reducer.combine( - tableReducer.pullback( - state: \.tableState, - action: /FavoriteAction.tableAction, - environment: { _ in TableEnvironment() } - ), - loginReducer.pullback( - state: \.loginState, - action: /FavoriteAction.loginAction, - environment: { env in LoginEnvironment(redisInstanceModel: env.redisInstanceModel) } - ), - Reducer { - state, action, env in - switch action { - // 查询所有收藏 - case .getAll: - state.tableState.datasource = RedisDefaults.getAll() -// state.tableState.defaultSelectIndex = 1 - return .none - // 设置默认选中 - case .initDefaultSelection: - var selectId:String? - let defaultFavorite = RedisDefaults.defaultSelectType() - if defaultFavorite == "last" { - selectId = RedisDefaults.getLastId() - } else { - selectId = defaultFavorite - } - - guard let selectId = selectId else { + + + var body: some Reducer { + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Scope(state: \.loginState, action: /Action.loginAction) { + LoginStore() + } + + Reduce { state, action in + switch action { + // 查询所有收藏 + case .getAll: + state.tableState.datasource = RedisDefaults.getAll() + // state.tableState.defaultSelectIndex = 1 + return .none + // 设置默认选中 + case .initDefaultSelection: + var selectId:String? + let defaultFavorite = RedisDefaults.defaultSelectType() + if defaultFavorite == "last" { + selectId = RedisDefaults.getLastId() + } else { + selectId = defaultFavorite + } + + guard let selectId = selectId else { + state.tableState.defaultSelectIndex = state.tableState.datasource.count > 0 ? 0 : -1 + return .none + } + + if let index = state.tableState.datasource.firstIndex(where: { (e) -> Bool in + return (e as! RedisModel).id == selectId + }) { + state.tableState.defaultSelectIndex = index + return .none + } + state.tableState.defaultSelectIndex = state.tableState.datasource.count > 0 ? 0 : -1 return .none - } - - if let index = state.tableState.datasource.firstIndex(where: { (e) -> Bool in - return (e as! RedisModel).id == selectId - }) { - state.tableState.defaultSelectIndex = index + case .addNew: + return .run { send in + await send(.save(RedisModel())) + } + case let .save(redisModel): + logger.info("save redis favorite: \(redisModel)") + + let index = RedisDefaults.save(redisModel) + state.tableState.selectIndex = index + return .run { send in + await send(.getAll) + } + case let .deleteConfirm(index): + if state.tableState.datasource.count <= index { + return .none + } + + let redisModel = state.tableState.datasource[index] as! RedisModel + + return .run { send in + Messages.confirm(String(format: NSLocalizedString("CONFIRM_FAVORITE_REDIS_TITLE'%@'", comment: ""), redisModel.name) + , message: String(format: NSLocalizedString("CONFIRM_FAVORITE_REDIS_MESSAGE'%@'", comment: ""), redisModel.name) + , primaryButton: "Delete" + , action: { + await send(.delete(index)) + }) + } + case let .delete(index): + let r = RedisDefaults.delete(index) + if r { + state.tableState.datasource.remove(at: index) + if state.tableState.datasource.count - 1 < state.tableState.selectIndex { + state.tableState.selectIndex = state.tableState.datasource.count - 1 + } + } + logger.info("delete redis favorite") return .none - } - - state.tableState.defaultSelectIndex = state.tableState.datasource.count > 0 ? 0 : -1 - return .none - case .addNew: - return .result { - .success(.save(RedisModel())) - } - case let .save(redisModel): - logger.info("save redis favorite: \(redisModel)") - - let index = RedisDefaults.save(redisModel) - state.tableState.selectIndex = index - return .result { - .success(.getAll) - } - case let .deleteConfirm(index): - if state.tableState.datasource.count <= index { + // login + case let .connect(index): + let redisModel = state.tableState.datasource[index] as! RedisModel + logger.info("connect to redis server, name: \(redisModel.name), host: \(redisModel.host)") + + return .run { send in + let r = await redisInstanceModel.connect(redisModel) + logger.info("on connect to redis server: \(redisModel) , result: \(r)") + RedisDefaults.saveLastUse(redisModel) + if r { + await send(.connectSuccess(redisModel)) + } + } + + case let .tableAction(.copy(index)): + let redisModel = state.tableState.datasource[index] as! RedisModel + PasteboardHelper.copy(redisModel.name) return .none - } - let redisModel = state.tableState.datasource[index] as! RedisModel - - return Effect.future { callback in - Messages.confirm(String(format: NSLocalizedString("CONFIRM_FAVORITE_REDIS_TITLE'%@'", comment: ""), redisModel.name) - , message: String(format: NSLocalizedString("CONFIRM_FAVORITE_REDIS_MESSAGE'%@'", comment: ""), redisModel.name) - , primaryButton: "Delete" - , action: { - callback(.success(.delete(index))) + case let .tableAction(.dragComplete(from, to)): + let _ = RedisDefaults.save(state.tableState.datasource as! [RedisModel]) + return .none + + case let .tableAction(.double(index)): + return .run { send in + await send(.connect(index)) } - ) - } - case let .delete(index): - let r = RedisDefaults.delete(index) - if r { - state.tableState.datasource.remove(at: index) - if state.tableState.datasource.count - 1 < state.tableState.selectIndex { - state.tableState.selectIndex = state.tableState.datasource.count - 1 + case let .tableAction(.selectionChange(index)): + guard index > -1 else { return .none } + + logger.info("redis favorite table selection change action, index: \(index)") + let redisModel = state.tableState.datasource[index] as! RedisModel + state.loginState.redisModel = redisModel + return .none + case let .tableAction(.delete(index)): + return .run { send in + await send(.deleteConfirm(index)) } + case .tableAction: + logger.info("redis favorite table action \(state.tableState.selectIndex)") + return .none + + case .loginAction(.connect): + let index = state.tableState.selectIndex + return .run { send in + await send(.connect(index)) + } + case .loginAction(.save): + let redisModel = state.loginState.redisModel + return .run { send in + await send(.save(redisModel)) + } + case .loginAction: + logger.info("redis favorite table action \(state.tableState.selectIndex)") + return .none + case .loadingAction: + return .none + case .connectSuccess: + return .none + case .none: + return .none } - logger.info("delete redis favorite") - return .none - // login - case let .connect(index): - let redisModel = state.tableState.datasource[index] as! RedisModel - logger.info("connect to redis server, name: \(redisModel.name), host: \(redisModel.host)") - - return Effect.task { - let r = await env.redisInstanceModel.connect(redisModel) - logger.info("on connect to redis server: \(redisModel) , result: \(r)") - RedisDefaults.saveLastUse(redisModel) - return r ? .connectSuccess(redisModel) : .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .tableAction(.copy(index)): - let redisModel = state.tableState.datasource[index] as! RedisModel - PasteboardHelper.copy(redisModel.name) - return .none - - case let .tableAction(.dragComplete(from, to)): - let _ = RedisDefaults.save(state.tableState.datasource as! [RedisModel]) - return .none - - case let .tableAction(.double(index)): - return .result { - .success(.connect(index)) - } - case let .tableAction(.selectionChange(index)): - guard index > -1 else { return .none } - - logger.info("redis favorite table selection change action, index: \(index)") - let redisModel = state.tableState.datasource[index] as! RedisModel - state.loginState.redisModel = redisModel - return .none - case let .tableAction(.delete(index)): - return .result { - .success(.deleteConfirm(index)) - } - case .tableAction: - logger.info("redis favorite table action \(state.tableState.selectIndex)") - return .none - - case .loginAction(.connect): - let index = state.tableState.selectIndex - return .result { - .success(.connect(index)) - } - case .loginAction(.save): - let redisModel = state.loginState.redisModel - return .result { - .success(.save(redisModel)) - } - case .loginAction: - logger.info("redis favorite table action \(state.tableState.selectIndex)") - return .none - case .loadingAction: - return .none - case .connectSuccess: - return .none - case .none: - return .none } } -) +} diff --git a/redis-pro/Store/GlobalStore.swift b/redis-pro/Store/GlobalStore.swift deleted file mode 100644 index 0e728e1..0000000 --- a/redis-pro/Store/GlobalStore.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// GlobalStore.swift -// redis-pro -// -// Created by chengpan on 2022/6/2. -// - - -import Logging -import Foundation -import ComposableArchitecture - -private let logger = Logger(label: "global-store") - -struct GlobalState: Equatable { - var loading:Bool = false - var loadingCount:Int = 0 - - init() { - logger.info("global state init ...") - } -} - -enum GlobalAction: Equatable { - case show - case hide -} - -struct GlobalEnvironment { -} - - -let globalReducer = Reducer.combine( - Reducer { - state, action, _ in - switch action { - case .show: - if state.loadingCount <= 0 { - state.loading = true - } - - state.loadingCount += 1 - return .none - case .hide: - state.loadingCount -= 1 - if state.loadingCount <= 0 { - state.loading = false - state.loadingCount = 0 - } - - return .none - } - }.debug() -) diff --git a/redis-pro/Store/HashValueStore.swift b/redis-pro/Store/HashValueStore.swift index 96474b3..b0c9662 100644 --- a/redis-pro/Store/HashValueStore.swift +++ b/redis-pro/Store/HashValueStore.swift @@ -12,259 +12,468 @@ import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "hash-value-store") -struct HashValueState: Equatable { - @BindableState var editModalVisible:Bool = false - @BindableState var field:String = "" - @BindableState var value:String = "" - var editIndex:Int = -1 - var isNew:Bool = false - var redisKeyModel:RedisKeyModel? - var pageState: PageState = PageState(showTotal: true) - var tableState: TableState = TableState( - columns: [.init(title: "Field", key: "field", width: 100), .init(title: "Value", key: "value", width: 200)] - , datasource: [] - , contextMenus: [.EDIT, .DELETE, .COPY, .COPY_FIELD, .COPY_VALUE] - , selectIndex: -1) + + +struct HashValueStore: Reducer { - init() { - logger.info("hash value state init ...") - pageState.showTotal = true + struct State: Equatable { + @BindingState var editModalVisible:Bool = false + @BindingState var field:String = "" + @BindingState var value:String = "" + var editIndex:Int = -1 + var isNew:Bool = false + var redisKeyModel:RedisKeyModel? + var pageState: PageStore.State = PageStore.State(showTotal: true) + var tableState: TableStore.State = TableStore.State( + columns: [.init(title: "Field", key: "field", width: 100), .init(title: "Value", key: "value", width: 200)] + , datasource: [] + , contextMenus: [.EDIT, .DELETE, .COPY, .COPY_FIELD, .COPY_VALUE] + , selectIndex: -1) + } -} - -enum HashValueAction:BindableAction, Equatable { - case initial - case refresh - case search(String) - case getValue - case setValue(Page, [RedisHashEntryModel]) - case addNew - case edit(Int) - case submit - case submitSuccess(Bool) + enum Action: BindableAction, Equatable { + case binding(BindingAction) + case initial + case refresh + case search(String) + case getValue + case setValue(Page, [RedisHashEntryModel]) + + case addNew + case edit(Int) + case submit + case submitSuccess(Bool) + + case deleteConfirm(Int) + case deleteKey(Int) + case deleteSuccess(Int) + + case none + case pageAction(PageStore.Action) + case tableAction(TableStore.Action) + } - case deleteConfirm(Int) - case deleteKey(Int) - case deleteSuccess(Int) + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + let mainQueue: AnySchedulerOf = .main - case none - case pageAction(PageAction) - case tableAction(TableAction) - case binding(BindingAction) -} - -struct HashValueEnvironment { - var redisInstanceModel:RedisInstanceModel - var mainQueue: AnySchedulerOf = .main -} + var body: some Reducer { + BindingReducer() + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Scope(state: \.pageState, action: /Action.pageAction) { + PageStore() + } -let hashValueReducer = Reducer.combine( - tableReducer.pullback( - state: \.tableState, - action: /HashValueAction.tableAction, - environment: { env in .init() } - ), - pageReducer.pullback( - state: \.pageState, - action: /HashValueAction.pageAction, - environment: { env in .init() } - ), - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - state.pageState.keywords = "" - state.pageState.current = 1 - - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case .refresh: - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case let .search(keywords): - state.pageState.current = 1 - state.pageState.keywords = keywords - return .result { - .success(.getValue) - } - - case .getValue: - guard let redisKeyModel = state.redisKeyModel else { + Reduce { state, action in + switch action { + case .binding: return .none - } - // 清空 - if redisKeyModel.isNew { - return .result { - .success(.tableAction(.reset)) - } - } - - let key = redisKeyModel.key - let page = state.pageState.page - return .task { - let res = await env.redisInstanceModel.getClient().pageHash(key, page: page) - return .setValue(page, res) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .setValue(page, datasource): - state.tableState.datasource = datasource - state.pageState.page = page - return .none - - case .addNew: - state.field = "" - state.value = "" - state.isNew = true - state.editModalVisible = true - return .none - case let .edit(index): - let item = state.tableState.datasource[index] as! RedisHashEntryModel - state.editIndex = index - state.field = item.field - state.value = item.value - state.isNew = false - state.editModalVisible = true - return .none - - case .submit: - guard let redisKeyModel = state.redisKeyModel else { + // 初始化已设置的值 + case .initial: + state.pageState.keywords = "" + state.pageState.current = 1 + + logger.info("value store initial...") + return .run { send in + await send(.getValue) + } + + case .refresh: + logger.info("value store initial...") + return .run { send in + await send(.getValue) + } + + case let .search(keywords): + state.pageState.current = 1 + state.pageState.keywords = keywords + return .run { send in + await send(.getValue) + } + + case .getValue: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } + // 清空 + if redisKeyModel.isNew { + return .run { send in + await send(.tableAction(.reset)) + } + } + + let key = redisKeyModel.key + let page = state.pageState.page + return .run { send in + let res = await redisInstanceModel.getClient().pageHash(key, page: page) + await send(.setValue(page, res)) + } + + case let .setValue(page, datasource): + state.tableState.datasource = datasource + state.pageState.page = page return .none - } - - let key = redisKeyModel.key - let field = state.field - let value = state.value - let isNewKey = state.redisKeyModel?.isNew ?? false - return .task { - let r = await env.redisInstanceModel.getClient().hset(key, field: field, value: value) - return .submitSuccess(isNewKey) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .submitSuccess(isNewKey): - let item = RedisHashEntryModel(field: state.field, value: state.value) - if state.isNew { - state.tableState.selectIndex = 0 - state.tableState.datasource.insert(item, at: 0) + + case .addNew: + state.field = "" + state.value = "" + state.isNew = true + state.editModalVisible = true + return .none + + case let .edit(index): + let item = state.tableState.datasource[index] as! RedisHashEntryModel + state.editIndex = index + state.field = item.field + state.value = item.value state.isNew = false - } else { - state.tableState.datasource[state.editIndex] = item - } - return .none - - - case let .deleteConfirm(index): - guard index < state.tableState.datasource.count else { + state.editModalVisible = true return .none - } - - let item = state.tableState.datasource[index] as! RedisHashEntryModel - return .future { callback in - Messages.confirm(String(format: NSLocalizedString("HASH_DELETE_CONFIRM_TITLE'%@'", comment: ""), item.field) - , message: String(format: NSLocalizedString("HASH_DELETE_CONFIRM_MESSAGE'%@'", comment: ""), item.field) - , primaryButton: "Delete" - , action: { - callback(.success(.deleteKey(index))) - }) - } - - case let .deleteKey(index): - - let redisKeyModel = state.redisKeyModel! - let item = state.tableState.datasource[index] as! RedisHashEntryModel - logger.info("delete hash field, key: \(redisKeyModel.key), field: \(item.field)") - - return .task { - let r = await env.redisInstanceModel.getClient().hdel(redisKeyModel.key, field: item.field) - logger.info("do delete hash field, key: \(redisKeyModel.key), field: \(item.field), r:\(r)") - return r > 0 ? .deleteSuccess(index) : .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .deleteSuccess(index): - state.tableState.datasource.remove(at: index) - - return .result { - .success(.refresh) - } - - case .none: - return .none - - //MARK: -Page action - case .pageAction(.updateSize): - return .result { - .success(.getValue) - } - case .pageAction(.nextPage): - return .result { - .success(.getValue) - } - case .pageAction(.prevPage): - return .result { - .success(.getValue) - } - case .pageAction: - return .none - - - //MARK: - table action - // context menu - case let .tableAction(.contextMenu(title, index)): - if title == "Delete" { - return .result { - .success(.deleteConfirm(index)) + case .submit: + guard let redisKeyModel = state.redisKeyModel else { + return .none } - } - - else if title == "Edit" { - return .result { - .success(.edit(index)) + + let key = redisKeyModel.key + let field = state.field + let value = state.value + let isNewKey = state.redisKeyModel?.isNew ?? false + return .run { send in + let r = await redisInstanceModel.getClient().hset(key, field: field, value: value) + await send(.submitSuccess(isNewKey)) } - } - - else if title == TableContextMenu.COPY_FIELD.rawValue { + + case let .submitSuccess(isNewKey): + let item = RedisHashEntryModel(field: state.field, value: state.value) + if state.isNew { + state.tableState.selectIndex = 0 + state.tableState.datasource.insert(item, at: 0) + state.isNew = false + } else { + state.tableState.datasource[state.editIndex] = item + } + return .none + + + case let .deleteConfirm(index): + guard index < state.tableState.datasource.count else { + return .none + } + let item = state.tableState.datasource[index] as! RedisHashEntryModel - PasteboardHelper.copy(item.field) - } - else if title == TableContextMenu.COPY_VALUE.rawValue { + return .run { send in + Messages.confirm(String(format: NSLocalizedString("HASH_DELETE_CONFIRM_TITLE'%@'", comment: ""), item.field) + , message: String(format: NSLocalizedString("HASH_DELETE_CONFIRM_MESSAGE'%@'", comment: ""), item.field) + , primaryButton: "Delete" + , action: { + await send(.deleteKey(index)) + }) + } + + case let .deleteKey(index): + + let redisKeyModel = state.redisKeyModel! let item = state.tableState.datasource[index] as! RedisHashEntryModel - PasteboardHelper.copy(item.value) - } - return .none - - case let .tableAction(.copy(index)): - let item = state.tableState.datasource[index] as! RedisHashEntryModel - PasteboardHelper.copy("Field: \(item.field) \n Value: \(item.value)") - return .none - - case let .tableAction(.double(index)): - return .result { - .success(.edit(index)) - } - - case let .tableAction(.delete(index)): - return .result { - .success(.deleteConfirm(index)) + logger.info("delete hash field, key: \(redisKeyModel.key), field: \(item.field)") + + return .run { send in + let r = await redisInstanceModel.getClient().hdel(redisKeyModel.key, field: item.field) + logger.info("do delete hash field, key: \(redisKeyModel.key), field: \(item.field), r:\(r)") + + if r > 0 { + await send(.deleteSuccess(index)) + } + } + + case let .deleteSuccess(index): + state.tableState.datasource.remove(at: index) + + return .run { send in + await send(.refresh) + } + + case .none: + return .none + + //MARK: -Page action + case .pageAction(.updateSize): + return .run { send in + await send(.getValue) + } + case .pageAction(.nextPage): + return .run { send in + await send(.getValue) + } + case .pageAction(.prevPage): + return .run { send in + await send(.getValue) + } + case .pageAction: + return .none + + + //MARK: - table action + // context menu + case let .tableAction(.contextMenu(title, index)): + if title == "Delete" { + return .run { send in + await send(.deleteConfirm(index)) + } + } + + else if title == "Edit" { + return .run { send in + await send(.edit(index)) + } + } + + else if title == TableContextMenu.COPY_FIELD.rawValue { + let item = state.tableState.datasource[index] as! RedisHashEntryModel + PasteboardHelper.copy(item.field) + } + else if title == TableContextMenu.COPY_VALUE.rawValue { + let item = state.tableState.datasource[index] as! RedisHashEntryModel + PasteboardHelper.copy(item.value) + } + return .none + + case let .tableAction(.copy(index)): + let item = state.tableState.datasource[index] as! RedisHashEntryModel + PasteboardHelper.copy("Field: \(item.field) \n Value: \(item.value)") + return .none + + case let .tableAction(.double(index)): + return .run { send in + await send(.edit(index)) + } + + case let .tableAction(.delete(index)): + return .run { send in + await send(.deleteConfirm(index)) + } + case .tableAction: + return .none } - case .tableAction: - return .none - case .binding: - return .none } + } -).binding().debug() +} + +//struct HashValueEnvironment { +// var redisInstanceModel:RedisInstanceModel +// var mainQueue: AnySchedulerOf = .main +//} +// +//let hashValueReducer = Reducer.combine( +// tableReducer.pullback( +// state: \.tableState, +// action: /HashValueAction.tableAction, +// environment: { env in .init() } +// ), +// pageReducer.pullback( +// state: \.pageState, +// action: /HashValueAction.pageAction, +// environment: { env in .init() } +// ), +// Reducer { +// state, action, env in +// switch action { +// // 初始化已设置的值 +// case .initial: +// state.pageState.keywords = "" +// state.pageState.current = 1 +// +// logger.info("value store initial...") +// return .run { send in +// .success(.getValue) +// } +// +// case .refresh: +// logger.info("value store initial...") +// return .result { +// .success(.getValue) +// } +// +// case let .search(keywords): +// state.pageState.current = 1 +// state.pageState.keywords = keywords +// return .result { +// .success(.getValue) +// } +// +// case .getValue: +// guard let redisKeyModel = state.redisKeyModel else { +// return .none +// } +// // 清空 +// if redisKeyModel.isNew { +// return .result { +// .success(.tableAction(.reset)) +// } +// } +// +// let key = redisKeyModel.key +// let page = state.pageState.page +// return .task { +// let res = await env.redisInstanceModel.getClient().pageHash(key, page: page) +// return .setValue(page, res) +// } +// .receive(on: env.mainQueue) +// .eraseToEffect() +// +// case let .setValue(page, datasource): +// state.tableState.datasource = datasource +// state.pageState.page = page +// return .none +// +// case .addNew: +// state.field = "" +// state.value = "" +// state.isNew = true +// state.editModalVisible = true +// return .none +// +// case let .edit(index): +// let item = state.tableState.datasource[index] as! RedisHashEntryModel +// state.editIndex = index +// state.field = item.field +// state.value = item.value +// state.isNew = false +// state.editModalVisible = true +// return .none +// +// case .submit: +// guard let redisKeyModel = state.redisKeyModel else { +// return .none +// } +// +// let key = redisKeyModel.key +// let field = state.field +// let value = state.value +// let isNewKey = state.redisKeyModel?.isNew ?? false +// return .task { +// let r = await env.redisInstanceModel.getClient().hset(key, field: field, value: value) +// return .submitSuccess(isNewKey) +// } +// .receive(on: env.mainQueue) +// .eraseToEffect() +// +// case let .submitSuccess(isNewKey): +// let item = RedisHashEntryModel(field: state.field, value: state.value) +// if state.isNew { +// state.tableState.selectIndex = 0 +// state.tableState.datasource.insert(item, at: 0) +// state.isNew = false +// } else { +// state.tableState.datasource[state.editIndex] = item +// } +// return .none +// +// +// case let .deleteConfirm(index): +// guard index < state.tableState.datasource.count else { +// return .none +// } +// +// let item = state.tableState.datasource[index] as! RedisHashEntryModel +// return .future { callback in +// Messages.confirm(String(format: NSLocalizedString("HASH_DELETE_CONFIRM_TITLE'%@'", comment: ""), item.field) +// , message: String(format: NSLocalizedString("HASH_DELETE_CONFIRM_MESSAGE'%@'", comment: ""), item.field) +// , primaryButton: "Delete" +// , action: { +// callback(.success(.deleteKey(index))) +// }) +// } +// +// case let .deleteKey(index): +// +// let redisKeyModel = state.redisKeyModel! +// let item = state.tableState.datasource[index] as! RedisHashEntryModel +// logger.info("delete hash field, key: \(redisKeyModel.key), field: \(item.field)") +// +// return .task { +// let r = await env.redisInstanceModel.getClient().hdel(redisKeyModel.key, field: item.field) +// logger.info("do delete hash field, key: \(redisKeyModel.key), field: \(item.field), r:\(r)") +// +// return r > 0 ? .deleteSuccess(index) : .none +// } +// .receive(on: env.mainQueue) +// .eraseToEffect() +// +// case let .deleteSuccess(index): +// state.tableState.datasource.remove(at: index) +// +// return .result { +// .success(.refresh) +// } +// +// case .none: +// return .none +// +// //MARK: -Page action +// case .pageAction(.updateSize): +// return .result { +// .success(.getValue) +// } +// case .pageAction(.nextPage): +// return .result { +// .success(.getValue) +// } +// case .pageAction(.prevPage): +// return .result { +// .success(.getValue) +// } +// case .pageAction: +// return .none +// +// +// //MARK: - table action +// // context menu +// case let .tableAction(.contextMenu(title, index)): +// if title == "Delete" { +// return .result { +// .success(.deleteConfirm(index)) +// } +// } +// +// else if title == "Edit" { +// return .result { +// .success(.edit(index)) +// } +// } +// +// else if title == TableContextMenu.COPY_FIELD.rawValue { +// let item = state.tableState.datasource[index] as! RedisHashEntryModel +// PasteboardHelper.copy(item.field) +// } +// else if title == TableContextMenu.COPY_VALUE.rawValue { +// let item = state.tableState.datasource[index] as! RedisHashEntryModel +// PasteboardHelper.copy(item.value) +// } +// return .none +// +// case let .tableAction(.copy(index)): +// let item = state.tableState.datasource[index] as! RedisHashEntryModel +// PasteboardHelper.copy("Field: \(item.field) \n Value: \(item.value)") +// return .none +// +// case let .tableAction(.double(index)): +// return .result { +// .success(.edit(index)) +// } +// +// case let .tableAction(.delete(index)): +// return .result { +// .success(.deleteConfirm(index)) +// } +// case .tableAction: +// return .none +// case .binding: +// return .none +// } +// } +//).binding().debug() diff --git a/redis-pro/Store/KeyObjectStore.swift b/redis-pro/Store/KeyObjectStore.swift new file mode 100644 index 0000000..28fe65e --- /dev/null +++ b/redis-pro/Store/KeyObjectStore.swift @@ -0,0 +1,77 @@ +// +// ObjectEncodingStore.swift +// redis-pro +// +// Created by chengpan on 2023/7/23. +// + +import Logging +import Foundation +import ComposableArchitecture + +private let logger = Logger(label: "key-object-store") + +struct KeyObjectStore: Reducer { + + struct State: Equatable { + var key: String = "" + var encoding: String = "" + + var redisKeyModel:RedisKeyModel { + get { + let r = RedisKeyModel() + r.key = key + return r + } + set(n) { + key = n.key + } + } + + init() { + logger.info("key object init ...") + } + } + + + enum Action: Equatable { + case initial + case refresh + case setKey(String) + case getEncoding + case setEncoding(String) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + + var body: some Reducer { + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + logger.info("key object initial...") + return .none + + case .refresh: + return .run { send in + await send(.getEncoding) + } + + case let .setKey(key): + state.key = key + return .none + + case .getEncoding: + let key = state.key + return .run { send in + let r = await redisInstanceModel.getClient().objectEncoding(key) + return await send(.setEncoding(r)) + } + + case let .setEncoding(encoding): + state.encoding = encoding + return .none + } + } + } +} diff --git a/redis-pro/Store/KeyStore.swift b/redis-pro/Store/KeyStore.swift index e01c8b4..dd58dcd 100644 --- a/redis-pro/Store/KeyStore.swift +++ b/redis-pro/Store/KeyStore.swift @@ -10,113 +10,109 @@ import ComposableArchitecture private let logger = Logger(label: "key-store") -struct KeyState: Equatable { - @BindableState var type: String = RedisKeyTypeEnum.STRING.rawValue - @BindableState var key: String = "" - var ttl: Int = -1 - - var isNew: Bool = false +struct KeyStore: Reducer { - var redisKeyModel:RedisKeyModel { - get { - let r = RedisKeyModel() - r.type = type - r.key = key - r.isNew = isNew - return r + struct State: Equatable { + @BindingState var type: String = RedisKeyTypeEnum.STRING.rawValue + @BindingState var key: String = "" + var ttl: Int = -1 + + var isNew: Bool = false + + var redisKeyModel:RedisKeyModel { + get { + let r = RedisKeyModel() + r.type = type + r.key = key + r.isNew = isNew + return r + } + set(n) { + type = n.type + key = n.key + isNew = n.isNew + } } - set(n) { - type = n.type - key = n.key - isNew = n.isNew + + init() { + logger.info("key state init ...") } } - - init() { - logger.info("key state init ...") - } -} - -enum KeyAction:BindableAction, Equatable { - case initial - case refresh - case setKey(String) - case getTtl - case submit - case saveTtl - case setTtl(Int) - case setType(String) - case none - case binding(BindingAction) -} -struct KeyEnvironment { - var redisInstanceModel:RedisInstanceModel + enum Action:BindableAction, Equatable { + case initial + case refresh + case setKey(String) + case getTtl + case submit + case saveTtl + case setTtl(Int) + case setType(String) + case none + case binding(BindingAction) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} - -let keyReducer = Reducer.combine( - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - logger.info("key store initial...") - return .none - - case .refresh: - return .result { - .success(.getTtl) - } - - case let .setKey(key): - state.key = key - return .none - case .getTtl: - if state.isNew { - state.ttl = -1 + + var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + logger.info("key store initial...") return .none - } - - let key = state.key - return .task { - let r = await env.redisInstanceModel.getClient().ttl(key) - return .setTtl(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() - case let .setTtl(ttl): - state.ttl = ttl - return .none - - case .submit: - return .result { - .success(.saveTtl) - } - - case .saveTtl: - if state.isNew { + + case .refresh: + return .run { send in + await send(.getTtl) + } + + case let .setKey(key): + state.key = key + return .none + case .getTtl: + if state.isNew { + state.ttl = -1 + return .none + } + + let key = state.key + return .run { send in + let r = await redisInstanceModel.getClient().ttl(key) + await send(.setTtl(r)) + } + case let .setTtl(ttl): + state.ttl = ttl return .none - } - logger.info("update redis key ttl: \(state.redisKeyModel)") - let key = state.key - let ttl = state.ttl - return Effect.task { - let _ = await env.redisInstanceModel.getClient().expire(key, seconds: ttl) + case .submit: + return .run { send in + await send(.saveTtl) + } + + case .saveTtl: + if state.isNew { + return .none + } + logger.info("update redis key ttl: \(state.redisKeyModel)") + + let key = state.key + let ttl = state.ttl + return .run { send in + let _ = await redisInstanceModel.getClient().expire(key, seconds: ttl) + } + + case let .setType(type): + state.type = type + return .none + case .none: + return .none + case .binding: return .none } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .setType(type): - state.type = type - return .none - case .none: - return .none - case .binding: - return .none } } -).binding().debug() +} diff --git a/redis-pro/Store/KeysDelStore.swift b/redis-pro/Store/KeysDelStore.swift new file mode 100644 index 0000000..b6e99fa --- /dev/null +++ b/redis-pro/Store/KeysDelStore.swift @@ -0,0 +1,93 @@ +// +// KeysDeleteStore.swift +// redis-pro +// +// Created by chengpan on 2023/7/30. +// + +import Foundation +import Logging +import ComposableArchitecture + +private let logger = Logger(label: "keys-del-store") + + +struct KeysDelStore: Reducer { + struct State: Equatable { + var tableState: TableStore.State = TableStore.State(columns: [.init(title: "Type", key: "type", width: 120), .init(title: "Key", key: "key", width: 100), .init(title: "Status", key: "statusText", width: 800)], datasource: [], selectIndex: -1) + + init() { + logger.info("keys del state init ...") + } + } + + enum Action: Equatable { + case initial([RedisKeyModel]) + case setValue([KeyDelModel]) + case deleting + case delKey(Int) + case delStatus(Int, Int) + case refresh + case tableAction(TableStore.Action) + } + + @Dependency(\.redisClient) var redisClient:RediStackClient + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + + + var body: some Reducer { + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case let .initial(keys): + + logger.info("key del store initial...") + let keysDel = keys.map { KeyDelModel($0)} + return .run { send in + await send(.setValue(keysDel)) + } + + case let .setValue(keys): + state.tableState.datasource = keys + return .none + + case .deleting: + let keys = state.tableState.datasource as! [KeyDelModel] + + return .run { send in + for (index, _) in keys.enumerated() { + await send(.delKey(index)) + } + } + + case let .delKey(index): + + let keyModel = state.tableState.datasource[index] as! KeyDelModel + + return .run { send in + let r = await redisInstanceModel.getClient().del(keyModel.key) + await send(.delStatus(index, r == 0 ? -1 : r)) + } + + + case let .delStatus(index, status): + let key = state.tableState.datasource[index] as! KeyDelModel + key.status = status + + state.tableState.datasource[index] = key + return .none + + case .refresh: + return .none + + case .tableAction: + return .none + } + } + } + +} + diff --git a/redis-pro/Store/ListValueStore.swift b/redis-pro/Store/ListValueStore.swift index 260e76c..8c5df42 100644 --- a/redis-pro/Store/ListValueStore.swift +++ b/redis-pro/Store/ListValueStore.swift @@ -11,275 +11,502 @@ import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "list-value-store") -struct ListValueState: Equatable { - @BindableState var editModalVisible:Bool = false - @BindableState var editValue:String = "" - // 1: LPUSH, 2: RPUSH - var pushType:Int = 0 - var editIndex:Int = -1 - var isNew:Bool = false - var redisKeyModel:RedisKeyModel? - var pageState: PageState = PageState(showTotal: true) - var tableState: TableState = TableState( - columns: [.init(title: "Index", key: "index", width: 100), .init(title: "Value", key: "value", width: 200)] - , datasource: [], contextMenus: [.COPY, .EDIT, .DELETE] - , selectIndex: -1) + +struct ListValueStore: Reducer { - init() { - logger.info("list value state init ...") - pageState.showTotal = true + struct State: Equatable { + @BindingState var editModalVisible:Bool = false + @BindingState var editValue:String = "" + // 1: LPUSH, 2: RPUSH + var pushType:Int = 0 + var editIndex:Int = -1 + var isNew:Bool = false + var redisKeyModel:RedisKeyModel? + var pageState: PageStore.State = PageStore.State(showTotal: true) + var tableState: TableStore.State = TableStore.State( + columns: [.init(title: "Index", key: "index", width: 100), .init(title: "Value", key: "value", width: 200)] + , datasource: [], contextMenus: [.COPY, .EDIT, .DELETE] + , selectIndex: -1) + } -} -enum ListValueAction:BindableAction, Equatable { - - case initial - case refresh - case search(String) - case getValue - case setValue(Page, [RedisListItemModel]) + enum Action:BindableAction, Equatable { + + case initial + case refresh + case search(String) + case getValue + case setValue(Page, [RedisListItemModel]) + + case addNew(Int) + case edit(Int) + case submit + case submitSuccess(Bool) + + case deleteConfirm(Int) + case deleteKey(Int) + case deleteSuccess(Int) + + case none + case pageAction(PageStore.Action) + case tableAction(TableStore.Action) + case binding(BindingAction) + } - case addNew(Int) - case edit(Int) - case submit - case submitSuccess(Bool) + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + var mainQueue: AnySchedulerOf = .main - case deleteConfirm(Int) - case deleteKey(Int) - case deleteSuccess(Int) - case none - case pageAction(PageAction) - case tableAction(TableAction) - case binding(BindingAction) -} - -struct ListValueEnvironment { - var redisInstanceModel:RedisInstanceModel - var mainQueue: AnySchedulerOf = .main -} - -let listValueReducer = Reducer.combine( - tableReducer.pullback( - state: \.tableState, - action: /ListValueAction.tableAction, - environment: { env in .init() } - ), - pageReducer.pullback( - state: \.pageState, - action: /ListValueAction.pageAction, - environment: { env in .init() } - ), - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - state.pageState.keywords = "" - state.pageState.current = 1 - - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case .refresh: - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case let .search(keywords): - state.pageState.current = 1 - state.pageState.keywords = keywords - return .result { - .success(.getValue) - } - - case .getValue: - guard let redisKeyModel = state.redisKeyModel else { - return .none - } - // 清空 - if redisKeyModel.isNew { - return .result { - .success(.tableAction(.reset)) + var body: some Reducer { + BindingReducer() + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Scope(state: \.pageState, action: /Action.pageAction) { + PageStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + state.pageState.keywords = "" + state.pageState.current = 1 + + logger.info("value store initial...") + return .run { send in + await send(.getValue) } - } - - let key = redisKeyModel.key - let page = state.pageState.page - return .task { - let res = await env.redisInstanceModel.getClient().pageList(key, page: page) - return .setValue(page, res) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .setValue(page, datasource): - state.tableState.datasource = datasource - state.pageState.page = page - return .none - - case let .addNew(type): - state.editValue = "" - state.editIndex = -1 - - state.isNew = true - state.pushType = type - state.editModalVisible = true - return .none - - case let .edit(index): - // 编辑 - state.pushType = 0 - let item = state.tableState.datasource[index] as! RedisListItemModel - state.editIndex = index - state.editValue = item.value - state.isNew = false - state.editModalVisible = true - return .none - - case .submit: - guard let redisKeyModel = state.redisKeyModel else { - return .none - } - - let key = redisKeyModel.key - let editValue = state.editValue - let isNewKey = state.redisKeyModel?.isNew ?? false - let pushType = state.pushType - let item = pushType == 0 ? state.tableState.datasource[state.editIndex] as? RedisListItemModel : nil - return .task { - if pushType == -1 { - let _ = await env.redisInstanceModel.getClient().lpush(key, value: editValue) - } else if pushType == -2 { - let _ = await env.redisInstanceModel.getClient().rpush(key, value: editValue) - } else if pushType == 0 { - let _ = await env.redisInstanceModel.getClient().lset(key, index: item!.index, value: editValue) - logger.info("redis list set success, update list") - } else { - Messages.show("System error!!!") + + case .refresh: + logger.info("value store initial...") + return .run { send in + await send(.getValue) + } + + case let .search(keywords): + state.pageState.current = 1 + state.pageState.keywords = keywords + return .run { send in + await send(.getValue) + } + + case .getValue: + guard let redisKeyModel = state.redisKeyModel else { return .none } + // 清空 + if redisKeyModel.isNew { + return .run { send in + await send(.tableAction(.reset)) + } + } - return .submitSuccess(isNewKey) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - // 提交成功, 刷新列表 - case let .submitSuccess(isNewKey): - if state.isNew { + let key = redisKeyModel.key + let page = state.pageState.page + return .run { send in + let res = await redisInstanceModel.getClient().pageList(key, page: page) + await send(.setValue(page, res)) + } + + case let .setValue(page, datasource): + state.tableState.datasource = datasource + state.pageState.page = page + return .none + + case let .addNew(type): + state.editValue = "" + state.editIndex = -1 + + state.isNew = true + state.pushType = type + state.editModalVisible = true + return .none + + case let .edit(index): + // 编辑 + state.pushType = 0 + let item = state.tableState.datasource[index] as! RedisListItemModel + state.editIndex = index + state.editValue = item.value state.isNew = false - } - let pushType = state.pushType - // 修改,刷新单个值 - if pushType == 0 { - let item = state.tableState.datasource[state.editIndex] as! RedisListItemModel - let newItem = RedisListItemModel(item.index, state.editValue) - state.tableState.datasource[state.editIndex] = newItem + state.editModalVisible = true return .none - } - // 刷新列表 - else { - return .result { - .success(.refresh) + + case .submit: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } + + let key = redisKeyModel.key + let editValue = state.editValue + let isNewKey = state.redisKeyModel?.isNew ?? false + let pushType = state.pushType + let item = pushType == 0 ? state.tableState.datasource[state.editIndex] as? RedisListItemModel : nil + return .run { send in + if pushType == -1 { + let _ = await redisInstanceModel.getClient().lpush(key, value: editValue) + } else if pushType == -2 { + let _ = await redisInstanceModel.getClient().rpush(key, value: editValue) + } else if pushType == 0 { + let _ = await redisInstanceModel.getClient().lset(key, index: item!.index, value: editValue) + logger.info("redis list set success, update list") + } else { + Messages.show("System error!!!") + return + } + + await send(.submitSuccess(isNewKey)) } - } - - case let .deleteConfirm(index): - guard index < state.tableState.datasource.count else { + // 提交成功, 刷新列表 + case let .submitSuccess(isNewKey): + if state.isNew { + state.isNew = false + } + let pushType = state.pushType + // 修改,刷新单个值 + if pushType == 0 { + let item = state.tableState.datasource[state.editIndex] as! RedisListItemModel + let newItem = RedisListItemModel(item.index, state.editValue) + state.tableState.datasource[state.editIndex] = newItem + return .none + } + // 刷新列表 + else { + return .run { send in + await send(.refresh) + } + } + + + case let .deleteConfirm(index): + guard index < state.tableState.datasource.count else { + return .none + } + + let item = state.tableState.datasource[index] as! RedisListItemModel + return .run { send in + Messages.confirm(String(format: NSLocalizedString("LIST_DELETE_CONFIRM_TITLE'%@'", comment: ""), item.value) + , message: String(format: NSLocalizedString("LIST_DELETE_CONFIRM_MESSAGE", comment: ""), item.index, item.value) + , primaryButton: "Delete" + , action: { + await send(.deleteKey(index)) + }) + } + + case let .deleteKey(index): + + let redisKeyModel = state.redisKeyModel! + let item = state.tableState.datasource[index] as! RedisListItemModel + logger.info("delete list item, key: \(redisKeyModel.key), index: \(item.index), value: \(item.value)") + + return .run { send in + let r = await redisInstanceModel.getClient().ldel(redisKeyModel.key, index: item.index, value: item.value) + logger.info("do delete list item, key: \(redisKeyModel.key), value: \(item.value), r:\(r)") + + if r > 0 { + await send(.deleteSuccess(index)) + } + } + + case let .deleteSuccess(index): + state.tableState.datasource.remove(at: index) + + return .run { send in + await send(.refresh) + } + + case .none: return .none - } - - let item = state.tableState.datasource[index] as! RedisListItemModel - return .future { callback in - Messages.confirm(String(format: NSLocalizedString("LIST_DELETE_CONFIRM_TITLE'%@'", comment: ""), item.value) - , message: String(format: NSLocalizedString("LIST_DELETE_CONFIRM_MESSAGE", comment: ""), item.index, item.value) - , primaryButton: "Delete" - , action: { - callback(.success(.deleteKey(index))) - }) - } - - case let .deleteKey(index): - - let redisKeyModel = state.redisKeyModel! - let item = state.tableState.datasource[index] as! RedisListItemModel - logger.info("delete list item, key: \(redisKeyModel.key), index: \(item.index), value: \(item.value)") - - return .task { - let r = await env.redisInstanceModel.getClient().ldel(redisKeyModel.key, index: item.index, value: item.value) - logger.info("do delete list item, key: \(redisKeyModel.key), value: \(item.value), r:\(r)") - return r > 0 ? .deleteSuccess(index) : .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .deleteSuccess(index): - state.tableState.datasource.remove(at: index) - - return .result { - .success(.refresh) - } - - case .none: - return .none - - // MARK: - page action - case .pageAction(.updateSize): - return .result { - .success(.getValue) - } - case .pageAction(.nextPage): - return .result { - .success(.getValue) - } - case .pageAction(.prevPage): - return .result { - .success(.getValue) - } - case .pageAction: - return .none - - // MARK: - table action - // delete key - case let .tableAction(.contextMenu(title, index)): - if title == "Delete" { - return .result { - .success(.deleteConfirm(index)) + // MARK: - page action + case .pageAction(.updateSize): + return .run { send in + await send(.getValue) } - } - - else if title == "Edit" { - return .result { - .success(.edit(index)) + case .pageAction(.nextPage): + return .run { send in + await send(.getValue) } - } - - return .none - - case let .tableAction(.copy(index)): - let item = state.tableState.datasource[index] as! RedisListItemModel - PasteboardHelper.copy(item.value) - return .none - - case let .tableAction(.double(index)): - return .result { - .success(.edit(index)) - } + case .pageAction(.prevPage): + return .run { send in + await send(.getValue) + } + case .pageAction: + return .none - case let .tableAction(.delete(index)): - return .result { - .success(.deleteConfirm(index)) + // MARK: - table action + // delete key + case let .tableAction(.contextMenu(title, index)): + if title == "Delete" { + return .run { send in + await send(.deleteConfirm(index)) + } + } + + else if title == "Edit" { + return .run { send in + await send(.edit(index)) + } + } + + return .none + + case let .tableAction(.copy(index)): + let item = state.tableState.datasource[index] as! RedisListItemModel + PasteboardHelper.copy(item.value) + return .none + + case let .tableAction(.double(index)): + return .run { send in + await send(.edit(index)) + } + + case let .tableAction(.delete(index)): + return .run { send in + await send(.deleteConfirm(index)) + } + case .tableAction: + return .none + case .binding: + return .none } - case .tableAction: - return .none - case .binding: - return .none } } -).binding().debug() +} + + +//struct ListValueEnvironment { +//} +// +//let listValueReducer = Reducer.combine( +// +// AnyReducer { environment in +// PageStore() +// } +// .pullback( +// state: \.pageState, +// action: /ListValueAction.pageAction, +// environment: { $0 } +// ), +// AnyReducer { environment in +// TableStore() +// } +// .pullback( +// state: \.tableState, +// action: /ListValueAction.tableAction, +// environment: { $0 } +// ), +// Reducer { +// state, action, env in +// switch action { +// // 初始化已设置的值 +// case .initial: +// state.pageState.keywords = "" +// state.pageState.current = 1 +// +// logger.info("value store initial...") +// return .run { send in +// .success(.getValue) +// } +// +// case .refresh: +// logger.info("value store initial...") +// return .run { send in +// .success(.getValue) +// } +// +// case let .search(keywords): +// state.pageState.current = 1 +// state.pageState.keywords = keywords +// return .run { send in +// .success(.getValue) +// } +// +// case .getValue: +// guard let redisKeyModel = state.redisKeyModel else { +// return .none +// } +// // 清空 +// if redisKeyModel.isNew { +// return .run { send in +// .success(.tableAction(.reset)) +// } +// } +// +// let key = redisKeyModel.key +// let page = state.pageState.page +// return .task { +// let res = await env.redisInstanceModel.getClient().pageList(key, page: page) +// return .setValue(page, res) +// } +// .receive(on: env.mainQueue) +// .eraseToEffect() +// +// case let .setValue(page, datasource): +// state.tableState.datasource = datasource +// state.pageState.page = page +// return .none +// +// case let .addNew(type): +// state.editValue = "" +// state.editIndex = -1 +// +// state.isNew = true +// state.pushType = type +// state.editModalVisible = true +// return .none +// +// case let .edit(index): +// // 编辑 +// state.pushType = 0 +// let item = state.tableState.datasource[index] as! RedisListItemModel +// state.editIndex = index +// state.editValue = item.value +// state.isNew = false +// state.editModalVisible = true +// return .none +// +// case .submit: +// guard let redisKeyModel = state.redisKeyModel else { +// return .none +// } +// +// let key = redisKeyModel.key +// let editValue = state.editValue +// let isNewKey = state.redisKeyModel?.isNew ?? false +// let pushType = state.pushType +// let item = pushType == 0 ? state.tableState.datasource[state.editIndex] as? RedisListItemModel : nil +// return .task { +// if pushType == -1 { +// let _ = await env.redisInstanceModel.getClient().lpush(key, value: editValue) +// } else if pushType == -2 { +// let _ = await env.redisInstanceModel.getClient().rpush(key, value: editValue) +// } else if pushType == 0 { +// let _ = await env.redisInstanceModel.getClient().lset(key, index: item!.index, value: editValue) +// logger.info("redis list set success, update list") +// } else { +// Messages.show("System error!!!") +// return .none +// } +// +// return .submitSuccess(isNewKey) +// } +// .receive(on: env.mainQueue) +// .eraseToEffect() +// +// // 提交成功, 刷新列表 +// case let .submitSuccess(isNewKey): +// if state.isNew { +// state.isNew = false +// } +// let pushType = state.pushType +// // 修改,刷新单个值 +// if pushType == 0 { +// let item = state.tableState.datasource[state.editIndex] as! RedisListItemModel +// let newItem = RedisListItemModel(item.index, state.editValue) +// state.tableState.datasource[state.editIndex] = newItem +// return .none +// } +// // 刷新列表 +// else { +// return .run { send in +// .success(.refresh) +// } +// } +// +// +// case let .deleteConfirm(index): +// guard index < state.tableState.datasource.count else { +// return .none +// } +// +// let item = state.tableState.datasource[index] as! RedisListItemModel +// return .future { callback in +// Messages.confirm(String(format: NSLocalizedString("LIST_DELETE_CONFIRM_TITLE'%@'", comment: ""), item.value) +// , message: String(format: NSLocalizedString("LIST_DELETE_CONFIRM_MESSAGE", comment: ""), item.index, item.value) +// , primaryButton: "Delete" +// , action: { +// callback(.success(.deleteKey(index))) +// }) +// } +// +// case let .deleteKey(index): +// +// let redisKeyModel = state.redisKeyModel! +// let item = state.tableState.datasource[index] as! RedisListItemModel +// logger.info("delete list item, key: \(redisKeyModel.key), index: \(item.index), value: \(item.value)") +// +// return .task { +// let r = await env.redisInstanceModel.getClient().ldel(redisKeyModel.key, index: item.index, value: item.value) +// logger.info("do delete list item, key: \(redisKeyModel.key), value: \(item.value), r:\(r)") +// +// return r > 0 ? .deleteSuccess(index) : .none +// } +// .receive(on: env.mainQueue) +// .eraseToEffect() +// +// case let .deleteSuccess(index): +// state.tableState.datasource.remove(at: index) +// +// return .run { send in +// .success(.refresh) +// } +// +// case .none: +// return .none +// +// // MARK: - page action +// case .pageAction(.updateSize): +// return .run { send in +// .success(.getValue) +// } +// case .pageAction(.nextPage): +// return .run { send in +// .success(.getValue) +// } +// case .pageAction(.prevPage): +// return .run { send in +// .success(.getValue) +// } +// case .pageAction: +// return .none +// +// // MARK: - table action +// // delete key +// case let .tableAction(.contextMenu(title, index)): +// if title == "Delete" { +// return .run { send in +// .success(.deleteConfirm(index)) +// } +// } +// +// else if title == "Edit" { +// return .run { send in +// .success(.edit(index)) +// } +// } +// +// return .none +// +// case let .tableAction(.copy(index)): +// let item = state.tableState.datasource[index] as! RedisListItemModel +// PasteboardHelper.copy(item.value) +// return .none +// +// case let .tableAction(.double(index)): +// return .run { send in +// .success(.edit(index)) +// } +// +// case let .tableAction(.delete(index)): +// return .run { send in +// .success(.deleteConfirm(index)) +// } +// case .tableAction: +// return .none +// case .binding: +// return .none +// } +// } +//).binding().debug() diff --git a/redis-pro/Store/LoadingStore.swift b/redis-pro/Store/LoadingStore.swift index 7fd5e53..6b71694 100644 --- a/redis-pro/Store/LoadingStore.swift +++ b/redis-pro/Store/LoadingStore.swift @@ -12,34 +12,32 @@ import ComposableArchitecture private let logger = Logger(label: "loading-store") -struct LoadingState: Equatable { - var loading: Bool = false - - init() { - logger.info("loading state init ...") +struct LoadingStore: Reducer { + struct State: Equatable { + var loading: Bool = false + + init() { + logger.info("loading state init ...") + } } -} - -enum LoadingAction:Equatable { - case show - case hide -} - -struct LoadingEnvironment { - // var redisInstanceModel:RedisInstanceModel = RedisInstanceModel(redisModel: RedisModel()) -} - -let loadingReducer = Reducer.combine( - Reducer { - state, action, _ in - switch action { - case .show: - state.loading = true - return .none - case .hide: - state.loading = false - return .none + enum Action:Equatable { + case show + case hide + } + + var body: some Reducer { + + Reduce { state, action in + switch action { + case .show: + state.loading = true + return .none + case .hide: + state.loading = false + return .none + } } - }.debug() -) + } + +} diff --git a/redis-pro/Store/LoginStore.swift b/redis-pro/Store/LoginStore.swift index 3e6b41b..874c357 100644 --- a/redis-pro/Store/LoginStore.swift +++ b/redis-pro/Store/LoginStore.swift @@ -9,110 +9,112 @@ import Logging import Foundation import ComposableArchitecture -struct LoginState: Equatable { - var id: String = "" - @BindableState var name:String = "" - @BindableState var host: String = "127.0.0.1" - @BindableState var port: Int = 6379 - @BindableState var database: Int = 0 - @BindableState var username: String = "" - @BindableState var password: String = "" - @BindableState var connectionType:String = "tcp" - - // ssh - @BindableState var sshHost:String = "" - @BindableState var sshPort:Int = 22 - @BindableState var sshUser:String = "" - @BindableState var sshPass:String = "" - - var pingR: String = "" - var loading: Bool = false - - var height:CGFloat { - connectionType == RedisConnectionTypeEnum.SSH.rawValue ? 500 : 380 - } +private let logger = Logger(label: "login-store") + +struct LoginStore: Reducer { - // 方便外部使用 - var redisModel:RedisModel { - get { - let redisModel = RedisModel(name: name) - redisModel.id = id - redisModel.host = host - redisModel.port = port - redisModel.database = database - redisModel.username = username - redisModel.password = password - redisModel.connectionType = connectionType - redisModel.sshHost = sshHost - redisModel.sshPort = sshPort - redisModel.sshUser = sshUser - redisModel.sshPass = sshPass - - return redisModel + struct State: Equatable { + var id: String = "" + @BindingState var name:String = "" + @BindingState var host: String = "127.0.0.1" + @BindingState var port: Int = 6379 + @BindingState var database: Int = 0 + @BindingState var username: String = "" + @BindingState var password: String = "" + @BindingState var connectionType:String = "tcp" + + // ssh + @BindingState var sshHost:String = "" + @BindingState var sshPort:Int = 22 + @BindingState var sshUser:String = "" + @BindingState var sshPass:String = "" + + var pingR: String = "" + var loading: Bool = false + + var height:CGFloat { + connectionType == RedisConnectionTypeEnum.SSH.rawValue ? 500 : 380 } - set(n) { - self.id = n.id - self.name = n.name - self.host = n.host - self.port = n.port - self.database = n.database - self.username = n.username - self.password = n.password - self.connectionType = n.connectionType - self.sshHost = n.sshHost - self.sshPort = n.sshPort - self.sshUser = n.sshUser - self.sshPass = n.sshPass + + // 方便外部使用 + var redisModel:RedisModel { + get { + let redisModel = RedisModel(name: name) + redisModel.id = id + redisModel.host = host + redisModel.port = port + redisModel.database = database + redisModel.username = username + redisModel.password = password + redisModel.connectionType = connectionType + redisModel.sshHost = sshHost + redisModel.sshPort = sshPort + redisModel.sshUser = sshUser + redisModel.sshPass = sshPass + + return redisModel + } + set(n) { + self.id = n.id + self.name = n.name + self.host = n.host + self.port = n.port + self.database = n.database + self.username = n.username + self.password = n.password + self.connectionType = n.connectionType + self.sshHost = n.sshHost + self.sshPort = n.sshPort + self.sshUser = n.sshUser + self.sshPass = n.sshPass + } } } -} -enum LoginAction:BindableAction,Equatable { - case add - case save - case testConnect - case connect - case setPingR(Bool) - case none - case binding(BindingAction) -} - -struct LoginEnvironment { - var redisInstanceModel:RedisInstanceModel + enum Action:BindableAction,Equatable { + case add + case save + case testConnect + case connect + case setPingR(Bool) + case none + case binding(BindingAction) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} - -private let logger = Logger(label: "login-store") -let loginReducer = Reducer { - state, action, env in - switch action { - case .add: - state.id = UUID().uuidString - return .result { - .success(.save) - } - case .save: - return .none - case .testConnect: - logger.info("test connect to redis server, name: \(state.name), host: \(state.host)") - state.loading = true - let redis = state.redisModel - - return .task { - let r = await env.redisInstanceModel.testConnect(redis) - return .setPingR(r) + + var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case .add: + state.id = UUID().uuidString + return .run { send in + await send(.save) + } + case .save: + return .none + case .testConnect: + logger.info("test connect to redis server, name: \(state.name), host: \(state.host)") + state.loading = true + let redis = state.redisModel + + return .run { send in + let r = await redisInstanceModel.testConnect(redis) + await send(.setPingR(r)) + } + case let .setPingR(r): + state.pingR = r ? "Connect successed!" : "Connect fail! " + state.loading = false + return .none + case .connect: + return .none + case .none: + return .none + case .binding: + return .none + } } - .receive(on: env.mainQueue) - .eraseToEffect() - case let .setPingR(r): - state.pingR = r ? "Connect successed!" : "Connect fail! " - state.loading = false - return .none - case .connect: - return .none - case .none: - return .none - case .binding: - return .none } -}.binding().debug() +} diff --git a/redis-pro/Store/LuaStore.swift b/redis-pro/Store/LuaStore.swift index 7a8a5d5..e4da93e 100644 --- a/redis-pro/Store/LuaStore.swift +++ b/redis-pro/Store/LuaStore.swift @@ -9,79 +9,72 @@ import Logging import Foundation import ComposableArchitecture -struct LuaState: Equatable { - @BindableState var lua:String = "\"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\" 2 key1 key2 arg1 arg2" - @BindableState var evalResult:String = "" - var luaSHA: String = "-" -} - -enum LuaAction:BindableAction,Equatable { - case eval - case scriptKill - case scriptFlush - case scriptLoad - case setLuaResult(String) - case setLuaSHA(String) - case none - case binding(BindingAction) -} -struct LuaEnvironment { - var redisInstanceModel:RedisInstanceModel -} +struct LuaStore: Reducer { + + struct State: Equatable { + @BindingState var lua:String = "\"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\" 2 key1 key2 arg1 arg2" + @BindingState var evalResult:String = "" + var luaSHA: String = "-" + } -private let logger = Logger(label: "lua-store") -let luaReducer = Reducer> { - state, action, env in - switch action { - - case .eval: - let lua = state.lua - return .task { - let r = await env.redisInstanceModel.getClient().eval(lua) - return .setLuaResult(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .scriptKill: - - return .task { - let _ = await env.redisInstanceModel.getClient().scriptKill() - return .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .scriptFlush: - - return .task { - let _ = await env.redisInstanceModel.getClient().scriptFlush() - return .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .scriptLoad: - - let lua = state.lua - return .task { - return .setLuaSHA("") + enum Action:BindableAction,Equatable { + case eval + case scriptKill + case scriptFlush + case scriptLoad + case setLuaResult(String) + case setLuaSHA(String) + case none + case binding(BindingAction) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + let mainQueue: AnySchedulerOf = .main + + var body: some Reducer { + Reduce { state, action in + switch action { + + case .eval: + let lua = state.lua + return .run { send in + let r = await redisInstanceModel.getClient().eval(lua) + await send(.setLuaResult(r)) + } + + case .scriptKill: + + return .run { send in + let _ = await redisInstanceModel.getClient().scriptKill() + } + + case .scriptFlush: + + return .run { send in + let _ = await redisInstanceModel.getClient().scriptFlush() + } + + case .scriptLoad: + + let lua = state.lua + return .run { send in + await send(.setLuaSHA("")) + } + + case let .setLuaResult(r): + state.evalResult = r + return .none + + case let .setLuaSHA(r): + state.luaSHA = r + return .none + + case .none: + return .none + case .binding: + return .none + } } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .setLuaResult(r): - state.evalResult = r - return .none - - case let .setLuaSHA(r): - state.luaSHA = r - return .none - - case .none: - return .none - case .binding: - return .none } -}.binding().debug() +} diff --git a/redis-pro/Store/MessageStore.swift b/redis-pro/Store/MessageStore.swift deleted file mode 100644 index 8a5e8f3..0000000 --- a/redis-pro/Store/MessageStore.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// MessageStore.swift -// redis-pro -// -// Created by chengpan on 2022/5/29. -// - -import Logging -import Foundation -import ComposableArchitecture - -private let logger = Logger(label: "alert-store") - -struct MessageState: Equatable { - - var alert: AlertState? - var action: (() -> Void)? - - init() { - logger.info("message state init ...") - } - - static func == (lhs: MessageState, rhs: MessageState) -> Bool { - lhs.alert == rhs.alert - } -} - -enum MessageAction: Equatable { - static func == (lhs: MessageAction, rhs: MessageAction) -> Bool { - lhs.value == rhs.value - } - - var value: String? { - return String(describing: self).components(separatedBy: "(").first - } - - case alert - case error(String) - case confirm(String, String, String, (() -> Void)) - case doAction - case clearAlert - case ok - case cancel - case none -} - -struct MessageEnvironment { -} - - -let messageReducer = Reducer.combine( - Reducer { - state, action, _ in - switch action { - case .alert: - state.alert = .init( - title: TextState("Delete"), - message: TextState("Are you sure you want to delete this? It cannot be undone."), - primaryButton: .default(TextState("Confirm"), action: .send(.none)), - secondaryButton: .cancel(TextState("Cancel")) - ) - return .none - case let .error(message): - state.alert = .init( - title: TextState("Error!"), - message: TextState(message), - dismissButton: .default(TextState("Ok")) - ) - return .none - case let .confirm(title, message, primaryButton, action): - state.action = action - state.alert = .init( - title: TextState(title), - message: TextState(message), - primaryButton: .default(TextState(primaryButton), action: .send(.doAction)), - secondaryButton: .cancel(TextState("Cancel")) - ) - return .none - case .doAction: - state.action?() - state.action = nil - return .none - case .clearAlert: - state.alert = nil - return .none - case .ok: - print("ok") - return .none - case .cancel: - print("cancel") - return .none - case .none: - return .none - } - }.debug() -) diff --git a/redis-pro/Store/PageStore.swift b/redis-pro/Store/PageStore.swift index 7b08c8e..00f2137 100644 --- a/redis-pro/Store/PageStore.swift +++ b/redis-pro/Store/PageStore.swift @@ -11,90 +11,96 @@ import ComposableArchitecture private let logger = Logger(label: "page-store") -struct PageState: Equatable { - var showTotal: Bool = false - var current:Int = 1 - var size:Int = 50 - var total:Int = 0 - var keywords:String = "" - - var totalPage:Int { - get { - return total < 1 ? 1 : (total % size == 0 ? total / size : total / size + 1) +struct PageStore: Reducer { + struct State: Equatable { + var showTotal: Bool = false + var current:Int = 1 + @BindingState var size:Int = 50 + var total:Int = 0 + var keywords:String = "" + var fastPage = true + var fastPageMax = 99 + + /// 总页数 + var totalPage:Int { + get { + return total < 1 ? 1 : (total % size == 0 ? total / size : total / size + 1) + } } - } - var hasPrev:Bool { - totalPage > 1 && current > 1 - } - var hasNext:Bool { - totalPage > 1 && current < totalPage - } - - var page:Page { - get { - let page = Page() - page.current = current - page.size = size - page.total = total - page.keywords = keywords - - return page + + var totalPageText: String { + get { + if fastPage { + return totalPage > fastPageMax ? "\(fastPageMax)+" : "\(totalPage)" + } + return "\(totalPage)" + } } - set(page) { - current = page.current - size = page.size - total = page.total + + + var hasPrev:Bool { + totalPage > 1 && current > 1 + } + var hasNext:Bool { + totalPage > 1 && current < totalPage + } + + var page:Page { + get { + let page = Page() + page.current = current + page.size = size + page.total = total + page.keywords = keywords + + return page + } + set(page) { + current = page.current + size = page.size + total = page.total + } } } - - init() { - logger.info("page state init ...") - } - init(showTotal:Bool) { - logger.info("page state init ...") - self.showTotal = showTotal + + + enum Action: BindableAction, Equatable { + case binding(BindingAction) + case initial + case updateSize(Int) + case nextPage + case prevPage + case none } -} - - -enum PageAction:BindableAction, Equatable { - case initial - case updateSize(Int) - case nextPage - case prevPage - case none - case binding(BindingAction) -} - -struct PageEnvironment { -} - -let pageReducer = Reducer.combine( - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - logger.info("page store initial...") - return .none - - case let .updateSize(size): - state.current = 1 - state.size = size - return .none - case .nextPage: - state.current = state.current + 1 - return .none - case .prevPage: - state.current -= 1 - if state.current <= 1 { + + + var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + logger.info("page store initial...") + return .none + + case let .updateSize(size): state.current = 1 + state.size = size + return .none + case .nextPage: + state.current = state.current + 1 + return .none + case .prevPage: + state.current -= 1 + if state.current <= 1 { + state.current = 1 + } + return .none + case .none: + return .none + case .binding: + return .none } - return .none - case .none: - return .none - case .binding: - return .none } } -).binding().debug() +} diff --git a/redis-pro/Store/RedisConfigStore.swift b/redis-pro/Store/RedisConfigStore.swift index 788f0e8..82c0375 100644 --- a/redis-pro/Store/RedisConfigStore.swift +++ b/redis-pro/Store/RedisConfigStore.swift @@ -12,133 +12,128 @@ import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "redis-config-store") -struct RedisConfigState: Equatable { - - @BindableState var editModalVisible:Bool = false - @BindableState var editValue:String = "" - var pattern:String = "" - var editKey:String = "" - var editIndex = 0 - - var tableState: TableState = TableState( - columns: [.init(title: "Key", key: "key", width: 200), .init(title: "Value", key: "value", width: 800)] - , datasource: [] - , contextMenus: [.EDIT] - , selectIndex: -1) - - init() { - logger.info("redis config state init ...") - } -} - -enum RedisConfigAction:BindableAction, Equatable { - case initial - case getValue - case setValue([RedisConfigItemModel]) - case refresh - case search(String) - case rewrite - case edit(Int) - case submit - case tableAction(TableAction) - case binding(BindingAction) -} - -struct RedisConfigEnvironment { - var redisInstanceModel:RedisInstanceModel -} -let redisConfigReducer = Reducer>.combine( - tableReducer.pullback( - state: \.tableState, - action: /RedisConfigAction.tableAction, - environment: { _ in .init() } - ), - Reducer> { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: +struct RedisConfigStore: Reducer { + struct State: Equatable { - logger.info("redis config store initial...") - return .result { - .success(.getValue) - } + @BindingState var editModalVisible:Bool = false + @BindingState var editValue:String = "" + var pattern:String = "" + var editKey:String = "" + var editIndex = 0 - case .getValue: - let pattern = state.pattern - return .task { - let r = await env.redisInstanceModel.getClient().getConfigList(pattern) - return .setValue(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() + var tableState: TableStore.State = TableStore.State( + columns: [.init(title: "Key", key: "key", width: 200), .init(title: "Value", key: "value", width: 800)] + , datasource: [] + , contextMenus: [.EDIT] + , selectIndex: -1) - case let .setValue(redisConfigs): - state.tableState.datasource = redisConfigs - - return .none - + init() { + logger.info("redis config state init ...") + } + } - case .refresh: - return .result { - .success(.getValue) - } - - case let .search(keywords): - state.pattern = keywords - return .result { - .success(.getValue) - } - - case .rewrite: - return .task { - let _ = await env.redisInstanceModel.getClient().configRewrite() - return .refresh - } - .receive(on: env.mainQueue) - .eraseToEffect() - case let .edit(index): - - state.editIndex = index - guard let item:RedisConfigItemModel = state.tableState.datasource[index] as? RedisConfigItemModel else { - return .none - } - state.editKey = item.key - state.editValue = item.value - state.editModalVisible = true - - return .none - - case .submit: - let key = state.editKey - let value = state.editValue + enum Action:BindableAction, Equatable { + case initial + case getValue + case setValue([RedisConfigItemModel]) + case refresh + case search(String) + case rewrite + case edit(Int) + case submit + case tableAction(TableStore.Action) + case binding(BindingAction) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + let mainQueue: AnySchedulerOf = .main + + var body: some Reducer { + BindingReducer() + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: - return .task { - let _ = await env.redisInstanceModel.getClient().setConfig(key: key, value: value) - return .refresh - } - .receive(on: env.mainQueue) - .eraseToEffect() + logger.info("redis config store initial...") + return .run { send in + await send(.getValue) + } - // table action - case let .tableAction(.contextMenu(title, index)): - if title == "Edit" { - return .result { - .success(.edit(index)) + case .getValue: + let pattern = state.pattern + return .run { send in + let r = await redisInstanceModel.getClient().getConfigList(pattern) + await send(.setValue(r)) } - } - return .none + case let .setValue(redisConfigs): + state.tableState.datasource = redisConfigs + + return .none + + + case .refresh: + return .run { send in + await send(.getValue) + } + + case let .search(keywords): + state.pattern = keywords + return .run { send in + await send(.getValue) + } - case let .tableAction(.double(index)): - return .result { - .success(.edit(index)) + case .rewrite: + return .run { send in + let _ = await redisInstanceModel.getClient().configRewrite() + await send(.refresh) + } + case let .edit(index): + + state.editIndex = index + guard let item:RedisConfigItemModel = state.tableState.datasource[index] as? RedisConfigItemModel else { + return .none + } + state.editKey = item.key + state.editValue = item.value + state.editModalVisible = true + + return .none + + case .submit: + let key = state.editKey + let value = state.editValue + + return .run { send in + let _ = await redisInstanceModel.getClient().setConfig(key: key, value: value) + await send(.refresh) + } + + // table action + case let .tableAction(.contextMenu(title, index)): + if title == "Edit" { + return .run { send in + await send(.edit(index)) + } + } + + return .none + + case let .tableAction(.double(index)): + return .run { send in + await send(.edit(index)) + } + case .tableAction: + return .none + case .binding: + return .none } - case .tableAction: - return .none - case .binding: - return .none } } -).binding().debug() + +} diff --git a/redis-pro/Store/RedisInfoStore.swift b/redis-pro/Store/RedisInfoStore.swift index 56b50aa..d594e1d 100644 --- a/redis-pro/Store/RedisInfoStore.swift +++ b/redis-pro/Store/RedisInfoStore.swift @@ -7,101 +7,98 @@ import Logging import Foundation -import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "redis-info-store") -struct RedisInfoState: Equatable { - var section:String = "Server" - var tableState: TableState = TableState(columns: [.init(title: "Key", key: "key", width: 120), .init(title: "Value", key: "value", width: 100), .init(title: "Desc", key: "desc", width: 800)] - , datasource: [], selectIndex: -1) - var redisInfoModels:[RedisInfoModel] = [RedisInfoModel(section: "Server")] - - init() { - logger.info("redis info state init ...") - } -} - -enum RedisInfoAction: Equatable { - case initial - case getValue - case setValue([RedisInfoModel]) - case setTab(String) - case refresh - case resetState - case tableAction(TableAction) -} -struct RedisInfoEnvironment { - var redisInstanceModel:RedisInstanceModel -} -let redisInfoReducer = Reducer>.combine( - tableReducer.pullback( - state: \.tableState, - action: /RedisInfoAction.tableAction, - environment: { _ in .init() } - ), - Reducer> { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - - logger.info("redis info store initial...") - return .result { - .success(.getValue) - } +struct RedisInfoStore: Reducer { + struct State: Equatable { + var section:String = "Server" + var tableState: TableStore.State = TableStore.State(columns: [.init(title: "Key", key: "key", width: 120), .init(title: "Value", key: "value", width: 100), .init(title: "Desc", key: "desc", width: 800)] + , datasource: [], selectIndex: -1) + var redisInfoModels:[RedisInfoModel] = [RedisInfoModel(section: "Server")] - case .getValue: - return .task { - let r = await env.redisInstanceModel.getClient().info() - return .setValue(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .setValue(redisInfos): - let section = redisInfos.count > 0 ? redisInfos[0].section : "" - state.redisInfoModels = redisInfos - - return .result { - .success(.setTab(section)) - } - - case let .setTab(tab): - state.section = tab - state.tableState.selectIndex = -1 - let redisInfoModels = state.redisInfoModels + init() { + logger.info("redis info state init ...") + } + } + + enum Action: Equatable { + case initial + case getValue + case setValue([RedisInfoModel]) + case setTab(String) + case refresh + case resetState + case tableAction(TableStore.Action) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + let mainQueue: AnySchedulerOf = .main + + + var body: some Reducer { + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: - guard redisInfoModels.count > 0 else { - return .result { - .success(.tableAction(.reset)) + logger.info("redis info store initial...") + return .run { send in + await send(.getValue) } - } - let redisInfoModel = redisInfoModels.first(where: { - $0.section == tab - }) - state.tableState.datasource = redisInfoModel?.infos ?? [] + case .getValue: + return .run { send in + let r = await redisInstanceModel.getClient().info() + return await send(.setValue(r)) + } - return .none - - case .refresh: - return .result { - .success(.getValue) - } + case let .setValue(redisInfos): + let section = redisInfos.count > 0 ? redisInfos[0].section : "" + state.redisInfoModels = redisInfos + + return .run { send in + await send(.setTab(section)) + } + + case let .setTab(tab): + state.section = tab + state.tableState.selectIndex = -1 + let redisInfoModels = state.redisInfoModels + + guard redisInfoModels.count > 0 else { + return .run { send in + await send(.tableAction(.reset)) + } + } + + let redisInfoModel = redisInfoModels.first(where: { + $0.section == tab + }) + state.tableState.datasource = redisInfoModel?.infos ?? [] + + return .none - case .resetState: - return .task { - let _ = await env.redisInstanceModel.getClient().resetState() - return .refresh + case .refresh: + return .run { send in + await send(.getValue) + } + + case .resetState: + return .run { send in + let _ = await redisInstanceModel.getClient().resetState() + return await send(.refresh) + } + + case .tableAction: + return .none } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .tableAction: - return .none } } -).debug() + +} diff --git a/redis-pro/Store/RedisKeysStore.swift b/redis-pro/Store/RedisKeysStore.swift index 858d116..c653cfc 100644 --- a/redis-pro/Store/RedisKeysStore.swift +++ b/redis-pro/Store/RedisKeysStore.swift @@ -12,381 +12,356 @@ import ComposableArchitecture private let logger = Logger(label: "redisKeys-store") -struct RedisKeysState: Equatable { - var database:Int = 0 - var dbsize:Int = 0 - var keywords:String = "" - var searchGroup = 0 - - var mainViewType: MainViewTypeEnum = .EDITOR - var tableState: TableState = TableState( - columns: [.init(type: .KEY_TYPE,title: "Type", key: "type", width: 40), .init(title: "Key", key: "key", width: 50)] - , datasource: [], contextMenus: [.COPY, .RENAME, .DELETE] - , selectIndex: -1) - var redisSystemState:RedisSystemState = RedisSystemState() - var valueState: ValueState = ValueState() - var databaseState: DatabaseState = DatabaseState() - var pageState: PageState = PageState() - var renameState: RenameState = RenameState() + +struct RedisKeysStore: Reducer { - init() { - logger.info("redisKeys state init ...") + struct State: Equatable { + var database:Int = 0 + var dbsize:Int = 0 + var keywords:String = "" + var searchGroup = 0 + + var mainViewType: MainViewTypeEnum = .EDITOR + var tableState: TableStore.State = TableStore.State( + columns: [.init(type: .KEY_TYPE,title: "Type", key: "type", width: 40), .init(title: "Key", key: "key", width: 50)] + , datasource: [], contextMenus: [.COPY, .RENAME, .DELETE] + , selectIndex: -1) + var redisSystemState:RedisSystemStore.State = RedisSystemStore.State() + var valueState: ValueStore.State = ValueStore.State() + var databaseState: DatabaseStore.State = DatabaseStore.State() + var pageState: PageStore.State = PageStore.State() + var renameState: RenameStore.State = RenameStore.State() + + init() { + logger.info("redisKeys state init ...") + } } -} -enum RedisKeysAction:Equatable { - case initial - case dbsize - case refresh - case refreshCount - case search(String) - case getKeys - // 1. cursor, 2. searchGroup 查询批次 - case countKeys(Int, Int) - case setKeys(Page, [RedisKeyModel]) - // 1. cursor, 2. count, 3. searchGroup 查询批次 - case setCount(Int, Int, Int) - case setMainViewType(MainViewTypeEnum) - case addNew - - case deleteConfirm(Int) - case deleteKey(Int) - case deleteSuccess(Int) - - case onKeyChange(Int) - case setDBSize(Int) - case flushDBConfirm - case flushDB - case tableAction(TableAction) - case redisSystemAction(RedisSystemAction) - case valueAction(ValueAction) - case databaseAction(DatabaseAction) - case pageAction(PageAction) - case renameAction(RenameAction) - case none -} + enum Action:Equatable { + case initial + case dbsize + case refresh + case refreshCount + case search(String) + case getKeys + // 1. cursor, 2. searchGroup 查询批次 + case countKeys(Int, Int) + case setKeys(Page, [RedisKeyModel]) + // 1. cursor, 2. count, 3. searchGroup 查询批次 + case setCount(Int, Int, Int) + case setMainViewType(MainViewTypeEnum) + case addNew + + case deleteConfirm(Int) + case deleteKey(Int) + case deleteSuccess(Int) + + case onKeyChange(Int) + case setDBSize(Int) + case flushDBConfirm + case flushDB + case tableAction(TableStore.Action) + case redisSystemAction(RedisSystemStore.Action) + case valueAction(ValueStore.Action) + case databaseAction(DatabaseStore.Action) + case pageAction(PageStore.Action) + case renameAction(RenameStore.Action) + case none + } -struct RedisKeysEnvironment { - var redisInstanceModel:RedisInstanceModel + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + @Dependency(\.redisClient) var redisClient:RediStackClient var mainQueue: AnySchedulerOf = .main -} - -let redisKeysReducer = Reducer.combine( - tableReducer.pullback( - state: \.tableState, - action: /RedisKeysAction.tableAction, - environment: { env in .init() } - ), - redisSystemReducer.pullback( - state: \.redisSystemState, - action: /RedisKeysAction.redisSystemAction, - environment: { env in .live(environment: RedisSystemEnvironment(redisInstanceModel: env.redisInstanceModel)) } - ), - valueReducer.pullback( - state: \.valueState, - action: /RedisKeysAction.valueAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - databaseReducer.pullback( - state: \.databaseState, - action: /RedisKeysAction.databaseAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - pageReducer.pullback( - state: \.pageState, - action: /RedisKeysAction.pageAction, - environment: { _ in .init() } - ), - renameReducer.pullback( - state: \.renameState, - action: /RedisKeysAction.renameAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - logger.info("redis keys store initial...") - - return .merge( - .result { - .success(.search("")) + + var body: some Reducer { + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Scope(state: \.pageState, action: /Action.pageAction) { + PageStore() + } + Scope(state: \.redisSystemState, action: /Action.redisSystemAction) { + RedisSystemStore() + } + Scope(state: \.valueState, action: /Action.valueAction) { + ValueStore() + } + Scope(state: \.databaseState, action: /Action.databaseAction) { + DatabaseStore() + } + Scope(state: \.renameState, action: /Action.renameAction) { + RenameStore() + } + + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + logger.info("redis keys store initial...") + + return .merge( + .send(.search("")), + .send(.dbsize) + ) + + // 只刷新数量,比如删除时不刷新列表数据, 只刷新数量 + case .refreshCount: + return .run { send in + await send(.dbsize) } - , .result { - .success(.dbsize) + + // 全部刷新 + case .refresh: + return .send(.initial) + + // 搜索 + case let .search(keywords): + state.searchGroup += 1 + let searchGroup = state.searchGroup + + state.pageState.current = 1 + state.pageState.total = 0 + state.pageState.keywords = keywords + + return .merge( + .send(.getKeys), + .send(.countKeys(0, searchGroup)) + ) + + // dbsize + case .dbsize: + return .run { send in + let r = await redisInstanceModel.getClient().dbsize() + await send(.setDBSize(r)) } - ) - - // 只刷新数量,比如删除时不刷新列表数据, 只刷新数量 - case .refreshCount: - return .result { - .success(.dbsize) - } - - // 全部刷新 - case .refresh: - return .result { - .success(.initial) - } - - // 搜索 - case let .search(keywords): - state.searchGroup += 1 - let searchGroup = state.searchGroup - - state.pageState.current = 1 - state.pageState.total = 0 - state.pageState.keywords = keywords - - return .merge( - .result { - .success(.getKeys) + + // 分页查询 key + case .getKeys: + let page = state.pageState.page + return .run { send in + let keysPage = await redisInstanceModel.getClient().pageKeys(page) + + await send(.setKeys(page, keysPage)) } - , .result { - .success(.countKeys(0, searchGroup)) + + // 异步计算key数量, 通过setCount 进行递归调用,直接cursor 返回0 + // 后续可能增加开关,是否查询数量 + case let .countKeys(cursor, searchGroup): + let page = state.pageState.page + if searchGroup < state.searchGroup { + logger.info("有新查询批次, 当前count终止") + return .none } - ) - - // dbsize - case .dbsize: - return .task { - let r = await env.redisInstanceModel.getClient().dbsize() - return .setDBSize(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - // 分页查询 key - case .getKeys: - let page = state.pageState.page - return .task { - let keysPage = await env.redisInstanceModel.getClient().pageKeys(page) - return .setKeys(page, keysPage) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - // 异步计算key数量, 通过setCount 进行递归调用,直接cursor 返回0 - // 后续可能增加开关,是否查询数量 - case let .countKeys(cursor, searchGroup): - let page = state.pageState.page - if searchGroup < state.searchGroup { - logger.info("有新查询批次, 当前count终止") + // 是否开启了快速分页, 默认启用 + state.pageState.fastPage = redisClient.settingViewStore?.fastPage ?? true + state.pageState.fastPageMax = redisClient.settingViewStore?.fastPageMax ?? 99 + + return .run { send in + let r = await redisInstanceModel.getClient().countKey(page, cursor: cursor) + return await send(.setCount(r.0, r.1, searchGroup)) + } + + + case let .setKeys(_, redisKeys): + state.tableState.datasource = redisKeys + + if !redisKeys.isEmpty { + state.tableState.selectIndex = 0 + } return .none - } - return .task { - let r = await env.redisInstanceModel.getClient().countKey(page, cursor: cursor) - return .setCount(r.0, r.1, searchGroup) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - - case let .setKeys(page, redisKeys): - state.tableState.datasource = redisKeys - - if !redisKeys.isEmpty { - state.tableState.selectIndex = 0 - } - return .none - - case let .setCount(cursor, count, searchGroup): - if searchGroup < state.searchGroup { + case let .setCount(cursor, count, searchGroup): + if searchGroup < state.searchGroup { + return .none + } + + state.pageState.total = state.pageState.total + count + + return cursor == 0 ? .none : .run { send in await send(.countKeys(cursor, searchGroup)) } + + case let .setMainViewType(mainViewType): + state.mainViewType = mainViewType return .none - } - - state.pageState.total = state.pageState.total + count - return cursor == 0 ? .none : .result { .success(.countKeys(cursor, searchGroup)) } - - case let .setMainViewType(mainViewType): - state.mainViewType = mainViewType - return .none - - case let .setDBSize(dbsize): - state.dbsize = dbsize - return .none - - case .addNew: - let newKey = RedisKeyModel() - newKey.initNew() - return .result{ - .success(.valueAction(.keyChange(newKey))) - } - - case let .deleteConfirm(index): - let redisKeyModel = state.tableState.datasource[index] as! RedisKeyModel - return .future { callback in - Messages.confirm(String(format: NSLocalizedString("REDIS_KEY_DELETE_CONFIRM_TITLE'%@'", comment: ""), redisKeyModel.key) - , message: String(format: NSLocalizedString("REDIS_KEY_DELETE_CONFIRM_MESSAGE'%@'", comment: ""), redisKeyModel.key) - , primaryButton: "Delete" - , action: { - callback(.success(.deleteKey(index))) - }) - } - - case let .deleteKey(index): - let redisKeyModel = state.tableState.datasource[index] as! RedisKeyModel - logger.info("delete key: \(redisKeyModel.key)") - - return .task { - let r = await env.redisInstanceModel.getClient().del(redisKeyModel.key) - logger.info("on delete redis key: \(index), r:\(r)") - return r > 0 ? .deleteSuccess(index) : .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .deleteSuccess(index): - state.tableState.datasource.remove(at: index) - - return .result { - .success(.refreshCount) - } - - case let .onKeyChange(index): - guard index > -1 else { return .none } - - state.mainViewType = .EDITOR - let redisKeyModel = state.tableState.datasource[index] as! RedisKeyModel - return .result{ - .success(.valueAction(.keyChange(redisKeyModel))) - } - - case .flushDBConfirm: - return Effect.future { callback in - Messages.confirm("Flush DB ?" - , message: "Are you sure you want to flush db? This operation cannot be undone." - , primaryButton: "Ok" - , action: { - callback(.success(.flushDB)) + case let .setDBSize(dbsize): + state.dbsize = dbsize + return .none + + case .addNew: + let newKey = RedisKeyModel() + newKey.initNew() + return .run{ send in + await send(.valueAction(.keyChange(newKey))) } - ) - } - - case .flushDB: - return .task { - let r = await env.redisInstanceModel.getClient().flushDB() - if r { - return .initial + + case let .deleteConfirm(index): + let redisKeyModel = state.tableState.datasource[index] as! RedisKeyModel + return .run { send in + Messages.confirm(String(format: NSLocalizedString("REDIS_KEY_DELETE_CONFIRM_TITLE'%@'", comment: ""), redisKeyModel.key) + , message: String(format: NSLocalizedString("REDIS_KEY_DELETE_CONFIRM_MESSAGE'%@'", comment: ""), redisKeyModel.key) + , primaryButton: "Delete" + , action: { + await send(.deleteKey(index)) + }) + } + + case let .deleteKey(index): + let redisKeyModel = state.tableState.datasource[index] as! RedisKeyModel + logger.info("delete key: \(redisKeyModel.key)") + + return .run { send in + let r = await redisInstanceModel.getClient().del(redisKeyModel.key) + logger.info("on delete redis key: \(index), r:\(r)") + + return r > 0 ? await send(.deleteSuccess(index)) : await send(.none) + } + + case let .deleteSuccess(index): + state.tableState.datasource.remove(at: index) + + return .run { send in + await send(.refreshCount) + } + + case let .onKeyChange(index): + guard index > -1 else { return .none } + + state.mainViewType = .EDITOR + let redisKeyModel = state.tableState.datasource[index] as! RedisKeyModel + return .run { send in + await send(.valueAction(.keyChange(redisKeyModel))) } + + case .flushDBConfirm: + return .run { send in + Messages.confirm("Flush DB ?" + , message: "Are you sure you want to flush db? This operation cannot be undone." + , primaryButton: "Ok" + , action: { + await send(.flushDB) + }) + } + + case .flushDB: + return .run { send in + let r = await redisInstanceModel.getClient().flushDB() + if r { + return await send(.initial) + } + return await send(.none) + } + + // redis 系统信息 + case .redisSystemAction(.setSystemView): + state.mainViewType = .SYSTEM return .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - // redis 系统信息 - case .redisSystemAction(.setSystemView): - state.mainViewType = .SYSTEM - return .none - - case .redisSystemAction: - return .none - - // submit 成功后, 如果是新增key,添加到列表 - case let .valueAction(.submitSuccess(isNew)): - let redisKeyModel = state.valueState.keyState.redisKeyModel - - if isNew { - // 此处直接设置 selectIndex, 不会触 selectionChange, 会在设置datasource 时一起设置 - state.tableState.selectIndex = 0 - state.tableState.datasource.insert(redisKeyModel, at: 0) - } - return .none - - case .valueAction: - return .none - - //MARK: --------------------------- table action --------------------------- - case let .tableAction(.copy(index)): - let item = state.tableState.datasource[index] as! RedisKeyModel - PasteboardHelper.copy(item.key) - return .none - - case let .tableAction(.selectionChange(index)): - return .result { - .success(.onKeyChange(index)) - } - - // delete key - case let .tableAction(.contextMenu(title, index)): - if title == "Delete" { - return .result { - .success(.deleteConfirm(index)) + case .redisSystemAction: + return .none + + // submit 成功后, 如果是新增key,添加到列表 + case let .valueAction(.submitSuccess(isNew)): + let redisKeyModel = state.valueState.keyState.redisKeyModel + + if isNew { + // 此处直接设置 selectIndex, 不会触 selectionChange, 会在设置datasource 时一起设置 + state.tableState.selectIndex = 0 + state.tableState.datasource.insert(redisKeyModel, at: 0) } - } - - else if title == "Rename" { + return .none + + case .valueAction: + return .none + + //MARK: --------------------------- table action --------------------------- + case let .tableAction(.copy(index)): + let item = state.tableState.datasource[index] as! RedisKeyModel + PasteboardHelper.copy(item.key) + return .none + + case let .tableAction(.selectionChange(index)): + return .run { send in + await send(.onKeyChange(index)) + } + + // delete key + case let .tableAction(.contextMenu(title, index)): + if title == "Delete" { + + return .run { send in + await send(.deleteConfirm(index)) + } + } + + else if title == "Rename" { + let redisKeyModel = state.tableState.datasource[state.tableState.selectIndex] as! RedisKeyModel + state.renameState.key = redisKeyModel.key + state.renameState.newKey = redisKeyModel.key + state.renameState.index = state.tableState.selectIndex + state.renameState.visible = true + + } + return .none + + case let .tableAction(.double(index)): let redisKeyModel = state.tableState.datasource[state.tableState.selectIndex] as! RedisKeyModel state.renameState.key = redisKeyModel.key state.renameState.newKey = redisKeyModel.key state.renameState.index = state.tableState.selectIndex state.renameState.visible = true - - } - return .none - - case let .tableAction(.double(index)): - let redisKeyModel = state.tableState.datasource[state.tableState.selectIndex] as! RedisKeyModel - state.renameState.key = redisKeyModel.key - state.renameState.newKey = redisKeyModel.key - state.renameState.index = state.tableState.selectIndex - state.renameState.visible = true - - return .none - - case let .tableAction(.delete(index)): - return .result { - .success(.deleteConfirm(index)) - } - - case .tableAction: - return .none - - //MARK: --------------------------- database action --------------------------- - case let .databaseAction(.onDBChange(database)): - logger.info("change database, \(database)") - return .result { - .success(.initial) - } - - case .databaseAction: - return .none - - //MARK: --------------------------- page action --------------------------- - case .pageAction(.updateSize): - return .result { - .success(.getKeys) - } - case .pageAction(.nextPage): - return .result { - .success(.getKeys) - } - case .pageAction(.prevPage): - return .result { - .success(.getKeys) + + return .none + + case let .tableAction(.delete(index)): + return .run { send in + await send(.deleteConfirm(index)) + } + + case .tableAction: + return .none + + //MARK: --------------------------- database action --------------------------- + case let .databaseAction(.onDBChange(database)): + logger.info("change database, \(database)") + return .run { send in + await send(.initial) + } + + case .databaseAction: + return .none + + //MARK: --------------------------- page action --------------------------- + case .pageAction(.updateSize): + return .run { send in + await send(.getKeys) + } + case .pageAction(.nextPage): + return .run { send in + await send(.getKeys) + } + case .pageAction(.prevPage): + return .run { send in + await send(.getKeys) + } + case .pageAction: + return .none + + case let .renameAction(.setKey(index, newKey)): + var datasource:[RedisKeyModel] = state.tableState.datasource as! [RedisKeyModel] + let old = datasource[index] + datasource[index] = RedisKeyModel(newKey, type: old.type) + state.tableState.datasource = datasource + return .none + + case .renameAction: + return .none + + case .none: + return .none } - case .pageAction: - return .none - - case let .renameAction(.setKey(index, newKey)): - var datasource:[RedisKeyModel] = state.tableState.datasource as! [RedisKeyModel] - let old = datasource[index] - datasource[index] = RedisKeyModel(newKey, type: old.type) - state.tableState.datasource = datasource - return .none - - case .renameAction: - return .none - - case .none: - return .none } } -).debug() - + +} diff --git a/redis-pro/Store/RedisSystemStore.swift b/redis-pro/Store/RedisSystemStore.swift index a5d573f..a331182 100644 --- a/redis-pro/Store/RedisSystemStore.swift +++ b/redis-pro/Store/RedisSystemStore.swift @@ -21,79 +21,70 @@ enum RedisSystemViewTypeEnum{ } private let logger = Logger(label: "redis-system-store") -struct RedisSystemState: Equatable { - var systemView: RedisSystemViewTypeEnum = .REDIS_INFO - var redisInfoState: RedisInfoState = RedisInfoState() - var redisConfigState: RedisConfigState = RedisConfigState() - var slowLogState: SlowLogState = SlowLogState() - var clientListState: ClientListState = ClientListState() - var luaState: LuaState = LuaState() + +struct RedisSystemStore: Reducer { - init() { - logger.info("redis system state init ...") + struct State: Equatable { + var systemView: RedisSystemViewTypeEnum = .REDIS_INFO + var redisInfoState: RedisInfoStore.State = RedisInfoStore.State() + var redisConfigState: RedisConfigStore.State = RedisConfigStore.State() + var slowLogState: SlowLogStore.State = SlowLogStore.State() + var clientListState: ClientListStore.State = ClientListStore.State() + var luaState: LuaStore.State = LuaStore.State() + + init() { + logger.info("redis system state init ...") + } } -} - -enum RedisSystemAction: Equatable { - case initial - case setSystemView(RedisSystemViewTypeEnum) - case redisInfoAction(RedisInfoAction) - case redisConfigAction(RedisConfigAction) - case slowLogAction(SlowLogAction) - case clientListAction(ClientListAction) - case luaAction(LuaAction) -} - -struct RedisSystemEnvironment { - var redisInstanceModel:RedisInstanceModel -} -let redisSystemReducer = Reducer>.combine( - redisInfoReducer.pullback( - state: \.redisInfoState, - action: /RedisSystemAction.redisInfoAction, - environment: { env in .live(environment: RedisInfoEnvironment(redisInstanceModel: env.redisInstanceModel)) } - ), - redisConfigReducer.pullback( - state: \.redisConfigState, - action: /RedisSystemAction.redisConfigAction, - environment: { env in .live(environment: RedisConfigEnvironment(redisInstanceModel: env.redisInstanceModel)) } - ), - slowLogReducer.pullback( - state: \.slowLogState, - action: /RedisSystemAction.slowLogAction, - environment: { env in .live(environment: SlowLogEnvironment(redisInstanceModel: env.redisInstanceModel)) } - ), - clientListReducer.pullback( - state: \.clientListState, - action: /RedisSystemAction.clientListAction, - environment: { env in .live(environment: ClientListEnvironment(redisInstanceModel: env.redisInstanceModel)) } - ), - luaReducer.pullback( - state: \.luaState, - action: /RedisSystemAction.luaAction, - environment: { env in .live(environment: LuaEnvironment(redisInstanceModel: env.redisInstanceModel)) } - ), - Reducer> { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - return .none - case let .setSystemView(type): - state.systemView = type - return .none - case .redisInfoAction: - return .none - - case .redisConfigAction: - return .none - case .slowLogAction: - return .none - case .clientListAction: - return .none - case .luaAction: - return .none + enum Action: Equatable { + case initial + case setSystemView(RedisSystemViewTypeEnum) + case redisInfoAction(RedisInfoStore.Action) + case redisConfigAction(RedisConfigStore.Action) + case slowLogAction(SlowLogStore.Action) + case clientListAction(ClientListStore.Action) + case luaAction(LuaStore.Action) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + + var body: some Reducer { + Scope(state: \.redisInfoState, action: /Action.redisInfoAction) { + RedisInfoStore() + } + Scope(state: \.redisConfigState, action: /Action.redisConfigAction) { + RedisConfigStore() + } + Scope(state: \.slowLogState, action: /Action.slowLogAction) { + SlowLogStore() + } + Scope(state: \.clientListState, action: /Action.clientListAction) { + ClientListStore() + } + Scope(state: \.luaState, action: /Action.luaAction) { + LuaStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + return .none + case let .setSystemView(type): + state.systemView = type + return .none + case .redisInfoAction: + return .none + + case .redisConfigAction: + return .none + case .slowLogAction: + return .none + case .clientListAction: + return .none + case .luaAction: + return .none + } } } -) +} diff --git a/redis-pro/Store/RenameStore.swift b/redis-pro/Store/RenameStore.swift index f1551f9..f15ee30 100644 --- a/redis-pro/Store/RenameStore.swift +++ b/redis-pro/Store/RenameStore.swift @@ -10,72 +10,72 @@ import Foundation import SwiftyJSON import ComposableArchitecture -private let logger = Logger(label: "string-value-store") -struct RenameState: Equatable { - var key:String = "" - var index:Int = -1 - var visible:Bool = false - @BindableState var newKey:String = "" - - init() { - logger.info("string value state init ...") - } -} +private let logger = Logger(label: "rename-store") -enum RenameAction:BindableAction, Equatable { - case initial - case submit - case setKey(Int, String) - case setNewKey(String) - case hide - case none - case binding(BindingAction) -} +struct RenameStore: Reducer { + struct State: Equatable { + var key:String = "" + var index:Int = -1 + var visible:Bool = false + @BindingState var newKey:String = "" + + init() { + logger.info("string value state init ...") + } + } -struct RenameEnvironment { - var redisInstanceModel:RedisInstanceModel + enum Action:BindableAction, Equatable { + case initial + case submit + case setKey(Int, String) + case setNewKey(String) + case hide + case none + case binding(BindingAction) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} - -let renameReducer = Reducer.combine( - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - - logger.info("rename store initial...") - return .none - case .hide: - state.visible = false - return .none - case .submit: - let key = state.key - let index = state.index - let newKey = state.newKey - return .task { + + var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: - let r = await env.redisInstanceModel.getClient().rename(key, newKey: newKey) - if r { - return .setKey(index, newKey) + logger.info("rename store initial...") + return .none + case .hide: + state.visible = false + return .none + case .submit: + let key = state.key + let index = state.index + let newKey = state.newKey + return .run { send in + + let r = await redisInstanceModel.getClient().rename(key, newKey: newKey) + if r { + await send(.setKey(index, newKey)) + } } + + case let .setKey(index, newKey): + state.visible = false return .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - case let .setKey(index, newKey): - state.visible = false - return .none - - case let .setNewKey(newKey): - state.newKey = newKey - return .none - case .none: - return .none - - case .binding: - return .none + case let .setNewKey(newKey): + state.newKey = newKey + return .none + case .none: + return .none + + case .binding: + return .none + } } } -).binding().debug() + + +} diff --git a/redis-pro/Store/ScanStore.swift b/redis-pro/Store/ScanStore.swift deleted file mode 100644 index 091ed41..0000000 --- a/redis-pro/Store/ScanStore.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// ScanStore.swift -// redis-pro -// -// Created by chengpan on 2022/5/14. -// - -import Logging -import Foundation -import ComposableArchitecture - -private let logger = Logger(label: "scan-store") - -struct ScanState: Equatable { - var showTotal: Bool = false - var current:Int = 1 - var size:Int = 50 - var total:Int = 0 - var keywords:String = "" - - var totalScan:Int { - get { - return total < 1 ? 1 : (total % size == 0 ? total / size : total / size + 1) - } - } - var hasPrev:Bool { - totalScan > 1 && current > 1 - } - var hasNext:Bool { - totalScan > 1 && current < totalScan - } - - var scanModel:ScanModel { - get { - let scan = ScanModel() - scan.current = current - scan.size = size - scan.total = total - scan.keywords = keywords - - return scan - } - set(scan) { - current = scan.current - size = scan.size - total = scan.total - } - } - - init() { - logger.info("scan state init ...") - } -} - - -enum ScanAction:BindableAction, Equatable { - case initial - case updateSize(Int) - case nextScan - case prevScan - case none - case binding(BindingAction) -} - -struct ScanEnvironment { -} - -let scanReducer = Reducer.combine( - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - logger.info("scan store initial...") - return .none - - case let .updateSize(size): - state.current = 1 - state.size = size - return .none - case .nextScan: - state.current = state.current + 1 - return .none - case .prevScan: - state.current -= 1 - if state.current <= 1 { - state.current = 1 - } - return .none - case .none: - return .none - case .binding: - return .none - } - } -).binding().debug() diff --git a/redis-pro/Store/SetValueStore.swift b/redis-pro/Store/SetValueStore.swift index cbaa68e..e824524 100644 --- a/redis-pro/Store/SetValueStore.swift +++ b/redis-pro/Store/SetValueStore.swift @@ -11,266 +11,258 @@ import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "set-value-store") -struct SetValueState: Equatable { - @BindableState var editModalVisible:Bool = false - @BindableState var editValue:String = "" - // 1: LPUSH, 2: RPUSH - var pushType:Int = 0 - var editIndex:Int = -1 - var isNew:Bool = false - var redisKeyModel:RedisKeyModel? - var pageState: PageState = PageState(showTotal: true) - var tableState: TableState = TableState( - columns: [.init(title: "Value", key: "self", width: 200)] - , datasource: [], contextMenus: [.COPY, .EDIT, .DELETE] - , selectIndex: -1) - - init() { - logger.info("set value state init ...") - pageState.showTotal = true + + +struct SetValueStore: Reducer { + struct State: Equatable { + @BindingState var editModalVisible:Bool = false + @BindingState var editValue:String = "" + // 1: LPUSH, 2: RPUSH + var pushType:Int = 0 + var editIndex:Int = -1 + var isNew:Bool = false + var redisKeyModel:RedisKeyModel? + var pageState: PageStore.State = PageStore.State(showTotal: true) + var tableState: TableStore.State = TableStore.State( + columns: [.init(title: "Value", key: "self", width: 200)] + , datasource: [], contextMenus: [.COPY, .EDIT, .DELETE] + , selectIndex: -1) + + init() { + logger.info("set value state init ...") + pageState.showTotal = true + } } -} -enum SetValueAction:BindableAction, Equatable { - - case initial - case refresh - case search(String) - case getValue - case setValue(Page, [String?]) - - case addNew - case edit(Int) - case submit - case submitSuccess(Bool) - - case deleteConfirm(Int) - case deleteKey(Int) - case deleteSuccess(Int) + enum Action:BindableAction, Equatable { + + case initial + case refresh + case search(String) + case getValue + case setValue(Page, [String?]) + + case addNew + case edit(Int) + case submit + case submitSuccess(Bool) + + case deleteConfirm(Int) + case deleteKey(Int) + case deleteSuccess(Int) + + case none + case pageAction(PageStore.Action) + case tableAction(TableStore.Action) + case binding(BindingAction) + } - case none - case pageAction(PageAction) - case tableAction(TableAction) - case binding(BindingAction) -} - -struct SetValueEnvironment { - var redisInstanceModel:RedisInstanceModel + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} - -let setValueReducer = Reducer.combine( - tableReducer.pullback( - state: \.tableState, - action: /SetValueAction.tableAction, - environment: { env in .init() } - ), - pageReducer.pullback( - state: \.pageState, - action: /SetValueAction.pageAction, - environment: { env in .init() } - ), - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - state.pageState.keywords = "" - state.pageState.current = 1 - - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case .refresh: - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case let .search(keywords): - state.pageState.current = 1 - state.pageState.keywords = keywords - return .result { - .success(.getValue) - } - - case .getValue: - guard let redisKeyModel = state.redisKeyModel else { - return .none - } - // 清空 - if redisKeyModel.isNew { - return .result { - .success(.tableAction(.reset)) + + var body: some Reducer { + BindingReducer() + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Scope(state: \.pageState, action: /Action.pageAction) { + PageStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + state.pageState.keywords = "" + state.pageState.current = 1 + + logger.info("value store initial...") + return .run { send in + await send(.getValue) } - } - - let key = redisKeyModel.key - let page = state.pageState.page - return .task { - let res = await env.redisInstanceModel.getClient().pageSet(key, page: page) - return .setValue(page, res) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .setValue(page, datasource): - state.tableState.datasource = datasource - state.pageState.page = page - return .none - - case .addNew: - state.editValue = "" - state.editIndex = -1 - - state.isNew = true - state.editModalVisible = true - return .none - - case let .edit(index): - // 编辑 - let item = state.tableState.datasource[index] as! String - state.editIndex = index - state.editValue = item - state.isNew = false - state.editModalVisible = true - return .none - - case .submit: - guard let redisKeyModel = state.redisKeyModel else { + + case .refresh: + logger.info("value store initial...") + return .run { send in + await send(.getValue) + } + + case let .search(keywords): + state.pageState.current = 1 + state.pageState.keywords = keywords + return .run { send in + await send(.getValue) + } + + case .getValue: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } + // 清空 + if redisKeyModel.isNew { + return .run { send in + await send(.tableAction(.reset)) + } + } + + let key = redisKeyModel.key + let page = state.pageState.page + return .run { send in + let res = await redisInstanceModel.getClient().pageSet(key, page: page) + await send(.setValue(page, res)) + } + + case let .setValue(page, datasource): + state.tableState.datasource = datasource + state.pageState.page = page return .none - } + + case .addNew: + state.editValue = "" + state.editIndex = -1 + + state.isNew = true + state.editModalVisible = true + return .none + + case let .edit(index): + // 编辑 + let item = state.tableState.datasource[index] as! String + state.editIndex = index + state.editValue = item + state.isNew = false + state.editModalVisible = true + return .none + + case .submit: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } - let key = redisKeyModel.key - let editValue = state.editValue - let isNew = state.isNew - let isNewKey = state.redisKeyModel?.isNew ?? false - let originEle = isNew ? nil : state.tableState.datasource[state.editIndex] as? String - return .task { - if isNew { - let _ = await env.redisInstanceModel.getClient().sadd(key, ele: editValue) - } else { - let _ = await env.redisInstanceModel.getClient().supdate(key, from: originEle!, to: editValue) + let key = redisKeyModel.key + let editValue = state.editValue + let isNew = state.isNew + let isNewKey = state.redisKeyModel?.isNew ?? false + let originEle = isNew ? nil : state.tableState.datasource[state.editIndex] as? String + return .run { send in + if isNew { + let _ = await redisInstanceModel.getClient().sadd(key, ele: editValue) + } else { + let _ = await redisInstanceModel.getClient().supdate(key, from: originEle!, to: editValue) + } + + await send(.submitSuccess(isNewKey)) + } + + // 提交成功, 刷新列表 + case let .submitSuccess(isNewKey): + let editValue = state.editValue + // 修改,刷新单个值 + if state.isNew { + state.tableState.selectIndex = 0 + state.tableState.datasource.insert(editValue, at: 0) + return .none + } + // 刷新列表 + else { + state.tableState.datasource[state.editIndex] = editValue + return .none } + - return .submitSuccess(isNewKey) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - // 提交成功, 刷新列表 - case let .submitSuccess(isNewKey): - let editValue = state.editValue - // 修改,刷新单个值 - if state.isNew { - state.tableState.selectIndex = 0 - state.tableState.datasource.insert(editValue, at: 0) + case let .deleteConfirm(index): + guard index < state.tableState.datasource.count else { + return .none + } + + let item = state.tableState.datasource[index] as! String + return .run { send in + Messages.confirm(String(format: NSLocalizedString("SET_DELETE_CONFIRM_TITLE", comment: ""), item) + , message: String(format: NSLocalizedString("SET_DELETE_CONFIRM_MESSAGE", comment: ""), item) + , primaryButton: "Delete" + , action: { + await send(.deleteKey(index)) + }) + } + + case let .deleteKey(index): + + let redisKeyModel = state.redisKeyModel! + let item = state.tableState.datasource[index] as! String + logger.info("delete set item, key: \(redisKeyModel.key), value: \(item)") + + return .run { send in + let r = await redisInstanceModel.getClient().srem(redisKeyModel.key, ele: item) + logger.info("do delete set item, key: \(redisKeyModel.key), value: \(item), r:\(r)") + + return r > 0 ? await send(.deleteSuccess(index)) : await send(.none) + } + + case let .deleteSuccess(index): + state.tableState.datasource.remove(at: index) + + return .run { send in + await send(.refresh) + } + + case .none: return .none - } - // 刷新列表 - else { - state.tableState.datasource[state.editIndex] = editValue + + // MARK: - page action + case .pageAction(.updateSize): + return .run { send in + await send(.getValue) + } + case .pageAction(.nextPage): + return .run { send in + await send(.getValue) + } + case .pageAction(.prevPage): + return .run { send in + await send(.getValue) + } + case .pageAction: return .none - } - - case let .deleteConfirm(index): - guard index < state.tableState.datasource.count else { + // MARK: - table action + // delete key + case let .tableAction(.contextMenu(title, index)): + if title == "Delete" { + return .run { send in + await send(.deleteConfirm(index)) + } + } + + else if title == "Edit" { + return .run { send in + await send(.edit(index)) + } + } + return .none - } - - let item = state.tableState.datasource[index] as! String - return .future { callback in - Messages.confirm(String(format: NSLocalizedString("SET_DELETE_CONFIRM_TITLE", comment: ""), item) - , message: String(format: NSLocalizedString("SET_DELETE_CONFIRM_MESSAGE", comment: ""), item) - , primaryButton: "Delete" - , action: { - callback(.success(.deleteKey(index))) - }) - } - - case let .deleteKey(index): - - let redisKeyModel = state.redisKeyModel! - let item = state.tableState.datasource[index] as! String - logger.info("delete set item, key: \(redisKeyModel.key), value: \(item)") - - return .task { - let r = await env.redisInstanceModel.getClient().srem(redisKeyModel.key, ele: item) - logger.info("do delete set item, key: \(redisKeyModel.key), value: \(item), r:\(r)") - return r > 0 ? .deleteSuccess(index) : .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .deleteSuccess(index): - state.tableState.datasource.remove(at: index) - - return .result { - .success(.refresh) - } - - case .none: - return .none - - // MARK: - page action - case .pageAction(.updateSize): - return .result { - .success(.getValue) - } - case .pageAction(.nextPage): - return .result { - .success(.getValue) - } - case .pageAction(.prevPage): - return .result { - .success(.getValue) - } - case .pageAction: - return .none - - // MARK: - table action - // delete key - case let .tableAction(.contextMenu(title, index)): - if title == "Delete" { - return .result { - .success(.deleteConfirm(index)) + case let .tableAction(.copy(index)): + guard let item = state.tableState.datasource[index] as? String else { + return .none } - } - - else if title == "Edit" { - return .result { - .success(.edit(index)) + + PasteboardHelper.copy(item) + return .none + + + case let .tableAction(.double(index)): + return .run { send in + await send(.edit(index)) } - } - - return .none - - case let .tableAction(.copy(index)): - guard let item = state.tableState.datasource[index] as? String else { + + case let .tableAction(.delete(index)): + return .run { send in + await send(.deleteConfirm(index)) + } + case .tableAction: + return .none + case .binding: return .none } - - PasteboardHelper.copy(item) - return .none - - - case let .tableAction(.double(index)): - return .result { - .success(.edit(index)) - } - - case let .tableAction(.delete(index)): - return .result { - .success(.deleteConfirm(index)) - } - case .tableAction: - return .none - case .binding: - return .none } } -).binding().debug() +} diff --git a/redis-pro/Store/SettingsStore.swift b/redis-pro/Store/SettingsStore.swift index b61f83a..082d451 100644 --- a/redis-pro/Store/SettingsStore.swift +++ b/redis-pro/Store/SettingsStore.swift @@ -12,84 +12,94 @@ import ComposableArchitecture private let logger = Logger(label: "settings-store") private let userDefaults = UserDefaults.standard -struct SettingsState: Equatable { - var colorSchemeValue:String? - var defaultFavorite:String = "last" - var stringMaxLength:Int = Const.DEFAULT_STRING_MAX_LENGTH - var keepalive:Int = 30 - var redisModels: [RedisModel] = [] - - init() { - logger.info("settings state init ...") - } -} -enum SettingsAction:Equatable { - case initial - case setColorScheme(String) - case setDefaultFavorite(String) - case setStringMaxLength(Int) - case setKeepalive(Int) -} - -struct SettingsEnvironment { -} +struct SettingsStore: Reducer { + struct State: Equatable { + var colorSchemeValue:String? + var defaultFavorite:String = "last" + var stringMaxLength:Int = Const.DEFAULT_STRING_MAX_LENGTH + var keepalive:Int = 30 + var redisModels: [RedisModel] = [] + var fastPage = true + // 快速分页阈值, 超过这个数值后, 不再继续查询, 提高查询性能, 减少对redis影响 + var fastPageMax = 99 + } -let settingsReducer = Reducer.combine( - Reducer { - state, action, _ in - switch action { - // 初始化已设置的值 - case .initial: - logger.info("settings store initial...") - state.colorSchemeValue = UserDefaults.standard.string(forKey: UserDefaulsKeysEnum.AppColorScheme.rawValue) ?? ColorSchemeEnum.SYSTEM.rawValue - - let stringMaxLength:String? = UserDefaults.standard.string(forKey: UserDefaulsKeysEnum.AppStringMaxLength.rawValue) - if let stringMaxLength = stringMaxLength { - state.stringMaxLength = Int(stringMaxLength) ?? Const.DEFAULT_STRING_MAX_LENGTH - } else { - state.stringMaxLength = Const.DEFAULT_STRING_MAX_LENGTH - } - - state.defaultFavorite = UserDefaults.standard.string(forKey: UserDefaulsKeysEnum.RedisFavoriteDefaultSelectType.rawValue) ?? RedisFavoriteDefaultSelectTypeEnum.LAST.rawValue - - - state.redisModels = RedisDefaults.getAll() - return .none - // 显示模式设置, 明亮,暗黑,系统 - case let .setColorScheme(colorSchemeValue): - logger.info("upate color scheme action, \(colorSchemeValue)") - state.colorSchemeValue = colorSchemeValue - UserDefaults.standard.set(colorSchemeValue, forKey: UserDefaulsKeysEnum.AppColorScheme.rawValue) - - if colorSchemeValue == ColorSchemeEnum.SYSTEM.rawValue { - NSApp.appearance = nil - } else { - NSApp.appearance = NSAppearance(named: colorSchemeValue == ColorSchemeEnum.DARK.rawValue ? .darkAqua : .aqua) + enum Action: Equatable { + case initial + case setColorScheme(String) + case setDefaultFavorite(String) + case setStringMaxLength(Int) + case setKeepalive(Int) + case setFastPage(Bool) + } + + var body: some Reducer { + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + + logger.info("settings store initial...") + state.colorSchemeValue = UserDefaults.standard.string(forKey: UserDefaulsKeysEnum.AppColorScheme.rawValue) ?? ColorSchemeEnum.SYSTEM.rawValue + + let stringMaxLength:String? = UserDefaults.standard.string(forKey: UserDefaulsKeysEnum.AppStringMaxLength.rawValue) + if let stringMaxLength = stringMaxLength { + state.stringMaxLength = Int(stringMaxLength) ?? Const.DEFAULT_STRING_MAX_LENGTH + } else { + state.stringMaxLength = Const.DEFAULT_STRING_MAX_LENGTH + } + + state.defaultFavorite = UserDefaults.standard.string(forKey: UserDefaulsKeysEnum.RedisFavoriteDefaultSelectType.rawValue) ?? RedisFavoriteDefaultSelectTypeEnum.LAST.rawValue + + // fast apge + state.fastPage = Bool(UserDefaults.standard.string(forKey: UserDefaulsKeysEnum.AppFastPage.rawValue) ?? "true") ?? true + + state.redisModels = RedisDefaults.getAll() + return .none + + // 显示模式设置, 明亮,暗黑,系统 + case let .setColorScheme(colorSchemeValue): + logger.info("upate color scheme action, \(colorSchemeValue)") + state.colorSchemeValue = colorSchemeValue + UserDefaults.standard.set(colorSchemeValue, forKey: UserDefaulsKeysEnum.AppColorScheme.rawValue) + + if colorSchemeValue == ColorSchemeEnum.SYSTEM.rawValue { + NSApp.appearance = nil + } else { + NSApp.appearance = NSAppearance(named: colorSchemeValue == ColorSchemeEnum.DARK.rawValue ? .darkAqua : .aqua) + } + return .none + + // 默认选中设置 + case let .setDefaultFavorite(defaultFavorite): + logger.info("upate default favorite action, \(defaultFavorite)") + + state.defaultFavorite = defaultFavorite + UserDefaults.standard.set(defaultFavorite, forKey: UserDefaulsKeysEnum.RedisFavoriteDefaultSelectType.rawValue) + return .none + + case let .setStringMaxLength(stringMaxLength): + logger.info("set stringMaxLength action, \(stringMaxLength)") + + state.stringMaxLength = stringMaxLength + UserDefaults.standard.set(stringMaxLength, forKey: UserDefaulsKeysEnum.AppStringMaxLength.rawValue) + return .none + + case let .setKeepalive(keepalive): + logger.info("set keepalive second action, \(keepalive)") + + state.keepalive = keepalive + UserDefaults.standard.set(keepalive, forKey: UserDefaulsKeysEnum.AppKeepalive.rawValue) + return .none + + case let .setFastPage(fastPage): + logger.info("set fast page action, \(fastPage)") + + state.fastPage = fastPage + UserDefaults.standard.set("\(fastPage)", forKey: UserDefaulsKeysEnum.AppFastPage.rawValue) + return .none } - return .none - // 默认选中设置 - case let .setDefaultFavorite(defaultFavorite): - logger.info("upate default favorite action, \(defaultFavorite)") - - state.defaultFavorite = defaultFavorite - UserDefaults.standard.set(defaultFavorite, forKey: UserDefaulsKeysEnum.RedisFavoriteDefaultSelectType.rawValue) - return .none - - case let .setStringMaxLength(stringMaxLength): - logger.info("set stringMaxLength action, \(stringMaxLength)") - - state.stringMaxLength = stringMaxLength - UserDefaults.standard.set(stringMaxLength, forKey: UserDefaulsKeysEnum.AppStringMaxLength.rawValue) - return .none - - case let .setKeepalive(keepalive): - logger.info("set keepalive second action, \(keepalive)") - - state.keepalive = keepalive - UserDefaults.standard.set(keepalive, forKey: UserDefaulsKeysEnum.AppKeepalive.rawValue) - return .none } } -).debug() - +} diff --git a/redis-pro/Store/SlowLogStore.swift b/redis-pro/Store/SlowLogStore.swift index d27d140..4cdd39c 100644 --- a/redis-pro/Store/SlowLogStore.swift +++ b/redis-pro/Store/SlowLogStore.swift @@ -12,124 +12,116 @@ import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "redis-config-store") -struct SlowLogState: Equatable { - - @BindableState var slowerThan:Int = 10000 - @BindableState var maxLen:Int = 128 - @BindableState var size:Int = 50 - var total:Int = 0 - - var tableState: TableState = TableState(columns: [ - .init(title: "Id", key: "id", width: 60), - .init(title: "Timestamp", key: "timestamp", width: 120), - .init(title: "Exec Time(us)", key: "execTime", width: 90), - .init(title: "Client", key: "client", width: 140), - .init(title: "Client Name", key: "clientName", width: 100), - .init(title: "Cmd", key: "cmd", width: 100), - ], datasource: [], selectIndex: -1) - - init() { - logger.info("slow log state init ...") - } -} - -enum SlowLogAction:BindableAction, Equatable { - case initial - case getValue - case setValue([SlowLogModel], Int, Int, Int) - case refresh - case reset - case setSlowerThan - case setMaxLen - case setSize - case none - case tableAction(TableAction) - case binding(BindingAction) -} -struct SlowLogEnvironment { - var redisInstanceModel:RedisInstanceModel -} -let slowLogReducer = Reducer>.combine( - tableReducer.pullback( - state: \.tableState, - action: /SlowLogAction.tableAction, - environment: { _ in .init() } - ), - Reducer> { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: +struct SlowLogStore: Reducer { + struct State: Equatable { - logger.info("redis config store initial...") - return .result { - .success(.getValue) - } - - case .refresh: - return .result { - .success(.getValue) - } - - case .getValue: - let size = state.size - return .task { - let datasource = await env.redisInstanceModel.getClient().getSlowLog(size) - let total = await env.redisInstanceModel.getClient().slowLogLen() - let maxLen = await env.redisInstanceModel.getClient().getConfigOne(key: "slowlog-max-len") - let slowerThan = await env.redisInstanceModel.getClient().getConfigOne(key: "slowlog-log-slower-than") - return .setValue(datasource, total, NumberHelper.toInt(maxLen), NumberHelper.toInt(slowerThan)) - } - .receive(on: env.mainQueue) - .eraseToEffect() + @BindingState var slowerThan:Int = 10000 + @BindingState var maxLen:Int = 128 + @BindingState var size:Int = 50 + var total:Int = 0 + + var tableState: TableStore.State = TableStore.State(columns: [ + .init(title: "Id", key: "id", width: 60), + .init(title: "Timestamp", key: "timestamp", width: 120), + .init(title: "Exec Time(us)", key: "execTime", width: 90), + .init(title: "Client", key: "client", width: 140), + .init(title: "Client Name", key: "clientName", width: 100), + .init(title: "Cmd", key: "cmd", width: 100), + ], datasource: [], selectIndex: -1) + + init() { + logger.info("slow log state init ...") + } + } - case let .setValue(slowLogs, total, maxLen, slowerThan): - state.tableState.datasource = slowLogs - state.total = total - state.maxLen = maxLen - state.slowerThan = slowerThan - - return .none - case .reset: - return .task { - let _ = await env.redisInstanceModel.getClient().slowLogReset() - return .refresh - } - .receive(on: env.mainQueue) - .eraseToEffect() + enum Action:BindableAction, Equatable { + case initial + case getValue + case setValue([SlowLogModel], Int, Int, Int) + case refresh + case reset + case setSlowerThan + case setMaxLen + case setSize + case none + case tableAction(TableStore.Action) + case binding(BindingAction) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + let mainQueue: AnySchedulerOf = .main + + var body: some Reducer { + BindingReducer() + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: - case .setSlowerThan: - let slowerThan = state.slowerThan - return .task { - let _ = await env.redisInstanceModel.getClient().setConfig(key: "slowlog-log-slower-than", value: "\(slowerThan)") + logger.info("redis config store initial...") + return .run { send in + await send(.getValue) + } + + case .refresh: + return .run { send in + await send(.getValue) + } + + case .getValue: + let size = state.size + return .run { send in + let datasource = await redisInstanceModel.getClient().getSlowLog(size) + let total = await redisInstanceModel.getClient().slowLogLen() + let maxLen = await redisInstanceModel.getClient().getConfigOne(key: "slowlog-max-len") + let slowerThan = await redisInstanceModel.getClient().getConfigOne(key: "slowlog-log-slower-than") + await send(.setValue(datasource, total, NumberHelper.toInt(maxLen), NumberHelper.toInt(slowerThan))) + } + + case let .setValue(slowLogs, total, maxLen, slowerThan): + state.tableState.datasource = slowLogs + state.total = total + state.maxLen = maxLen + state.slowerThan = slowerThan + return .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - case .setMaxLen: - let maxLen = state.maxLen - return .task { - let _ = await env.redisInstanceModel.getClient().setConfig(key: "slowlog-max-len", value: "\(maxLen)") + case .reset: + return .run { send in + let _ = await redisInstanceModel.getClient().slowLogReset() + await send(.refresh) + } + + case .setSlowerThan: + let slowerThan = state.slowerThan + return .run { send in + let _ = await redisInstanceModel.getClient().setConfig(key: "slowlog-log-slower-than", value: "\(slowerThan)") + await send(.none) + } + case .setMaxLen: + let maxLen = state.maxLen + return .run { send in + let _ = await redisInstanceModel.getClient().setConfig(key: "slowlog-max-len", value: "\(maxLen)") + } + + case .setSize: + return .run { send in + await send(.getValue) + } + + + // table action + case .tableAction: + return .none + case .binding: + return .none + case .none: return .none } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .setSize: - return .result { - .success(.getValue) - } - - - // table action - case .tableAction: - return .none - case .binding: - return .none - case .none: - return .none } } -).binding().debug() +} diff --git a/redis-pro/Store/StringValueStore.swift b/redis-pro/Store/StringValueStore.swift index 8a080d4..e25ab5e 100644 --- a/redis-pro/Store/StringValueStore.swift +++ b/redis-pro/Store/StringValueStore.swift @@ -11,150 +11,142 @@ import SwiftyJSON import ComposableArchitecture private let logger = Logger(label: "string-value-store") -struct StringValueState: Equatable { - var redisKeyModel:RedisKeyModel? - // 是否是完整字符串, 如果设置最大显示长度, 使用getrange命令取出部分字符串, 防止长字符串过大 - var isIntactString: Bool = true - var stringMaxLength:Int = -1 - var length: Int = -1 - @BindableState var text:String = "" + +struct StringValueStore: Reducer { + struct State: Equatable { + var redisKeyModel:RedisKeyModel? + // 是否是完整字符串, 如果设置最大显示长度, 使用getrange命令取出部分字符串, 防止长字符串过大 + var isIntactString: Bool = true + var stringMaxLength:Int = -1 + var length: Int = -1 + @BindingState var text:String = "" + } - init() { - logger.info("string value state init ...") + enum Action: BindableAction, Equatable { + case binding(BindingAction) + case initial + case submit + case submitSuccess(Bool) + case getLength + case getValue + case getIntactString + case updateLength(Int) + case updateText(String) + case jsonFormat + case refresh + case none } -} - -enum StringValueAction:BindableAction, Equatable { - case initial - case submit - case submitSuccess(Bool) - case getLength - case getValue - case getIntactString - case updateLength(Int) - case updateText(String) - case jsonFormat - case refresh - case none - case binding(BindingAction) -} - -struct StringValueEnvironment { - var redisInstanceModel:RedisInstanceModel - var mainQueue: AnySchedulerOf = .main -} - -let stringValueReducer = Reducer.combine( - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - - logger.info("value store initial...") - return .result { - .success(.getLength) - } - - case .getLength: - guard let redisKeyModel = state.redisKeyModel else { - return .none - } - if redisKeyModel.isNew { - state.text = "" - return .none - } - let key = redisKeyModel.key - - return .task { - let r = await env.redisInstanceModel.getClient().strLen(key) - return .updateLength(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .getValue: - guard let redisKeyModel = state.redisKeyModel else { + + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + let mainQueue: AnySchedulerOf = .main + + var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case .binding: return .none - } - if redisKeyModel.isNew { - state.text = "" + // 初始化已设置的值 + case .initial: + + logger.info("value store initial...") + return .run { send in + await send(.getLength) + } + + case .getLength: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } + if redisKeyModel.isNew { + state.text = "" + return .none + } + let key = redisKeyModel.key + + return .run { send in + let r = await redisInstanceModel.getClient().strLen(key) + await send(.updateLength(r)) + } + + case .getValue: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } + if redisKeyModel.isNew { + state.text = "" + return .none + } + + let stringMaxLength = state.stringMaxLength + let isIntactString = state.isIntactString + + let key = redisKeyModel.key + return .run { send in + let r = isIntactString ? await redisInstanceModel.getClient().get(key) : await redisInstanceModel.getClient().getRange(key, end: stringMaxLength) + await send(.updateText(r)) + } + + case .getIntactString: + state.isIntactString = true + return .run { send in + await send(.getValue) + } + + case .submit: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } + + let key = redisKeyModel.key + let isNew = redisKeyModel.isNew + let text = state.text + return .run { send in + await redisInstanceModel.getClient().set(key, value: text) + await send(.submitSuccess(isNew)) + } + + case .submitSuccess: return .none - } - - let stringMaxLength = state.stringMaxLength - let isIntactString = state.isIntactString - - let key = redisKeyModel.key - return .task { - let r = isIntactString ? await env.redisInstanceModel.getClient().get(key) : await env.redisInstanceModel.getClient().getRange(key, end: stringMaxLength) - return .updateText(r) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .getIntactString: - state.isIntactString = true - return .result { - .success(.getValue) - } - - case .submit: - guard let redisKeyModel = state.redisKeyModel else { + + case let .updateLength(length): + state.length = length + let stringMaxLength = RedisDefaults.getStringMaxLength() + + state.stringMaxLength = stringMaxLength + state.isIntactString = stringMaxLength == -1 || length <= stringMaxLength + return .run { send in + await send(.getValue) + } + + case let .updateText(text): + state.text = text return .none - } - - let key = redisKeyModel.key - let isNew = redisKeyModel.isNew - let text = state.text - return .task { - await env.redisInstanceModel.getClient().set(key, value: text) - return .submitSuccess(isNew) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case .submitSuccess: - return .none - - case let .updateLength(length): - state.length = length - let stringMaxLength = RedisDefaults.getStringMaxLength() - - state.stringMaxLength = stringMaxLength - state.isIntactString = stringMaxLength == -1 || length <= stringMaxLength - return .result { - .success(.getValue) - } - - case let .updateText(text): - state.text = text - return .none - - case .jsonFormat: - if state.text.count < 2 { - Messages.show(BizError("Format json error")) + case .jsonFormat: + if state.text.count < 2 { + + Messages.show(BizError("Format json error")) + return .none + } + let jsonObj = JSON(parseJSON: state.text) + if jsonObj == JSON.null { + Messages.show(BizError("Format json error")) + return .none + } + if let string = jsonObj.rawString() { + state.text = string + } return .none - } - let jsonObj = JSON(parseJSON: state.text) - if jsonObj == JSON.null { - Messages.show(BizError("Format json error")) + + case .refresh: + return .run { send in + await send(.getLength) + } + case .none: return .none } - if let string = jsonObj.rawString() { - state.text = string - } - return .none - - case .refresh: - return .result { - .success(.getLength) - } - case .none: - return .none - case .binding: - return .none } } -).binding().debug() +} diff --git a/redis-pro/Store/TableStore.swift b/redis-pro/Store/TableStore.swift index 2cf27c5..db2e827 100644 --- a/redis-pro/Store/TableStore.swift +++ b/redis-pro/Store/TableStore.swift @@ -10,79 +10,134 @@ import Foundation import ComposableArchitecture private let logger = Logger(label: "table-store") -struct TableState: Equatable { - var columns:[NTableColumn] = [] - var datasource: Array = [] - var contextMenus: [TableContextMenu] = [] - // 一定要设置-1, 其它值会在view 刷新时, 陷入无限循环 - var selectIndex:Int = -1 - var defaultSelectIndex:Int = -1 - var dragable: Bool = false -} - -enum TableAction:Equatable { - case selectionChange(Int) - case double(Int) - case delete(Int) - case copy(Int) - case contextMenu(String, Int) - case refresh - case reset - case dragComplete(Int, Int) -} -struct TableEnvironment { } +struct TableStore: Reducer { + struct State: Equatable { + var columns:[NTableColumn] = [] + var datasource: Array = [] + var contextMenus: [TableContextMenu] = [] + // 一定要设置-1, 其它值会在view 刷新时, 陷入无限循环 + var selectIndex:Int = -1 + var defaultSelectIndex:Int = -1 + var dragable: Bool = false + } -let tableReducer = Reducer { - state, action, _ in - switch action { - // 查询所有收藏 - case .refresh: - return .none - - case let .selectionChange(index): - logger.info("table view on selection change action, index: \(index)") - state.selectIndex = index - return .none - - case let .double(index): - logger.info("table view on double click action, index: \(index)") - return .none - - case let .delete(index): - logger.info("table view on delete action, index: \(index)") - return .none - - case let .copy(index): - logger.info("table view on copy action, index: \(index)") - return .none - - case let .contextMenu(sender, index): - logger.info("table view on context menu action, sender: \(sender), index: \(index)") - return .none - - case .reset: - state.selectIndex = -1 - state.datasource = [] - return .none - - case let .dragComplete(from, to): - - let f = state.datasource[from] - // 先删除原有的 - state.datasource.remove(at: from) - + enum Action:Equatable { + case selectionChange(Int) + case double(Int) + case delete(Int) + case copy(Int) + case contextMenu(String, Int) + case refresh + case reset + case dragComplete(Int, Int) + } -// state.datasource[from] = state.datasource[to] - - if from > to { - state.datasource.insert(f, at: to) - state.selectIndex = to - } else { - state.datasource.insert(f, at: to - 1) - state.selectIndex = to - 1 + var body: some Reducer { + Reduce { state, action in + switch action { + // 查询所有收藏 + case .refresh: + return .none + + case let .selectionChange(index): + logger.info("table view on selection change action, index: \(index)") + state.selectIndex = index + return .none + + case let .double(index): + logger.info("table view on double click action, index: \(index)") + return .none + + case let .delete(index): + logger.info("table view on delete action, index: \(index)") + return .none + + case let .copy(index): + logger.info("table view on copy action, index: \(index)") + return .none + + case let .contextMenu(sender, index): + logger.info("table view on context menu action, sender: \(sender), index: \(index)") + return .none + + case .reset: + state.selectIndex = -1 + state.datasource = [] + return .none + + case let .dragComplete(from, to): + + let f = state.datasource[from] + // 先删除原有的 + state.datasource.remove(at: from) + + if from > to { + state.datasource.insert(f, at: to) + state.selectIndex = to + } else { + state.datasource.insert(f, at: to - 1) + state.selectIndex = to - 1 + } + + return .none + } } - - return .none } -}.debug() +} + +//struct TableEnvironment { } +// +//let tableReducer = Reducer { +// state, action, _ in +// switch action { +// // 查询所有收藏 +// case .refresh: +// return .none +// +// case let .selectionChange(index): +// logger.info("table view on selection change action, index: \(index)") +// state.selectIndex = index +// return .none +// +// case let .double(index): +// logger.info("table view on double click action, index: \(index)") +// return .none +// +// case let .delete(index): +// logger.info("table view on delete action, index: \(index)") +// return .none +// +// case let .copy(index): +// logger.info("table view on copy action, index: \(index)") +// return .none +// +// case let .contextMenu(sender, index): +// logger.info("table view on context menu action, sender: \(sender), index: \(index)") +// return .none +// +// case .reset: +// state.selectIndex = -1 +// state.datasource = [] +// return .none +// +// case let .dragComplete(from, to): +// +// let f = state.datasource[from] +// // 先删除原有的 +// state.datasource.remove(at: from) +// +// +//// state.datasource[from] = state.datasource[to] +// +// if from > to { +// state.datasource.insert(f, at: to) +// state.selectIndex = to +// } else { +// state.datasource.insert(f, at: to - 1) +// state.selectIndex = to - 1 +// } +// +// return .none +// } +//}.debug() diff --git a/redis-pro/Store/ValueStore.swift b/redis-pro/Store/ValueStore.swift index e942ccf..e209aa4 100644 --- a/redis-pro/Store/ValueStore.swift +++ b/redis-pro/Store/ValueStore.swift @@ -11,221 +11,220 @@ import Foundation import ComposableArchitecture private let logger = Logger(label: "value-store") -struct ValueState: Equatable { - var keyState: KeyState = KeyState() - var stringValueState: StringValueState = StringValueState() - var hashValueState: HashValueState = HashValueState() - var listValueState: ListValueState = ListValueState() - var setValueState: SetValueState = SetValueState() - var zsetValueState: ZSetValueState = ZSetValueState() - - init() { - logger.info("value state init ...") - } -} -enum ValueAction:BindableAction, Equatable { - case initial - case refresh - case none - case setKeyModel((RedisKeyModel)) - case keyChange(RedisKeyModel) - case submitSuccess(Bool) - case keyAction(KeyAction) - case stringValueAction(StringValueAction) - case hashValueAction(HashValueAction) - case listValueAction(ListValueAction) - case setValueAction(SetValueAction) - case zsetValueAction(ZSetValueAction) - case binding(BindingAction) -} +struct ValueStore: Reducer { + + struct State: Equatable { + var keyState: KeyStore.State = KeyStore.State() + var keyObjectState: KeyObjectStore.State = KeyObjectStore.State() + var stringValueState = StringValueStore.State() + var hashValueState: HashValueStore.State = HashValueStore.State() + var listValueState: ListValueStore.State = ListValueStore.State() + var setValueState: SetValueStore.State = SetValueStore.State() + var zsetValueState: ZSetValueStore.State = ZSetValueStore.State() + + init() { + logger.info("value state init ...") + } + } -struct ValueEnvironment { - var redisInstanceModel:RedisInstanceModel -} -let valueReducer = Reducer.combine( - keyReducer.pullback( - state: \.keyState, - action: /ValueAction.keyAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - stringValueReducer.pullback( - state: \.stringValueState, - action: /ValueAction.stringValueAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - hashValueReducer.pullback( - state: \.hashValueState, - action: /ValueAction.hashValueAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - listValueReducer.pullback( - state: \.listValueState, - action: /ValueAction.listValueAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - setValueReducer.pullback( - state: \.setValueState, - action: /ValueAction.setValueAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - zsetValueReducer.pullback( - state: \.zsetValueState, - action: /ValueAction.zsetValueAction, - environment: { env in .init(redisInstanceModel: env.redisInstanceModel) } - ), - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - logger.info("value store initial...") - return .none - - case .refresh: - return .result { - .success(.keyAction(.getTtl)) - } - - case .none: - return .none - - case let .setKeyModel(redisKeyModel): - state.keyState.redisKeyModel = redisKeyModel - - if redisKeyModel.type == RedisKeyTypeEnum.STRING.rawValue { - state.stringValueState.redisKeyModel = redisKeyModel - } else if redisKeyModel.type == RedisKeyTypeEnum.HASH.rawValue { - state.hashValueState.redisKeyModel = redisKeyModel - } else if redisKeyModel.type == RedisKeyTypeEnum.LIST.rawValue { - state.listValueState.redisKeyModel = redisKeyModel - } else if redisKeyModel.type == RedisKeyTypeEnum.SET.rawValue { - state.setValueState.redisKeyModel = redisKeyModel - } else if redisKeyModel.type == RedisKeyTypeEnum.ZSET.rawValue { - state.zsetValueState.redisKeyModel = redisKeyModel - } - return .none - // key 变化统计走此action 分发 - case let .keyChange(redisKeyModel): - state.keyState.redisKeyModel = redisKeyModel - - var valueAction:ValueAction = .stringValueAction(.initial) - - if redisKeyModel.type == RedisKeyTypeEnum.STRING.rawValue { - state.stringValueState.redisKeyModel = redisKeyModel - } else if redisKeyModel.type == RedisKeyTypeEnum.HASH.rawValue { - state.hashValueState.redisKeyModel = redisKeyModel - valueAction = .hashValueAction(.initial) - } else if redisKeyModel.type == RedisKeyTypeEnum.LIST.rawValue { - state.listValueState.redisKeyModel = redisKeyModel - valueAction = .listValueAction(.initial) - } else if redisKeyModel.type == RedisKeyTypeEnum.SET.rawValue { - state.setValueState.redisKeyModel = redisKeyModel - valueAction = .setValueAction(.initial) - } else if redisKeyModel.type == RedisKeyTypeEnum.ZSET.rawValue { - state.zsetValueState.redisKeyModel = redisKeyModel - valueAction = .zsetValueAction(.initial) - } - - return .merge( - .result { - .success(.keyAction(.refresh)) - }, - .result { - .success(valueAction) - } - ) - - // 各个编辑器成功后调用此action - case let .submitSuccess(isNew): - if isNew { - state.keyState.isNew = false - } - return .result { - .success(.keyAction(.refresh)) - } - - - case let .keyAction(.setKey(key)): - let redisKeyModel = state.keyState.redisKeyModel - return .result { - .success(.setKeyModel(redisKeyModel)) - } - case .keyAction(.setType): - let redisKeyModel = state.keyState.redisKeyModel - return .result { - .success(.keyChange(redisKeyModel)) - } - - case .keyAction: - return .none - - // submit 成功后统一调用 submitSuccess, 此后的动作再依次分发 - case let .stringValueAction(.submitSuccess(isNew)): - return .result { - .success(.submitSuccess(isNew)) - } - - case .stringValueAction(.refresh): - return .result { - .success(.refresh) - } - - case .stringValueAction: - return .none - - case .hashValueAction(.refresh): - return .result { - .success(.refresh) - } - - case let .hashValueAction(.submitSuccess(isNew)): - return .result { - .success(.submitSuccess(isNew)) - } - case .hashValueAction: - return .none - - // list action - case .listValueAction(.refresh): - return .result { - .success(.refresh) - } - - case let .listValueAction(.submitSuccess(isNew)): - return .result { - .success(.submitSuccess(isNew)) - } - case .listValueAction: - return .none - - // set action - case .setValueAction(.refresh): - return .result { - .success(.refresh) - } - case let .setValueAction(.submitSuccess(isNew)): - return .result { - .success(.submitSuccess(isNew)) - } - case .setValueAction: - return .none + enum Action: Equatable { + case initial + case refresh + case none + case setKeyModel((RedisKeyModel)) + case keyChange(RedisKeyModel) + case submitSuccess(Bool) + case keyAction(KeyStore.Action) + case keyObjectAction(KeyObjectStore.Action) + case stringValueAction(StringValueStore.Action) + case hashValueAction(HashValueStore.Action) + case listValueAction(ListValueStore.Action) + case setValueAction(SetValueStore.Action) + case zsetValueAction(ZSetValueStore.Action) + } + + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel + + var body: some Reducer { + Scope(state: \.keyState, action: /Action.keyAction) { + KeyStore() + } + Scope(state: \.keyObjectState, action: /Action.keyObjectAction) { + KeyObjectStore() + } + Scope(state: \.stringValueState, action: /Action.stringValueAction) { + StringValueStore() + } + Scope(state: \.hashValueState, action: /Action.hashValueAction) { + HashValueStore() + } + Scope(state: \.listValueState, action: /Action.listValueAction) { + ListValueStore() + } + Scope(state: \.setValueState, action: /Action.setValueAction) { + SetValueStore() + } + Scope(state: \.zsetValueState, action: /Action.zsetValueAction) { + ZSetValueStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + logger.info("value store initial...") + return .none + + case .refresh: + return .run { send in + await send(.keyAction(.getTtl)) + } + + case .none: + return .none + + case let .setKeyModel(redisKeyModel): + state.keyState.redisKeyModel = redisKeyModel + + if redisKeyModel.type == RedisKeyTypeEnum.STRING.rawValue { + state.stringValueState.redisKeyModel = redisKeyModel + } else if redisKeyModel.type == RedisKeyTypeEnum.HASH.rawValue { + state.hashValueState.redisKeyModel = redisKeyModel + } else if redisKeyModel.type == RedisKeyTypeEnum.LIST.rawValue { + state.listValueState.redisKeyModel = redisKeyModel + } else if redisKeyModel.type == RedisKeyTypeEnum.SET.rawValue { + state.setValueState.redisKeyModel = redisKeyModel + } else if redisKeyModel.type == RedisKeyTypeEnum.ZSET.rawValue { + state.zsetValueState.redisKeyModel = redisKeyModel + } + return .none + // key 变化统计走此action 分发 + case let .keyChange(redisKeyModel): + state.keyState.redisKeyModel = redisKeyModel + state.keyObjectState.key = redisKeyModel.key + + var valueAction:ValueStore.Action = .stringValueAction(.initial) + + if redisKeyModel.type == RedisKeyTypeEnum.STRING.rawValue { + state.stringValueState.redisKeyModel = redisKeyModel + } else if redisKeyModel.type == RedisKeyTypeEnum.HASH.rawValue { + state.hashValueState.redisKeyModel = redisKeyModel + valueAction = .hashValueAction(.initial) + } else if redisKeyModel.type == RedisKeyTypeEnum.LIST.rawValue { + state.listValueState.redisKeyModel = redisKeyModel + valueAction = .listValueAction(.initial) + } else if redisKeyModel.type == RedisKeyTypeEnum.SET.rawValue { + state.setValueState.redisKeyModel = redisKeyModel + valueAction = .setValueAction(.initial) + } else if redisKeyModel.type == RedisKeyTypeEnum.ZSET.rawValue { + state.zsetValueState.redisKeyModel = redisKeyModel + valueAction = .zsetValueAction(.initial) + } + + return .merge( + .send(.keyAction(.refresh)), + .send(.keyObjectAction(.refresh)), + .send(valueAction) + ) + + // 各个编辑器成功后调用此action + case let .submitSuccess(isNew): + if isNew { + state.keyState.isNew = false + } + return .run { send in + await send(.keyAction(.refresh)) + } - // zset action - case let .zsetValueAction(.submitSuccess(isNew)): - return .result { - .success(.submitSuccess(isNew)) - } - case .zsetValueAction(.refresh): - return .result { - .success(.refresh) + // MARK: key action + case let .keyAction(.setKey(key)): + let redisKeyModel = state.keyState.redisKeyModel + return .run { send in + await send(.setKeyModel(redisKeyModel)) + } + + case .keyAction(.setType): + let redisKeyModel = state.keyState.redisKeyModel + return .run { send in + await send(.keyChange(redisKeyModel)) + } + + case .keyAction: + return .none + + // MARK: key object action + case .keyObjectAction: + return .none + + // MARK: string value action + // submit 成功后统一调用 submitSuccess, 此后的动作再依次分发 + case let .stringValueAction(.submitSuccess(isNew)): + return .run { send in + await send(.submitSuccess(isNew)) + } + + case .stringValueAction(.refresh): + return .run { send in + await send(.refresh) + } + + case .stringValueAction: + return .none + + // MARK: hash value action + case .hashValueAction(.refresh): + return .run { send in + await send(.refresh) + } + + case let .hashValueAction(.submitSuccess(isNew)): + return .run { send in + await send(.submitSuccess(isNew)) + } + case .hashValueAction: + return .none + + + // MARK: list value action + case .listValueAction(.refresh): + return .run { send in + await send(.refresh) + } + + case let .listValueAction(.submitSuccess(isNew)): + return .run { send in + await send(.submitSuccess(isNew)) + } + case .listValueAction: + return .none + + //MARK: set action + case .setValueAction(.refresh): + return .run { send in + await send(.refresh) + } + case let .setValueAction(.submitSuccess(isNew)): + return .run { send in + await send(.submitSuccess(isNew)) + } + case .setValueAction: + return .none + + //MARK: zset action + case let .zsetValueAction(.submitSuccess(isNew)): + return .run { send in + await send(.submitSuccess(isNew)) + } + case .zsetValueAction(.refresh): + return .run { send in + await send(.refresh) + } + case .zsetValueAction: + return .none } - case .zsetValueAction: - return .none - case .binding: - return .none } } -).binding().debug() + +} diff --git a/redis-pro/Store/ZSetValueStore.swift b/redis-pro/Store/ZSetValueStore.swift index 23e258a..9397ce6 100644 --- a/redis-pro/Store/ZSetValueStore.swift +++ b/redis-pro/Store/ZSetValueStore.swift @@ -12,272 +12,266 @@ import ComposableArchitecture private let logger = Logger(label: "zset-value-store") -// MARK: - state -struct ZSetValueState: Equatable { - @BindableState var editModalVisible:Bool = false - @BindableState var editValue:String = "" - @BindableState var editScore:Double = 0 +struct ZSetValueStore: Reducer { - var editIndex:Int = -1 - var isNew:Bool = false - var redisKeyModel:RedisKeyModel? - var pageState: PageState = PageState(showTotal: true) - var tableState: TableState = TableState( - columns: [.init(title: "Score", key: "score", width: 80), .init(title: "Value", key: "value", width: 200)] - , datasource: [], contextMenus: [.COPY, .EDIT, .DELETE] - , selectIndex: -1) - - init() { - logger.info("zset value state init ...") - pageState.showTotal = true + // MARK: - state + struct State: Equatable { + @BindingState var editModalVisible:Bool = false + @BindingState var editValue:String = "" + @BindingState var editScore:Double = 0 + + var editIndex:Int = -1 + var isNew:Bool = false + var redisKeyModel:RedisKeyModel? + var pageState: PageStore.State = PageStore.State(showTotal: true) + var tableState: TableStore.State = TableStore.State( + columns: [.init(title: "Score", key: "score", width: 80), .init(title: "Value", key: "value", width: 200)] + , datasource: [], contextMenus: [.COPY, .EDIT, .DELETE] + , selectIndex: -1) + + init() { + logger.info("zset value state init ...") + pageState.showTotal = true + } } -} -// MARK: - action -enum ZSetValueAction:BindableAction, Equatable { - - case initial - case refresh - case search(String) - case getValue - case setValue(Page, [RedisZSetItemModel]) - - case addNew - case edit(Int) - case submit - case submitSuccess(Bool) + // MARK: - action + enum Action:BindableAction, Equatable { + + case initial + case refresh + case search(String) + case getValue + case setValue(Page, [RedisZSetItemModel]) + + case addNew + case edit(Int) + case submit + case submitSuccess(Bool) + + case deleteConfirm(Int) + case deleteKey(Int) + case deleteSuccess(Int) + + case none + case pageAction(PageStore.Action) + case tableAction(TableStore.Action) + case binding(BindingAction) + } - case deleteConfirm(Int) - case deleteKey(Int) - case deleteSuccess(Int) - case none - case pageAction(PageAction) - case tableAction(TableAction) - case binding(BindingAction) -} - -struct ZSetValueEnvironment { - var redisInstanceModel:RedisInstanceModel + @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel var mainQueue: AnySchedulerOf = .main -} -// MARK: - reducer -let zsetValueReducer = Reducer.combine( - tableReducer.pullback( - state: \.tableState, - action: /ZSetValueAction.tableAction, - environment: { env in .init() } - ), - pageReducer.pullback( - state: \.pageState, - action: /ZSetValueAction.pageAction, - environment: { env in .init() } - ), - Reducer { - state, action, env in - switch action { - // 初始化已设置的值 - case .initial: - state.pageState.keywords = "" - state.pageState.current = 1 - - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case .refresh: - logger.info("value store initial...") - return .result { - .success(.getValue) - } - - case let .search(keywords): - state.pageState.current = 1 - state.pageState.keywords = keywords - return .result { - .success(.getValue) - } - - case .getValue: - guard let redisKeyModel = state.redisKeyModel else { - return .none - } - // 清空 - if redisKeyModel.isNew { - return .result { - .success(.tableAction(.reset)) + + var body: some Reducer { + BindingReducer() + Scope(state: \.tableState, action: /Action.tableAction) { + TableStore() + } + Scope(state: \.pageState, action: /Action.pageAction) { + PageStore() + } + Reduce { state, action in + switch action { + // 初始化已设置的值 + case .initial: + state.pageState.keywords = "" + state.pageState.current = 1 + + logger.info("value store initial...") + return .run { send in + await send(.getValue) } - } - - let key = redisKeyModel.key - let page = state.pageState.page - return .task { - let res = await env.redisInstanceModel.getClient().pageZSet(key, page: page) - return .setValue(page, res) - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .setValue(page, datasource): - state.tableState.datasource = datasource - state.pageState.page = page - return .none - - case .addNew: - state.editValue = "" - state.editScore = 0 - state.editIndex = -1 - - state.isNew = true - state.editModalVisible = true - return .none - - case let .edit(index): - // 编辑 - let item = state.tableState.datasource[index] as! RedisZSetItemModel - state.editIndex = index - state.editValue = item.value - state.editScore = Double(item.score) ?? 0 - state.isNew = false - state.editModalVisible = true - return .none - - case .submit: - guard let redisKeyModel = state.redisKeyModel else { + + case .refresh: + logger.info("value store initial...") + return .run { send in + await send(.getValue) + } + + case let .search(keywords): + state.pageState.current = 1 + state.pageState.keywords = keywords + return .run { send in + await send(.getValue) + } + + case .getValue: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } + // 清空 + if redisKeyModel.isNew { + return .run { send in + await send(.tableAction(.reset)) + } + } + + let key = redisKeyModel.key + let page = state.pageState.page + return .run { send in + let res = await redisInstanceModel.getClient().pageZSet(key, page: page) + await send(.setValue(page, res)) + } + + case let .setValue(page, datasource): + state.tableState.datasource = datasource + state.pageState.page = page return .none - } + + case .addNew: + state.editValue = "" + state.editScore = 0 + state.editIndex = -1 + + state.isNew = true + state.editModalVisible = true + return .none + + case let .edit(index): + // 编辑 + let item = state.tableState.datasource[index] as! RedisZSetItemModel + state.editIndex = index + state.editValue = item.value + state.editScore = Double(item.score) ?? 0 + state.isNew = false + state.editModalVisible = true + return .none + + case .submit: + guard let redisKeyModel = state.redisKeyModel else { + return .none + } - let key = redisKeyModel.key - let editValue = state.editValue - let editScore = state.editScore - let isNew = state.isNew - let isNewKey = state.redisKeyModel?.isNew ?? false - let originEle = isNew ? nil : state.tableState.datasource[state.editIndex] as? RedisZSetItemModel - return .task { - var r = false - if isNew { - r = await env.redisInstanceModel.getClient().zadd(key, score: editScore, ele: editValue) - } else { - r = await env.redisInstanceModel.getClient().zupdate(key, from: originEle!.value, to: editValue, score: editScore) + let key = redisKeyModel.key + let editValue = state.editValue + let editScore = state.editScore + let isNew = state.isNew + let isNewKey = state.redisKeyModel?.isNew ?? false + let originEle = isNew ? nil : state.tableState.datasource[state.editIndex] as? RedisZSetItemModel + return .run { send in + var r = false + if isNew { + r = await redisInstanceModel.getClient().zadd(key, score: editScore, ele: editValue) + } else { + r = await redisInstanceModel.getClient().zupdate(key, from: originEle!.value, to: editValue, score: editScore) + } + + return r ? await send(.submitSuccess(isNewKey)) : await send(.none) + + } + + // 提交成功, 刷新列表 + case let .submitSuccess(isNewKey): + let editValue = state.editValue + let editScore = "\(state.editScore)" + // 修改,刷新单个值 + if state.isNew { + state.tableState.selectIndex = 0 + state.tableState.datasource.insert(RedisZSetItemModel(value: editValue, score: editScore), at: 0) + return .none + } + // 刷新列表 + else { + state.tableState.datasource[state.editIndex] = RedisZSetItemModel(value: editValue, score: editScore) + return .none } + - return r ? .submitSuccess(isNewKey) : .none + case let .deleteConfirm(index): + guard index < state.tableState.datasource.count else { + return .none + } - } - .receive(on: env.mainQueue) - .eraseToEffect() - - // 提交成功, 刷新列表 - case let .submitSuccess(isNewKey): - let editValue = state.editValue - let editScore = "\(state.editScore)" - // 修改,刷新单个值 - if state.isNew { - state.tableState.selectIndex = 0 - state.tableState.datasource.insert(RedisZSetItemModel(value: editValue, score: editScore), at: 0) + let item = state.tableState.datasource[index] as! RedisZSetItemModel + return .run { send in + Messages.confirm(StringHelper.format("ZSET_DELETE_CONFIRM_TITLE", item.value) + , message: StringHelper.format("ZSET_DELETE_CONFIRM_MESSAGE", item.value) + , primaryButton: "Delete" + , action: { + await send(.deleteKey(index)) + }) + } + + case let .deleteKey(index): + + let redisKeyModel = state.redisKeyModel! + let item = state.tableState.datasource[index] as! RedisZSetItemModel + logger.info("delete zset item, key: \(redisKeyModel.key), value: \(item.value)") + + return .run { send in + let r = await redisInstanceModel.getClient().zrem(redisKeyModel.key, ele: item.value) + logger.info("do delete zset item, key: \(redisKeyModel.key), value: \(item), r:\(r)") + + if r > 0 { + await send(.deleteSuccess(index)) + } + } + + case let .deleteSuccess(index): + state.tableState.datasource.remove(at: index) + + return .run { send in + await send(.refresh) + } + + case .none: return .none - } - // 刷新列表 - else { - state.tableState.datasource[state.editIndex] = RedisZSetItemModel(value: editValue, score: editScore) + + // MARK: - page action + case .pageAction(.updateSize): + return .run { send in + await send(.getValue) + } + case .pageAction(.nextPage): + return .run { send in + await send(.getValue) + } + case .pageAction(.prevPage): + return .run { send in + await send(.getValue) + } + case .pageAction: return .none - } - - case let .deleteConfirm(index): - guard index < state.tableState.datasource.count else { + // MARK: - table action + // delete key + case let .tableAction(.contextMenu(title, index)): + if title == "Delete" { + return .run { send in + await send(.deleteConfirm(index)) + } + } + + else if title == "Edit" { + return .run { send in + await send(.edit(index)) + } + } + return .none - } - - let item = state.tableState.datasource[index] as! RedisZSetItemModel - return .future { callback in - Messages.confirm(StringHelper.format("ZSET_DELETE_CONFIRM_TITLE", item.value) - , message: StringHelper.format("ZSET_DELETE_CONFIRM_MESSAGE", item.value) - , primaryButton: "Delete" - , action: { - callback(.success(.deleteKey(index))) - }) - } - - case let .deleteKey(index): - - let redisKeyModel = state.redisKeyModel! - let item = state.tableState.datasource[index] as! RedisZSetItemModel - logger.info("delete zset item, key: \(redisKeyModel.key), value: \(item.value)") - - return .task { - let r = await env.redisInstanceModel.getClient().zrem(redisKeyModel.key, ele: item.value) - logger.info("do delete zset item, key: \(redisKeyModel.key), value: \(item), r:\(r)") - return r > 0 ? .deleteSuccess(index) : .none - } - .receive(on: env.mainQueue) - .eraseToEffect() - - case let .deleteSuccess(index): - state.tableState.datasource.remove(at: index) - - return .result { - .success(.refresh) - } - - case .none: - return .none - - // MARK: - page action - case .pageAction(.updateSize): - return .result { - .success(.getValue) - } - case .pageAction(.nextPage): - return .result { - .success(.getValue) - } - case .pageAction(.prevPage): - return .result { - .success(.getValue) - } - case .pageAction: - return .none - - // MARK: - table action - // delete key - case let .tableAction(.contextMenu(title, index)): - if title == "Delete" { - return .result { - .success(.deleteConfirm(index)) + case let .tableAction(.copy(index)): + let item = state.tableState.datasource[index] as! RedisZSetItemModel + + PasteboardHelper.copy("Score: \(item.score) \nValue: \(item.value)") + return .none + + case let .tableAction(.double(index)): + return .run { send in + await send(.edit(index)) } - } - - else if title == "Edit" { - return .result { - .success(.edit(index)) + + case let .tableAction(.delete(index)): + return .run { send in + await send(.deleteConfirm(index)) } + case .tableAction: + return .none + case .binding: + return .none } - - return .none - - case let .tableAction(.copy(index)): - let item = state.tableState.datasource[index] as! RedisZSetItemModel - - PasteboardHelper.copy("Score: \(item.score) \nValue: \(item.value)") - return .none - - case let .tableAction(.double(index)): - return .result { - .success(.edit(index)) - } - - case let .tableAction(.delete(index)): - return .result { - .success(.deleteConfirm(index)) - } - case .tableAction: - return .none - case .binding: - return .none } } -).binding().debug() +} diff --git a/redis-pro/Views/App/Settings/SettingsView.swift b/redis-pro/Views/App/Settings/SettingsView.swift index 7e87afd..5087fdc 100644 --- a/redis-pro/Views/App/Settings/SettingsView.swift +++ b/redis-pro/Views/App/Settings/SettingsView.swift @@ -11,21 +11,17 @@ import ComposableArchitecture struct SettingsView: View { - private var labelWidth:CGFloat = 100 - let store:Store + private let labelWidth:CGFloat = 160 + var store:StoreOf private let logger = Logger(label: "settings-view") - init(store:Store) { - logger.info("settings view init") - self.store = store - } var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) { viewStore in Form { VStack(alignment: .leading, spacing: 8) { - Picker(selection: viewStore.binding(get: {$0.defaultFavorite}, send: SettingsAction.setDefaultFavorite), + Picker(selection: viewStore.binding(get: {$0.defaultFavorite}, send: SettingsStore.Action.setDefaultFavorite), label: Text("Default Favorite:").frame(width: labelWidth, alignment: .trailing) ) { Section { @@ -37,14 +33,21 @@ struct SettingsView: View { } } - Picker(selection: viewStore.binding(get: {$0.colorSchemeValue ?? ColorSchemeEnum.SYSTEM.rawValue}, send: SettingsAction.setColorScheme), + Picker(selection: viewStore.binding(get: {$0.colorSchemeValue ?? ColorSchemeEnum.SYSTEM.rawValue}, send: SettingsStore.Action.setColorScheme), label: Text("Appearance:").frame(width: labelWidth, alignment: .trailing)) { ForEach(ColorSchemeEnum.allCases.map({$0.rawValue}), id: \.self) { item in Text(verbatim: item) } } - FormItemInt(label: "String Max Length", labelWidth:120, tips:"HELP_STRING_GET_RANGE_LENGTH", value: viewStore.binding(get: {$0.stringMaxLength}, send: SettingsAction.setStringMaxLength)) + FormItemInt(label: "String Max Length", labelWidth: labelWidth, tips:"HELP_STRING_GET_RANGE_LENGTH", value: viewStore.binding(get: {$0.stringMaxLength}, send: SettingsStore.Action.setStringMaxLength)) + + Toggle(isOn: viewStore.binding(get: {$0.fastPage}, send: SettingsStore.Action.setFastPage)) { + Text("Fast Page:") + .frame(width: labelWidth, alignment: .trailing) + } + .toggleStyle(.switch) + .help("HELP_FAST_PAGE") Spacer() } } @@ -56,9 +59,3 @@ struct SettingsView: View { } } } - -//struct SettingsView_Previews: PreviewProvider { -// static var previews: some View { -// SettingsView() -// } -//} diff --git a/redis-pro/Views/Components/AlertView.swift b/redis-pro/Views/Components/AlertView.swift deleted file mode 100644 index db016dd..0000000 --- a/redis-pro/Views/Components/AlertView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// Alert.swift -// redis-pro -// -// Created by chengpan on 2022/5/3. -// - -import Logging -import SwiftUI -import ComposableArchitecture -import RediStack - - - -struct AlertView: View { - let store:Store - private var logger = Logger(label: "alert-view") - - init(_ store: Store) { - logger.info("alert view init...") - self.store = store - AlertUtil.initial(store) - } - - var body: some View { - HStack{ - EmptyView() - } - .frame(height: 0) - .alert(self.store.scope(state: \.alert), dismiss: .clearAlert) - } -} - -class AlertUtil { - var store:Store - var viewStore: ViewStore - - static var instance:AlertUtil? - - private var logger = Logger(label: "alert-util") - - init(_ store: Store) { - logger.info("alert util init...") - - self.store = store - self.viewStore = ViewStore(store) - } - - static func initial(_ store: Store) { - if instance != nil { - return - } - - AlertUtil.instance = .init(store) - } - - static func show(_ title:String) { - DispatchQueue.main.async { - instance?.viewStore.send(.alert) - } - } - static func show(_ error:Error) { - var message = "" - if error is BizError { - message = (error as! BizError).message - } else if error is RedisError { - message = (error as! RedisError).message - } else { - message = "\(error)" - } - DispatchQueue.main.async { - instance?.viewStore.send(.error(message)) - } - } - - // 确认弹框 - static func confirm(_ title:String, message:String, primaryButton:String = "Ok", action: @escaping (() -> Void)) { - - DispatchQueue.main.async { - instance?.viewStore.send(.confirm(title, message, primaryButton, action)) - } - } - -} diff --git a/redis-pro/Views/Components/Form/FormText.swift b/redis-pro/Views/Components/Form/FormText.swift new file mode 100644 index 0000000..31a7917 --- /dev/null +++ b/redis-pro/Views/Components/Form/FormText.swift @@ -0,0 +1,22 @@ +// +// FormText.swift +// redis-pro +// +// Created by chengpan on 2023/9/10. +// + +import SwiftUI + +struct FormText: View { + var label: String? + var value: String? + + var body: some View { + HStack { + Text(label ?? "") + .foregroundColor(.secondary) + Text(value ?? "") + .foregroundColor(.primary) + } + } +} diff --git a/redis-pro/Views/Components/KeyObjectBar.swift b/redis-pro/Views/Components/KeyObjectBar.swift new file mode 100644 index 0000000..47c7bc8 --- /dev/null +++ b/redis-pro/Views/Components/KeyObjectBar.swift @@ -0,0 +1,24 @@ +// +// KeyObjectBar.swift +// redis-pro +// +// Created by chengpan on 2023/7/30. +// + +import SwiftUI +import Logging +import ComposableArchitecture + +struct KeyObjectBar: View { + var store:StoreOf + + let logger = Logger(label: "key-object-bar") + + var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + FormText(label: "Object Encoding:", value: viewStore.encoding) + .padding(EdgeInsets(top: 0, leading: MTheme.H_SPACING, bottom: 0, trailing: MTheme.H_SPACING)) + + } + } +} diff --git a/redis-pro/Views/Components/LoadingView.swift b/redis-pro/Views/Components/LoadingView.swift index c37f012..1e7172a 100644 --- a/redis-pro/Views/Components/LoadingView.swift +++ b/redis-pro/Views/Components/LoadingView.swift @@ -10,18 +10,17 @@ import SwiftUI import ComposableArchitecture struct LoadingView: View { - let store:Store + let store:StoreOf private var logger = Logger(label: "loading-view") - init(_ store: Store) { + init(_ store: Store) { logger.info("loading view init...") self.store = store -// LoadingUtil.initial(store) } var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in HStack{ EmptyView() } @@ -30,49 +29,3 @@ struct LoadingView: View { } } } - -// -//class LoadingUtil { -// var store:Store -// var viewStore: ViewStore -// static var globalContext:[String: ViewStore] = [:] -// -// static var instance:LoadingUtil? -// -// private var logger = Logger(label: "loading-util") -// -// init(_ store: Store) { -// logger.info("loading util init...") -// -// self.store = store -// self.viewStore = ViewStore(store) -// } -// -// static func setContext(_ id:String?, store: Store) { -// guard let id = id else { -// return -// } -// globalContext[id] = ViewStore(store) -// } -// -// static func initial(_ store: Store) { -// if instance != nil { -// return -// } -// -// LoadingUtil.instance = .init(store) -// } -// -// static func show() { -// DispatchQueue.main.async { -// instance?.viewStore.send(.show) -// } -// -// } -// static func hide() { -// DispatchQueue.main.async { -// instance?.viewStore.send(.hide) -// } -// } -// -//} diff --git a/redis-pro/Views/Components/Messages.swift b/redis-pro/Views/Components/Messages.swift index 004751c..9d720f1 100644 --- a/redis-pro/Views/Components/Messages.swift +++ b/redis-pro/Views/Components/Messages.swift @@ -20,7 +20,7 @@ class Messages { static let logger = Logger(label: "alert") - static func confirm(_ title:String, message:String = "", primaryButton:String = "Ok", action: @escaping (() -> Void)) { + static func confirm(_ title:String, message:String = "", primaryButton:String = "Ok", action: @escaping (() async -> Void)) { DispatchQueue.main.async { confirmAlert.messageText = StringHelper.ellipses(title, len: 100) @@ -34,7 +34,9 @@ class Messages { confirmAlert.beginSheetModal(for: NSApplication.shared.keyWindow!, completionHandler: { (modalResponse: NSApplication.ModalResponse) -> Void in if(modalResponse == NSApplication.ModalResponse.alertFirstButtonReturn){ self.logger.info("alert ok action") - action() + Task { + await action() + } } else if (modalResponse == NSApplication.ModalResponse.alertSecondButtonReturn) { self.logger.info("alert second action") } diff --git a/redis-pro/Views/Components/PageBar.swift b/redis-pro/Views/Components/PageBar.swift index 2b56da1..b1dcb91 100644 --- a/redis-pro/Views/Components/PageBar.swift +++ b/redis-pro/Views/Components/PageBar.swift @@ -10,13 +10,13 @@ import Logging import ComposableArchitecture struct PageBar: View { - var store:Store + var store:StoreOf let logger = Logger(label: "page-bar") var body: some View { - WithViewStore(store) { viewStore in - + WithViewStore(self.store, observe: { $0 }) { viewStore in + HStack(alignment:.center, spacing: 4) { if viewStore.showTotal { Text("Total: \(viewStore.total)") @@ -24,7 +24,7 @@ struct PageBar: View { .lineLimit(1) .multilineTextAlignment(.trailing) } - Picker("", selection: viewStore.binding(get: \.size, send: PageAction.updateSize)) { + Picker("", selection: viewStore.$size) { Text("10").tag(10) Text("50").tag(50) Text("100").tag(100) @@ -34,7 +34,7 @@ struct PageBar: View { HStack(alignment:.center, spacing: 2) { MIcon(icon: "chevron.left", disabled: !viewStore.hasPrev, action: {viewStore.send(.prevPage)}) - Text("\(viewStore.current)/\(viewStore.totalPage)") + Text("\(viewStore.current)/\(viewStore.totalPageText)") .font(MTheme.FONT_FOOTER) .multilineTextAlignment(.center) .lineLimit(1) diff --git a/redis-pro/Views/Components/ScanBar.swift b/redis-pro/Views/Components/ScanBar.swift deleted file mode 100644 index d9aacfd..0000000 --- a/redis-pro/Views/Components/ScanBar.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// ScanBar.swift -// redis-pro -// -// Created by chengpanwang on 2021/6/1. -// - -import Foundation -import SwiftUI -import Logging -import ComposableArchitecture - -struct ScanBar: View { - @EnvironmentObject var globalContext:GlobalContext - @ObservedObject var scanModel:ScanModel - var action: (() throws -> Void) - var totalLabel:String = "Total" - var showTotal:Bool = true - - - var store:Store? - let logger = Logger(label: "scan-bar") - - var body: some View { - - WithViewStore(store!) { viewStore in - HStack(alignment:.center, spacing: 4) { - if showTotal { - Text("\(totalLabel): \(scanModel.total)") - .font(MTheme.FONT_FOOTER) - .lineLimit(1) - .multilineTextAlignment(.trailing) - } - Picker("", selection: $scanModel.size) { - Text("10").tag(10) - Text("50").tag(50) - Text("100").tag(100) - Text("200").tag(200) - } - .onChange(of: scanModel.size, perform: { value in - logger.info("on scan size change: \(value)") - scanModel.reset() - doAction() - }) - .frame(width: 65) - - HStack(alignment:.center, spacing: 2) { - MIcon(icon: "chevron.left", disabled: !scanModel.hasPrev || globalContext.loading, action: onPrevPageAction) - Text("\(scanModel.current)") - .font(MTheme.FONT_FOOTER) - .multilineTextAlignment(.center) - .lineLimit(1) - .layoutPriority(1) - MIcon(icon: "chevron.right", disabled: !scanModel.hasNext || globalContext.loading, action: onNextPageAction) - } - .frame(minWidth: 60, idealWidth: 60) - } - } - } - - func onNextPageAction() -> Void { - self.scanModel.nextPage() - try! action() - } - - func onPrevPageAction() -> Void { - scanModel.prevPage() - try! action() - } - - func doAction() -> Void { - logger.info("scan bar on change action, scanModel: \(scanModel)") - scanModel.reset() - try! action() - } -} diff --git a/redis-pro/Views/Components/SearchBar.swift b/redis-pro/Views/Components/SearchBar.swift index 66d44f1..22fe1e4 100644 --- a/redis-pro/Views/Components/SearchBar.swift +++ b/redis-pro/Views/Components/SearchBar.swift @@ -15,8 +15,6 @@ struct SearchBar: View { var placeholder:String = "Search..." var onCommit: ((String) -> Void)? - - var store:Store? let logger = Logger(label: "search-bar") var body: some View { @@ -33,10 +31,3 @@ struct SearchBar: View { onCommit?(keywords) } } - -//struct SearchBar_Previews: PreviewProvider { -// @State static var keywords:String = "" -// static var previews: some View { -// SearchBar() -// } -//} diff --git a/redis-pro/Views/Components/Table/NTable.swift b/redis-pro/Views/Components/Table/NTable.swift index b9efd24..20c0e7d 100644 --- a/redis-pro/Views/Components/Table/NTable.swift +++ b/redis-pro/Views/Components/Table/NTable.swift @@ -13,7 +13,7 @@ import ComposableArchitecture struct NTableView: NSViewControllerRepresentable { - let store: Store + let store: StoreOf let logger = Logger(label: "ntable") @@ -63,16 +63,16 @@ class NTableController: NSViewController{ // drag let pasteboardType = NSPasteboard.PasteboardType.string - var viewStore: ViewStore + var viewStore: ViewStore var cancellables: Set = [] var observation: NSKeyValueObservation? let logger = Logger(label: "table-view-controller") - init(_ store: Store) { + init(_ store: StoreOf) { logger.info("table controller init...") - self.viewStore = ViewStore(store) + self.viewStore = ViewStore(store, observe: {$0}) // init table data self.datasource = self.viewStore.datasource @@ -80,7 +80,7 @@ class NTableController: NSViewController{ super.init(nibName: nil, bundle: nil) - // set table dark mode + // set table dark light mode self.view.appearance = NSApp.appearance self.tableView.appearance = NSApp.appearance } @@ -130,7 +130,7 @@ class NTableController: NSViewController{ // 监听默认选中 self.viewStore.publisher.defaultSelectIndex .sink(receiveValue: { - self.logger.debug("table store select index publisher, index: \($0)") + self.logger.info("table store select index publisher, index: \($0)") let selectIndex = min($0, self.viewStore.datasource.count - 1) self.arrayController.setSelectionIndex(selectIndex) }) @@ -139,7 +139,7 @@ class NTableController: NSViewController{ // 监听数据变化 self.viewStore.publisher.datasource .sink(receiveValue: { - self.logger.debug("table store data source publisher, data source length: \($0.count)") + self.logger.info("table store data source publisher, data source length: \($0.count)") let selectIndex = min(self.viewStore.selectIndex, $0.count - 1) self.datasource = $0 diff --git a/redis-pro/Views/HomeView.swift b/redis-pro/Views/HomeView.swift index 6b9a185..2f9a277 100644 --- a/redis-pro/Views/HomeView.swift +++ b/redis-pro/Views/HomeView.swift @@ -9,21 +9,18 @@ import SwiftUI import Logging import ComposableArchitecture + struct HomeView: View { - var store:Store let logger = Logger(label: "home-view") - - init(_ store:Store) { - logger.info("home view init...") - self.store = store - } - + var store:StoreOf + var body: some View { - WithViewStore(store.scope(state: \.title)) {viewStore in + WithViewStore(self.store, observe: { $0.title }) { viewStore in RedisKeysListView(store) .onAppear { logger.info("redis pro home view init complete") + viewStore.send(.initial) } .onDisappear { logger.info("redis pro home view destroy...") diff --git a/redis-pro/Views/IndexView.swift b/redis-pro/Views/IndexView.swift index 5816b38..28bd0bd 100644 --- a/redis-pro/Views/IndexView.swift +++ b/redis-pro/Views/IndexView.swift @@ -10,54 +10,50 @@ import Logging import ComposableArchitecture struct IndexView: View { - @State var appState:AppState? + @State var appState:AppStore.State? + var settingStore: StoreOf + let logger = Logger(label: "index-view") - private let logger = Logger(label: "index-view") - - init() { + init(settingStore: StoreOf) { logger.info("index view init ...") + self.settingStore = settingStore } var body: some View { if let state = appState { - let env = AppEnvironment() - let store: Store = Store( - initialState: state, - reducer: appReducer, - environment: .live(environment: env) - ) + let redisInstanceModel = RedisInstanceModel(RedisModel(), settingViewStore: ViewStore(settingStore, observe: { $0 })) + let redisClient = RediStackClient(RedisModel(), settingViewStore: ViewStore(settingStore, observe: { $0 })) + + let store: StoreOf = Store(initialState: state) { + AppStore() + } withDependencies: { + $0.redisInstance = redisInstanceModel + $0.redisClient = redisClient + } - WithViewStore(store.scope(state: \.isConnect)) { viewStore in + + WithViewStore(store, observe: { $0.isConnect }) {viewStore in ZStack { VStack { if (viewStore.state) { - HomeView(store) + HomeView(store: store) } else { LoginView(store: store) } - } -// AlertView(store.scope(state: \.appAlertState, action: AppAction.alertAction)) - LoadingView(store.scope(state: \.globalState, action: AppAction.globalAction)) + LoadingView(store.scope(state: \.globalState, action: AppStore.Action.globalAction)) }.onAppear { - env.redisInstanceModel.setAppStore(store) - viewStore.send(.initContext) + redisInstanceModel.setAppStore(store) } } } else { Spacer() .onAppear { - appState = AppState() + appState = AppStore.State() } } } } - -//struct IndexView_Previews: PreviewProvider { -// static var previews: some View { -// IndexView() -// } -//} diff --git a/redis-pro/Views/Login/LoginForm.swift b/redis-pro/Views/Login/LoginForm.swift index 0ac17cd..451ae5e 100644 --- a/redis-pro/Views/Login/LoginForm.swift +++ b/redis-pro/Views/Login/LoginForm.swift @@ -15,118 +15,218 @@ struct LoginForm: View { @Environment(\.openURL) var openURL - let store:Store + let store:StoreOf + var viewStore1: ViewStoreOf + + struct ViewState: Equatable { + @BindingViewState var name: String + @BindingViewState var host: String + @BindingViewState var port: Int + @BindingViewState var username: String + @BindingViewState var password: String + @BindingViewState var database: Int + @BindingViewState var sshHost: String + @BindingViewState var sshPort: Int + @BindingViewState var sshUser: String + @BindingViewState var sshPass: String + + init(bindingViewStore: BindingViewStore) { + self._name = bindingViewStore.$name + self._host = bindingViewStore.$host + self._port = bindingViewStore.$port + self._username = bindingViewStore.$username + self._password = bindingViewStore.$password + self._database = bindingViewStore.$database + self._sshHost = bindingViewStore.$sshHost + self._sshPort = bindingViewStore.$sshPort + self._sshUser = bindingViewStore.$sshUser + self._sshPass = bindingViewStore.$sshPass + } + } + + init(store: StoreOf) { + self.store = store + self.viewStore1 = ViewStore(store, observe: {$0}) + } - private func footer(_ viewStore: ViewStore) -> some View { - Section { - Divider() - .padding(.vertical, 8) - VStack(alignment: .center, spacing: 10) { - HStack(alignment: .center){ - if !viewStore.loading { - Button(action: { - guard let url = URL(string: Const.REPO_URL) else { - return + var footer: some View { + WithViewStore(self.store, observe: {$0} ) { viewStore in + Section { + Divider() + .padding(.vertical, 8) + VStack(alignment: .center, spacing: 10) { + HStack(alignment: .center){ + if !viewStore.loading { + Button(action: { + guard let url = URL(string: Const.REPO_URL) else { + return + } + openURL(url) + }) { + Image(systemName: "questionmark.circle") + .font(.system(size: 16.0)) } - openURL(url) - }) { - Image(systemName: "questionmark.circle") - .font(.system(size: 16.0)) + .buttonStyle(PlainButtonStyle()) } - .buttonStyle(PlainButtonStyle()) - } - - MLoading(text: viewStore.pingR, - loadingText: "Connecting...", - loading: viewStore.loading) + + MLoading(text: viewStore.pingR, + loadingText: "Connecting...", + loading: viewStore.loading) .help(viewStore.pingR) + + Spacer() + + MButton(text: "Connect" + , action: { + viewStore.send(.connect) + } + , disabled: viewStore.loading, keyEquivalent: .return) + .buttonStyle(BorderedButtonStyle()) + .keyboardShortcut(.defaultAction) + + } - Spacer() - - MButton(text: "Connect" - , action: { - viewStore.send(.connect) - } - , disabled: viewStore.loading, keyEquivalent: .return) - .buttonStyle(BorderedButtonStyle()) - .keyboardShortcut(.defaultAction) - - } - - HStack(alignment: .center){ - MButton(text: "Add to Favorites", action: { - viewStore.send(.add) - }) - Spacer() - MButton(text: "Save changes", action: { - viewStore.send(.save) - }) - Spacer() - MButton(text: "Test connection", action: { - viewStore.send(.testConnect) - }, disabled: viewStore.loading) + HStack(alignment: .center){ + MButton(text: "Add to Favorites", action: { + viewStore.send(.add) + }) + Spacer() + MButton(text: "Save changes", action: { + viewStore.send(.save) + }) + Spacer() + MButton(text: "Test connection", action: { + viewStore.send(.testConnect) + }, disabled: viewStore.loading) + } } } } } - private func tcpTab(_ viewStore: ViewStore) -> some View { - Form { - Section { - VStack(alignment: .leading, spacing: 14) { - FormItemText(label: "Name", placeholder: "name", value: viewStore.binding(\.$name)) - FormItemText(label: "Host", placeholder: "host", value: viewStore.binding(\.$host)) - FormItemInt(label: "Port", placeholder: "port", value: viewStore.binding(\.$port)) - FormItemText(label: "User", placeholder: "default", value: viewStore.binding(\.$username)) - FormItemPassword(label: "Password", value: viewStore.binding(\.$password)) - FormItemInt(label: "Database", value: viewStore.binding(\.$database)) + var tcpView: some View { + WithViewStore(self.store, observe: ViewState.init) { viewStore in + Form { + VStack { + Section { + VStack(alignment: .leading, spacing: 14) { + FormItemText(label: "Name", placeholder: "name", value: viewStore.$name) + FormItemText(label: "Host", placeholder: "host", value: viewStore.$host) + FormItemInt(label: "Port", placeholder: "port", value: viewStore.$port) + FormItemText(label: "User", placeholder: "default", value: viewStore.$username) + FormItemPassword(label: "Password", value: viewStore.$password) + FormItemInt(label: "Database", value: viewStore.$database) + } + } + + footer } } - - footer(viewStore) + .padding(.horizontal, 18.0) } - .padding(.horizontal, 18.0) - .tabItem { - Text("TCP/IP") - }.tag(RedisConnectionTypeEnum.TCP.rawValue) } - private func sshTab(_ viewStore: ViewStore) -> some View { - Form { - Section { - VStack(alignment: .leading, spacing: 12) { - FormItemText(label: "Name", placeholder: "name", value: viewStore.binding(\.$name)) - FormItemText(label: "Host", placeholder: "host", value: viewStore.binding(\.$host)) - FormItemInt(label: "Port", placeholder: "port", value: viewStore.binding(\.$port)) - FormItemText(label: "User", placeholder: "default", value: viewStore.binding(\.$username)) - FormItemPassword(label: "Password", value: viewStore.binding(\.$password)) - FormItemInt(label: "Database", value: viewStore.binding(\.$database)) - } - } - - Section() { - Divider().padding(.vertical, 2) +// private func tcpTab() -> some View { +// WithViewStore(self.store, observe: ViewState.init) { viewStore in +// Form { +// Section { +// VStack(alignment: .leading, spacing: 14) { +// FormItemText(label: "Name", placeholder: "name", value: viewStore.$name) +// FormItemText(label: "Host", placeholder: "host", value: viewStore.$host) +// FormItemInt(label: "Port", placeholder: "port", value: viewStore.$port) +// FormItemText(label: "User", placeholder: "default", value: viewStore.$username) +// FormItemPassword(label: "Password", value: viewStore.$password) +// FormItemInt(label: "Database", value: viewStore.$database) +// } +// } +// +// footer(viewStore) +// } +// .padding(.horizontal, 18.0) +// .tabItem { +// Text("TCP/IP") +// } +// .tag(RedisConnectionTypeEnum.TCP.rawValue) +// } +// } +// + + var sshTab: some View { + WithViewStore(self.store, observe: ViewState.init) { viewStore in + Form { + VStack { + Section { + VStack(alignment: .leading, spacing: 12) { + FormItemText(label: "Name", placeholder: "name", value: viewStore.$name) + FormItemText(label: "Host", placeholder: "host", value: viewStore.$host) + FormItemInt(label: "Port", placeholder: "port", value: viewStore.$port) + FormItemText(label: "User", placeholder: "default", value: viewStore.$username) + FormItemPassword(label: "Password", value: viewStore.$password) + FormItemInt(label: "Database", value: viewStore.$database) + } + } - VStack(alignment: .leading, spacing: 12) { - FormItemText(label: "SSH Host", placeholder: "name", value: viewStore.binding(\.$sshHost)) - FormItemInt(label: "SSH Port", placeholder: "port", value: viewStore.binding(\.$sshPort)) - FormItemText(label: "SSH User", placeholder: "host", value: viewStore.binding(\.$sshUser)) - FormItemPassword(label: "SSH Pass", value: viewStore.binding(\.$sshPass)) + Divider().padding(.vertical, 2) + Section { + VStack(alignment: .leading, spacing: 12) { + FormItemText(label: "SSH Host", placeholder: "name", value: viewStore.$sshHost) + FormItemInt(label: "SSH Port", placeholder: "port", value: viewStore.$sshPort) + FormItemText(label: "SSH User", placeholder: "host", value: viewStore.$sshUser) + FormItemPassword(label: "SSH Pass", value: viewStore.$sshPass) + } } + + footer } - footer(viewStore) + } + .padding(.horizontal, 18.0) } - .padding(.horizontal, 18.0) - .tabItem { - Label("SSH", systemImage: "bolt.fill") - }.tag(RedisConnectionTypeEnum.SSH.rawValue) } +// private func sshTab(_ viewStore: ViewStoreOf) -> some View { +// Form { +// Section { +// VStack(alignment: .leading, spacing: 12) { +// FormItemText(label: "Name", placeholder: "name", value: viewStore.$name) +// FormItemText(label: "Host", placeholder: "host", value: viewStore.$host) +// FormItemInt(label: "Port", placeholder: "port", value: viewStore.$port) +// FormItemText(label: "User", placeholder: "default", value: viewStore.$username) +// FormItemPassword(label: "Password", value: viewStore.$password) +// FormItemInt(label: "Database", value: viewStore.$database) +// } +// } +// +// Section() { +// Divider().padding(.vertical, 2) +// +// VStack(alignment: .leading, spacing: 12) { +// FormItemText(label: "SSH Host", placeholder: "name", value: viewStore.$sshHost) +// FormItemInt(label: "SSH Port", placeholder: "port", value: viewStore.$sshPort) +// FormItemText(label: "SSH User", placeholder: "host", value: viewStore.$sshUser) +// FormItemPassword(label: "SSH Pass", value: viewStore.$sshPass) +// } +// } +// footer(viewStore) +// } +// .padding(.horizontal, 18.0) +// .tabItem { +// Label("SSH", systemImage: "bolt.fill") +// }.tag(RedisConnectionTypeEnum.SSH.rawValue) +// } var body: some View { - WithViewStore(store) { viewStore in - TabView(selection: viewStore.binding(\.$connectionType)) { - tcpTab(viewStore) - sshTab(viewStore) + WithViewStore(self.store, observe: { $0 }) { viewStore in + TabView(selection: viewStore.$connectionType) { + // tcp + tcpView + .tabItem { + Text("TCP/IP") + }.tag(RedisConnectionTypeEnum.TCP.rawValue) + + // ssh + sshTab + .tabItem { + Label("SSH", systemImage: "bolt.fill") + }.tag(RedisConnectionTypeEnum.SSH.rawValue) } .padding(20.0) .frame(width: 500.0, height: viewStore.height) diff --git a/redis-pro/Views/Login/LoginView.swift b/redis-pro/Views/Login/LoginView.swift index 22335e2..74c1f6b 100644 --- a/redis-pro/Views/Login/LoginView.swift +++ b/redis-pro/Views/Login/LoginView.swift @@ -11,17 +11,18 @@ import RediStack import Logging import ComposableArchitecture + struct LoginView: View { let logger = Logger(label: "login-view") - let store: Store + let store: StoreOf - init(store: Store) { + init(store: StoreOf) { logger.info("login view init...") self.store = store } var body: some View { - RedisListView(store: store.scope(state: \.favoriteState, action: AppAction.favoriteAction)) + RedisListView(store: store.scope(state: \.favoriteState, action: AppStore.Action.favoriteAction)) .onDisappear { logger.info("redis pro login view destroy...") } diff --git a/redis-pro/Views/Login/RedisListView.swift b/redis-pro/Views/Login/RedisListView.swift index a389606..9a50294 100644 --- a/redis-pro/Views/Login/RedisListView.swift +++ b/redis-pro/Views/Login/RedisListView.swift @@ -12,16 +12,16 @@ import ComposableArchitecture struct RedisListView: View { let logger = Logger(label: "redis-login") - var store:Store + var store:StoreOf var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in HSplitView { VStack(alignment: .leading, spacing: 0) { NTableView( - store: store.scope(state: \.tableState, action: FavoriteAction.tableAction) + store: store.scope(state: \.tableState, action: FavoriteStore.Action.tableAction) ) // footer @@ -41,27 +41,14 @@ struct RedisListView: View { .onAppear{ onLoad(viewStore) } - LoginForm(store: store.scope(state: \.loginState, action: FavoriteAction.loginAction)) - .frame(minWidth: 700, maxWidth: .infinity, minHeight: 520, maxHeight: .infinity) + LoginForm(store: store.scope(state: \.loginState, action: FavoriteStore.Action.loginAction)) + .frame(minWidth: 800, maxWidth: .infinity, minHeight: 520, maxHeight: .infinity) } } } - func onLoad(_ viewStore:ViewStore) { + func onLoad(_ viewStore:ViewStore) { viewStore.send(.getAll) viewStore.send(.initDefaultSelection) } } - - -//struct RedisInstanceList_Previews: PreviewProvider { -// private static var redisFavoriteModel: RedisFavoriteModel = RedisFavoriteModel() -// static var previews: some View { -// RedisListView() -// .environmentObject(redisFavoriteModel) -// .onAppear{ -// redisFavoriteModel.loadAll() -// } -// -// } -//} diff --git a/redis-pro/Views/RedisEditorView/HashEditorView.swift b/redis-pro/Views/RedisEditorView/HashEditorView.swift index 05a1988..7c3f2b9 100644 --- a/redis-pro/Views/RedisEditorView/HashEditorView.swift +++ b/redis-pro/Views/RedisEditorView/HashEditorView.swift @@ -10,25 +10,33 @@ import Logging import ComposableArchitecture struct HashEditorView: View { - var store: Store + var store: StoreOf + var keyObjectStore: StoreOf private let logger = Logger(label: "redis-hash-editor") + init(store: StoreOf) { + self.store = store.scope(state: \.hashValueState, action: ValueStore.Action.hashValueAction) + self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction) + } + + var body: some View { - WithViewStore(store) {viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in VStack(alignment: .leading, spacing: 0) { HStack(alignment: .center , spacing: MTheme.H_SPACING) { IconButton(icon: "plus", name: "Add", action: {viewStore.send(.addNew)}) IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))}) SearchBar(placeholder: "Search field...", onCommit: {viewStore.send(.search($0))}) - PageBar(store: store.scope(state: \.pageState, action: HashValueAction.pageAction)) + PageBar(store: store.scope(state: \.pageState, action: HashValueStore.Action.pageAction)) } .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) - NTableView(store: store.scope(state: \.tableState, action: HashValueAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: HashValueStore.Action.tableAction)) // footer HStack(alignment: .center, spacing: 4) { + KeyObjectBar(store: keyObjectStore) Spacer() IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)}) @@ -39,12 +47,12 @@ struct HashEditorView: View { logger.info("redis hash editor view appear ...") viewStore.send(.initial) } - .sheet(isPresented: viewStore.binding(\.$editModalVisible), onDismiss: { + .sheet(isPresented: viewStore.$editModalVisible, onDismiss: { }) { ModalView("Edit hash entry", action: {viewStore.send(.submit)}) { VStack(alignment:.leading, spacing: 8) { - FormItemText(placeholder: "Field", editable: viewStore.isNew, value: viewStore.binding(\.$field)) - FormItemTextArea(placeholder: "Value", value: viewStore.binding(\.$value)) + FormItemText(placeholder: "Field", editable: viewStore.isNew, value: viewStore.$field) + FormItemTextArea(placeholder: "Value", value: viewStore.$value) } } } @@ -52,10 +60,3 @@ struct HashEditorView: View { } } - -//struct KeyValueRowEditorView_Previews: PreviewProvider { -// static var redisKeyModel:RedisKeyModel = RedisKeyModel("tes", type: "string") -// static var previews: some View { -// HashEditorView(redisKeyModel: redisKeyModel) -// } -//} diff --git a/redis-pro/Views/RedisEditorView/ListEditorView.swift b/redis-pro/Views/RedisEditorView/ListEditorView.swift index 362911c..7c5ebb6 100644 --- a/redis-pro/Views/RedisEditorView/ListEditorView.swift +++ b/redis-pro/Views/RedisEditorView/ListEditorView.swift @@ -11,11 +11,17 @@ import ComposableArchitecture struct ListEditorView: View { - var store:Store + var store:StoreOf + var keyObjectStore: StoreOf let logger = Logger(label: "redis-list-editor") + init(store: StoreOf) { + self.store = store.scope(state: \.listValueState, action: ValueStore.Action.listValueAction) + self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction) + } + var body: some View { - WithViewStore(store) {viewStore in + WithViewStore(self.store, observe: { $0 }) { viewStore in VStack(alignment: .leading, spacing: 0) { HStack(alignment: .center , spacing: 4) { IconButton(icon: "plus", name: "Add head", action: { viewStore.send(.addNew(-1))}) @@ -23,25 +29,26 @@ struct ListEditorView: View { IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))}) Spacer() - PageBar(store: store.scope(state: \.pageState, action: ListValueAction.pageAction)) + PageBar(store: store.scope(state: \.pageState, action: ListValueStore.Action.pageAction)) } .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) - NTableView(store: store.scope(state: \.tableState, action: ListValueAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: ListValueStore.Action.tableAction)) // footer HStack(alignment: .center, spacing: MTheme.H_SPACING) { + KeyObjectBar(store: keyObjectStore) Spacer() IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)}) } .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) } - .sheet(isPresented: viewStore.binding(\.$editModalVisible), onDismiss: { + .sheet(isPresented: viewStore.$editModalVisible, onDismiss: { }) { ModalView("Edit list item", action: {viewStore.send(.submit)}) { VStack(alignment:.leading, spacing: MTheme.V_SPACING) { - FormItemTextArea(label: "", placeholder: "value", value: viewStore.binding(\.$editValue)) + FormItemTextArea(label: "", placeholder: "value", value: viewStore.$editValue) } } diff --git a/redis-pro/Views/RedisEditorView/RedisValueEditView.swift b/redis-pro/Views/RedisEditorView/RedisValueEditView.swift index b2ac61c..d770681 100644 --- a/redis-pro/Views/RedisEditorView/RedisValueEditView.swift +++ b/redis-pro/Views/RedisEditorView/RedisValueEditView.swift @@ -11,31 +11,31 @@ import ComposableArchitecture struct RedisValueEditView: View { - var store: Store + var store: StoreOf let logger = Logger(label: "redis-value-edit-view") var body: some View { - WithViewStore(store.scope(state: \.keyState)) {viewStore in + WithViewStore(self.store, observe: { $0.keyState }) { viewStore in VStack(alignment: .leading, spacing: 4) { if viewStore.type == RedisKeyTypeEnum.STRING.rawValue { - StringEditorView(store: store.scope(state: \.stringValueState, action: ValueAction.stringValueAction)) + StringEditorView(store: store) } // HASH else if viewStore.type == RedisKeyTypeEnum.HASH.rawValue { - HashEditorView(store: store.scope(state: \.hashValueState, action: ValueAction.hashValueAction)) + HashEditorView(store: store) } // LIST else if viewStore.type == RedisKeyTypeEnum.LIST.rawValue { - ListEditorView(store: store.scope(state: \.listValueState, action: ValueAction.listValueAction)) + ListEditorView(store: store) } // SET else if viewStore.type == RedisKeyTypeEnum.SET.rawValue { - SetEditorView(store: store.scope(state: \.setValueState, action: ValueAction.setValueAction)) + SetEditorView(store: store) } // ZSET else if viewStore.type == RedisKeyTypeEnum.ZSET.rawValue { - ZSetEditorView(store: store.scope(state: \.zsetValueState, action: ValueAction.zsetValueAction)) + ZSetEditorView(store: store) } else { EmptyView() } diff --git a/redis-pro/Views/RedisEditorView/RedisValueHeaderView.swift b/redis-pro/Views/RedisEditorView/RedisValueHeaderView.swift index 2071c64..da90c51 100644 --- a/redis-pro/Views/RedisEditorView/RedisValueHeaderView.swift +++ b/redis-pro/Views/RedisEditorView/RedisValueHeaderView.swift @@ -11,12 +11,12 @@ import ComposableArchitecture struct RedisValueHeaderView: View { - var store: Store + var store: StoreOf let logger = Logger(label: "redis-value-header") - private func ttlView(_ viewStore: ViewStore) -> some View { + private func ttlView(_ viewStore: ViewStore) -> some View { HStack(alignment:.center, spacing: 0) { - FormItemInt(label: "TTL(s)", value: viewStore.binding(get: \.ttl, send: KeyAction.setTtl), suffix: "square.and.pencil", onCommit: { viewStore.send(.saveTtl)}) + FormItemInt(label: "TTL(s)", value: viewStore.binding(get: \.ttl, send: KeyStore.Action.setTtl), suffix: "square.and.pencil", onCommit: { viewStore.send(.saveTtl)}) .disabled(viewStore.isNew) .help("HELP_TTL") .frame(width: 260) @@ -24,14 +24,14 @@ struct RedisValueHeaderView: View { } var body: some View { - WithViewStore(store) {viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in HStack(alignment: .center, spacing: 6) { - FormItemText(label: "Key", labelWidth: 40, required: true, editable: viewStore.isNew, value: viewStore.binding(get: \.key, send: KeyAction.setKey)) + FormItemText(label: "Key", labelWidth: 40, required: true, editable: viewStore.isNew, value: viewStore.binding(get: \.key, send: KeyStore.Action.setKey)) .frame(maxWidth: .infinity) Spacer() - RedisKeyTypePicker(label: "Type", value: viewStore.binding(get: \.type, send: KeyAction.setType), disabled: !viewStore.isNew) + RedisKeyTypePicker(label: "Type", value: viewStore.binding(get: \.type, send: KeyStore.Action.setType), disabled: !viewStore.isNew) ttlView(viewStore) } } diff --git a/redis-pro/Views/RedisEditorView/RedisValueView.swift b/redis-pro/Views/RedisEditorView/RedisValueView.swift index a435ec4..0ef6f5f 100644 --- a/redis-pro/Views/RedisEditorView/RedisValueView.swift +++ b/redis-pro/Views/RedisEditorView/RedisValueView.swift @@ -9,11 +9,11 @@ import SwiftUI import ComposableArchitecture struct RedisValueView: View { - var store: Store + var store: StoreOf var body: some View { VStack(alignment: .leading, spacing: 0) { - RedisValueHeaderView(store: store.scope(state: \.keyState, action: ValueAction.keyAction)) + RedisValueHeaderView(store: store.scope(state: \.keyState, action: ValueStore.Action.keyAction)) .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) Rectangle().frame(height: 1) .padding(.horizontal, 0).foregroundColor(Color.gray.opacity(0.1)) diff --git a/redis-pro/Views/RedisEditorView/SetEditorView.swift b/redis-pro/Views/RedisEditorView/SetEditorView.swift index d63b467..9a11e19 100644 --- a/redis-pro/Views/RedisEditorView/SetEditorView.swift +++ b/redis-pro/Views/RedisEditorView/SetEditorView.swift @@ -11,11 +11,17 @@ import ComposableArchitecture struct SetEditorView: View { - var store:Store + var store:StoreOf + var keyObjectStore: StoreOf let logger = Logger(label: "redis-set-editor") + init(store: StoreOf) { + self.store = store.scope(state: \.setValueState, action: ValueStore.Action.setValueAction) + self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction) + } + var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) { viewStore in VStack(alignment: .leading, spacing: 0) { HStack(alignment: .center , spacing: 4) { @@ -23,25 +29,26 @@ struct SetEditorView: View { IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))}) SearchBar(placeholder: "Search element...", onCommit: {viewStore.send(.search($0))}) - PageBar(store: store.scope(state: \.pageState, action: SetValueAction.pageAction)) + PageBar(store: store.scope(state: \.pageState, action: SetValueStore.Action.pageAction)) } .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) - NTableView(store: store.scope(state: \.tableState, action: SetValueAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: SetValueStore.Action.tableAction)) // footer HStack(alignment: .center, spacing: MTheme.H_SPACING) { + KeyObjectBar(store: keyObjectStore) Spacer() IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)}) } .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) } - .sheet(isPresented: viewStore.binding(\.$editModalVisible), onDismiss: { + .sheet(isPresented: viewStore.$editModalVisible, onDismiss: { }) { ModalView("Edit set element", action: {viewStore.send(.submit)}) { VStack(alignment:.leading, spacing: MTheme.V_SPACING) { - FormItemTextArea(placeholder: "value", value: viewStore.binding(\.$editValue)) + FormItemTextArea(placeholder: "value", value: viewStore.$editValue) } } } diff --git a/redis-pro/Views/RedisEditorView/StringEditorView.swift b/redis-pro/Views/RedisEditorView/StringEditorView.swift index 73952d7..b0ccd9a 100644 --- a/redis-pro/Views/RedisEditorView/StringEditorView.swift +++ b/redis-pro/Views/RedisEditorView/StringEditorView.swift @@ -10,24 +10,31 @@ import Logging import ComposableArchitecture struct StringEditorView: View { - var store: Store + var store: StoreOf + var keyObjectStore: StoreOf private let logger = Logger(label: "string-editor") + init(store: StoreOf) { + self.store = store.scope(state: \.stringValueState, action: ValueStore.Action.stringValueAction) + self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction) + } + var body: some View { - WithViewStore(store) {viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: MTheme.V_SPACING){ - // text editor - MTextEditor(text: viewStore.binding(\.$text)) + MTextEditor(text: viewStore.$text) } .background(Color.init(NSColor.textBackgroundColor)) // footer HStack(alignment: .center, spacing: MTheme.H_SPACING) { + KeyObjectBar(store: keyObjectStore) + if (viewStore.isIntactString) { - Text("length: \(viewStore.length)") + FormText(label: "Length:", value: "\(viewStore.length)") } else { - Text("range: 0~\(viewStore.stringMaxLength + 1) / \(viewStore.length)") + Text("Range: 0~\(viewStore.stringMaxLength + 1) / \(viewStore.length)") MButton(text: "Show Intact", action: {viewStore.send(.getIntactString)}) } diff --git a/redis-pro/Views/RedisEditorView/ZSetEditorView.swift b/redis-pro/Views/RedisEditorView/ZSetEditorView.swift index 0dd4d95..c780ad4 100644 --- a/redis-pro/Views/RedisEditorView/ZSetEditorView.swift +++ b/redis-pro/Views/RedisEditorView/ZSetEditorView.swift @@ -11,37 +11,45 @@ import ComposableArchitecture struct ZSetEditorView: View { - var store:Store + var store:StoreOf + var keyObjectStore: StoreOf let logger = Logger(label: "redis-set-editor") + + init(store: StoreOf) { + self.store = store.scope(state: \.zsetValueState, action: ValueStore.Action.zsetValueAction) + self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction) + } + var body: some View { - - WithViewStore(store) { viewStore in + + WithViewStore(self.store, observe: { $0 }) { viewStore in VStack(alignment: .leading, spacing: 0) { HStack(alignment: .center , spacing: 4) { IconButton(icon: "plus", name: "Add", action: {viewStore.send(.addNew)}) IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))}) SearchBar(placeholder: "Search element...", onCommit: {viewStore.send(.search($0))}) - PageBar(store: store.scope(state: \.pageState, action: ZSetValueAction.pageAction)) + PageBar(store: store.scope(state: \.pageState, action: ZSetValueStore.Action.pageAction)) } .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) - NTableView(store: store.scope(state: \.tableState, action: ZSetValueAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: ZSetValueStore.Action.tableAction)) // footer HStack(alignment: .center, spacing: 4) { + KeyObjectBar(store: keyObjectStore) Spacer() IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)}) } .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)) } - .sheet(isPresented: viewStore.binding(\.$editModalVisible), onDismiss: { + .sheet(isPresented: viewStore.$editModalVisible, onDismiss: { }) { ModalView("Edit zset element", action: {viewStore.send(.submit)}) { VStack(alignment:.leading, spacing: 8) { - FormItemDouble(label: "Score", placeholder: "score", value: viewStore.binding(\.$editScore)) - FormItemTextArea(label: "Value", placeholder: "value", value: viewStore.binding(\.$editValue)) + FormItemDouble(label: "Score", placeholder: "score", value: viewStore.$editScore) + FormItemTextArea(label: "Value", placeholder: "value", value: viewStore.$editValue) } } } diff --git a/redis-pro/Views/Sidebar/DatabasePicker.swift b/redis-pro/Views/Sidebar/DatabasePicker.swift index 70b5b8a..3af7700 100644 --- a/redis-pro/Views/Sidebar/DatabasePicker.swift +++ b/redis-pro/Views/Sidebar/DatabasePicker.swift @@ -10,10 +10,10 @@ import ComposableArchitecture struct DatabasePicker: View { - var store:Store + var store:StoreOf var body: some View { - WithViewStore(store) {viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in Menu(content: { ForEach(0 ..< viewStore.databases, id: \.self) { item in diff --git a/redis-pro/Views/Sidebar/RedisKeysListView.swift b/redis-pro/Views/Sidebar/RedisKeysListView.swift index 408c415..9b82b86 100644 --- a/redis-pro/Views/Sidebar/RedisKeysListView.swift +++ b/redis-pro/Views/Sidebar/RedisKeysListView.swift @@ -11,16 +11,16 @@ import ComposableArchitecture struct RedisKeysListView: View { - var appStore:Store - var store:Store + var appStore:StoreOf + var store:Store let logger = Logger(label: "redis-key-list-view") - init(_ store:Store) { + init(_ store:StoreOf) { self.appStore = store - self.store = store.scope(state: \.redisKeysState, action: AppAction.redisKeysAction) + self.store = store.scope(state: \.redisKeysState, action: AppStore.Action.redisKeysAction) } - private func sidebarHeader(_ viewStore: ViewStore) -> some View { + private func sidebarHeader(_ viewStore: ViewStore) -> some View { VStack(alignment: .center, spacing: 0) { VStack(alignment: .center, spacing: 2) { // redis search ... @@ -34,7 +34,7 @@ struct RedisKeysListView: View { ,action: { viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))}) Spacer() - DatabasePicker(store: store.scope(state: \.databaseState, action: RedisKeysAction.databaseAction)) + DatabasePicker(store: store.scope(state: \.databaseState, action: RedisKeysStore.Action.databaseAction)) } } .padding(EdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)) @@ -43,7 +43,7 @@ struct RedisKeysListView: View { } } - private func sidebarFoot(_ viewStore: ViewStore) -> some View { + private func sidebarFoot(_ viewStore: ViewStore) -> some View { HStack(alignment: .center, spacing: 4) { Menu(content: { Button("Redis Info", action: { viewStore.send(.redisSystemAction(.setSystemView(.REDIS_INFO))) }) @@ -68,16 +68,16 @@ struct RedisKeysListView: View { Text("dbsize: \(viewStore.dbsize)") .font(MTheme.FONT_FOOTER) .lineLimit(1) - PageBar(store: store.scope(state: \.pageState, action: RedisKeysAction.pageAction)) + PageBar(store: store.scope(state: \.pageState, action: RedisKeysStore.Action.pageAction)) } } - private func sidebar(_ viewStore: ViewStore) -> some View { + private func sidebar(_ viewStore: ViewStore) -> some View { VStack(alignment: .leading, spacing: 0) { // header area sidebarHeader(viewStore) - NTableView(store: store.scope(state: \.tableState, action: RedisKeysAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: RedisKeysStore.Action.tableAction)) // footer sidebarFoot(viewStore) @@ -87,7 +87,7 @@ struct RedisKeysListView: View { } var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in HSplitView { // sidebar sidebar(viewStore) @@ -96,13 +96,13 @@ struct RedisKeysListView: View { .layoutPriority(0) // content -// MainView(store: store.scope(state: \.valueState, action: RedisKeysAction.valueAction)) +// MainView(store: store.scope(state: \.valueState, action: RedisKeysStore.Action.valueAction)) VStack(alignment: .leading, spacing: 0){ if viewStore.mainViewType == MainViewTypeEnum.EDITOR { - RedisValueView(store: store.scope(state: \.valueState, action: RedisKeysAction.valueAction)) + RedisValueView(store: store.scope(state: \.valueState, action: RedisKeysStore.Action.valueAction)) } else if viewStore.mainViewType == MainViewTypeEnum.SYSTEM { - RedisSystemView(store: store.scope(state: \.redisSystemState, action: RedisKeysAction.redisSystemAction)) + RedisSystemView(store: store.scope(state: \.redisSystemState, action: RedisKeysStore.Action.redisSystemAction)) } else { EmptyView() } @@ -114,7 +114,7 @@ struct RedisKeysListView: View { .layoutPriority(1) } .onAppear{ - viewStore.send(.initial) +// viewStore.send(.setDBSize(20)) } .sheet(isPresented: viewStore.binding(get: \.renameState.visible, send: .renameAction(.hide))) { ModalView("Rename", width: MTheme.DIALOG_W, height: 100, action: {viewStore.send(.renameAction(.submit))}) { @@ -126,17 +126,3 @@ struct RedisKeysListView: View { } } } - -// -//func testData() -> [NSRedisKeyModel] { -// let redisKeys:[NSRedisKeyModel] = [NSRedisKeyModel](repeating: NSRedisKeyModel(UUID().uuidString.lowercased(), type: "string"), count: 0) -// return redisKeys -//} - -//struct RedisKeysList_Previews: PreviewProvider { -// static var redisInstanceModel:RedisInstanceModel = RedisInstanceModel(redisModel: RedisModel()) -// static var previews: some View { -// RedisKeysListView(redisKeyModels: testData(), selectedRedisKeyIndex: -1) -// .environmentObject(redisInstanceModel) -// } -//} diff --git a/redis-pro/Views/System/ClientsListView.swift b/redis-pro/Views/System/ClientsListView.swift index 4e299d3..e288c30 100644 --- a/redis-pro/Views/System/ClientsListView.swift +++ b/redis-pro/Views/System/ClientsListView.swift @@ -11,13 +11,13 @@ import ComposableArchitecture struct ClientsListView: View { - var store:Store + var store:StoreOf var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in VStack(alignment: .leading, spacing: MTheme.V_SPACING) { - NTableView(store: store.scope(state: \.tableState, action: ClientListAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: ClientListStore.Action.tableAction)) HStack(alignment: .center , spacing: 8) { Spacer() diff --git a/redis-pro/Views/System/LuaView.swift b/redis-pro/Views/System/LuaView.swift index ec01eeb..e332aee 100644 --- a/redis-pro/Views/System/LuaView.swift +++ b/redis-pro/Views/System/LuaView.swift @@ -11,11 +11,11 @@ import ComposableArchitecture struct LuaView: View { - var store:Store + var store:StoreOf let logger = Logger(label: "lua-view") var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in VStack(alignment: .leading, spacing: MTheme.V_SPACING) { // header @@ -28,7 +28,7 @@ struct LuaView: View { VSplitView { VStack(alignment: .leading, spacing: MTheme.V_SPACING){ // text editor - MTextEditor(text: viewStore.binding(\.$lua)) + MTextEditor(text: viewStore.$lua) // btns HStack(alignment: .center, spacing: MTheme.H_SPACING) { @@ -42,7 +42,7 @@ struct LuaView: View { } - MTextEditor(text: viewStore.binding(\.$evalResult)) + MTextEditor(text: viewStore.$evalResult) } } diff --git a/redis-pro/Views/System/RedisConfigView.swift b/redis-pro/Views/System/RedisConfigView.swift index 05e6065..3245d0b 100644 --- a/redis-pro/Views/System/RedisConfigView.swift +++ b/redis-pro/Views/System/RedisConfigView.swift @@ -11,12 +11,12 @@ import ComposableArchitecture struct RedisConfigView: View { - var store:Store + var store:StoreOf let logger = Logger(label: "redis-config-view") var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in VStack(alignment: .leading, spacing: MTheme.V_SPACING) { HStack(alignment: .center , spacing: MTheme.H_SPACING) { @@ -27,18 +27,18 @@ struct RedisConfigView: View { .help("REDIS_CONFIG_REWRITE") }.padding(MTheme.HEADER_PADDING) - NTableView(store: store.scope(state: \.tableState, action: RedisConfigAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: RedisConfigStore.Action.tableAction)) HStack(alignment: .center , spacing: MTheme.H_SPACING) { Spacer() MButton(text: "Refresh", action: {viewStore.send(.refresh)}) } } - .sheet(isPresented: viewStore.binding(\.$editModalVisible), onDismiss: { + .sheet(isPresented: viewStore.$editModalVisible, onDismiss: { }) { ModalView("Edit Config Key: \(viewStore.editKey)", action: {viewStore.send(.submit)}) { VStack(alignment:.leading, spacing: MTheme.V_SPACING) { - MTextView(text: viewStore.binding(\.$editValue)) + MTextView(text: viewStore.$editValue) } .frame(minWidth:500, minHeight:300) } diff --git a/redis-pro/Views/System/RedisInfoView.swift b/redis-pro/Views/System/RedisInfoView.swift index a0a3cff..440c100 100644 --- a/redis-pro/Views/System/RedisInfoView.swift +++ b/redis-pro/Views/System/RedisInfoView.swift @@ -10,14 +10,14 @@ import Logging import ComposableArchitecture struct RedisInfoView: View { - var store:Store + var store:StoreOf var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in VStack(alignment: .leading, spacing: MTheme.V_SPACING) { - TabView(selection: viewStore.binding(get: \.section, send: RedisInfoAction.setTab)) { + TabView(selection: viewStore.binding(get: \.section, send: RedisInfoStore.Action.setTab)) { ForEach(viewStore.redisInfoModels.indices, id:\.self) { index in - NTableView(store: store.scope(state: \.tableState, action: RedisInfoAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: RedisInfoStore.Action.tableAction)) .tabItem { Text(viewStore.redisInfoModels[index].section) } diff --git a/redis-pro/Views/System/RedisSystemView.swift b/redis-pro/Views/System/RedisSystemView.swift index 17935a8..1d687b3 100644 --- a/redis-pro/Views/System/RedisSystemView.swift +++ b/redis-pro/Views/System/RedisSystemView.swift @@ -10,20 +10,22 @@ import Logging import ComposableArchitecture struct RedisSystemView: View { - var store:Store + var store:StoreOf var body: some View { - WithViewStore(store.scope(state: \.systemView)){ viewStore in + WithViewStore(self.store, observe: { $0.systemView }) {viewStore in + +// WithViewStore(store.scope(state: \.systemView)){ viewStore in if viewStore.state == RedisSystemViewTypeEnum.REDIS_INFO { - RedisInfoView(store: store.scope(state: \.redisInfoState, action: RedisSystemAction.redisInfoAction)) + RedisInfoView(store: store.scope(state: \.redisInfoState, action: RedisSystemStore.Action.redisInfoAction)) } else if viewStore.state == RedisSystemViewTypeEnum.CLIENT_LIST { - ClientsListView(store: store.scope(state: \.clientListState, action: RedisSystemAction.clientListAction)) + ClientsListView(store: store.scope(state: \.clientListState, action: RedisSystemStore.Action.clientListAction)) } else if viewStore.state == RedisSystemViewTypeEnum.SLOW_LOG { - SlowLogView(store: store.scope(state: \.slowLogState, action: RedisSystemAction.slowLogAction)) + SlowLogView(store: store.scope(state: \.slowLogState, action: RedisSystemStore.Action.slowLogAction)) } else if viewStore.state == RedisSystemViewTypeEnum.REDIS_CONFIG { - RedisConfigView(store: store.scope(state: \.redisConfigState, action: RedisSystemAction.redisConfigAction)) + RedisConfigView(store: store.scope(state: \.redisConfigState, action: RedisSystemStore.Action.redisConfigAction)) } else if viewStore.state == RedisSystemViewTypeEnum.LUA { - LuaView(store: store.scope(state: \.luaState, action: RedisSystemAction.luaAction)) + LuaView(store: store.scope(state: \.luaState, action: RedisSystemStore.Action.luaAction)) } else { EmptyView() } diff --git a/redis-pro/Views/System/SlowLogView.swift b/redis-pro/Views/System/SlowLogView.swift index e955c71..4f59a1b 100644 --- a/redis-pro/Views/System/SlowLogView.swift +++ b/redis-pro/Views/System/SlowLogView.swift @@ -10,22 +10,22 @@ import Logging import ComposableArchitecture struct SlowLogView: View { - var store:Store + var store:StoreOf let logger = Logger(label: "slow-log-view") var body: some View { - WithViewStore(store) { viewStore in + WithViewStore(self.store, observe: { $0 }) {viewStore in VStack(alignment: .leading, spacing: MTheme.V_SPACING) { // header HStack(alignment: .center, spacing: MTheme.H_SPACING) { - FormItemInt(label: "Slower Than(us)", labelWidth: 120, value: viewStore.binding(\.$slowerThan), suffix: "square.and.pencil", onCommit: {viewStore.send(.setSlowerThan)}) + FormItemInt(label: "Slower Than(us)", labelWidth: 120, value: viewStore.$slowerThan, suffix: "square.and.pencil", onCommit: {viewStore.send(.setSlowerThan)}) .help("REDIS_SLOW_LOG_SLOWER_THAN") .frame(width: 320) - FormItemInt(label: "Max Len", value: viewStore.binding(\.$maxLen), suffix: "square.and.pencil", onCommit: {viewStore.send(.setMaxLen)}) + FormItemInt(label: "Max Len", value: viewStore.$maxLen, suffix: "square.and.pencil", onCommit: {viewStore.send(.setMaxLen)}) .help("REDIS_SLOW_LOG_MAX_LEN") .frame(width: 200) - FormItemInt(label: "Size", value: viewStore.binding(\.$size), suffix: "square.and.pencil", onCommit: {viewStore.send(.setSize)}) + FormItemInt(label: "Size", value: viewStore.$size, suffix: "square.and.pencil", onCommit: {viewStore.send(.setSize)}) .help("REDIS_SLOW_LOG_SIZE") .frame(width: 200) @@ -34,7 +34,7 @@ struct SlowLogView: View { .help("REDIS_SLOW_LOG_RESET") } - NTableView(store: store.scope(state: \.tableState, action: SlowLogAction.tableAction)) + NTableView(store: store.scope(state: \.tableState, action: SlowLogStore.Action.tableAction)) // footer HStack(alignment: .center, spacing: MTheme.H_SPACING_L) { diff --git a/redis-pro/en.lproj/Localizable.strings b/redis-pro/en.lproj/Localizable.strings index 7292060..f5c9d08 100644 --- a/redis-pro/en.lproj/Localizable.strings +++ b/redis-pro/en.lproj/Localizable.strings @@ -11,6 +11,7 @@ "HELP_SEARCH_BAR" = "支持redis glob 风格的模式参数, 示例: key*, re?is"; "HELP_TTL" = "单位(秒), -1表示永不过期, -2 表示key不存在, 修改后回车提交修改!"; "HELP_REFRESH" = "Refresh"; +"HELP_FAST_PAGE" = "快速分页, true: 表示最多查询99页, 超99页显示99+, 对性能更友好; false: 表示精确分页,会调用scan精确查询模糊匹配的key数量。"; // -------------------------------------------------- redis help message end ----------------------------------------------------- diff --git a/redis-pro/redis_proApp.swift b/redis-pro/redis_proApp.swift index 232db5c..10d2855 100644 --- a/redis-pro/redis_proApp.swift +++ b/redis-pro/redis_proApp.swift @@ -17,10 +17,14 @@ import ComposableArchitecture @main struct redis_proApp: App { - @Environment(\.scenePhase) var scenePhase + // 会造成indexView 多次初始化 + // @Environment(\.scenePhase) var scenePhase @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - let settingsStore = Store(initialState: SettingsState(), reducer: settingsReducer, environment: SettingsEnvironment()) + // settings + var settingsStore:Store = Store(initialState: SettingsStore.State()) { + SettingsStore() + } let logger = Logger(label: "app") // 应用启动只初始化一次 @@ -32,7 +36,10 @@ struct redis_proApp: App { var body: some Scene { WindowGroup { - IndexView() + IndexView(settingStore: settingsStore) + .onAppear { + ViewStore(settingsStore, observe: { $0 }).send(.initial) + } } .commands { RedisProCommands() diff --git a/redis-pro/zh-Hans.lproj/Localizable.strings b/redis-pro/zh-Hans.lproj/Localizable.strings index 7c859af..b252745 100644 --- a/redis-pro/zh-Hans.lproj/Localizable.strings +++ b/redis-pro/zh-Hans.lproj/Localizable.strings @@ -11,6 +11,7 @@ "HELP_SEARCH_BAR" = "支持redis glob 风格的模式参数, 示例: key*, re?is"; "HELP_TTL" = "单位(秒), -1表示永不过期, -2 表示key不存在, 修改后回车提交修改!"; "HELP_REFRESH" = "刷新"; +"HELP_FAST_PAGE" = "快速分页, true: 表示最多查询99页, 超99页显示99+, 对性能更友好; false: 表示精确分页,会调用scan精确查询模糊匹配的key数量。"; // -------------------------------------------------- redis help message end ----------------------------------------------------- // -------------------------------------------------- redis string message start -------------------------------------------------