Add a button to open in android studio.
This commit is contained in:
1 parent
0e8bc70151
commit
bb90255002
8 files changed
+298
No files matched your search
@@ -629,6 +629,16 @@ export default function App() {
|
||||
setLocate({ file, seq: ++locateSeq.current });
|
||||
}, []);
|
||||
|
||||
// Success is Studio coming to the front, so only a failure needs saying here.
|
||||
const openInStudio = useCallback(
|
||||
(file: string, line?: number) => {
|
||||
api
|
||||
.openInStudio(file, line)
|
||||
.catch((e) => flash(e instanceof Error ? e.message : String(e)));
|
||||
},
|
||||
[flash],
|
||||
);
|
||||
|
||||
const draftCount = useMemo(
|
||||
() => comments.filter((c) => c.status === 'draft').length,
|
||||
[comments],
|
||||
@@ -963,6 +973,7 @@ export default function App() {
|
||||
reveal={reveal}
|
||||
onSetViewed={setFileViewed}
|
||||
onLocate={locateFile}
|
||||
onOpenInStudio={repo?.studio ? openInStudio : undefined}
|
||||
onStartDraft={setDraft}
|
||||
onCancelDraft={cancelDraft}
|
||||
onSubmitDraft={submitDraft}
|
||||
|
||||
@@ -202,6 +202,11 @@ export const api = {
|
||||
reopen: (id: string) => postJSON(`${apiBase}/comments/${id}/reopen`).then(json<Comment>),
|
||||
|
||||
// Submits every draft in the review — the same set the rail shows.
|
||||
// Hands the file to Android Studio's launcher, which opens it in the window of
|
||||
// the project containing it. Resolves once the launcher has started, not once
|
||||
// the file is on screen: nothing comes back from the IDE either way.
|
||||
openInStudio: (path: string, line?: number) =>
|
||||
postJSON(`${apiBase}/studio/open`, { path, line }).then(json<void>),
|
||||
submit: () => postJSON(`${apiBase}/review/submit`).then(json<{ submitted: number }>),
|
||||
|
||||
// Deletes every comment, whatever its status. The reviewer's viewed marks are
|
||||
|
||||
@@ -66,6 +66,9 @@ interface Props {
|
||||
onSetViewed: (file: string, viewed: boolean, source?: string) => void;
|
||||
// Reveals the file in the left rail's file list.
|
||||
onLocate: (file: string) => void;
|
||||
// Opens the file in Android Studio at `line`. Absent when there's no Studio to
|
||||
// open it in, which hides the button.
|
||||
onOpenInStudio?: (file: string, line?: number) => void;
|
||||
onStartDraft: (d: DraftTarget) => void;
|
||||
onCancelDraft: () => void;
|
||||
onSubmitDraft: (body: string) => Promise<void>;
|
||||
@@ -435,6 +438,7 @@ export function DiffView({
|
||||
reveal,
|
||||
onSetViewed,
|
||||
onLocate,
|
||||
onOpenInStudio,
|
||||
onStartDraft,
|
||||
onCancelDraft,
|
||||
onSubmitDraft,
|
||||
@@ -484,6 +488,7 @@ export function DiffView({
|
||||
bodyRef={bodyRef(path)}
|
||||
onSetViewed={onSetViewed}
|
||||
onLocate={onLocate}
|
||||
onOpenInStudio={onOpenInStudio}
|
||||
onStartDraft={onStartDraft}
|
||||
onCancelDraft={onCancelDraft}
|
||||
onSubmitDraft={onSubmitDraft}
|
||||
@@ -495,6 +500,21 @@ export function DiffView({
|
||||
);
|
||||
}
|
||||
|
||||
// Where a file's first change sits in the work tree — the line to open it at. A
|
||||
// deletion has no line of its own on the new side, so it lands on the line that
|
||||
// now sits where the removed ones were.
|
||||
function firstChangedLine(hunks: HunkData[]): number | undefined {
|
||||
const hunk = hunks[0];
|
||||
if (!hunk) return undefined;
|
||||
let next = hunk.newStart;
|
||||
for (const change of hunk.changes) {
|
||||
if (change.type === 'insert') return change.lineNumber;
|
||||
if (change.type === 'delete') return Math.max(1, next);
|
||||
next = change.newLineNumber + 1;
|
||||
}
|
||||
return Math.max(1, hunk.newStart);
|
||||
}
|
||||
|
||||
function statusLabel(type: string): string {
|
||||
switch (type) {
|
||||
case 'add':
|
||||
@@ -542,6 +562,7 @@ const FileView = memo(function FileView({
|
||||
bodyRef,
|
||||
onSetViewed,
|
||||
onLocate,
|
||||
onOpenInStudio,
|
||||
onStartDraft,
|
||||
onCancelDraft,
|
||||
onSubmitDraft,
|
||||
@@ -567,6 +588,7 @@ const FileView = memo(function FileView({
|
||||
bodyRef: (el: HTMLElement | null) => void;
|
||||
onSetViewed: Props['onSetViewed'];
|
||||
onLocate: Props['onLocate'];
|
||||
onOpenInStudio: Props['onOpenInStudio'];
|
||||
onStartDraft: (d: DraftTarget) => void;
|
||||
onCancelDraft: () => void;
|
||||
onSubmitDraft: Props['onSubmitDraft'];
|
||||
@@ -763,6 +785,21 @@ const FileView = memo(function FileView({
|
||||
>
|
||||
<Icon name="focus-center" />
|
||||
</button>
|
||||
{onOpenInStudio && file.type !== 'delete' && (
|
||||
<button
|
||||
className="icon-btn"
|
||||
onClick={() =>
|
||||
onOpenInStudio(
|
||||
path,
|
||||
firstChangedLine(showingSince ? sinceHunks! : file.hunks),
|
||||
)
|
||||
}
|
||||
title="Open in Android Studio"
|
||||
aria-label="Open in Android Studio"
|
||||
>
|
||||
<Icon name="code" />
|
||||
</button>
|
||||
)}
|
||||
{file.type !== 'modify' && (
|
||||
<span className={`file-status file-status-${file.type}`}>
|
||||
{statusLabel(file.type)}
|
||||
|
||||
@@ -21,6 +21,7 @@ const PATHS = {
|
||||
'M.513 1.513A1.75 1.75 0 0 1 1.75 1h3.5c.55 0 1.07.26 1.4.7l.9 1.2a.25.25 0 0 0 .2.1H13a1 1 0 0 1 1 1v.5H2.75a.75.75 0 0 0 0 1.5h11.978a1 1 0 0 1 .994 1.117L15 13.25A1.75 1.75 0 0 1 13.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75c0-.464.184-.91.513-1.237Z',
|
||||
'file-diff':
|
||||
'M1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177l-2.914-2.914a.25.25 0 0 0-.177-.073ZM8 3.25a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0V7h-1.5a.75.75 0 0 1 0-1.5h1.5V4A.75.75 0 0 1 8 3.25Zm-3 8a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z',
|
||||
code: 'm11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z',
|
||||
copy: 'M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25ZM5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z',
|
||||
unfold:
|
||||
'm8.177.677 2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25a.75.75 0 0 1-1.5 0V4H5.104a.25.25 0 0 1-.177-.427L7.823.677a.25.25 0 0 1 .354 0ZM7.25 10.75a.75.75 0 0 1 1.5 0V12h2.146a.25.25 0 0 1 .177.427l-2.896 2.896a.25.25 0 0 1-.354 0l-2.896-2.896A.25.25 0 0 1 5.104 12H7.25v-1.25Zm-5-2a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z',
|
||||
|
||||
@@ -96,6 +96,9 @@ export interface RepoState extends RepoInfo {
|
||||
// page publishes one. The browser is the source of truth for what's on screen;
|
||||
// this copy is what lets an agent review, and anchor comments to, the same diff.
|
||||
context?: DiffContext | null;
|
||||
// Whether this machine has an Android Studio for the file headers to open
|
||||
// files in (see api.openInStudio).
|
||||
studio: boolean;
|
||||
}
|
||||
|
||||
export interface DiffFile {
|
||||
|
||||
Reference in new issue
Block a user