mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
feat: let users pick the default server opened on launch (#26)
Repurposes the previously-unused ServerEntry.openOnLaunch flag so users can nominate Local or any registered remote as the server Scarf opens into when a fresh window has no prior binding (first launch or File → New Window). - ServerRegistry gains `defaultServerID` (returns the flagged entry's ID or falls back to Local) and `setDefaultServer(_:)` (flips the flag on the named entry and clears it elsewhere, then persists). - ScarfApp's WindowGroup defaultValue closure now returns `registry.defaultServerID` instead of hardcoded `ServerContext.local.id`. - ManageServersView gains a Local row at the top of the list plus a star button per row: filled yellow on the current default, outline on the others. Click to promote. Backward compatible: the openOnLaunch field was already in the persisted schema (default false), so existing servers.json files load unchanged — Local remains the default until the user picks otherwise. Refs #26
This commit is contained in:
@@ -9,8 +9,10 @@ struct ServerEntry: Identifiable, Codable, Hashable, Sendable {
|
|||||||
var id: ServerID
|
var id: ServerID
|
||||||
var displayName: String
|
var displayName: String
|
||||||
var kind: ServerKind
|
var kind: ServerKind
|
||||||
/// User preference: open this server in a window on launch. Phase 3
|
/// User preference: this server is the one Scarf opens into when a
|
||||||
/// multi-window uses this; Phase 2 ignores it.
|
/// fresh window has no prior binding (first launch or File → New).
|
||||||
|
/// At most one entry should have this set — `ServerRegistry` enforces
|
||||||
|
/// mutual exclusivity. If none do, Local is the implicit default.
|
||||||
var openOnLaunch: Bool = false
|
var openOnLaunch: Bool = false
|
||||||
|
|
||||||
var context: ServerContext {
|
var context: ServerContext {
|
||||||
@@ -69,6 +71,32 @@ final class ServerRegistry {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The server a fresh window should open into. Returns the ID of the
|
||||||
|
/// remote entry flagged `openOnLaunch`, or Local's ID if none is
|
||||||
|
/// flagged (or if the flagged entry was removed out from under us).
|
||||||
|
/// Consumed by the `WindowGroup`'s `defaultValue` closure.
|
||||||
|
var defaultServerID: ServerID {
|
||||||
|
entries.first(where: { $0.openOnLaunch })?.id ?? ServerContext.local.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flip the default server to `id`. Passing `ServerContext.local.id`
|
||||||
|
/// clears the flag on every remote entry, making Local the implicit
|
||||||
|
/// default. Passing an unknown ID is a no-op. Persisted on return.
|
||||||
|
func setDefaultServer(_ id: ServerID) {
|
||||||
|
var changed = false
|
||||||
|
for idx in entries.indices {
|
||||||
|
let shouldBeDefault = (entries[idx].id == id)
|
||||||
|
if entries[idx].openOnLaunch != shouldBeDefault {
|
||||||
|
entries[idx].openOnLaunch = shouldBeDefault
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
save()
|
||||||
|
onEntriesChanged?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Mutations
|
// MARK: - Mutations
|
||||||
|
|
||||||
/// Optional callback fired whenever `entries` changes. The app wires
|
/// Optional callback fired whenever `entries` changes. The app wires
|
||||||
|
|||||||
@@ -87,9 +87,28 @@ struct ManageServersView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var list: some View {
|
private var list: some View {
|
||||||
List {
|
let defaultID = registry.defaultServerID
|
||||||
|
return List {
|
||||||
|
// Local sits at the top so users can mark it as the open-on-launch
|
||||||
|
// default alongside remote servers. It's synthesized (not in
|
||||||
|
// `registry.entries`), so render it explicitly.
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
defaultStar(for: ServerContext.local.id, currentDefault: defaultID)
|
||||||
|
Image(systemName: "laptopcomputer")
|
||||||
|
.foregroundStyle(.blue)
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text("Local").font(.body)
|
||||||
|
Text("This Mac")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
|
||||||
ForEach(registry.entries) { entry in
|
ForEach(registry.entries) { entry in
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
|
defaultStar(for: entry.id, currentDefault: defaultID)
|
||||||
Image(systemName: "server.rack")
|
Image(systemName: "server.rack")
|
||||||
.foregroundStyle(.blue)
|
.foregroundStyle(.blue)
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
@@ -123,6 +142,23 @@ struct ManageServersView: View {
|
|||||||
.listStyle(.inset)
|
.listStyle(.inset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A star button that marks the open-on-launch default. Filled + yellow
|
||||||
|
/// on the current default row (and non-interactive — clicking it is a
|
||||||
|
/// no-op since the flag is already set); outline + secondary elsewhere,
|
||||||
|
/// clicking promotes that row to default.
|
||||||
|
@ViewBuilder
|
||||||
|
private func defaultStar(for id: ServerID, currentDefault: ServerID) -> some View {
|
||||||
|
let isDefault = id == currentDefault
|
||||||
|
Button {
|
||||||
|
if !isDefault { registry.setDefaultServer(id) }
|
||||||
|
} label: {
|
||||||
|
Image(systemName: isDefault ? "star.fill" : "star")
|
||||||
|
.foregroundStyle(isDefault ? .yellow : .secondary)
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.help(isDefault ? "Opens on launch" : "Set as default — open this server when Scarf launches.")
|
||||||
|
}
|
||||||
|
|
||||||
private func summary(for config: SSHConfig) -> String {
|
private func summary(for config: SSHConfig) -> String {
|
||||||
var s = ""
|
var s = ""
|
||||||
if let user = config.user, !user.isEmpty { s += "\(user)@" }
|
if let user = config.user, !user.isEmpty { s += "\(user)@" }
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ struct ScarfApp: App {
|
|||||||
.environment(updater)
|
.environment(updater)
|
||||||
}
|
}
|
||||||
} defaultValue: {
|
} defaultValue: {
|
||||||
ServerContext.local.id
|
// Honour the user's "open on launch" choice from the Manage
|
||||||
|
// Servers popover. Falls back to Local when no entry is flagged
|
||||||
|
// (the default behaviour for fresh installs) or when the
|
||||||
|
// flagged entry was removed while the app was closed.
|
||||||
|
registry.defaultServerID
|
||||||
}
|
}
|
||||||
.defaultSize(width: 1100, height: 700)
|
.defaultSize(width: 1100, height: 700)
|
||||||
.commands {
|
.commands {
|
||||||
|
|||||||
Reference in New Issue
Block a user