parent
17f1331de0
commit
f4505d0efd
|
@ -32,7 +32,7 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "f681f968f39ca514e29ac6c0abcf658c224e4c04"
|
"revision" : "7efa18eb75b7a102781be3c62cd31a08607f03c8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,7 @@ let package = Package(
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"),
|
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"),
|
||||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "f681f968f39ca514e29ac6c0abcf658c224e4c04"),
|
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "7efa18eb75b7a102781be3c62cd31a08607f03c8"),
|
||||||
// .package(path: "../../../passepartoutkit-source"),
|
// .package(path: "../../../passepartoutkit-source"),
|
||||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"),
|
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"),
|
||||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),
|
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),
|
||||||
|
|
|
@ -31,65 +31,46 @@ import PassepartoutKit
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class ProfileEditor: ObservableObject {
|
final class ProfileEditor: ObservableObject {
|
||||||
private(set) var id: Profile.ID
|
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var name: String
|
private var editableProfile: EditableProfile
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var isShared: Bool
|
var isShared: Bool
|
||||||
|
|
||||||
@Published
|
private(set) var removedModules: [UUID: any ModuleBuilder]
|
||||||
private(set) var modules: [any EditableModule]
|
|
||||||
|
|
||||||
@Published
|
|
||||||
private(set) var activeModulesIds: Set<UUID>
|
|
||||||
|
|
||||||
@Published
|
|
||||||
private var moduleNames: [UUID: String]
|
|
||||||
|
|
||||||
private(set) var removedModules: [UUID: any EditableModule]
|
|
||||||
|
|
||||||
convenience init() {
|
convenience init() {
|
||||||
self.init(modules: [])
|
self.init(modules: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
init(modules: [any EditableModule]) {
|
init(modules: [any ModuleBuilder]) {
|
||||||
id = UUID()
|
editableProfile = EditableProfile(
|
||||||
name = ""
|
modules: modules,
|
||||||
self.modules = modules
|
activeModulesIds: Set(modules.map(\.id))
|
||||||
activeModulesIds = Set(modules.map(\.id))
|
)
|
||||||
moduleNames = [:]
|
|
||||||
removedModules = [:]
|
|
||||||
isShared = false
|
isShared = false
|
||||||
|
removedModules = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
init(profile: Profile) {
|
init(profile: Profile) {
|
||||||
id = profile.id
|
editableProfile = profile.editable()
|
||||||
name = profile.name
|
|
||||||
modules = profile.modulesBuilders
|
|
||||||
activeModulesIds = profile.activeModulesIds
|
|
||||||
moduleNames = profile.moduleNames
|
|
||||||
removedModules = [:]
|
|
||||||
isShared = false
|
isShared = false
|
||||||
|
removedModules = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func editProfile(_ profile: Profile, isShared: Bool) {
|
func editProfile(_ profile: Profile, isShared: Bool) {
|
||||||
id = profile.id
|
editableProfile = profile.editable()
|
||||||
name = profile.name
|
|
||||||
modules = profile.modulesBuilders
|
|
||||||
activeModulesIds = profile.activeModulesIds
|
|
||||||
moduleNames = profile.moduleNames
|
|
||||||
removedModules = [:]
|
|
||||||
self.isShared = isShared
|
self.isShared = isShared
|
||||||
|
removedModules = [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CRUD
|
// MARK: - Types
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
var moduleTypes: [ModuleType] {
|
var moduleTypes: [ModuleType] {
|
||||||
modules
|
editableProfile.modules
|
||||||
.compactMap {
|
.compactMap {
|
||||||
$0 as? ModuleTypeProviding
|
$0 as? ModuleTypeProviding
|
||||||
}
|
}
|
||||||
|
@ -110,83 +91,52 @@ extension ProfileEditor {
|
||||||
$0.localizedDescription < $1.localizedDescription
|
$0.localizedDescription < $1.localizedDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Metadata
|
||||||
|
|
||||||
|
extension ProfileEditor {
|
||||||
|
var id: Profile.ID {
|
||||||
|
editableProfile.id
|
||||||
|
}
|
||||||
|
|
||||||
|
var name: String {
|
||||||
|
get {
|
||||||
|
editableProfile.name
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
editableProfile.name = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func displayName(forModuleWithId moduleId: UUID) -> String? {
|
func displayName(forModuleWithId moduleId: UUID) -> String? {
|
||||||
guard let name = moduleNames[moduleId] else {
|
editableProfile.displayName(forModuleWithId: moduleId)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
return !trimmedName.isEmpty ? trimmedName : nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func name(forModuleWithId moduleId: UUID) -> String? {
|
func name(forModuleWithId moduleId: UUID) -> String? {
|
||||||
moduleNames[moduleId]
|
editableProfile.name(forModuleWithId: moduleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setName(_ name: String, forModuleWithId moduleId: UUID) {
|
func setName(_ name: String, forModuleWithId moduleId: UUID) {
|
||||||
moduleNames[moduleId] = name
|
editableProfile.setName(name, forModuleWithId: moduleId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func module(withId moduleId: UUID) -> (any EditableModule)? {
|
// MARK: - Modules
|
||||||
modules.first {
|
|
||||||
|
extension ProfileEditor {
|
||||||
|
var modules: [any ModuleBuilder] {
|
||||||
|
editableProfile.modules
|
||||||
|
}
|
||||||
|
|
||||||
|
func module(withId moduleId: UUID) -> (any ModuleBuilder)? {
|
||||||
|
editableProfile.modules.first {
|
||||||
$0.id == moduleId
|
$0.id == moduleId
|
||||||
} ?? removedModules[moduleId]
|
} ?? removedModules[moduleId]
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveModules(from offsets: IndexSet, to newOffset: Int) {
|
|
||||||
modules.move(fromOffsets: offsets, toOffset: newOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeModules(at offsets: IndexSet) {
|
|
||||||
offsets.forEach {
|
|
||||||
let module = modules[$0]
|
|
||||||
removedModules[module.id] = module
|
|
||||||
modules.remove(at: $0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeModule(withId moduleId: UUID) {
|
|
||||||
guard let index = modules.firstIndex(where: { $0.id == moduleId }) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let module = modules[index]
|
|
||||||
removedModules[module.id] = module
|
|
||||||
modules.remove(at: index)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveModule(_ module: any EditableModule, activating: Bool) {
|
|
||||||
if let index = modules.firstIndex(where: { $0.id == module.id }) {
|
|
||||||
modules[index] = module
|
|
||||||
} else {
|
|
||||||
modules.append(module)
|
|
||||||
}
|
|
||||||
if activating {
|
|
||||||
activateModule(module)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Active modules
|
|
||||||
|
|
||||||
extension ProfileEditor {
|
|
||||||
func isActiveModule(withId moduleId: UUID) -> Bool {
|
func isActiveModule(withId moduleId: UUID) -> Bool {
|
||||||
activeModulesIds.contains(moduleId) && !removedModules.keys.contains(moduleId)
|
editableProfile.isActiveModule(withId: moduleId)
|
||||||
}
|
|
||||||
|
|
||||||
var activeConnectionModule: (any EditableModule)? {
|
|
||||||
modules.first {
|
|
||||||
isActiveModule(withId: $0.id) && $0.buildsConnectionModule
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var activeModules: [any EditableModule] {
|
|
||||||
modules.filter {
|
|
||||||
activeModulesIds.contains($0.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func activateModule(_ module: any EditableModule) {
|
|
||||||
activeModulesIds.insert(module.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleModule(withId moduleId: UUID) {
|
func toggleModule(withId moduleId: UUID) {
|
||||||
|
@ -194,26 +144,48 @@ extension ProfileEditor {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isActiveModule(withId: moduleId) {
|
if isActiveModule(withId: moduleId) {
|
||||||
activeModulesIds.remove(moduleId)
|
editableProfile.activeModulesIds.remove(moduleId)
|
||||||
} else {
|
} else {
|
||||||
activateModule(existingModule)
|
activateModule(existingModule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveModules(from offsets: IndexSet, to newOffset: Int) {
|
||||||
|
editableProfile.modules.move(fromOffsets: offsets, toOffset: newOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeModules(at offsets: IndexSet) {
|
||||||
|
offsets.forEach {
|
||||||
|
let module = editableProfile.modules[$0]
|
||||||
|
removedModules[module.id] = module
|
||||||
|
editableProfile.modules.remove(at: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeModule(withId moduleId: UUID) {
|
||||||
|
guard let index = editableProfile.modules.firstIndex(where: { $0.id == moduleId }) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let module = editableProfile.modules[index]
|
||||||
|
removedModules[module.id] = module
|
||||||
|
editableProfile.modules.remove(at: index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveModule(_ module: any ModuleBuilder, activating: Bool) {
|
||||||
|
if let index = editableProfile.modules.firstIndex(where: { $0.id == module.id }) {
|
||||||
|
editableProfile.modules[index] = module
|
||||||
|
} else {
|
||||||
|
editableProfile.modules.append(module)
|
||||||
|
}
|
||||||
|
if activating {
|
||||||
|
activateModule(module)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ProfileEditor {
|
private extension ProfileEditor {
|
||||||
func checkConstraints() throws {
|
func activateModule(_ module: any ModuleBuilder) {
|
||||||
if activeConnectionModule == nil,
|
editableProfile.activeModulesIds.insert(module.id)
|
||||||
let ipModule = modules.first(where: { activeModulesIds.contains($0.id) && $0 is IPModule.Builder }) {
|
|
||||||
throw AppError.ipModuleRequiresConnection(ipModule)
|
|
||||||
}
|
|
||||||
|
|
||||||
let connectionModules = modules.filter {
|
|
||||||
activeModulesIds.contains($0.id) && $0.buildsConnectionModule
|
|
||||||
}
|
|
||||||
guard connectionModules.count <= 1 else {
|
|
||||||
throw AppError.multipleConnectionModules(connectionModules)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,51 +193,17 @@ private extension ProfileEditor {
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
func build() throws -> Profile {
|
func build() throws -> Profile {
|
||||||
try checkConstraints()
|
let builder = try editableProfile.builder()
|
||||||
|
|
||||||
var builder = Profile.Builder(id: id)
|
|
||||||
let trimmedName = name.trimmingCharacters(in: .whitespaces)
|
|
||||||
guard !trimmedName.isEmpty else {
|
|
||||||
throw AppError.emptyProfileName
|
|
||||||
}
|
|
||||||
builder.name = trimmedName
|
|
||||||
builder.modules = try modules.compactMap {
|
|
||||||
do {
|
|
||||||
return try $0.tryBuild()
|
|
||||||
} catch {
|
|
||||||
throw AppError.malformedModule($0, error: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.activeModulesIds = activeModulesIds
|
|
||||||
builder.moduleNames = moduleNames.reduce(into: [:]) {
|
|
||||||
let trimmedName = $1.value.trimmingCharacters(in: .whitespaces)
|
|
||||||
guard !trimmedName.isEmpty else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
$0[$1.key] = trimmedName
|
|
||||||
}
|
|
||||||
let profile = try builder.tryBuild()
|
let profile = try builder.tryBuild()
|
||||||
|
|
||||||
// update local view
|
// update local view
|
||||||
modules = profile.modulesBuilders
|
editableProfile.modules = profile.modulesBuilders
|
||||||
removedModules.removeAll()
|
removedModules.removeAll()
|
||||||
|
|
||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension Profile {
|
|
||||||
var modulesBuilders: [any EditableModule] {
|
|
||||||
modules.compactMap {
|
|
||||||
guard let buildableModule = $0 as? any BuildableType else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
let builder = buildableModule.builder() as any BuilderType
|
|
||||||
return builder as? any EditableModule
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Saving
|
// MARK: - Saving
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
|
@ -279,3 +217,11 @@ extension ProfileEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Testing
|
||||||
|
|
||||||
|
extension ProfileEditor {
|
||||||
|
var activeModulesIds: Set<UUID> {
|
||||||
|
editableProfile.activeModulesIds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,11 +29,11 @@ import PassepartoutKit
|
||||||
enum AppError {
|
enum AppError {
|
||||||
case emptyProfileName
|
case emptyProfileName
|
||||||
|
|
||||||
case malformedModule(any EditableModule, error: Error)
|
case malformedModule(any ModuleBuilder, error: Error)
|
||||||
|
|
||||||
case multipleConnectionModules([any EditableModule])
|
case multipleConnectionModules([any ModuleBuilder])
|
||||||
|
|
||||||
case ipModuleRequiresConnection(any EditableModule)
|
case ipModuleRequiresConnection(any ModuleBuilder)
|
||||||
|
|
||||||
case permissionDenied
|
case permissionDenied
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
//
|
||||||
|
// EditableProfile.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 10/6/24.
|
||||||
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of Passepartout.
|
||||||
|
//
|
||||||
|
// Passepartout is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Passepartout is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PassepartoutKit
|
||||||
|
|
||||||
|
struct EditableProfile: MutableProfileType {
|
||||||
|
var id = UUID()
|
||||||
|
|
||||||
|
var name: String = ""
|
||||||
|
|
||||||
|
var modules: [any ModuleBuilder] = []
|
||||||
|
|
||||||
|
var activeModulesIds: Set<UUID> = []
|
||||||
|
|
||||||
|
var modulesMetadata: [UUID: ModuleMetadata]?
|
||||||
|
|
||||||
|
func builder() throws -> Profile.Builder {
|
||||||
|
try checkConstraints()
|
||||||
|
|
||||||
|
var builder = Profile.Builder(id: id)
|
||||||
|
builder.modules = try modules.compactMap {
|
||||||
|
do {
|
||||||
|
return try $0.tryBuild()
|
||||||
|
} catch {
|
||||||
|
throw AppError.malformedModule($0, error: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.activeModulesIds = activeModulesIds
|
||||||
|
|
||||||
|
let trimmedName = name.trimmingCharacters(in: .whitespaces)
|
||||||
|
guard !trimmedName.isEmpty else {
|
||||||
|
throw AppError.emptyProfileName
|
||||||
|
}
|
||||||
|
builder.name = trimmedName
|
||||||
|
|
||||||
|
builder.modulesMetadata = modulesMetadata?.reduce(into: [:]) {
|
||||||
|
var metadata = $1.value
|
||||||
|
guard let name = metadata.name else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let trimmedName = name.trimmingCharacters(in: .whitespaces)
|
||||||
|
guard !trimmedName.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata.name = trimmedName
|
||||||
|
$0[$1.key] = metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Profile {
|
||||||
|
func editable() -> EditableProfile {
|
||||||
|
EditableProfile(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
modules: modulesBuilders,
|
||||||
|
activeModulesIds: activeModulesIds,
|
||||||
|
modulesMetadata: modulesMetadata
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var modulesBuilders: [any ModuleBuilder] {
|
||||||
|
modules.compactMap {
|
||||||
|
guard let buildableModule = $0 as? any BuildableType else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let builder = buildableModule.builder() as any BuilderType
|
||||||
|
return builder as? any ModuleBuilder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension EditableProfile {
|
||||||
|
var activeConnectionModule: (any ModuleBuilder)? {
|
||||||
|
modules.first {
|
||||||
|
isActiveModule(withId: $0.id) && $0.buildsConnectionModule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConstraints() throws {
|
||||||
|
if activeConnectionModule == nil,
|
||||||
|
let ipModule = modules.first(where: { activeModulesIds.contains($0.id) && $0 is IPModule.Builder }) {
|
||||||
|
throw AppError.ipModuleRequiresConnection(ipModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
let connectionModules = modules.filter {
|
||||||
|
activeModulesIds.contains($0.id) && $0.buildsConnectionModule
|
||||||
|
}
|
||||||
|
guard connectionModules.count <= 1 else {
|
||||||
|
throw AppError.multipleConnectionModules(connectionModules)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension ModuleType {
|
extension ModuleType {
|
||||||
func newModule() -> any EditableModule {
|
func newModule() -> any ModuleBuilder {
|
||||||
switch self {
|
switch self {
|
||||||
case .openVPN:
|
case .openVPN:
|
||||||
return OpenVPNModule.Builder()
|
return OpenVPNModule.Builder()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// EditableModule+Description.swift
|
// ModuleBuilder+Description.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 9/6/24.
|
// Created by Davide De Rosa on 9/6/24.
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension EditableModule {
|
extension ModuleBuilder {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func description(inEditor editor: ProfileEditor) -> String {
|
func description(inEditor editor: ProfileEditor) -> String {
|
||||||
|
@ -34,7 +34,7 @@ extension EditableModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EditableModule {
|
extension ModuleBuilder {
|
||||||
var typeDescription: String {
|
var typeDescription: String {
|
||||||
guard let providing = self as? ModuleTypeProviding else {
|
guard let providing = self as? ModuleTypeProviding else {
|
||||||
return String(describing: self)
|
return String(describing: self)
|
||||||
|
|
|
@ -95,7 +95,7 @@ private extension ProfileEditView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func moduleRow(for module: any EditableModule) -> some View {
|
func moduleRow(for module: any ModuleBuilder) -> some View {
|
||||||
EditorModuleToggle(profileEditor: profileEditor, module: module) {
|
EditorModuleToggle(profileEditor: profileEditor, module: module) {
|
||||||
Button {
|
Button {
|
||||||
push(.moduleDetail(moduleId: module.id))
|
push(.moduleDetail(moduleId: module.id))
|
||||||
|
|
|
@ -69,7 +69,7 @@ struct ModuleListView: View, Routable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ModuleListView {
|
private extension ModuleListView {
|
||||||
func moduleRow(for module: any EditableModule) -> some View {
|
func moduleRow(for module: any ModuleBuilder) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text(module.description(inEditor: profileEditor))
|
Text(module.description(inEditor: profileEditor))
|
||||||
.themeError(malformedModuleIds.contains(module.id))
|
.themeError(malformedModuleIds.contains(module.id))
|
||||||
|
|
|
@ -56,7 +56,7 @@ private extension ProfileEditor {
|
||||||
extension View {
|
extension View {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func asModuleView<T>(with editor: ProfileEditor, draft: T, withName: Bool = true) -> some View where T: EditableModule, T: Equatable {
|
func asModuleView<T>(with editor: ProfileEditor, draft: T, withName: Bool = true) -> some View where T: ModuleBuilder, T: Equatable {
|
||||||
Form {
|
Form {
|
||||||
if withName {
|
if withName {
|
||||||
NameSection(
|
NameSection(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// EditableModule+Previews.swift
|
// ModuleBuilder+Previews.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/19/24.
|
// Created by Davide De Rosa on 8/19/24.
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension EditableModule where Self: ModuleViewProviding {
|
extension ModuleBuilder where Self: ModuleViewProviding {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func preview(title: String = "") -> some View {
|
func preview(title: String = "") -> some View {
|
||||||
|
|
|
@ -35,7 +35,7 @@ extension ProfileEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func binding<T>(forModule module: T) -> Binding<T> where T: EditableModule {
|
func binding<T>(forModule module: T) -> Binding<T> where T: ModuleBuilder {
|
||||||
Binding { [weak self] in
|
Binding { [weak self] in
|
||||||
guard let foundModule = self?.module(withId: module.id) else {
|
guard let foundModule = self?.module(withId: module.id) else {
|
||||||
fatalError("Module not found in editor: \(module.id)")
|
fatalError("Module not found in editor: \(module.id)")
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct EditorModuleToggle<Label>: View where Label: View {
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var profileEditor: ProfileEditor
|
var profileEditor: ProfileEditor
|
||||||
|
|
||||||
let module: any EditableModule
|
let module: any ModuleBuilder
|
||||||
|
|
||||||
let label: () -> Label
|
let label: () -> Label
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue