Restore pasting and rename sections to workspaces
Replacing the main menu dropped the Edit menu that AppKit routes Cmd+V/C/A/Z through, so the composer could not paste at all. The menu is rebuilt with those items, which --diag now dumps so the wiring can be checked without a keyboard. Workspaces also gained an order: the Cmd+K switcher has up/down controls, creating one opens a naming modal instead of prefilling the composer, and the UI now says workspace throughout. The stored JSON keeps the original `sections` keys so existing data and sync payloads stay valid.
This commit is contained in:
@@ -10,6 +10,8 @@ struct PanelView: View {
|
||||
@State private var search = ""
|
||||
@State private var showSearch = false
|
||||
@State private var showSectionSwitcher = false
|
||||
@State private var showNewWorkspace = false
|
||||
@State private var newWorkspaceName = ""
|
||||
@State private var showSync = false
|
||||
@State private var joinCode = ""
|
||||
@State private var selection = Set<UUID>()
|
||||
@@ -17,7 +19,7 @@ struct PanelView: View {
|
||||
@State private var copiedID: UUID?
|
||||
@State private var accessibilityGranted = AXIsProcessTrusted()
|
||||
|
||||
private enum Field: Hashable { case composer, search }
|
||||
private enum Field: Hashable { case composer, search, newWorkspace }
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
var body: some View {
|
||||
@@ -41,6 +43,7 @@ struct PanelView: View {
|
||||
.background(shortcutButtons)
|
||||
.overlay(sectionSwitcher)
|
||||
.overlay(syncOverlay)
|
||||
.overlay(newWorkspaceModal)
|
||||
.onDrop(of: [UTType.image.identifier, UTType.fileURL.identifier], isTargeted: nil, perform: handleDrop)
|
||||
.onReceive(NotificationCenter.default.publisher(for: .promptPanelDidShow)) { _ in
|
||||
focusedField = .composer
|
||||
@@ -66,7 +69,7 @@ struct PanelView: View {
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help("Switch section (Cmd+K)")
|
||||
.help("Switch workspace (Cmd+K)")
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -113,7 +116,7 @@ struct PanelView: View {
|
||||
private var searchField: some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "magnifyingglass").foregroundStyle(.secondary)
|
||||
TextField("Search all sections", text: $search)
|
||||
TextField("Search all workspaces", text: $search)
|
||||
.textFieldStyle(.plain)
|
||||
.focused($focusedField, equals: .search)
|
||||
.onExitCommand {
|
||||
@@ -315,39 +318,57 @@ struct PanelView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
VStack(spacing: 0) {
|
||||
ForEach(store.sections) { section in
|
||||
Button {
|
||||
ForEach(Array(store.sections.enumerated()), id: \.element.id) { index, section in
|
||||
HStack(spacing: 6) {
|
||||
Text(section.name)
|
||||
.font(.callout)
|
||||
Spacer()
|
||||
Text("\(section.items.count)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
if section.id == store.activeSection.id {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.accentColor)
|
||||
}
|
||||
// Reorder the workspace list; the boundary rows are
|
||||
// disabled rather than hidden so the list does not jump.
|
||||
HStack(spacing: 0) {
|
||||
Button {
|
||||
store.moveSection(section.id, by: -1)
|
||||
} label: {
|
||||
Image(systemName: "chevron.up")
|
||||
}
|
||||
.disabled(index == 0)
|
||||
Button {
|
||||
store.moveSection(section.id, by: 1)
|
||||
} label: {
|
||||
Image(systemName: "chevron.down")
|
||||
}
|
||||
.disabled(index == store.sections.count - 1)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.font(.system(size: 9, weight: .bold))
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 8)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
store.activeSectionID = section.id
|
||||
showSectionSwitcher = false
|
||||
} label: {
|
||||
HStack {
|
||||
Text(section.name)
|
||||
.font(.callout)
|
||||
Spacer()
|
||||
Text("\(section.items.count)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
if section.id == store.activeSection.id {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.accentColor)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 8)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
Divider()
|
||||
Button {
|
||||
composer = "# "
|
||||
showSectionSwitcher = false
|
||||
focusedField = .composer
|
||||
newWorkspaceName = ""
|
||||
showNewWorkspace = true
|
||||
focusedField = .newWorkspace
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "plus")
|
||||
Text("New Section")
|
||||
Text("New Workspace")
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
@@ -465,6 +486,53 @@ struct PanelView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - New workspace
|
||||
|
||||
@ViewBuilder
|
||||
private var newWorkspaceModal: some View {
|
||||
if showNewWorkspace {
|
||||
ZStack {
|
||||
Color.black.opacity(0.25)
|
||||
.onTapGesture { dismissNewWorkspace() }
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("New Workspace")
|
||||
.font(.headline)
|
||||
TextField("Name", text: $newWorkspaceName)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.focused($focusedField, equals: .newWorkspace)
|
||||
.onSubmit(createWorkspace)
|
||||
.onExitCommand { dismissNewWorkspace() }
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Cancel") { dismissNewWorkspace() }
|
||||
Button("Create") { createWorkspace() }
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.disabled(newWorkspaceName.trimmingCharacters(in: .whitespaces).isEmpty)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.frame(width: 260)
|
||||
.background(.regularMaterial)
|
||||
.cornerRadius(12)
|
||||
.shadow(radius: 18)
|
||||
}
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
|
||||
private func createWorkspace() {
|
||||
let name = newWorkspaceName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !name.isEmpty else { return }
|
||||
store.createSection(named: name)
|
||||
dismissNewWorkspace()
|
||||
}
|
||||
|
||||
private func dismissNewWorkspace() {
|
||||
showNewWorkspace = false
|
||||
newWorkspaceName = ""
|
||||
focusedField = .composer
|
||||
}
|
||||
|
||||
// MARK: - Selection
|
||||
|
||||
private var selectionBar: some View {
|
||||
|
||||
Reference in New Issue
Block a user