From fe60dde38833f9f3e88906d6cd73fe1eb2444a89 Mon Sep 17 00:00:00 2001 From: Qijia Liu Date: Fri, 3 May 2024 10:22:42 -0400 Subject: [PATCH] fix punctuation config ui (#118) --- assets/zh-Hans.lproj/Localizable.strings | Bin 11490 -> 11858 bytes src/config/config.swift | 21 ++++++++++- src/config/optionmodels.swift | 33 ++++++++++++++++- src/config/optionviews.swift | 45 +++++++++++++++++------ 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/assets/zh-Hans.lproj/Localizable.strings b/assets/zh-Hans.lproj/Localizable.strings index b09d3088cd9e9181e73458b0e3e25b151928e026..2dcfa7408e2eb084ed38152cb464e23d8c83a2ba 100644 GIT binary patch delta 127 zcmaD9c`0T?7K?==Lk>d;Ln=cNLmopSkd?_$2IMI)_yTzaK$r;>PiIg9ir50N62t$r p$9@WFEHM)wL~PDtIVZq}km5|%n+g Config { let description = json["Description"].stringValue // Option if let type = json["Type"].string, - !type.contains("$") + !type.contains("$") || type.hasPrefix("List|") { do { let option = try jsonToOption(json, type) @@ -104,6 +104,8 @@ private func jsonToOption(_ json: JSON, _ type: String) throws -> any Option { return try ListOption.decode(json: json) } else if type == "List|Enum" { return try ListOption.decode(json: json) + } else if type == "List|Entries$PunctuationMapEntryConfig" { + return try ListOption.decode(json: json) } else if type == "External" { return try ExternalOption.decode(json: json) } else { @@ -214,6 +216,23 @@ extension Array: FcitxCodable where Element: FcitxCodable { } } +extension Dictionary: FcitxCodable where Key == String, Value: FcitxCodable { + static func decode(json: JSON) throws -> Self { + var result: [Key: Value] = [:] + for (key, subJson): (String, JSON) in json { + result[key] = try Value.decode(json: subJson) + } + return result + } + func encodeValueJSON() -> JSON { + var json = JSON() + for (key, value) in self { + json[key] = value.encodeValueJSON() + } + return json + } +} + extension Optional: FcitxCodable where Wrapped: FcitxCodable { static func decode(json: JSON) throws -> Self { do { diff --git a/src/config/optionmodels.swift b/src/config/optionmodels.swift index dc0b379..b30d9e5 100644 --- a/src/config/optionmodels.swift +++ b/src/config/optionmodels.swift @@ -351,6 +351,35 @@ class AppIMOption: Option, ObservableObject, EmptyConstructible { } } +class PunctuationMapOption: Option, ObservableObject, EmptyConstructible { + typealias Storage = [String: String] + let defaultValue: [String: String] + var value: [String: String] + + required init(defaultValue: Storage, value: Storage?) { + self.defaultValue = defaultValue + self.value = value ?? defaultValue + } + + static func decode(json: JSON) throws -> Self { + return Self( + defaultValue: try Storage.decode(json: json["DefaultValue"]), + value: try Storage?.decode(json: json["Value"]) + ) + } + + func encodeValueJSON() -> JSON { + return value.encodeValueJSON() + } + + func resetToDefault() { + } + + static func empty(json: JSON) throws -> Self { + return Self(defaultValue: [:], value: [:]) + } +} + class ListOption: Option, ObservableObject { let defaultValue: [T] @Published var value: [Identified] @@ -366,10 +395,10 @@ class ListOption: Option, ObservableObject { static func decode(json: JSON) throws -> Self { let defaultOptions = try [T.Storage].decode(json: json["DefaultValue"]).map { - try T.decode(json: try json.merged(with: ["DefaultValue": $0, "Value": $0])) + return try T.decode(json: ["DefaultValue": $0, "Value": $0]) } let options = try [T.Storage].decode(json: json["Value"]).map { - try T.decode(json: try json.merged(with: ["DefaultValue": $0, "Value": $0])) + try T.decode(json: ["DefaultValue": $0, "Value": $0]) } return Self( defaultValue: defaultOptions, diff --git a/src/config/optionviews.swift b/src/config/optionviews.swift index 6112c22..5f5c0fd 100644 --- a/src/config/optionviews.swift +++ b/src/config/optionviews.swift @@ -253,12 +253,6 @@ struct ListOptionView: OptionView { .disabled(index == 0) .buttonStyle(BorderlessButtonStyle()) - Button(action: { moveDown(index: index) }) { - Image(systemName: "arrow.down") - } - .disabled(index == model.value.count - 1) - .buttonStyle(BorderlessButtonStyle()) - Button(action: { remove(at: index) }) { Image(systemName: "minus") } @@ -310,12 +304,6 @@ struct ListOptionView: OptionView { model.value.swapAt(index, index - 1) } } - - private func moveDown(index: Int) { - if index < model.value.count - 1 { - model.value.swapAt(index, index + 1) - } - } } struct FontOptionView: OptionView { @@ -461,6 +449,35 @@ struct AppIMOptionView: OptionView { } } +struct PunctuationMapOptionView: OptionView { + let label: String + let overrideLabel: String? = nil + @ObservedObject var model: PunctuationMapOption + + var body: some View { + HStack { + TextField( + NSLocalizedString("Key", comment: ""), + text: Binding( + get: { model.value["Key"] ?? "" }, + set: { model.value["Key"] = $0 } + )) + TextField( + NSLocalizedString("Mapping", comment: ""), + text: Binding( + get: { model.value["Mapping"] ?? "" }, + set: { model.value["Mapping"] = $0 } + )) + TextField( + NSLocalizedString("Alternative Mapping", comment: ""), + text: Binding( + get: { model.value["AltMapping"] ?? "" }, + set: { model.value["AltMapping"] = $0 } + )) + } + } +} + struct GroupOptionView: OptionView { let label: String let overrideLabel: String? = nil @@ -525,6 +542,8 @@ func buildViewImpl(label: String, option: any Option) -> any OptionView { return IntegerOptionView(label: label, model: option) } else if let option = option as? ColorOption { return ColorOptionView(label: label, model: option) + } else if let option = option as? PunctuationMapOption { + return PunctuationMapOptionView(label: label, model: option) } else if let option = option as? ListOption { return ListOptionView(label: label, model: option) } else if let option = option as? ListOption { @@ -533,6 +552,8 @@ func buildViewImpl(label: String, option: any Option) -> any OptionView { return ListOptionView(label: label, model: option) } else if let option = option as? ListOption { return ListOptionView(label: label, model: option) + } else if let option = option as? ListOption { + return ListOptionView(label: label, model: option) } else { return UnsupportedOptionView(model: option) }