mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
fix(studio): the global shortcut setting reads like its neighbours; the chord is shown in the notation people use, the three one-shot actions are pill buttons instead of a selector strip, recording is visible on the field, and the explanatory text wraps instead of being cut off
This commit is contained in:
1 parent
0a38f2e47a
commit
8658f020af
3 files changed
+65
-18
No files matched your search
@@ -33,7 +33,7 @@
|
||||
.prefs-col[data-cols="2"] { max-width: 1400px; column-count: 2; column-gap: 16px; }
|
||||
.prefs-col[data-cols="2"] > .prefs-card { break-inside: avoid; }
|
||||
}
|
||||
.prefs-card > .srow { display: flex; align-items: center; gap: 16px; padding: 10px 0; border-top: 1px solid var(--hair); }
|
||||
.prefs-card > .srow, .grp[data-setting="global-shortcut"] .srow { display: flex; align-items: center; gap: 16px; padding: 10px 0; border-top: 1px solid var(--hair); }
|
||||
.srow-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
||||
.srow-name { color: var(--text); font: var(--w-med) var(--fs-ui)/1.4 var(--ui); }
|
||||
.srow-desc { color: var(--faint); font: var(--w-reg) var(--fs-meta)/1.5 var(--ui); }
|
||||
@@ -41,6 +41,16 @@
|
||||
.srow-ctl { flex: none; display: flex; align-items: center; gap: 8px; }
|
||||
.spill { height: 24px; padding: 0 10px; border: 1px solid var(--border); border-radius: 999px; background: transparent; color: var(--text); font: var(--w-reg) var(--fs-meta) var(--ui); white-space: nowrap; cursor: pointer; }
|
||||
.spill:hover:not(:disabled) { background: var(--overlay); }
|
||||
.spill:disabled { opacity: .5; cursor: default; }
|
||||
.keybind { box-sizing: border-box; width: 220px; height: 28px; padding: 0 10px; border: 1px solid var(--border); border-radius: var(--r-sm); background: transparent; color: var(--text); font: var(--w-reg) var(--fs-meta) var(--mono); }
|
||||
.keybind::placeholder { color: var(--faint); }
|
||||
.keybind:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
|
||||
.keybind[data-recording] { border-color: var(--net); background: var(--net-wash); }
|
||||
.keyacts { flex-wrap: wrap; row-gap: 8px; }
|
||||
.keyacts > .srow-meta { flex: 1 1 16em; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
|
||||
.keyacts > .srow-meta > .srow-desc { flex: 1 1 14em; min-width: 0; }
|
||||
.keycap { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--raised); color: var(--text); font: var(--w-reg) var(--fs-micro) var(--mono); }
|
||||
.grp[data-setting="global-shortcut"] > .hint { overflow: visible; text-overflow: clip; white-space: normal; }
|
||||
.srow-more { padding: 0 0 6px; }
|
||||
.srow-more .lrow { padding: 8px 0 8px 16px; border-top: 1px solid var(--hair); }
|
||||
.apply::before { display: none; }
|
||||
|
||||
@@ -63,25 +63,33 @@ export function GlobalShortcutSection() {
|
||||
return (
|
||||
<Group id="global-shortcut" title={t("settings.global_show_hide_shortcut")} hint={t("settings.off_by_default_while_studio") }>
|
||||
<form data-action="window.global-shortcut" data-value="apply" onSubmit={event => { event.preventDefault(); void apply(draft); }}>
|
||||
<label className="lrow">
|
||||
<span className="tx"><span className="lb">{t("settings.shortcut_binding")}</span></span>
|
||||
<input ref={input} className="fontown" data-action-change="window.global-shortcut" data-action-keydown="window.global-shortcut" data-value="binding" aria-label={t("settings.shortcut_binding")} maxLength={128} placeholder={recording ? t("settings.press_a_modifier_and_key") : "CommandOrControl+Shift+Space"} value={draft} readOnly={recording} onChange={event => setDraft(event.target.value)} onBlur={() => setRecording(false)} onKeyDown={event => {
|
||||
if (!recording || event.key === "Tab") return;
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
if (event.key === "Escape" && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) { setRecording(false); return; }
|
||||
const binding = capturedBinding(event);
|
||||
if (binding) { setDraft(binding); setRecording(false); }
|
||||
}} disabled={!now || busy} />
|
||||
</label>
|
||||
{draft && <p className="note"><kbd>{acceleratorLabel(draft)}</kbd></p>}
|
||||
<p className="note">{t("settings.click_record_and_press_a")}</p>
|
||||
<div className="seg" data-text>
|
||||
<button type="button" data-action="window.global-shortcut" data-value="record" disabled={!now || busy} onClick={() => { setRecording(true); input.current?.focus(); }}>{t("settings.record_shortcut")}</button>
|
||||
<button type="submit" data-action="window.global-shortcut" data-value="apply" disabled={!now || busy}>{t("settings.apply_shortcut")}</button>
|
||||
<button type="button" data-action="window.global-shortcut" data-value="disable" disabled={!now || busy || (!now.binding && !draft)} onClick={() => void apply("")}>{t("settings.clear_shortcut")}</button>
|
||||
<div className="srow">
|
||||
<div className="srow-meta">
|
||||
<span className="srow-name">{t("settings.shortcut_binding")}</span>
|
||||
{now && <span className="srow-desc" role="status">{t(STATUS[now.status], { value: acceleratorLabel(now.binding) })}</span>}
|
||||
</div>
|
||||
<div className="srow-ctl">
|
||||
<input ref={input} className="keybind" data-recording={recording ? "" : undefined} data-action-change="window.global-shortcut" data-action-keydown="window.global-shortcut" data-value="binding" aria-label={t("settings.shortcut_binding")} maxLength={128} placeholder={recording ? t("settings.press_a_modifier_and_key") : acceleratorLabel("CommandOrControl+Shift+Space")} value={draft} readOnly={recording} onChange={event => setDraft(event.target.value)} onBlur={() => setRecording(false)} onKeyDown={event => {
|
||||
if (!recording || event.key === "Tab") return;
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
if (event.key === "Escape" && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) { setRecording(false); return; }
|
||||
const binding = capturedBinding(event);
|
||||
if (binding) { setDraft(binding); setRecording(false); }
|
||||
}} disabled={!now || busy} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="srow keyacts">
|
||||
<div className="srow-meta">
|
||||
{draft && <kbd className="keycap">{acceleratorLabel(draft)}</kbd>}
|
||||
<span className="srow-desc">{t("settings.click_record_and_press_a")}</span>
|
||||
</div>
|
||||
<div className="srow-ctl">
|
||||
<button type="button" className="spill" data-action="window.global-shortcut" data-value="record" disabled={!now || busy} onClick={() => { setRecording(true); input.current?.focus(); }}>{t("settings.record_shortcut")}</button>
|
||||
<button type="submit" className="spill" data-action="window.global-shortcut" data-value="apply" disabled={!now || busy}>{t("settings.apply_shortcut")}</button>
|
||||
<button type="button" className="spill" data-action="window.global-shortcut" data-value="disable" disabled={!now || busy || (!now.binding && !draft)} onClick={() => void apply("")}>{t("settings.clear_shortcut")}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{now && <p className="note" role="status">{t(STATUS[now.status], { value: acceleratorLabel(now.binding) })}</p>}
|
||||
{now?.error && <p className="note" role="alert">{t(ERRORS[now.error])}</p>}
|
||||
{failed && <p className="note" role="alert">{t("settings.could_not_read_or_change")}</p>}
|
||||
</Group>
|
||||
|
||||
@@ -140,3 +140,32 @@ it.each([
|
||||
expect(input.value).toBe(binding);
|
||||
expect(fixture.apply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("suggests a chord in the notation the person reads, not Electron's accelerator grammar", async () => {
|
||||
render(<GlobalShortcutSection />);
|
||||
await screen.findByRole("status");
|
||||
const input = screen.getByRole("textbox") as HTMLInputElement;
|
||||
expect(input.placeholder).toBe(acceleratorLabel("CommandOrControl+Shift+Space"));
|
||||
expect(input.placeholder).not.toContain("CommandOrControl");
|
||||
});
|
||||
|
||||
it("marks the field while it is listening for a chord and releases it afterwards", async () => {
|
||||
render(<GlobalShortcutSection />);
|
||||
await screen.findByRole("status");
|
||||
const input = screen.getByRole("textbox") as HTMLInputElement;
|
||||
expect(input.hasAttribute("data-recording")).toBe(false);
|
||||
await userEvent.click(screen.getByRole("button", { name: t("settings.record_shortcut") }));
|
||||
expect(input.hasAttribute("data-recording")).toBe(true);
|
||||
fireEvent.keyDown(input, { key: "k", code: "KeyK", ctrlKey: true });
|
||||
expect(input.hasAttribute("data-recording")).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the status and the actions inside one form, so Enter applies what the field holds", async () => {
|
||||
fixture.apply.mockImplementation(async (binding: string) => ({ binding, status: "registered" }));
|
||||
render(<GlobalShortcutSection />);
|
||||
await screen.findByRole("status");
|
||||
const input = screen.getByRole("textbox") as HTMLInputElement;
|
||||
await userEvent.type(input, "Control+Space{Enter}");
|
||||
expect(fixture.apply).toHaveBeenLastCalledWith("Control+Space");
|
||||
expect(input.form).toBe(screen.getByRole("button", { name: t("settings.apply_shortcut") }).closest("form"));
|
||||
});
|
||||
Reference in new issue
Block a user