Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion App/Sources/App/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ struct ContentView: View {
}
}
.sheet(isPresented: $isPresentingConfigEditor) {
ConfigVariableEditor(reader: viewModel.configVariableReader) { variables in
ConfigVariableEditor(
reader: viewModel.configVariableReader,
customSectionTitle: "Actions"
) {
Button("Do something", role: .destructive) {
print("Did something!")
}
} onSave: { variables in
print(variables)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,42 @@ import SwiftUI
/// variable's detail view.
///
/// The toolbar provides Cancel, Save, and an overflow menu with Undo, Redo, and Clear Editor Overrides actions.
struct ConfigVariableListView<ViewModel: ConfigVariableListViewModeling>: View {
struct ConfigVariableListView<ViewModel: ConfigVariableListViewModeling, CustomSection: View>: View {
@State var viewModel: ViewModel

/// The title for the custom section at the top of the list.
private let customSectionTitle: Text

/// The custom section content to display at the top of the list.
private let customSection: CustomSection

@Environment(\.dismiss) private var dismiss


/// Creates a new list view.
///
/// - Parameters:
/// - viewModel: The view model for the list.
/// - customSectionTitle: The title for the custom section.
/// - customSection: A view builder that produces custom content to display in a section at the top of the list.
init(viewModel: ViewModel, customSectionTitle: Text, @ViewBuilder customSection: () -> CustomSection) {
self._viewModel = State(initialValue: viewModel)
self.customSectionTitle = customSectionTitle
self.customSection = customSection()
}


var body: some View {
NavigationStack {
List {
if CustomSection.self != EmptyView.self {
Section {
customSection
} header: {
customSectionTitle
}
}

Section(localizedStringResource("editorView.variablesSection.header")) {
ForEach(viewModel.variables, id: \.key) { item in
NavigationLink(value: item.key) {
Expand Down
114 changes: 95 additions & 19 deletions Sources/DevConfiguration/Editor/ConfigVariableEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,121 @@ import SwiftUI
/// // Handle changed variables
/// }
/// }
public struct ConfigVariableEditor: View {
///
/// To add a custom section at the top of the list, provide a section title and content:
///
/// ConfigVariableEditor(
/// reader: reader,
/// customSectionTitle: "Actions"
/// ) {
/// Button("Reset All") { … }
/// } onSave: { changedVariables in
/// // Handle changed variables
/// }
public struct ConfigVariableEditor<CustomSection: View>: View {
/// The list view model created from the reader.
@State private var viewModel: ConfigVariableListViewModel?

/// The title for the custom section.
private let customSectionTitle: Text

/// Creates a new configuration variable editor.
/// The custom section content.
private let customSection: CustomSection


/// Creates a new configuration variable editor with a custom section at the top of the list.
///
/// - Parameters:
/// - reader: The configuration variable reader. If the reader was not created with `isEditorEnabled` set to
/// `true`, the view is empty.
/// - customSectionTitle: The title for the custom section.
/// - customSection: A view builder that produces custom content to display in a section at the top of the list.
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
public init(
reader: ConfigVariableReader,
customSectionTitle: LocalizedStringKey,
@ViewBuilder customSection: () -> CustomSection,
onSave: @escaping ([RegisteredConfigVariable]) -> Void
) {
if let editorOverrideProvider = reader.editorOverrideProvider {
// Exclude the editor override provider from the named providers passed to the document,
// since it is always the first entry in the reader's provider list
let namedProviders = Array(reader.namedProviders.dropFirst())

let document = EditorDocument(
editorOverrideProvider: editorOverrideProvider,
workingCopyDisplayName: localizedString("editorOverrideProvider.name"),
namedProviders: namedProviders,
registeredVariables: Array(reader.registeredVariables.values),
undoManager: UndoManager()
)
self.init(
reader: reader,
customSectionTitle: Text(customSectionTitle),
customSection: customSection,
onSave: onSave
)
}

self._viewModel = State(
initialValue: ConfigVariableListViewModel(document: document, onSave: onSave)
)
}

/// Creates a new configuration variable editor with a custom section at the top of the list.
///
/// - Parameters:
/// - reader: The configuration variable reader. If the reader was not created with `isEditorEnabled` set to
/// `true`, the view is empty.
/// - customSectionTitle: A `Text` view to use as the title for the custom section.
/// - customSection: A view builder that produces custom content to display in a section at the top of the list.
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
public init(
reader: ConfigVariableReader,
customSectionTitle: Text,
@ViewBuilder customSection: () -> CustomSection,
onSave: @escaping ([RegisteredConfigVariable]) -> Void
) {
self.customSectionTitle = customSectionTitle
self.customSection = customSection()
self._viewModel = Self.makeViewModel(reader: reader, onSave: onSave)
}


public var body: some View {
if let viewModel {
ConfigVariableListView(viewModel: viewModel)
ConfigVariableListView(
viewModel: viewModel,
customSectionTitle: customSectionTitle,
customSection: { customSection }
)
}
}


private static func makeViewModel(
reader: ConfigVariableReader,
onSave: @escaping ([RegisteredConfigVariable]) -> Void
) -> State<ConfigVariableListViewModel?> {
guard let editorOverrideProvider = reader.editorOverrideProvider else {
return State(initialValue: nil)
}

// Exclude the editor override provider from the named providers passed to the document,
// since it is always the first entry in the reader's provider list
let namedProviders = Array(reader.namedProviders.dropFirst())

let document = EditorDocument(
editorOverrideProvider: editorOverrideProvider,
workingCopyDisplayName: localizedString("editorOverrideProvider.name"),
namedProviders: namedProviders,
registeredVariables: Array(reader.registeredVariables.values),
undoManager: UndoManager()
)

return State(initialValue: ConfigVariableListViewModel(document: document, onSave: onSave))
}
}


extension ConfigVariableEditor where CustomSection == EmptyView {
/// Creates a new configuration variable editor.
///
/// - Parameters:
/// - reader: The configuration variable reader. If the reader was not created with `isEditorEnabled` set to
/// `true`, the view is empty.
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
public init(
reader: ConfigVariableReader,
onSave: @escaping ([RegisteredConfigVariable]) -> Void
) {
self.customSectionTitle = Text(verbatim: "")
self.customSection = EmptyView()
self._viewModel = Self.makeViewModel(reader: reader, onSave: onSave)
}
}

Expand Down