Skip to content

Commit 76791ef

Browse files
authored
Merge pull request #102 from unsignedapps/vexillographer/read-only
Added a read-only view to Vexillographer
2 parents 949d2f3 + b5569ae commit 76791ef

8 files changed

Lines changed: 130 additions & 54 deletions

Sources/Vexillographer/Flag Value Controls/BooleanFlagControl.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,21 @@ struct BooleanFlagControl: View {
2828
@Binding var value: Bool
2929

3030
let hasChanges: Bool
31+
let isEditable: Bool
3132
@Binding var showDetail: Bool
3233

3334

3435
// MARK: - Views
3536

3637
var body: some View {
3738
HStack {
38-
Toggle(self.label, isOn: self.$value)
39+
if self.isEditable {
40+
Toggle(self.label, isOn: self.$value)
41+
} else {
42+
Text(self.label).font(.headline)
43+
Spacer()
44+
FlagDisplayValueView(value: self.value)
45+
}
3946
DetailButton(hasChanges: self.hasChanges, showDetail: self.$showDetail)
4047
}
4148
}
@@ -63,6 +70,7 @@ extension UnfurledFlag: BooleanEditableFlag where Value.BoxedValueType == Bool {
6370
transformer: BoxedPassthroughTransformer.self
6471
),
6572
hasChanges: manager.hasValueInSource(flag: self.flag),
73+
isEditable: manager.isEditable,
6674
showDetail: showDetail
6775
)
6876
.eraseToAnyView()
@@ -90,6 +98,7 @@ extension UnfurledFlag: OptionalBooleanEditableFlag where Value: FlagValue, Valu
9098
transformer: OptionalTransformer<Value.BoxedValueType, Bool, BoxedPassthroughTransformer>.self
9199
),
92100
hasChanges: manager.hasValueInSource(flag: self.flag),
101+
isEditable: manager.isEditable,
93102
showDetail: showDetail
94103
)
95104
.eraseToAnyView()

Sources/Vexillographer/Flag Value Controls/CaseIterableFlagControl.swift

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,31 @@ struct CaseIterableFlagControl<Value>: View where Value: FlagValue, Value: CaseI
2323
@Binding var value: Value
2424

2525
let hasChanges: Bool
26+
let isEditable: Bool
2627
@Binding var showDetail: Bool
2728

2829
@Binding var showPicker: Bool
2930

3031
// MARK: - View Body
3132

33+
var content: some View {
34+
HStack {
35+
Text(self.label).font(.headline)
36+
Spacer()
37+
FlagDisplayValueView(value: self.value)
38+
}
39+
}
40+
3241
#if os(iOS)
3342

