246 lines
8.2 KiB
Swift
246 lines
8.2 KiB
Swift
import SwiftUI
|
|
|
|
public struct SettingsView: View {
|
|
@State public var config: SyncConfig
|
|
public var onSave: (SyncConfig) -> Void
|
|
|
|
@State private var serverURL: String = ""
|
|
@State private var apiToken: String = ""
|
|
@State private var syncFolder: String = ""
|
|
@State private var defaultUploadFolder: String = ""
|
|
@State private var autoSync: Bool = true
|
|
@State private var syncInterval: Double = 30
|
|
@State private var selectedIcon: String = "tray.circle.fill"
|
|
|
|
private let labelWidth: CGFloat = 120
|
|
|
|
public init(config: SyncConfig, onSave: @escaping (SyncConfig) -> Void) {
|
|
self.config = config
|
|
self.onSave = onSave
|
|
}
|
|
|
|
public var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 0) {
|
|
serverSection
|
|
Divider().padding(.vertical, 16)
|
|
syncSection
|
|
Divider().padding(.vertical, 16)
|
|
uploadsSection
|
|
Divider().padding(.vertical, 16)
|
|
appearanceSection
|
|
|
|
Spacer(minLength: 32)
|
|
|
|
saveButton
|
|
}
|
|
.padding(24)
|
|
}
|
|
.frame(width: 520, height: 600)
|
|
.onAppear {
|
|
serverURL = config.serverURL
|
|
apiToken = config.apiToken ?? ""
|
|
syncFolder = config.syncFolder ?? ""
|
|
defaultUploadFolder = config.defaultUploadFolder ?? ""
|
|
autoSync = config.autoSync
|
|
syncInterval = Double(config.syncInterval)
|
|
selectedIcon = config.iconName
|
|
}
|
|
}
|
|
|
|
// MARK: - Sections
|
|
|
|
private var serverSection: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
sectionHeader("Server", icon: "server.rack")
|
|
|
|
formRow(label: "Server URL") {
|
|
TextField("", text: $serverURL)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(width: 280)
|
|
}
|
|
|
|
formRow(label: "API Token") {
|
|
SecureField("", text: $apiToken)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(width: 280)
|
|
}
|
|
|
|
HStack {
|
|
Spacer()
|
|
.frame(width: labelWidth)
|
|
Text("Create an API token from the web app at /account")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(nil)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.frame(width: 280, alignment: .leading)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var syncSection: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
sectionHeader("Sync", icon: "arrow.triangle.2.circlepath")
|
|
|
|
formRow(label: "Sync Folder") {
|
|
HStack(spacing: 8) {
|
|
TextField("", text: $syncFolder)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(width: 200)
|
|
|
|
Button("Choose...") {
|
|
chooseFolder()
|
|
}
|
|
.controlSize(.small)
|
|
}
|
|
}
|
|
|
|
formRow(label: "") {
|
|
Toggle("Auto Sync", isOn: $autoSync)
|
|
}
|
|
|
|
if autoSync {
|
|
formRow(label: "Interval") {
|
|
HStack(spacing: 12) {
|
|
Slider(value: $syncInterval, in: 5...300, step: 5)
|
|
.frame(width: 160)
|
|
Text("\(Int(syncInterval))s")
|
|
.font(.system(size: 12, design: .monospaced))
|
|
.foregroundColor(.secondary)
|
|
.frame(width: 40, alignment: .trailing)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var uploadsSection: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
sectionHeader("Uploads", icon: "arrow.up.doc")
|
|
|
|
formRow(label: "Upload Folder") {
|
|
TextField("e.g. inbox", text: $defaultUploadFolder)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(width: 280)
|
|
}
|
|
|
|
HStack {
|
|
Spacer()
|
|
.frame(width: labelWidth)
|
|
Text("Notes and uploads will be saved to this folder")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
.frame(width: 280, alignment: .leading)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var appearanceSection: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
sectionHeader("Appearance", icon: "paintbrush")
|
|
|
|
formRow(label: "Menu Icon") {
|
|
Picker("", selection: $selectedIcon) {
|
|
ForEach(SyncConfig.iconOptions, id: \.self) { icon in
|
|
HStack(spacing: 8) {
|
|
Image(systemName: icon)
|
|
.frame(width: 20, alignment: .center)
|
|
Text(iconNameToLabel(icon))
|
|
}
|
|
.tag(icon)
|
|
}
|
|
}
|
|
.pickerStyle(.menu)
|
|
.frame(width: 180)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var saveButton: some View {
|
|
HStack {
|
|
Spacer()
|
|
Button {
|
|
let newConfig = SyncConfig(
|
|
serverURL: serverURL,
|
|
apiToken: apiToken.isEmpty ? nil : apiToken,
|
|
syncFolder: syncFolder.isEmpty ? nil : syncFolder,
|
|
defaultUploadFolder: defaultUploadFolder.isEmpty ? nil : defaultUploadFolder,
|
|
autoSync: autoSync,
|
|
syncInterval: Int(syncInterval),
|
|
deviceId: config.deviceId,
|
|
iconName: selectedIcon
|
|
)
|
|
onSave(newConfig)
|
|
} label: {
|
|
Text("Save")
|
|
.frame(width: 80)
|
|
}
|
|
.keyboardShortcut(.defaultAction)
|
|
.controlSize(.large)
|
|
.buttonStyle(.borderedProminent)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private func sectionHeader(_ title: String, icon: String) -> some View {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: icon)
|
|
.foregroundColor(.accentColor)
|
|
.font(.system(size: 16, weight: .medium))
|
|
Text(title)
|
|
.font(.system(size: 16, weight: .semibold))
|
|
Spacer()
|
|
}
|
|
.padding(.bottom, 4)
|
|
}
|
|
|
|
private func formRow<Content: View>(label: String, @ViewBuilder content: () -> Content) -> some View {
|
|
HStack(alignment: .firstTextBaseline, spacing: 16) {
|
|
Text(label)
|
|
.font(.system(size: 13))
|
|
.foregroundColor(.primary)
|
|
.frame(width: labelWidth, alignment: .trailing)
|
|
|
|
content()
|
|
|
|
Spacer()
|
|
}
|
|
}
|
|
|
|
private func chooseFolder() {
|
|
let panel = NSOpenPanel()
|
|
panel.canChooseFiles = false
|
|
panel.canChooseDirectories = true
|
|
panel.allowsMultipleSelection = false
|
|
panel.prompt = "Select Folder"
|
|
|
|
if panel.runModal() == .OK {
|
|
syncFolder = panel.url?.path ?? ""
|
|
}
|
|
}
|
|
|
|
private func iconNameToLabel(_ name: String) -> String {
|
|
let labels: [String: String] = [
|
|
"tray.circle.fill": "Tray (Default)",
|
|
"arrow.triangle.2.circlepath": "Sync",
|
|
"arrow.up.doc": "Upload",
|
|
"doc.badge.arrow.up": "Doc Upload",
|
|
"arrow.up.and.down.and.arrow.left.and.right": "Transfer",
|
|
"cloud": "Cloud",
|
|
"externaldrive.connected.to.line.below": "Server",
|
|
"note.text": "Notes",
|
|
"doc.text": "Documents",
|
|
"arrow.clockwise.circle": "Refresh",
|
|
"rectangle.stack.badge.plus": "Stack"
|
|
]
|
|
return labels[name] ?? name
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
SettingsView(config: SyncConfig()) { _ in }
|
|
}
|