macos task switch focus
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -170,8 +170,12 @@ final class ChatModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func takeFocusStart() -> Bool {
|
func takeFocus() -> Bool {
|
||||||
let take = focusStart && session == nil
|
let asked = taskId != nil && store.composerFocus == taskId
|
||||||
|
if asked {
|
||||||
|
store.composerFocus = nil
|
||||||
|
}
|
||||||
|
let take = asked || (focusStart && session == nil)
|
||||||
focusStart = false
|
focusStart = false
|
||||||
return take
|
return take
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -332,7 +332,12 @@ struct ComposerView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
text = store.drafts?.composerText(draftKey) ?? ""
|
text = store.drafts?.composerText(draftKey) ?? ""
|
||||||
if chat.takeFocusStart() {
|
if chat.takeFocus() {
|
||||||
|
focused = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: store.composerFocus) {
|
||||||
|
if chat.takeFocus() {
|
||||||
focused = true
|
focused = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ final class Store {
|
|||||||
var leadSessions: [AgentSession] = []
|
var leadSessions: [AgentSession] = []
|
||||||
let leadFeed: Feed
|
let leadFeed: Feed
|
||||||
var jump: (taskId: String, seq: Int64)?
|
var jump: (taskId: String, seq: Int64)?
|
||||||
|
var composerFocus: String?
|
||||||
var summaryReads: [String: Int64] = [:]
|
var summaryReads: [String: Int64] = [:]
|
||||||
private(set) var leadMore = false
|
private(set) var leadMore = false
|
||||||
var todoStarting: Set<String> = []
|
var todoStarting: Set<String> = []
|
||||||
|
|||||||
@@ -260,8 +260,10 @@ final class SwitcherModel {
|
|||||||
if store.inbox.contains(where: { $0.taskId == taskId }) {
|
if store.inbox.contains(where: { $0.taskId == taskId }) {
|
||||||
store.focusTask(taskId, .inbox)
|
store.focusTask(taskId, .inbox)
|
||||||
} else if let task = store.findTask(taskId), store.unreadSummary(task) {
|
} else if let task = store.findTask(taskId), store.unreadSummary(task) {
|
||||||
|
store.composerFocus = taskId
|
||||||
store.openSummary(task)
|
store.openSummary(task)
|
||||||
} else {
|
} else {
|
||||||
|
store.composerFocus = taskId
|
||||||
store.focusTask(taskId, .transcript)
|
store.focusTask(taskId, .transcript)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
@@ -16,6 +16,7 @@ export const state = {
|
|||||||
leadSessions: [],
|
leadSessions: [],
|
||||||
leadTranscript: [],
|
leadTranscript: [],
|
||||||
jump: undefined,
|
jump: undefined,
|
||||||
|
composerFocus: undefined,
|
||||||
summaryReads: {},
|
summaryReads: {},
|
||||||
};
|
};
|
||||||
const listeners = new Map();
|
const listeners = new Map();
|
||||||
@@ -359,6 +360,10 @@ export function openSummary(task) {
|
|||||||
focusTask(task.id, "transcript");
|
focusTask(task.id, "transcript");
|
||||||
notify("jump");
|
notify("jump");
|
||||||
}
|
}
|
||||||
|
export function focusComposer(taskId) {
|
||||||
|
state.composerFocus = taskId;
|
||||||
|
notify("composer");
|
||||||
|
}
|
||||||
export function selectTask(taskId) {
|
export function selectTask(taskId) {
|
||||||
markVisited(state.selectedTaskId);
|
markVisited(state.selectedTaskId);
|
||||||
markVisited(taskId);
|
markVisited(taskId);
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+3
-1
@@ -2,7 +2,7 @@ import { get, post } from "./api.js";
|
|||||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||||
import { focusLead } from "./panes.js";
|
import { focusLead } from "./panes.js";
|
||||||
import { summaryMark } from "./project.js";
|
import { summaryMark } from "./project.js";
|
||||||
import { activeTasks, findTask, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, openSummary, state, taskState, unreadSummary } from "./state.js";
|
import { activeTasks, findTask, focusComposer, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, openSummary, state, taskState, unreadSummary } from "./state.js";
|
||||||
import { confirmMerge, models } from "./task.js";
|
import { confirmMerge, models } from "./task.js";
|
||||||
import { taskStyle } from "./taskcolor.js";
|
import { taskStyle } from "./taskcolor.js";
|
||||||
export function bindShortcuts() {
|
export function bindShortcuts() {
|
||||||
@@ -122,9 +122,11 @@ export function openSwitcher() {
|
|||||||
focusTask(taskId, "inbox");
|
focusTask(taskId, "inbox");
|
||||||
}
|
}
|
||||||
else if (task && unreadSummary(task)) {
|
else if (task && unreadSummary(task)) {
|
||||||
|
focusComposer(taskId);
|
||||||
openSummary(task);
|
openSummary(task);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
focusComposer(taskId);
|
||||||
focusTask(taskId, "transcript");
|
focusTask(taskId, "transcript");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+15
-1
@@ -280,6 +280,14 @@ export function mountChat(root, src, makeHead) {
|
|||||||
};
|
};
|
||||||
let composerModel = "";
|
let composerModel = "";
|
||||||
let focusStart = false;
|
let focusStart = false;
|
||||||
|
const takeFocus = () => {
|
||||||
|
const taskId = src.taskId();
|
||||||
|
if (!taskId || state.composerFocus !== taskId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.composerFocus = undefined;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
const renderComposer = () => {
|
const renderComposer = () => {
|
||||||
const key = src.key();
|
const key = src.key();
|
||||||
const old = composer.querySelector("textarea");
|
const old = composer.querySelector("textarea");
|
||||||
@@ -297,11 +305,12 @@ export function mountChat(root, src, makeHead) {
|
|||||||
const caret = old !== null && document.activeElement === old ? [old.selectionStart, old.selectionEnd] : undefined;
|
const caret = old !== null && document.activeElement === old ? [old.selectionStart, old.selectionEnd] : undefined;
|
||||||
replace(composer, form);
|
replace(composer, form);
|
||||||
const text = form.querySelector("textarea");
|
const text = form.querySelector("textarea");
|
||||||
|
const asked = takeFocus();
|
||||||
if (caret) {
|
if (caret) {
|
||||||
text.focus();
|
text.focus();
|
||||||
text.setSelectionRange(caret[0], caret[1]);
|
text.setSelectionRange(caret[0], caret[1]);
|
||||||
}
|
}
|
||||||
else if (focusStart && !sess) {
|
else if (asked || (focusStart && !sess)) {
|
||||||
text.focus();
|
text.focus();
|
||||||
}
|
}
|
||||||
focusStart = false;
|
focusStart = false;
|
||||||
@@ -642,6 +651,11 @@ export function mountChat(root, src, makeHead) {
|
|||||||
syncMergeAfterReview(transcript);
|
syncMergeAfterReview(transcript);
|
||||||
}),
|
}),
|
||||||
on("jump", jump),
|
on("jump", jump),
|
||||||
|
on("composer", () => {
|
||||||
|
if (takeFocus()) {
|
||||||
|
composer.querySelector("textarea")?.focus();
|
||||||
|
}
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
|
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
|
||||||
renderAll();
|
renderAll();
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+8
-1
@@ -21,7 +21,8 @@ export type Topic =
|
|||||||
| "ready"
|
| "ready"
|
||||||
| "lead.ready"
|
| "lead.ready"
|
||||||
| "summaries"
|
| "summaries"
|
||||||
| "jump";
|
| "jump"
|
||||||
|
| "composer";
|
||||||
|
|
||||||
export const tabs: TaskTab[] = ["transcript", "changes", "files", "terminal", "inbox", "reviews", "workflow", "project"];
|
export const tabs: TaskTab[] = ["transcript", "changes", "files", "terminal", "inbox", "reviews", "workflow", "project"];
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ export const state = {
|
|||||||
leadSessions: [] as AgentSession[],
|
leadSessions: [] as AgentSession[],
|
||||||
leadTranscript: [] as ByreEvent[],
|
leadTranscript: [] as ByreEvent[],
|
||||||
jump: undefined as { taskId: string; seq: number } | undefined,
|
jump: undefined as { taskId: string; seq: number } | undefined,
|
||||||
|
composerFocus: undefined as string | undefined,
|
||||||
summaryReads: {} as Record<string, number>,
|
summaryReads: {} as Record<string, number>,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -433,6 +435,11 @@ export function openSummary(task: Task): void {
|
|||||||
notify("jump");
|
notify("jump");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function focusComposer(taskId: string): void {
|
||||||
|
state.composerFocus = taskId;
|
||||||
|
notify("composer");
|
||||||
|
}
|
||||||
|
|
||||||
export function selectTask(taskId: string | undefined): void {
|
export function selectTask(taskId: string | undefined): void {
|
||||||
markVisited(state.selectedTaskId);
|
markVisited(state.selectedTaskId);
|
||||||
markVisited(taskId);
|
markVisited(taskId);
|
||||||
|
|||||||
+3
-1
@@ -2,7 +2,7 @@ import { get, post, type Project, type WorkflowSummary } from "./api.js";
|
|||||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||||
import { focusLead } from "./panes.js";
|
import { focusLead } from "./panes.js";
|
||||||
import { summaryMark } from "./project.js";
|
import { summaryMark } from "./project.js";
|
||||||
import { activeTasks, findTask, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, openSummary, state, taskState, unreadSummary } from "./state.js";
|
import { activeTasks, findTask, focusComposer, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, openSummary, state, taskState, unreadSummary } from "./state.js";
|
||||||
import { confirmMerge, models } from "./task.js";
|
import { confirmMerge, models } from "./task.js";
|
||||||
import { taskStyle } from "./taskcolor.js";
|
import { taskStyle } from "./taskcolor.js";
|
||||||
|
|
||||||
@@ -131,8 +131,10 @@ export function openSwitcher(): void {
|
|||||||
if (state.inbox.some((item) => item.task_id === taskId)) {
|
if (state.inbox.some((item) => item.task_id === taskId)) {
|
||||||
focusTask(taskId, "inbox");
|
focusTask(taskId, "inbox");
|
||||||
} else if (task && unreadSummary(task)) {
|
} else if (task && unreadSummary(task)) {
|
||||||
|
focusComposer(taskId);
|
||||||
openSummary(task);
|
openSummary(task);
|
||||||
} else {
|
} else {
|
||||||
|
focusComposer(taskId);
|
||||||
focusTask(taskId, "transcript");
|
focusTask(taskId, "transcript");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+15
-1
@@ -360,6 +360,14 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
|||||||
|
|
||||||
let composerModel = "";
|
let composerModel = "";
|
||||||
let focusStart = false;
|
let focusStart = false;
|
||||||
|
const takeFocus = () => {
|
||||||
|
const taskId = src.taskId();
|
||||||
|
if (!taskId || state.composerFocus !== taskId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.composerFocus = undefined;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
const renderComposer = () => {
|
const renderComposer = () => {
|
||||||
const key = src.key();
|
const key = src.key();
|
||||||
const old = composer.querySelector("textarea");
|
const old = composer.querySelector("textarea");
|
||||||
@@ -377,10 +385,11 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
|||||||
const caret = old !== null && document.activeElement === old ? [old.selectionStart, old.selectionEnd] : undefined;
|
const caret = old !== null && document.activeElement === old ? [old.selectionStart, old.selectionEnd] : undefined;
|
||||||
replace(composer, form);
|
replace(composer, form);
|
||||||
const text = form.querySelector("textarea")!;
|
const text = form.querySelector("textarea")!;
|
||||||
|
const asked = takeFocus();
|
||||||
if (caret) {
|
if (caret) {
|
||||||
text.focus();
|
text.focus();
|
||||||
text.setSelectionRange(caret[0]!, caret[1]!);
|
text.setSelectionRange(caret[0]!, caret[1]!);
|
||||||
} else if (focusStart && !sess) {
|
} else if (asked || (focusStart && !sess)) {
|
||||||
text.focus();
|
text.focus();
|
||||||
}
|
}
|
||||||
focusStart = false;
|
focusStart = false;
|
||||||
@@ -740,6 +749,11 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
|||||||
syncMergeAfterReview(transcript);
|
syncMergeAfterReview(transcript);
|
||||||
}),
|
}),
|
||||||
on("jump", jump),
|
on("jump", jump),
|
||||||
|
on("composer", () => {
|
||||||
|
if (takeFocus()) {
|
||||||
|
composer.querySelector("textarea")?.focus();
|
||||||
|
}
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
|
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
|
||||||
|
|||||||
Reference in New Issue
Block a user