3443
var body: some View {
3544
HStack {
36-
NavigationLink(destination: self.selector, isActive: self.$showPicker) {
37-
HStack {
38-
Text(self.label).font(.headline)
39-
Spacer()
40-
FlagDisplayValueView(value: self.value)
45+
if self.isEditable {
46+
NavigationLink(destination: self.selector, isActive: self.$showPicker) {
47+
self.content
4148
}
49+
} else {
50+
self.content
4251
}
4352
DetailButton(hasChanges: self.hasChanges, showDetail: self.$showDetail)
4453
}
@@ -52,6 +61,16 @@ struct CaseIterableFlagControl<Value>: View where Value: FlagValue, Value: CaseI
5261
#elseif os(macOS)
5362

5463
var body: some View {
64+
Group {
65+
if self.isEditable {
66+
self.picker
67+
} else {
68+
self.content
69+
}
70+
}
71+
}
72+
73+
var picker: some View {
5574
let picker = Picker (
5675
selection: self.$value,
5776
label: Text(self.label),
@@ -138,6 +157,7 @@ extension UnfurledFlag: CaseIterableEditableFlag
138157
transformer: PassthroughTransformer<Value>.self
139158
),
140159
hasChanges: manager.hasValueInSource(flag: self.flag),
160+
isEditable: manager.isEditable,
141161
showDetail: showDetail,
142162
showPicker: showPicker
143163
)

Sources/Vexillographer/Flag Value Controls/OptionalCaseIterableFlagControl.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,29 @@ struct OptionalCaseIterableFlagControl<Value>: View
2525
@Binding var value: Value
2626

2727
let hasChanges: Bool
28+
let isEditable: Bool
2829
@Binding var showDetail: Bool
2930

3031
@Binding var showPicker: Bool
3132

3233
// MARK: - View Body
3334

35+
var content: some View {
36+
HStack {
37+
Text(self.label).font(.headline)
38+
Spacer()
39+
FlagDisplayValueView(value: self.value.wrapped)
40+
}
41+
}
42+
3443
var body: some View {
3544
HStack {
36-
NavigationLink(destination: self.selector, isActive: self.$showPicker) {
37-
HStack {
38-
Text(self.label).font(.headline)
39-
Spacer()
40-
FlagDisplayValueView(value: self.value.wrapped)
45+
if self.isEditable {
46+
NavigationLink(destination: self.selector, isActive: self.$showPicker) {
47+
self.content
4148
}
49+
} else {
50+
self.content
4251
}
4352
DetailButton(hasChanges: self.hasChanges, showDetail: self.$showDetail)
4453
}
@@ -136,7 +145,7 @@ extension UnfurledFlag: OptionalCaseIterableEditableFlag
136145
return OptionalCaseIterableFlagControl<Value> (
137146
label: label,
138147
value: Binding (
139-
get: { Value(manager.rawValue(key: key)) },
148+
get: { Value(manager.flagValue(key: key)) },
140149
set: { newValue in
141150
do {
142151
try manager.setFlagValue(newValue.wrapped, key: key)
@@ -147,6 +156,7 @@ extension UnfurledFlag: OptionalCaseIterableEditableFlag
147156
}
148157
),
149158
hasChanges: manager.hasValueInSource(flag: self.flag),
159+
isEditable: manager.isEditable,
150160
showDetail: showDetail,
151161
showPicker: showPicker
152162
)

Sources/Vexillographer/Flag Value Controls/StringFlagControl.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct StringFlagControl: View {
2323
@Binding var value: String
2424

2525
let hasChanges: Bool
26+
let isEditable: Bool
2627
@Binding var showDetail: Bool
2728

2829

@@ -32,8 +33,12 @@ struct StringFlagControl: View {
3233
HStack {
3334
Text(self.label)
3435
Spacer()
35-
TextField("", text: self.$value)
36-
.multilineTextAlignment(.trailing)
36+
if self.isEditable {
37+
TextField("", text: self.$value)
38+
.multilineTextAlignment(.trailing)
39+
} else {
40+
FlagDisplayValueView(value: self.value)
41+
}
3742
DetailButton(hasChanges: self.hasChanges, showDetail: self.$showDetail)
3843
}
3944
}
@@ -60,6 +65,7 @@ extension UnfurledFlag: StringEditableFlag where Value.BoxedValueType: LosslessS
6065
transformer: LosslessStringTransformer<Value.BoxedValueType>.self
6166
),
6267
hasChanges: manager.hasValueInSource(flag: self.flag),
68+
isEditable: manager.isEditable,
6369
showDetail: showDetail
6470
)
6571
.flagValueKeyboard(type: Value.self)
@@ -90,6 +96,7 @@ extension UnfurledFlag: OptionalStringEditableFlag
9096
transformer: OptionalTransformer<Value.BoxedValueType, String, LosslessStringTransformer<Value.BoxedValueType.WrappedFlagValue>>.self
9197
),
9298
hasChanges: manager.hasValueInSource(flag: self.flag),
99+
isEditable: manager.isEditable,
93100
showDetail: showDetail
94101
)
95102
.flagValueKeyboard(type: Value.self)

Sources/Vexillographer/FlagDetailView.swift

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct FlagDetailView<Value, RootGroup>: View where Value: FlagValue, RootGroup:
1616
// MARK: - Properties
1717

1818
let flag: UnfurledFlag<Value, RootGroup>
19+
let isEditable: Bool
1920

2021
@ObservedObject var manager: FlagValueManager<RootGroup>
2122

@@ -25,6 +26,7 @@ struct FlagDetailView<Value, RootGroup>: View where Value: FlagValue, RootGroup:
2526
init (flag: UnfurledFlag<Value, RootGroup>, manager: FlagValueManager<RootGroup>) {
2627
self.flag = flag
2728
self.manager = manager
29+
self.isEditable = manager.isEditable
2830
}
2931

3032

@@ -72,22 +74,24 @@ struct FlagDetailView<Value, RootGroup>: View where Value: FlagValue, RootGroup:
7274
}
7375
}
7476

75-
FlagDetailSection(header: Text("Current Source")) {
76-
HStack {
77-
Text(self.manager.source.name)
78-
.font(.headline)
79-
Spacer()
80-
self.description(source: self.manager.source)
81-
}
77+
if self.manager.source != nil {
78+
FlagDetailSection(header: Text("Current Source")) {
79+
HStack {
80+
Text(self.manager.source!.name)
81+
.font(.headline)
82+
Spacer()
83+
self.description(source: self.manager.source!)
84+
}
8285

83-
Button(action: self.clearValue) {
84-
Text("Clear Flag Value in Current Source")
86+
Button(action: self.clearValue) {
87+
Text("Clear Flag Value in Current Source")
88+
}
89+
.foregroundColor(.red)
90+
.opacity(self.isCurrentSourceSet ? 1 : 0.3)
91+
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
92+
.disabled(self.isCurrentSourceSet == false)
93+
.animation(.easeInOut, value: self.isCurrentSourceSet)
8594
}
86-
.foregroundColor(.red)
87-
.opacity(self.isCurrentSourceSet ? 1 : 0.3)
88-
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
89-
.disabled(self.isCurrentSourceSet == false)
90-
.animation(.easeInOut, value: self.isCurrentSourceSet)
9195
}
9296

9397
FlagDetailSection(header: Text("FlagPole Source Hierarchy")) {
@@ -125,11 +129,14 @@ struct FlagDetailView<Value, RootGroup>: View where Value: FlagValue, RootGroup:
125129
}
126130

127131
func clearValue () {
128-
try? self.manager.source.setFlagValue(Optional<Value>.none, key: self.flag.flag.key) // swiftlint:disable:this syntactic_sugar
132+
try? self.manager.source?.setFlagValue(Optional<Value>.none, key: self.flag.flag.key) // swiftlint:disable:this syntactic_sugar
129133
}
130134

131135
var isCurrentSourceSet: Bool {
132-
self.flagValue(source: self.manager.source) != nil
136+
guard let source = self.manager.source else {
137+
return false
138+
}
139+
return self.flagValue(source: source) != nil
133140
}
134141

135142
private var flagKeyView: some View {

Sources/Vexillographer/FlagDisplayValueView.swift

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,50 @@ struct FlagDisplayValueView<Value>: View where Value: FlagValue {
1717

1818
let value: Value
1919

20+
var string: String? {
21+
if let value = self.value as? OptionalFlagDisplayValue {
22+
return value.flagDisplayValue
23+
}
24+
if let displayValue = value as? FlagDisplayValue {
25+
return displayValue.flagDisplayValue
26+
}
27+
return String(describing: value)
28+
}
2029

2130
// MARK: - Body
2231

2332
var body: some View {
24-
return (self.value as? OptionalFlagDisplayValue)?.flagDisplayText
25-
?? (self.value as? FlagDisplayValue)?.flagDisplayText
26-
?? Text(String(describing: self.value)).eraseToAnyView()
33+
Group {
34+
if self.string != nil {
35+
Text(string!)
36+
.contextMenu {
37+
CopyButton(action: self.string!.copyToPasteboard)
38+
}
39+
40+
} else {
41+
Text("nil").foregroundColor(.red)
42+
}
43+
}
2744
}
2845

2946
}
3047

3148
private protocol OptionalFlagDisplayValue {
32-
var flagDisplayText: AnyView { get }
49+
var flagDisplayValue: String? { get }
3350
}
3451

3552
extension Optional: OptionalFlagDisplayValue where Wrapped: FlagValue {
36-
var flagDisplayText: AnyView {
53+
var flagDisplayValue: String? {
3754
guard let value = self else {
38-
return Text("nil")
39-
.foregroundColor(.red)
40-
.eraseToAnyView()
55+
return nil
4156
}
4257

43-
let string = (value as? FlagDisplayValue)?.flagDisplayValue ?? String(describing: value)
44-
return Text(string)
45-
.eraseToAnyView()
46-
}
47-
}
58+
if let displayValue = value as? FlagDisplayValue {
59+
return displayValue.flagDisplayValue
60+
}
4861

49-
private extension FlagDisplayValue {
50-
var flagDisplayText: AnyView {
51-
return Text(self.flagDisplayValue)
52-
.eraseToAnyView()
62+
return String(describing: value)
5363
}
5464
}
5565

56-
5766
#endif

Sources/Vexillographer/FlagValueManager.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ class FlagValueManager<RootGroup>: ObservableObject where RootGroup: FlagContain
1818
// MARK: - Properties
1919

2020
let flagPole: FlagPole<RootGroup>
21-
let source: FlagValueSource
21+
let source: FlagValueSource?
2222
private var cancellables = Set<AnyCancellable>()
2323

24+
var isEditable: Bool {
25+
return self.source != nil
26+
}
27+
2428

2529
// MARK: - Initialisation
2630

27-
init (flagPole: FlagPole<RootGroup>, source: FlagValueSource) {
31+
init (flagPole: FlagPole<RootGroup>, source: FlagValueSource?) {
2832
self.flagPole = flagPole
2933
self.source = source
3034

@@ -41,7 +45,7 @@ class FlagValueManager<RootGroup>: ObservableObject where RootGroup: FlagContain
4145
// MARK: - Flag Values
4246

4347
func rawValue<Value> (key: String) -> Value? where Value: FlagValue {
44-
return self.source.flagValue(key: key)
48+
return self.source?.flagValue(key: key)
4549
}
4650

4751
func flagValue<Value> (key: String) -> Value? where Value: FlagValue {
@@ -50,13 +54,17 @@ class FlagValueManager<RootGroup>: ObservableObject where RootGroup: FlagContain
5054
}
5155

5256
func setFlagValue<Value> (_ value: Value?, key: String) throws where Value: FlagValue {
57+
guard let source = source else {
58+
return
59+
}
60+
5361
let snapshot = self.flagPole.emptySnapshot()
5462
try snapshot.setFlagValue(value, key: key)
55-
try self.flagPole.save(snapshot: snapshot, to: self.source)
63+
try self.flagPole.save(snapshot: snapshot, to: source)
5664
}
5765

5866
func hasValueInSource<Value> (flag: Flag<Value>) -> Bool {
59-
if let _: Value = self.source.flagValue(key: flag.key) {
67+
if let _: Value = self.source?.flagValue(key: flag.key) {
6068
return true
6169

6270
} else {

Sources/Vexillographer/Vexillographer.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ public struct Vexillographer<RootGroup>: View where RootGroup: FlagContainer {
2222

2323
// MARK: - Initialisation
2424

25-
public init (flagPole: FlagPole<RootGroup>, source: FlagValueSource) {
25+
/// Initialises a new `Vexillographer` instance with the provided FlagPole and source
26+
///
27+
/// - Parameters;
28+
/// - flagPole: A `FlagPole` instance manages the flag and source hierarchy we want to display
29+
/// - source: An optional `FlagValueSource` for editing the flag values in. If `nil` the flag values are displayed read-only
30+
///
31+
public init (flagPole: FlagPole<RootGroup>, source: FlagValueSource?) {
2632
self.manager = FlagValueManager(flagPole: flagPole, source: source)
2733
}
2834

0 commit comments

Comments
 (0)