style: align translation controls with launcher design
This commit is contained in:
@@ -81,9 +81,9 @@ Applications with identical display names show a muted containing-folder hint be
|
||||
|
||||
## Conjure Translate
|
||||
|
||||
Search for **Conjure Translate**, labeled **Command**, and open it with Return, double-click, or **Open Command**. The launcher fades into an English → Japanese translation tool; the existing search bar becomes the English input. Escape or **Back** returns to the previous search.
|
||||
Search for **Conjure Translate**, labeled **Command**, and open it with Return, double-click, or **Open Command**. The launcher fades into an English → Japanese translation tool; the existing search bar becomes the English input and retains the launcher's placeholder styling. Escape returns to the previous search.
|
||||
|
||||
Choose **MyMemory (no key)** or **OpenAI · gpt-6-luna** from the provider dropdown. MyMemory is the initial default, and the selection is saved locally. Switching providers cancels pending work and clears the previous translation; there is no automatic fallback. Press Return from the English input or click **Translate** to send text to the selected provider.
|
||||
Choose **MyMemory (no key)** or **OpenAI · gpt-6-luna** from the provider dropdown. MyMemory is the initial default, and the selection is saved locally. Switching providers cancels pending work and clears the previous translation; there is no automatic fallback. Press Return from the English input or click **Translate** in the bottom-right launcher-style action pill, beside its Return key badge, to send text to the selected provider.
|
||||
|
||||
[MyMemory](https://mymemory.translated.net/doc/spec.php) requires no account or key. Anonymous use allows 500 UTF-8 bytes per request and [5,000 characters per day](https://mymemory.translated.net/doc/usagelimits.php). Oversized input is rejected before sending; quota and service errors appear in the tool. Text is sent to a third-party translation service: avoid sensitive content.
|
||||
|
||||
|
||||
@@ -776,6 +776,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
translation = controller
|
||||
controller.input = { [weak self] in self?.searchField.stringValue ?? "" }
|
||||
let previousQuery = searchField.stringValue
|
||||
let previousPlaceholder = searchField.placeholderAttributedString
|
||||
controller.closed = { [weak self, weak controller] in
|
||||
guard let self, let controller else { return }
|
||||
controller.view.removeFromSuperview()
|
||||
@@ -785,7 +786,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
view.setAccessibilityHidden(false)
|
||||
view.alphaValue = 1
|
||||
}
|
||||
self.searchField.placeholderString = "Search apps, calculate, or convert…"
|
||||
self.searchField.placeholderAttributedString = previousPlaceholder
|
||||
self.searchField.setAccessibilityLabel("Search applications, calculate, or convert units and currencies")
|
||||
self.searchField.stringValue = previousQuery
|
||||
self.openButton.keyEquivalent = "\r"
|
||||
@@ -796,7 +797,10 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
(window as? LauncherPanel)?.resetNumberHints()
|
||||
openButton.keyEquivalent = ""
|
||||
searchField.stringValue = ""
|
||||
searchField.placeholderString = "Enter English text to translate…"
|
||||
searchField.placeholderAttributedString = NSAttributedString(
|
||||
string: "Enter English text to translate…",
|
||||
attributes: previousPlaceholder?.attributes(at: 0, effectiveRange: nil) ?? [:]
|
||||
)
|
||||
searchField.setAccessibilityLabel("English text to translate into Japanese")
|
||||
for view in launcherViews.dropFirst() {
|
||||
view.isHidden = true
|
||||
|
||||
@@ -10,7 +10,7 @@ final class TranslationController: NSViewController {
|
||||
private var usesMyMemory: Bool { provider.indexOfSelectedItem == 0 }
|
||||
private let output = NSTextView()
|
||||
private let status = NSTextField(wrappingLabelWithString: "")
|
||||
private let translateButton = NSButton(title: "Translate ↵", target: nil, action: nil)
|
||||
private let translateButton = NSButton(title: "Translate", target: nil, action: nil)
|
||||
private var task: Task<Void, Never>?
|
||||
var input: (() -> String)?
|
||||
var closed: (() -> Void)?
|
||||
@@ -22,10 +22,9 @@ final class TranslationController: NSViewController {
|
||||
|
||||
override func loadView() {
|
||||
view = NSView()
|
||||
let back = NSButton(title: "‹ Back", target: self, action: #selector(goBack))
|
||||
let title = NSTextField(labelWithString: "Conjure Translate · English → Japanese")
|
||||
title.font = .systemFont(ofSize: 13, weight: .semibold)
|
||||
let heading = NSStackView(views: [back, title, NSView()])
|
||||
let heading = NSStackView(views: [title, NSView()])
|
||||
provider.addItems(withTitles: ["MyMemory (no key)", "OpenAI · gpt-6-luna"])
|
||||
provider.selectItem(at: UserDefaults.standard.string(forKey: "translationProvider") == "openai" ? 1 : 0)
|
||||
provider.setAccessibilityLabel("Translation provider")
|
||||
@@ -41,6 +40,33 @@ final class TranslationController: NSViewController {
|
||||
translateButton.target = self
|
||||
translateButton.action = #selector(translate)
|
||||
translateButton.keyEquivalent = "\r"
|
||||
translateButton.isBordered = false
|
||||
translateButton.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
translateButton.contentTintColor = SunsetPalette.text
|
||||
translateButton.toolTip = "Translate the English input (Return). Escape returns to the launcher."
|
||||
let returnKey = ShortcutIndicator("↵")
|
||||
let actionPill = NSView()
|
||||
actionPill.wantsLayer = true
|
||||
actionPill.layer?.cornerRadius = 18
|
||||
actionPill.layer?.borderWidth = 0.5
|
||||
actionPill.layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
|
||||
actionPill.layer?.backgroundColor = SunsetPalette.background.withAlphaComponent(0.25).cgColor
|
||||
for control in [translateButton, returnKey] {
|
||||
control.translatesAutoresizingMaskIntoConstraints = false
|
||||
actionPill.addSubview(control)
|
||||
}
|
||||
NSLayoutConstraint.activate([
|
||||
actionPill.heightAnchor.constraint(equalToConstant: 36),
|
||||
translateButton.leadingAnchor.constraint(equalTo: actionPill.leadingAnchor, constant: 14),
|
||||
translateButton.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
||||
returnKey.leadingAnchor.constraint(equalTo: translateButton.trailingAnchor, constant: 8),
|
||||
returnKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
||||
returnKey.trailingAnchor.constraint(equalTo: actionPill.trailingAnchor, constant: -12),
|
||||
])
|
||||
let footer = NSStackView(views: [NSView(), actionPill])
|
||||
footer.orientation = .horizontal
|
||||
footer.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(footer)
|
||||
output.isEditable = false
|
||||
output.isSelectable = true
|
||||
output.drawsBackground = false
|
||||
@@ -56,7 +82,7 @@ final class TranslationController: NSViewController {
|
||||
output.textContainer?.widthTracksTextView = true
|
||||
status.textColor = SunsetPalette.secondaryText
|
||||
status.font = .systemFont(ofSize: 12)
|
||||
let stack = NSStackView(views: [heading, provider, credentials, scroll, status, translateButton])
|
||||
let stack = NSStackView(views: [heading, provider, credentials, scroll, status])
|
||||
stack.orientation = .vertical
|
||||
stack.alignment = .leading
|
||||
stack.spacing = 12
|
||||
@@ -66,7 +92,11 @@ final class TranslationController: NSViewController {
|
||||
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
||||
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
||||
stack.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
stack.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -16),
|
||||
stack.bottomAnchor.constraint(equalTo: footer.topAnchor, constant: -12),
|
||||
footer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
||||
footer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8),
|
||||
footer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8),
|
||||
footer.heightAnchor.constraint(equalToConstant: 36),
|
||||
credentials.widthAnchor.constraint(equalTo: stack.widthAnchor),
|
||||
scroll.widthAnchor.constraint(equalTo: stack.widthAnchor),
|
||||
scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 100),
|
||||
|
||||
@@ -44,9 +44,7 @@ import Testing
|
||||
#expect(search.accessibilityLabel() == "English text to translate into Japanese")
|
||||
search.stringValue = "Good morning"
|
||||
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
|
||||
#expect(descendants(content).contains { ($0 as? NSButton)?.title == "Translate ↵" })
|
||||
_ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.cancelOperation(_:)))
|
||||
#expect(search.stringValue == "translate")
|
||||
#expect(!descendants(content).contains { ($0 as? NSButton)?.title == "Translate ↵" })
|
||||
#expect(table.numberOfRows == 1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user