1024 lines
31 KiB
Swift
1024 lines
31 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
public struct PluginMetadata: Identifiable {
|
|
public let id: String
|
|
public let title: String
|
|
public let iconName: String
|
|
public let iconTint: Color
|
|
public let order: Int
|
|
public let defaultDescription: String
|
|
|
|
public init(
|
|
id: String,
|
|
title: String,
|
|
iconName: String,
|
|
iconTint: Color,
|
|
order: Int,
|
|
defaultDescription: String
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.iconName = iconName
|
|
self.iconTint = iconTint
|
|
self.order = order
|
|
self.defaultDescription = defaultDescription
|
|
}
|
|
}
|
|
|
|
public enum PluginControlStyle {
|
|
case `switch`
|
|
case disclosure
|
|
case button
|
|
}
|
|
|
|
public enum PluginPanelAction: Equatable {
|
|
public enum SliderPhase: Equatable {
|
|
case changed
|
|
case ended
|
|
}
|
|
|
|
case setSwitch(Bool)
|
|
case setDisclosureExpanded(Bool)
|
|
case setSelection(controlID: String, optionID: String)
|
|
case setNavigationSelection(controlID: String, optionID: String)
|
|
case clearNavigationSelection(controlID: String)
|
|
case setDate(controlID: String, value: Date)
|
|
case setSlider(controlID: String, value: Double, phase: SliderPhase)
|
|
case invokeAction(controlID: String)
|
|
}
|
|
|
|
public enum PluginPanelDescriptionTone {
|
|
case secondary
|
|
case error
|
|
}
|
|
|
|
public enum PluginMenuActionBehavior {
|
|
case keepPresented
|
|
case dismissBeforeHandling
|
|
}
|
|
|
|
public enum PluginStatusTone {
|
|
case neutral
|
|
case positive
|
|
case caution
|
|
}
|
|
|
|
public enum PluginPermissionKind {
|
|
case accessibility
|
|
case inputMonitoring
|
|
case calendarFullAccess
|
|
case automation
|
|
case screenRecording
|
|
}
|
|
|
|
public enum SettingsDestination: Hashable {
|
|
case general
|
|
case pluginConfiguration
|
|
case about
|
|
}
|
|
|
|
public enum PluginDeactivationReason: Equatable {
|
|
case disabled
|
|
case uninstalling
|
|
case updating
|
|
case hostShutdown
|
|
|
|
/// `true` when the plugin should revert any external side-effects it owns
|
|
/// (user disabled, plugin uninstalled, or app is quitting).
|
|
/// `false` during hot-reload updates — the new version will re-activate.
|
|
public var requiresStateCleanup: Bool {
|
|
switch self {
|
|
case .disabled, .uninstalling, .hostShutdown: true
|
|
case .updating: false
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum MenuBarControlItemDefaults {
|
|
public static let visibleAutosaveName = "MacTools.ControlItem.Visible"
|
|
public static let hiddenAutosaveName = "MacTools.ControlItem.Hidden"
|
|
public static let alwaysHiddenAutosaveName = "MacTools.ControlItem.AlwaysHidden"
|
|
public static let visibleDefaultPreferredPosition: Double = 0
|
|
public static let hiddenDefaultPreferredPosition: Double = 1
|
|
public static let adjacentPreferredPositionOffset: Double = 0.5
|
|
private static let visiblePreferredPositionBackupKey = "MacTools.ControlItem.Visible.PreferredPositionBackup"
|
|
|
|
public static func prepareVisibleControlItem(userDefaults: UserDefaults = .standard) {
|
|
restoreVisibleControlItemPreferredPositionIfMissing(userDefaults: userDefaults)
|
|
prepareControlItemVisibility(autosaveName: visibleAutosaveName, userDefaults: userDefaults)
|
|
}
|
|
|
|
public static func resetVisibleControlItemPosition(userDefaults: UserDefaults = .standard) {
|
|
prepareControlItem(
|
|
autosaveName: visibleAutosaveName,
|
|
preferredPosition: preferredPositionForVisibleControlItemRightOfHiddenDivider(userDefaults: userDefaults),
|
|
alwaysResetPreferredPosition: true,
|
|
userDefaults: userDefaults
|
|
)
|
|
}
|
|
|
|
public static func visibleControlItemPreferredPosition(userDefaults: UserDefaults = .standard) -> Double? {
|
|
userDefaults.object(forKey: preferredPositionKey(visibleAutosaveName)) as? Double
|
|
}
|
|
|
|
public static func setVisibleControlItemPreferredPosition(
|
|
_ position: Double?,
|
|
userDefaults: UserDefaults = .standard
|
|
) {
|
|
setPreferredPosition(position, autosaveName: visibleAutosaveName, userDefaults: userDefaults)
|
|
}
|
|
|
|
public static func snapshotVisibleControlItemPreferredPosition(userDefaults: UserDefaults = .standard) {
|
|
guard let preferredPosition = visibleControlItemPreferredPosition(userDefaults: userDefaults) else {
|
|
return
|
|
}
|
|
|
|
userDefaults.set(preferredPosition, forKey: visiblePreferredPositionBackupKey)
|
|
}
|
|
|
|
public static func restoreVisibleControlItemPreferredPositionIfMissing(
|
|
userDefaults: UserDefaults = .standard
|
|
) {
|
|
guard
|
|
visibleControlItemPreferredPosition(userDefaults: userDefaults) == nil,
|
|
let preferredPosition = userDefaults.object(forKey: visiblePreferredPositionBackupKey) as? Double
|
|
else {
|
|
return
|
|
}
|
|
|
|
setVisibleControlItemPreferredPosition(preferredPosition, userDefaults: userDefaults)
|
|
}
|
|
|
|
public static func visibleControlItemNeedsRecovery(userDefaults: UserDefaults = .standard) -> Bool {
|
|
guard
|
|
let visiblePosition = visibleControlItemPreferredPosition(userDefaults: userDefaults),
|
|
let hiddenDividerPosition = hiddenDividerControlItemPreferredPosition(userDefaults: userDefaults)
|
|
else {
|
|
return false
|
|
}
|
|
|
|
return visiblePosition >= hiddenDividerPosition
|
|
}
|
|
|
|
public static func prepareHiddenDividerControlItem(
|
|
preferredPosition: Double = hiddenDefaultPreferredPosition,
|
|
userDefaults: UserDefaults = .standard
|
|
) {
|
|
prepareControlItem(
|
|
autosaveName: hiddenAutosaveName,
|
|
preferredPosition: preferredPosition,
|
|
alwaysResetPreferredPosition: false,
|
|
userDefaults: userDefaults
|
|
)
|
|
}
|
|
|
|
public static func hiddenDividerControlItemPreferredPosition(userDefaults: UserDefaults = .standard) -> Double? {
|
|
userDefaults.object(forKey: preferredPositionKey(hiddenAutosaveName)) as? Double
|
|
}
|
|
|
|
public static func setHiddenDividerControlItemPreferredPosition(
|
|
_ position: Double?,
|
|
userDefaults: UserDefaults = .standard
|
|
) {
|
|
setPreferredPosition(position, autosaveName: hiddenAutosaveName, userDefaults: userDefaults)
|
|
}
|
|
|
|
public static func prepareAlwaysHiddenDividerControlItem(userDefaults: UserDefaults = .standard) {
|
|
setPreferredPosition(nil, autosaveName: alwaysHiddenAutosaveName, userDefaults: userDefaults)
|
|
prepareControlItemVisibility(
|
|
autosaveName: alwaysHiddenAutosaveName,
|
|
userDefaults: userDefaults
|
|
)
|
|
}
|
|
|
|
public static func alwaysHiddenDividerControlItemPreferredPosition(userDefaults: UserDefaults = .standard) -> Double? {
|
|
userDefaults.object(forKey: preferredPositionKey(alwaysHiddenAutosaveName)) as? Double
|
|
}
|
|
|
|
public static func setAlwaysHiddenDividerControlItemPreferredPosition(
|
|
_ position: Double?,
|
|
userDefaults: UserDefaults = .standard
|
|
) {
|
|
setPreferredPosition(position, autosaveName: alwaysHiddenAutosaveName, userDefaults: userDefaults)
|
|
}
|
|
|
|
public static func preferredPositionForVisibleControlItemRightOfHiddenDivider(
|
|
userDefaults: UserDefaults = .standard
|
|
) -> Double {
|
|
let dividerPosition = hiddenDividerControlItemPreferredPosition(userDefaults: userDefaults)
|
|
?? hiddenDefaultPreferredPosition
|
|
return dividerPosition - adjacentPreferredPositionOffset
|
|
}
|
|
|
|
public static func preferredPositionForHiddenDividerLeftOfVisibleControlItem(
|
|
userDefaults: UserDefaults = .standard
|
|
) -> Double {
|
|
let visiblePosition = visibleControlItemPreferredPosition(userDefaults: userDefaults)
|
|
?? visibleDefaultPreferredPosition
|
|
return visiblePosition + adjacentPreferredPositionOffset
|
|
}
|
|
|
|
private static func prepareControlItem(
|
|
autosaveName: String,
|
|
preferredPosition: Double,
|
|
alwaysResetPreferredPosition: Bool,
|
|
userDefaults: UserDefaults
|
|
) {
|
|
let preferredPositionKey = key("Preferred Position", autosaveName: autosaveName)
|
|
if alwaysResetPreferredPosition || userDefaults.object(forKey: preferredPositionKey) == nil {
|
|
userDefaults.set(preferredPosition, forKey: preferredPositionKey)
|
|
}
|
|
|
|
prepareControlItemVisibility(autosaveName: autosaveName, userDefaults: userDefaults)
|
|
}
|
|
|
|
private static func prepareControlItemVisibility(
|
|
autosaveName: String,
|
|
userDefaults: UserDefaults
|
|
) {
|
|
let visibleKey = key("Visible", autosaveName: autosaveName)
|
|
if userDefaults.object(forKey: visibleKey) == nil {
|
|
userDefaults.set(true, forKey: visibleKey)
|
|
}
|
|
|
|
let visibleControlCenterKey = key("VisibleCC", autosaveName: autosaveName)
|
|
if userDefaults.object(forKey: visibleControlCenterKey) == nil {
|
|
userDefaults.set(true, forKey: visibleControlCenterKey)
|
|
}
|
|
}
|
|
|
|
private static func key(_ rawValue: String, autosaveName: String) -> String {
|
|
"NSStatusItem \(rawValue) \(autosaveName)"
|
|
}
|
|
|
|
private static func preferredPositionKey(_ autosaveName: String) -> String {
|
|
key("Preferred Position", autosaveName: autosaveName)
|
|
}
|
|
|
|
private static func setPreferredPosition(
|
|
_ position: Double?,
|
|
autosaveName: String,
|
|
userDefaults: UserDefaults
|
|
) {
|
|
let key = preferredPositionKey(autosaveName)
|
|
if let position {
|
|
userDefaults.set(position, forKey: key)
|
|
} else {
|
|
userDefaults.removeObject(forKey: key)
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct PluginConfigurationContext {
|
|
public let pluginID: String
|
|
|
|
public init(pluginID: String) {
|
|
self.pluginID = pluginID
|
|
}
|
|
}
|
|
|
|
public struct PluginConfiguration {
|
|
public let description: String?
|
|
public let prefersFullHeight: Bool
|
|
public let makeView: (PluginConfigurationContext) -> AnyView
|
|
|
|
public init<Content: View>(
|
|
description: String? = nil,
|
|
prefersFullHeight: Bool = false,
|
|
@ViewBuilder content: @escaping (PluginConfigurationContext) -> Content
|
|
) {
|
|
self.description = description
|
|
self.prefersFullHeight = prefersFullHeight
|
|
self.makeView = { context in
|
|
AnyView(content(context))
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct PluginPrimaryPanelDescriptor {
|
|
public let controlStyle: PluginControlStyle
|
|
public let menuActionBehavior: PluginMenuActionBehavior
|
|
private let staticButtonTitle: String?
|
|
private let buttonTitleProvider: (() -> String?)?
|
|
|
|
public var buttonTitle: String? {
|
|
buttonTitleProvider?() ?? staticButtonTitle
|
|
}
|
|
|
|
public init(
|
|
controlStyle: PluginControlStyle,
|
|
menuActionBehavior: PluginMenuActionBehavior,
|
|
buttonTitle: String? = nil
|
|
) {
|
|
self.controlStyle = controlStyle
|
|
self.menuActionBehavior = menuActionBehavior
|
|
self.staticButtonTitle = buttonTitle
|
|
self.buttonTitleProvider = nil
|
|
}
|
|
|
|
/// Keeps a button label current when a plugin supports runtime language
|
|
/// switching without recreating the plugin or its active state.
|
|
public init(
|
|
controlStyle: PluginControlStyle,
|
|
menuActionBehavior: PluginMenuActionBehavior,
|
|
buttonTitleProvider: @escaping () -> String?
|
|
) {
|
|
self.controlStyle = controlStyle
|
|
self.menuActionBehavior = menuActionBehavior
|
|
self.staticButtonTitle = nil
|
|
self.buttonTitleProvider = buttonTitleProvider
|
|
}
|
|
}
|
|
|
|
public struct PluginComponentSpan: Equatable, Hashable, Sendable {
|
|
public static let maximumWidth = 4
|
|
|
|
public let width: Int
|
|
public let height: Int
|
|
|
|
public init?(width: Int, height: Int) {
|
|
guard Self.isValid(width: width, height: height) else {
|
|
return nil
|
|
}
|
|
|
|
self.width = width
|
|
self.height = height
|
|
}
|
|
|
|
private init(uncheckedWidth width: Int, height: Int) {
|
|
self.width = width
|
|
self.height = height
|
|
}
|
|
|
|
public static let oneByOne = PluginComponentSpan(uncheckedWidth: 1, height: 1)
|
|
public static let oneByTwo = PluginComponentSpan(uncheckedWidth: 1, height: 2)
|
|
public static let twoByOne = PluginComponentSpan(uncheckedWidth: 2, height: 1)
|
|
public static let twoByTwo = PluginComponentSpan(uncheckedWidth: 2, height: 2)
|
|
public static let fourByTwo = PluginComponentSpan(uncheckedWidth: 4, height: 2)
|
|
|
|
public static func isValid(width: Int, height: Int) -> Bool {
|
|
(1...maximumWidth).contains(width) && height >= 1
|
|
}
|
|
}
|
|
|
|
public struct PluginComponentPanelLayoutMetrics: Equatable, Sendable {
|
|
public static let cardCornerRadius: CGFloat = 12
|
|
|
|
public let columns: Int
|
|
public let cellWidth: CGFloat
|
|
public let cellHeight: CGFloat
|
|
public let horizontalSpacing: CGFloat
|
|
public let verticalSpacing: CGFloat
|
|
public let originalCellHeight: CGFloat
|
|
|
|
public init(
|
|
columns: Int,
|
|
cellWidth: CGFloat,
|
|
cellHeight: CGFloat,
|
|
horizontalSpacing: CGFloat,
|
|
verticalSpacing: CGFloat,
|
|
originalCellHeight: CGFloat
|
|
) {
|
|
self.columns = columns
|
|
self.cellWidth = cellWidth
|
|
self.cellHeight = cellHeight
|
|
self.horizontalSpacing = horizontalSpacing
|
|
self.verticalSpacing = verticalSpacing
|
|
self.originalCellHeight = originalCellHeight
|
|
}
|
|
|
|
public static let `default`: PluginComponentPanelLayoutMetrics = {
|
|
let originalCellHeight: CGFloat = 94
|
|
let verticalSpacing: CGFloat = 8
|
|
return PluginComponentPanelLayoutMetrics(
|
|
columns: PluginComponentSpan.maximumWidth,
|
|
cellWidth: 70,
|
|
cellHeight: 8,
|
|
horizontalSpacing: 8,
|
|
verticalSpacing: verticalSpacing,
|
|
originalCellHeight: originalCellHeight
|
|
)
|
|
}()
|
|
|
|
public var gridWidth: CGFloat {
|
|
CGFloat(columns) * cellWidth + CGFloat(max(columns - 1, 0)) * horizontalSpacing
|
|
}
|
|
|
|
public func itemWidth(forSpanWidth width: Int) -> CGFloat {
|
|
CGFloat(width) * cellWidth + CGFloat(max(width - 1, 0)) * horizontalSpacing
|
|
}
|
|
|
|
public func itemHeight(forSpanHeight height: Int) -> CGFloat {
|
|
CGFloat(height) * cellHeight
|
|
}
|
|
|
|
public func offsetX(forColumn column: Int) -> CGFloat {
|
|
CGFloat(column) * (cellWidth + horizontalSpacing)
|
|
}
|
|
|
|
public func offsetY(forRow row: Int) -> CGFloat {
|
|
CGFloat(row) * cellHeight
|
|
}
|
|
|
|
public func heightSpan(fittingContentHeight contentHeight: CGFloat) -> Int {
|
|
guard contentHeight > 0 else {
|
|
return 1
|
|
}
|
|
|
|
guard cellHeight > 0 else {
|
|
return 1
|
|
}
|
|
|
|
return max(1, Int(ceil(contentHeight / cellHeight)))
|
|
}
|
|
|
|
public func heightSpan(closestToOriginalSpanHeight originalSpanHeight: Int) -> Int {
|
|
guard originalSpanHeight > 0 else {
|
|
return 1
|
|
}
|
|
|
|
let targetHeight = CGFloat(originalSpanHeight) * originalCellHeight
|
|
+ CGFloat(max(originalSpanHeight - 1, 0)) * verticalSpacing
|
|
guard cellHeight > 0 else {
|
|
return 1
|
|
}
|
|
|
|
return max(1, Int((targetHeight / cellHeight).rounded()))
|
|
}
|
|
}
|
|
|
|
public struct PluginComponentDescriptor {
|
|
public let span: PluginComponentSpan
|
|
|
|
public init(span: PluginComponentSpan) {
|
|
self.span = span
|
|
}
|
|
}
|
|
|
|
public struct PluginComponentState {
|
|
public let subtitle: String
|
|
public let isActive: Bool
|
|
public let isEnabled: Bool
|
|
public let isVisible: Bool
|
|
public let errorMessage: String?
|
|
|
|
public init(
|
|
subtitle: String,
|
|
isActive: Bool,
|
|
isEnabled: Bool,
|
|
isVisible: Bool,
|
|
errorMessage: String?
|
|
) {
|
|
self.subtitle = subtitle
|
|
self.isActive = isActive
|
|
self.isEnabled = isEnabled
|
|
self.isVisible = isVisible
|
|
self.errorMessage = errorMessage
|
|
}
|
|
}
|
|
|
|
public struct PluginComponentContext {
|
|
public let pluginID: String
|
|
public let dismiss: () -> Void
|
|
public let isPanelVisible: Bool
|
|
|
|
public init(pluginID: String, dismiss: @escaping () -> Void, isPanelVisible: Bool) {
|
|
self.pluginID = pluginID
|
|
self.dismiss = dismiss
|
|
self.isPanelVisible = isPanelVisible
|
|
}
|
|
}
|
|
|
|
public struct PluginComponentViewItem: Identifiable {
|
|
public let id: String
|
|
public let content: AnyView
|
|
|
|
public init(id: String, content: AnyView) {
|
|
self.id = id
|
|
self.content = content
|
|
}
|
|
}
|
|
|
|
public struct PluginComponentItem: Identifiable {
|
|
public let id: String
|
|
public let title: String
|
|
public let iconName: String
|
|
public let iconTint: Color
|
|
public let description: String
|
|
public let helpText: String
|
|
public let descriptionTone: PluginPanelDescriptionTone
|
|
public let span: PluginComponentSpan
|
|
public let isActive: Bool
|
|
public let isEnabled: Bool
|
|
|
|
public init(
|
|
id: String,
|
|
title: String,
|
|
iconName: String,
|
|
iconTint: Color,
|
|
description: String,
|
|
helpText: String,
|
|
descriptionTone: PluginPanelDescriptionTone,
|
|
span: PluginComponentSpan,
|
|
isActive: Bool,
|
|
isEnabled: Bool
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.iconName = iconName
|
|
self.iconTint = iconTint
|
|
self.description = description
|
|
self.helpText = helpText
|
|
self.descriptionTone = descriptionTone
|
|
self.span = span
|
|
self.isActive = isActive
|
|
self.isEnabled = isEnabled
|
|
}
|
|
}
|
|
|
|
public struct PluginPanelState {
|
|
public let subtitle: String
|
|
public let isOn: Bool
|
|
public let isExpanded: Bool
|
|
public let isEnabled: Bool
|
|
public let isVisible: Bool
|
|
public let detail: PluginPanelDetail?
|
|
public let errorMessage: String?
|
|
|
|
public init(
|
|
subtitle: String,
|
|
isOn: Bool,
|
|
isExpanded: Bool,
|
|
isEnabled: Bool,
|
|
isVisible: Bool,
|
|
detail: PluginPanelDetail?,
|
|
errorMessage: String?
|
|
) {
|
|
self.subtitle = subtitle
|
|
self.isOn = isOn
|
|
self.isExpanded = isExpanded
|
|
self.isEnabled = isEnabled
|
|
self.isVisible = isVisible
|
|
self.detail = detail
|
|
self.errorMessage = errorMessage
|
|
}
|
|
}
|
|
|
|
public struct PluginPrimaryPanelIndicator: Equatable {
|
|
public let text: String
|
|
public let systemImage: String
|
|
|
|
public init(text: String, systemImage: String) {
|
|
self.text = text
|
|
self.systemImage = systemImage
|
|
}
|
|
}
|
|
|
|
public enum PluginPanelControlKind {
|
|
case segmented
|
|
case datePicker
|
|
case selectList
|
|
case navigationList
|
|
case slider
|
|
case actionRow
|
|
case switchRow
|
|
}
|
|
|
|
public enum PluginPanelDatePickerStyle {
|
|
case compact
|
|
case dateTimeCard
|
|
}
|
|
|
|
public struct PluginPanelControlOption: Identifiable, Equatable {
|
|
public let id: String
|
|
public let title: String
|
|
public let subtitle: String?
|
|
|
|
public init(id: String, title: String, subtitle: String? = nil) {
|
|
self.id = id
|
|
self.title = title
|
|
self.subtitle = subtitle
|
|
}
|
|
}
|
|
|
|
public struct PluginPanelControl: Identifiable {
|
|
// Dynamic plugin bundles pass this value type across the PluginKit boundary.
|
|
// Bump PluginKitCompatibility before changing stored property layout.
|
|
public let id: String
|
|
public let kind: PluginPanelControlKind
|
|
public let options: [PluginPanelControlOption]
|
|
public let selectedOptionID: String?
|
|
public let dateValue: Date?
|
|
public let minimumDate: Date?
|
|
public let displayedComponents: DatePickerComponents?
|
|
public let datePickerStyle: PluginPanelDatePickerStyle?
|
|
public let sectionTitle: String?
|
|
public let sliderValue: Double?
|
|
public let sliderBounds: ClosedRange<Double>?
|
|
public let sliderStep: Double?
|
|
public let valueLabel: String?
|
|
public let actionTitle: String?
|
|
public let actionIconSystemName: String?
|
|
public let actionBehavior: PluginMenuActionBehavior
|
|
public let showsLeadingDivider: Bool
|
|
public let isEnabled: Bool
|
|
|
|
public init(
|
|
id: String,
|
|
kind: PluginPanelControlKind,
|
|
options: [PluginPanelControlOption],
|
|
selectedOptionID: String?,
|
|
dateValue: Date?,
|
|
minimumDate: Date?,
|
|
displayedComponents: DatePickerComponents?,
|
|
datePickerStyle: PluginPanelDatePickerStyle?,
|
|
sectionTitle: String?,
|
|
sliderValue: Double? = nil,
|
|
sliderBounds: ClosedRange<Double>? = nil,
|
|
sliderStep: Double? = nil,
|
|
valueLabel: String? = nil,
|
|
actionTitle: String? = nil,
|
|
actionIconSystemName: String? = nil,
|
|
actionBehavior: PluginMenuActionBehavior = .keepPresented,
|
|
showsLeadingDivider: Bool = false,
|
|
isEnabled: Bool
|
|
) {
|
|
self.id = id
|
|
self.kind = kind
|
|
self.options = options
|
|
self.selectedOptionID = selectedOptionID
|
|
self.dateValue = dateValue
|
|
self.minimumDate = minimumDate
|
|
self.displayedComponents = displayedComponents
|
|
self.datePickerStyle = datePickerStyle
|
|
self.sectionTitle = sectionTitle
|
|
self.sliderValue = sliderValue
|
|
self.sliderBounds = sliderBounds
|
|
self.sliderStep = sliderStep
|
|
self.valueLabel = valueLabel
|
|
self.actionTitle = actionTitle
|
|
self.actionIconSystemName = actionIconSystemName
|
|
self.actionBehavior = actionBehavior
|
|
self.showsLeadingDivider = showsLeadingDivider
|
|
self.isEnabled = isEnabled
|
|
}
|
|
}
|
|
|
|
public struct PluginPanelSecondaryPanel {
|
|
public let title: String
|
|
public let controls: [PluginPanelControl]
|
|
|
|
public init(title: String, controls: [PluginPanelControl]) {
|
|
self.title = title
|
|
self.controls = controls
|
|
}
|
|
}
|
|
|
|
public struct PluginPanelNavigationSecondaryPanel {
|
|
public let controlID: String
|
|
public let optionID: String
|
|
public let panel: PluginPanelSecondaryPanel
|
|
|
|
public init(controlID: String, optionID: String, panel: PluginPanelSecondaryPanel) {
|
|
self.controlID = controlID
|
|
self.optionID = optionID
|
|
self.panel = panel
|
|
}
|
|
}
|
|
|
|
public struct PluginPanelDetail {
|
|
public let primaryControls: [PluginPanelControl]
|
|
public let secondaryPanel: PluginPanelSecondaryPanel?
|
|
public let navigationSecondaryPanels: [PluginPanelNavigationSecondaryPanel]
|
|
|
|
public var controls: [PluginPanelControl] {
|
|
primaryControls
|
|
}
|
|
|
|
public init(
|
|
primaryControls: [PluginPanelControl],
|
|
secondaryPanel: PluginPanelSecondaryPanel?,
|
|
navigationSecondaryPanels: [PluginPanelNavigationSecondaryPanel] = []
|
|
) {
|
|
self.primaryControls = primaryControls
|
|
self.secondaryPanel = secondaryPanel
|
|
self.navigationSecondaryPanels = navigationSecondaryPanels
|
|
}
|
|
|
|
public init(controls: [PluginPanelControl]) {
|
|
self.init(primaryControls: controls, secondaryPanel: nil)
|
|
}
|
|
|
|
public func secondaryPanel(controlID: String, optionID: String) -> PluginPanelSecondaryPanel? {
|
|
if navigationSecondaryPanels.isEmpty {
|
|
return secondaryPanel
|
|
}
|
|
|
|
return navigationSecondaryPanels.first {
|
|
$0.controlID == controlID && $0.optionID == optionID
|
|
}?.panel
|
|
}
|
|
}
|
|
|
|
public struct PluginPermissionRequirement: Identifiable {
|
|
public let id: String
|
|
public let kind: PluginPermissionKind
|
|
public let title: String
|
|
public let description: String
|
|
|
|
public init(id: String, kind: PluginPermissionKind, title: String, description: String) {
|
|
self.id = id
|
|
self.kind = kind
|
|
self.title = title
|
|
self.description = description
|
|
}
|
|
}
|
|
|
|
public struct PluginPermissionState {
|
|
public let isGranted: Bool
|
|
public let footnote: String?
|
|
public let statusText: String?
|
|
public let statusSystemImage: String?
|
|
public let statusTone: PluginStatusTone?
|
|
|
|
public init(
|
|
isGranted: Bool,
|
|
footnote: String?,
|
|
statusText: String? = nil,
|
|
statusSystemImage: String? = nil,
|
|
statusTone: PluginStatusTone? = nil
|
|
) {
|
|
self.isGranted = isGranted
|
|
self.footnote = footnote
|
|
self.statusText = statusText
|
|
self.statusSystemImage = statusSystemImage
|
|
self.statusTone = statusTone
|
|
}
|
|
}
|
|
|
|
public struct PluginSettingsSection: Identifiable {
|
|
public struct Status {
|
|
public let text: String
|
|
public let systemImage: String
|
|
public let tone: PluginStatusTone
|
|
|
|
public init(text: String, systemImage: String, tone: PluginStatusTone) {
|
|
self.text = text
|
|
self.systemImage = systemImage
|
|
self.tone = tone
|
|
}
|
|
}
|
|
|
|
public let id: String
|
|
public let title: String
|
|
public let description: String
|
|
public let status: Status
|
|
public let footnote: String?
|
|
public let buttonTitle: String?
|
|
public let actionID: String?
|
|
|
|
public init(
|
|
id: String,
|
|
title: String,
|
|
description: String,
|
|
status: Status,
|
|
footnote: String?,
|
|
buttonTitle: String?,
|
|
actionID: String?
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.description = description
|
|
self.status = status
|
|
self.footnote = footnote
|
|
self.buttonTitle = buttonTitle
|
|
self.actionID = actionID
|
|
}
|
|
}
|
|
|
|
public struct PluginPanelItem: Identifiable {
|
|
public let id: String
|
|
public let title: String
|
|
public let iconName: String
|
|
public let iconTint: Color
|
|
public let controlStyle: PluginControlStyle
|
|
public let menuActionBehavior: PluginMenuActionBehavior
|
|
public let description: String
|
|
public let helpText: String
|
|
public let descriptionTone: PluginPanelDescriptionTone
|
|
public let isOn: Bool
|
|
public let isExpanded: Bool
|
|
public let isEnabled: Bool
|
|
public let detail: PluginPanelDetail?
|
|
public let buttonActionID: String?
|
|
public let buttonTitle: String?
|
|
|
|
public init(
|
|
id: String,
|
|
title: String,
|
|
iconName: String,
|
|
iconTint: Color,
|
|
controlStyle: PluginControlStyle,
|
|
menuActionBehavior: PluginMenuActionBehavior,
|
|
description: String,
|
|
helpText: String,
|
|
descriptionTone: PluginPanelDescriptionTone,
|
|
isOn: Bool,
|
|
isExpanded: Bool,
|
|
isEnabled: Bool,
|
|
detail: PluginPanelDetail?,
|
|
buttonActionID: String?,
|
|
buttonTitle: String?
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.iconName = iconName
|
|
self.iconTint = iconTint
|
|
self.controlStyle = controlStyle
|
|
self.menuActionBehavior = menuActionBehavior
|
|
self.description = description
|
|
self.helpText = helpText
|
|
self.descriptionTone = descriptionTone
|
|
self.isOn = isOn
|
|
self.isExpanded = isExpanded
|
|
self.isEnabled = isEnabled
|
|
self.detail = detail
|
|
self.buttonActionID = buttonActionID
|
|
self.buttonTitle = buttonTitle
|
|
}
|
|
}
|
|
|
|
public enum PluginFeaturePresentation: Equatable {
|
|
case featurePanel
|
|
case componentPanel
|
|
case featureAndComponentPanel
|
|
}
|
|
|
|
public struct PluginFeatureManagementItem: Identifiable {
|
|
public let id: String
|
|
public let title: String
|
|
public let description: String
|
|
public let iconName: String
|
|
public let iconTint: Color
|
|
public let isVisible: Bool
|
|
public let isActive: Bool
|
|
public let presentation: PluginFeaturePresentation
|
|
public let category: String?
|
|
public let releaseChannel: String?
|
|
|
|
public init(
|
|
id: String,
|
|
title: String,
|
|
description: String,
|
|
iconName: String,
|
|
iconTint: Color,
|
|
isVisible: Bool,
|
|
isActive: Bool,
|
|
presentation: PluginFeaturePresentation,
|
|
category: String? = nil,
|
|
releaseChannel: String? = nil
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.description = description
|
|
self.iconName = iconName
|
|
self.iconTint = iconTint
|
|
self.isVisible = isVisible
|
|
self.isActive = isActive
|
|
self.presentation = presentation
|
|
self.category = category
|
|
self.releaseChannel = releaseChannel
|
|
}
|
|
}
|
|
|
|
public struct PluginConfigurationItem: Identifiable {
|
|
public let id: String
|
|
public let pluginID: String
|
|
public let title: String
|
|
public let description: String
|
|
public let iconName: String
|
|
public let iconTint: Color
|
|
public let settingsCards: [PluginSettingsCard]
|
|
public let permissionCards: [PluginPermissionCard]
|
|
public let shortcutItems: [ShortcutSettingsItem]
|
|
public let hasCustomConfiguration: Bool
|
|
public let prefersFullHeight: Bool
|
|
|
|
public init(
|
|
id: String,
|
|
pluginID: String,
|
|
title: String,
|
|
description: String,
|
|
iconName: String,
|
|
iconTint: Color,
|
|
settingsCards: [PluginSettingsCard],
|
|
permissionCards: [PluginPermissionCard],
|
|
shortcutItems: [ShortcutSettingsItem],
|
|
hasCustomConfiguration: Bool,
|
|
prefersFullHeight: Bool = false
|
|
) {
|
|
self.id = id
|
|
self.pluginID = pluginID
|
|
self.title = title
|
|
self.description = description
|
|
self.iconName = iconName
|
|
self.iconTint = iconTint
|
|
self.settingsCards = settingsCards
|
|
self.permissionCards = permissionCards
|
|
self.shortcutItems = shortcutItems
|
|
self.hasCustomConfiguration = hasCustomConfiguration
|
|
self.prefersFullHeight = prefersFullHeight
|
|
}
|
|
}
|
|
|
|
public struct PluginConfigurationViewItem: Identifiable {
|
|
public let id: String
|
|
public let content: AnyView
|
|
|
|
public init(id: String, content: AnyView) {
|
|
self.id = id
|
|
self.content = content
|
|
}
|
|
}
|
|
|
|
public struct PluginPermissionCard: Identifiable {
|
|
public let id: String
|
|
public let pluginID: String
|
|
public let permissionID: String
|
|
public let title: String
|
|
public let description: String
|
|
public let iconSystemImage: String
|
|
public let iconVisualScale: CGFloat
|
|
public let statusText: String
|
|
public let statusSystemImage: String
|
|
public let statusTone: PluginStatusTone
|
|
public let footnote: String?
|
|
public let buttonTitle: String
|
|
|
|
public init(
|
|
id: String,
|
|
pluginID: String,
|
|
permissionID: String,
|
|
title: String,
|
|
description: String,
|
|
iconSystemImage: String,
|
|
iconVisualScale: CGFloat = 1,
|
|
statusText: String,
|
|
statusSystemImage: String,
|
|
statusTone: PluginStatusTone,
|
|
footnote: String?,
|
|
buttonTitle: String
|
|
) {
|
|
self.id = id
|
|
self.pluginID = pluginID
|
|
self.permissionID = permissionID
|
|
self.title = title
|
|
self.description = description
|
|
self.iconSystemImage = iconSystemImage
|
|
self.iconVisualScale = iconVisualScale
|
|
self.statusText = statusText
|
|
self.statusSystemImage = statusSystemImage
|
|
self.statusTone = statusTone
|
|
self.footnote = footnote
|
|
self.buttonTitle = buttonTitle
|
|
}
|
|
}
|
|
|
|
public struct PluginSettingsCard: Identifiable {
|
|
public let id: String
|
|
public let pluginID: String
|
|
public let title: String
|
|
public let description: String
|
|
public let statusText: String
|
|
public let statusSystemImage: String
|
|
public let statusTone: PluginStatusTone
|
|
public let footnote: String?
|
|
public let buttonTitle: String?
|
|
public let actionID: String?
|
|
|
|
public init(
|
|
id: String,
|
|
pluginID: String,
|
|
title: String,
|
|
description: String,
|
|
statusText: String,
|
|
statusSystemImage: String,
|
|
statusTone: PluginStatusTone,
|
|
footnote: String?,
|
|
buttonTitle: String?,
|
|
actionID: String?
|
|
) {
|
|
self.id = id
|
|
self.pluginID = pluginID
|
|
self.title = title
|
|
self.description = description
|
|
self.statusText = statusText
|
|
self.statusSystemImage = statusSystemImage
|
|
self.statusTone = statusTone
|
|
self.footnote = footnote
|
|
self.buttonTitle = buttonTitle
|
|
self.actionID = actionID
|
|
}
|
|
}
|