Skip to content

Commit 5cb1750

Browse files
feat: Add Option+Command sync without opening file (#586)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 9a2348d commit 5cb1750

3 files changed

Lines changed: 34 additions & 4 deletions

File tree

docs/diff.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ The `/diff` route renders raw git patch output with `@pierre/diffs` (see
2929
https://diffs.com) so we can use the same diff rendering primitives in the app
3030
and keep the UI behavior consistent with the diffs.com component model.
3131

32+
## Sync shortcuts
33+
34+
Hold <kbd>Option</kbd> to reveal the left/right sync arrows in each diffed file
35+
header.
36+
37+
- Click an arrow to sync and open the destination file.
38+
- Click while holding <kbd>Option</kbd> + <kbd>Command</kbd> to sync without
39+
opening the destination file.
40+
3241
## Cache
3342

3443
Because the diffs are pretty expensive, we load them asynchronously (streamed to

packages/workshop-app/app/components/diff.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,21 @@ function DiffFileActions({
203203
syncTo={{ appFile: app2Path, appName: app2Name }}
204204
className={arrowButtonClassName}
205205
>
206-
<Icon name="ArrowLeft" title="Copy app 2 file to app 1" />
206+
<Icon
207+
name="ArrowLeft"
208+
title="Copy app 2 file to app 1 (Option+Command to sync without opening)"
209+
/>
207210
</LaunchEditor>
208211
<LaunchEditor
209212
appFile={app2Path}
210213
appName={app2Name}
211214
syncTo={{ appFile: app1Path, appName: app1Name }}
212215
className={arrowButtonClassName}
213216
>
214-
<Icon name="ArrowRight" title="Copy app 1 file to app 2" />
217+
<Icon
218+
name="ArrowRight"
219+
title="Copy app 1 file to app 2 (Option+Command to sync without opening)"
220+
/>
215221
</LaunchEditor>
216222
</div>
217223
<LaunchEditor

packages/workshop-app/app/routes/launch-editor.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'path'
33
import { getAppByName } from '@epic-web/workshop-utils/apps.server'
44
import { launchEditor } from '@epic-web/workshop-utils/launch-editor.server'
55
import fsExtra from 'fs-extra'
6-
import { useEffect } from 'react'
6+
import { type MouseEvent, useEffect } from 'react'
77
import { Link, useFetcher } from 'react-router'
88
import { z, type ZodTypeAny } from 'zod'
99
import { showProgressBarField } from '#app/components/progress-bar.tsx'
@@ -38,6 +38,7 @@ const LaunchSchema = z.intersection(
3838
export async function action({ request }: Route.ActionArgs) {
3939
ensureUndeployed()
4040
const formData = await request.formData()
41+
const skipOpenAfterSync = formData.get('skipOpenAfterSync') === 'true'
4142
const appFileValues = formData
4243
.getAll('appFile')
4344
.filter((value): value is string => typeof value === 'string')
@@ -134,7 +135,8 @@ export async function action({ request }: Route.ActionArgs) {
134135
}
135136

136137
const filesToOpen = await getFiles(form)
137-
if ('syncTo' in form && form.syncTo) {
138+
const hasSyncTarget = 'syncTo' in form && Boolean(form.syncTo)
139+
if (hasSyncTarget && form.syncTo) {
138140
const originFiles = await getFiles(form.syncTo)
139141
for (let index = 0; index < originFiles.length; index++) {
140142
const originFile = originFiles[index]
@@ -149,6 +151,9 @@ export async function action({ request }: Route.ActionArgs) {
149151
await fsExtra.promises.copyFile(originFile.filepath, destFile.filepath)
150152
}
151153
}
154+
if (hasSyncTarget && skipOpenAfterSync) {
155+
return dataWithPE(request, formData, { status: 'success' } as const)
156+
}
152157
const results: Array<
153158
{ status: 'success' } | { status: 'error'; message: string }
154159
> = []
@@ -231,6 +236,15 @@ function LaunchEditorImpl({
231236
}: LaunchEditorProps) {
232237
const fetcher = useLaunchFetcher(onUpdate)
233238
const peRedirectInput = usePERedirectInput()
239+
const handleSubmitButtonClick = (event: MouseEvent<HTMLButtonElement>) => {
240+
if (!syncTo || !event.altKey || !event.metaKey) return
241+
event.preventDefault()
242+
const form = event.currentTarget.form
243+
if (!form) return
244+
const formData = new FormData(form)
245+
formData.set('skipOpenAfterSync', 'true')
246+
void fetcher.submit(formData, { method: 'POST', action: '/launch-editor' })
247+
}
234248

235249
if (!file && !appFile) {
236250
console.error('LaunchEditor: requires either "file" or "appFile" prop.')
@@ -270,6 +284,7 @@ function LaunchEditorImpl({
270284
) : null}
271285
<button
272286
type="submit"
287+
onClick={handleSubmitButtonClick}
273288
className={cn(
274289
'launch_button',
275290
fetcher.state === 'idle' ? null : 'cursor-progress',

0 commit comments

Comments
 (0)