Skip to content

Commit 2b1911a

Browse files
authored
Merge pull request #9838 from gitbutlerapp/clipboard-backend-abstraction
clipboard-backend-abstraction
2 parents a4d9701 + 3299224 commit 2b1911a

14 files changed

+88
-52
lines changed

apps/desktop/src/components/BranchHeaderContextMenu.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import ReduxResult from '$components/ReduxResult.svelte';
2121
import { PROMPT_SERVICE } from '$lib/ai/promptService';
2222
import { AI_SERVICE } from '$lib/ai/service';
23-
import { writeClipboard } from '$lib/backend/clipboard';
23+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
2424
import { projectAiGenEnabled } from '$lib/config/config';
2525
import { DEFAULT_FORGE_FACTORY } from '$lib/forge/forgeFactory.svelte';
2626
import { STACK_SERVICE } from '$lib/stacks/stackService.svelte';
@@ -60,6 +60,7 @@
6060
const forge = inject(DEFAULT_FORGE_FACTORY);
6161
const promptService = inject(PROMPT_SERVICE);
6262
const urlService = inject(URL_SERVICE);
63+
const clipboardService = inject(CLIPBOARD_SERVICE);
6364
const [insertBlankCommitInBranch, commitInsertion] = stackService.insertBlankCommit;
6465
const [updateBranchNameMutation] = stackService.updateBranchName;
6566
const [createRef, refCreation] = stackService.createReference;
@@ -190,7 +191,7 @@
190191
label="Copy branch name"
191192
testId={TestId.BranchHeaderContextMenu_CopyBranchName}
192193
onclick={() => {
193-
writeClipboard(branch?.name);
194+
clipboardService.write(branch?.name);
194195
close();
195196
}}
196197
/>
@@ -308,7 +309,7 @@
308309
label="Copy PR link"
309310
testId={TestId.BranchHeaderContextMenu_CopyPRLink}
310311
onclick={() => {
311-
writeClipboard(pr.htmlUrl);
312+
clipboardService.write(pr.htmlUrl);
312313
close();
313314
}}
314315
/>

apps/desktop/src/components/CommitContextMenu.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
</script>
4343

4444
<script lang="ts">
45-
import { writeClipboard } from '$lib/backend/clipboard';
45+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
4646
import { rewrapCommitMessage } from '$lib/config/uiFeatureFlags';
4747
import { STACK_SERVICE } from '$lib/stacks/stackService.svelte';
4848
import { URL_SERVICE } from '$lib/utils/url';
@@ -68,6 +68,7 @@
6868
6969
const urlService = inject(URL_SERVICE);
7070
const stackService = inject(STACK_SERVICE);
71+
const clipboardService = inject(CLIPBOARD_SERVICE);
7172
const [insertBlankCommitInBranch, commitInsertion] = stackService.insertBlankCommit;
7273
const [createRef, refCreation] = stackService.createReference;
7374
@@ -172,22 +173,22 @@
172173
<ContextMenuItem
173174
label="Copy commit link"
174175
onclick={() => {
175-
writeClipboard(commitUrl);
176+
clipboardService.write(commitUrl);
176177
close();
177178
}}
178179
/>
179180
{/if}
180181
<ContextMenuItem
181182
label="Copy commit hash"
182183
onclick={() => {
183-
writeClipboard(commitId);
184+
clipboardService.write(commitId);
184185
close();
185186
}}
186187
/>
187188
<ContextMenuItem
188189
label="Copy commit message"
189190
onclick={() => {
190-
writeClipboard(commitMessage);
191+
clipboardService.write(commitMessage);
191192
close();
192193
}}
193194
/>

apps/desktop/src/components/CommitDetails.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { writeClipboard } from '$lib/backend/clipboard';
2+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
33
import { type Commit, type UpstreamCommit } from '$lib/branches/v3';
44
import { rewrapCommitMessage } from '$lib/config/uiFeatureFlags';
55
import { SETTINGS } from '$lib/settings/userSettings';
@@ -21,6 +21,7 @@
2121
2222
const userService = inject(USER_SERVICE);
2323
const userSettings = inject(SETTINGS);
24+
const clipboardService = inject(CLIPBOARD_SERVICE);
2425
const zoom = $derived($userSettings.zoom);
2526
2627
const user = $derived(userService.user);
@@ -69,7 +70,7 @@
6970
type="button"
7071
class="copy-sha underline-dotted"
7172
onclick={() => {
72-
writeClipboard(commit.id, {
73+
clipboardService.write(commit.id, {
7374
message: 'Commit SHA copied'
7475
});
7576
}}

apps/desktop/src/components/FileContextMenu.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { ACTION_SERVICE } from '$lib/actions/actionService.svelte';
55
import { AI_SERVICE } from '$lib/ai/service';
66
import { BACKEND } from '$lib/backend';
7-
import { writeClipboard } from '$lib/backend/clipboard';
7+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
88
import { changesToDiffSpec } from '$lib/commits/utils';
99
import { projectAiExperimentalFeaturesEnabled, projectAiGenEnabled } from '$lib/config/config';
1010
import { FILE_SERVICE } from '$lib/files/fileService';
@@ -64,6 +64,7 @@
6464
const actionService = inject(ACTION_SERVICE);
6565
const fileService = inject(FILE_SERVICE);
6666
const urlService = inject(URL_SERVICE);
67+
const clipboardService = inject(CLIPBOARD_SERVICE);
6768
const backend = inject(BACKEND);
6869
const [autoCommit, autoCommitting] = actionService.autoCommit;
6970
const [branchChanges, branchingChanges] = actionService.branchChanges;
@@ -399,7 +400,7 @@
399400
const projectPath = project?.path;
400401
if (projectPath) {
401402
const absPath = await backend.joinPath(projectPath, item.changes[0]!.path);
402-
await writeClipboard(absPath, {
403+
await clipboardService.write(absPath, {
403404
errorMessage: 'Failed to copy absolute path'
404405
});
405406
}
@@ -409,7 +410,7 @@
409410
<ContextMenuItem
410411
label="Copy Relative Path"
411412
onclick={async () => {
412-
await writeClipboard(item.changes[0]!.path, {
413+
await clipboardService.write(item.changes[0]!.path, {
413414
errorMessage: 'Failed to copy relative path'
414415
});
415416
contextMenu.close();

apps/desktop/src/components/GithubIntegration.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { writeClipboard } from '$lib/backend/clipboard';
2+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
33
import { GITHUB_USER_SERVICE } from '$lib/forge/github/githubUserService.svelte';
44
import { USER_SERVICE } from '$lib/user/userService';
55
import { URL_SERVICE } from '$lib/utils/url';
@@ -19,6 +19,7 @@
1919
const userService = inject(USER_SERVICE);
2020
const user = userService.user;
2121
const urlService = inject(URL_SERVICE);
22+
const clipboardService = inject(CLIPBOARD_SERVICE);
2223
2324
// step flags
2425
let codeCopied = $state(false);
@@ -139,7 +140,7 @@
139140
icon="copy"
140141
disabled={codeCopied}
141142
onclick={() => {
142-
writeClipboard(userCode);
143+
clipboardService.write(userCode);
143144
codeCopied = true;
144145
}}
145146
>

apps/desktop/src/components/IntegrateUpstreamModal.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { writeClipboard } from '$lib/backend/clipboard';
2+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
33
import { BASE_BRANCH_SERVICE } from '$lib/baseBranch/baseBranchService.svelte';
44
import { DEFAULT_FORGE_FACTORY } from '$lib/forge/forgeFactory.svelte';
55
import {
@@ -51,6 +51,7 @@
5151
const baseBranchResponse = $derived(baseBranchService.baseBranch(projectId));
5252
const base = $derived(baseBranchResponse.current.data);
5353
const urlService = inject(URL_SERVICE);
54+
const clipboardService = inject(CLIPBOARD_SERVICE);
5455
5556
let modal = $state<Modal>();
5657
let integratingUpstream = $state<OperationState>('inert');
@@ -324,7 +325,7 @@
324325
author={commit.author.name}
325326
url={commitUrl}
326327
onOpen={(url) => urlService.openExternalUrl(url)}
327-
onCopy={() => writeClipboard(commit.id)}
328+
onCopy={() => clipboardService.write(commit.id)}
328329
/>
329330
{/each}
330331
</ScrollableContainer>

apps/desktop/src/components/PullRequestCard.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import PrStatusBadge from '$components/PrStatusBadge.svelte';
33
import PullRequestPolling from '$components/PullRequestPolling.svelte';
44
import ReduxResult from '$components/ReduxResult.svelte';
5-
import { writeClipboard } from '$lib/backend/clipboard';
5+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
66
import { DEFAULT_FORGE_FACTORY } from '$lib/forge/forgeFactory.svelte';
77
import { URL_SERVICE } from '$lib/utils/url';
88
import { inject } from '@gitbutler/shared/context';
@@ -58,6 +58,7 @@
5858
const prService = $derived(forge.current.prService);
5959
const checksService = $derived(forge.current.checks);
6060
const urlService = inject(URL_SERVICE);
61+
const clipboardService = inject(CLIPBOARD_SERVICE);
6162
6263
const prResult = $derived(prService?.get(prNumber, { forceRefetch: true }));
6364
const pr = $derived(prResult?.current.data);
@@ -125,7 +126,7 @@
125126
<ContextMenuItem
126127
label="Copy link"
127128
onclick={() => {
128-
writeClipboard(pr.htmlUrl);
129+
clipboardService.write(pr.htmlUrl);
129130
contextMenuEl?.close();
130131
}}
131132
/>
@@ -152,7 +153,7 @@
152153
<ContextMenuItem
153154
label="Copy checks"
154155
onclick={() => {
155-
writeClipboard(`${pr.htmlUrl}/checks`);
156+
clipboardService.write(`${pr.htmlUrl}/checks`);
156157
contextMenuEl?.close();
157158
}}
158159
/>
@@ -178,7 +179,7 @@
178179
icon="copy-small"
179180
tooltip="Copy {abbr} link"
180181
onclick={() => {
181-
writeClipboard(pr.htmlUrl);
182+
clipboardService.write(pr.htmlUrl);
182183
}}
183184
/>
184185
<Button

apps/desktop/src/components/PushButton.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import ReduxResult from '$components/ReduxResult.svelte';
3-
import { writeClipboard } from '$lib/backend/clipboard';
3+
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
44
import { DEFAULT_FORGE_FACTORY } from '$lib/forge/forgeFactory.svelte';
55
import { PROJECTS_SERVICE } from '$lib/project/projectsService';
66
import {
@@ -49,6 +49,8 @@
4949
const uiState = inject(UI_STATE);
5050
const forge = inject(DEFAULT_FORGE_FACTORY);
5151
const urlService = inject(URL_SERVICE);
52+
const clipboardService = inject(CLIPBOARD_SERVICE);
53+
5254
const branchDetails = $derived(stackService.branchDetails(projectId, stackId, branchName));
5355
const projectResult = $derived(projectsService.getProject(projectId));
5456
const [pushStack, pushResult] = stackService.pushStack;
@@ -201,7 +203,7 @@
201203
author={commit.author.name}
202204
url={commitUrl}
203205
onOpen={(url) => urlService.openExternalUrl(url)}
204-
onCopy={() => writeClipboard(commit.id)}
206+
onCopy={() => clipboardService.write(commit.id)}
205207
/>
206208
{/each}
207209
</ScrollableContainer>

apps/desktop/src/lib/backend/backend.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,6 @@ export interface IBackend {
104104
filePicker<T extends OpenDialogOptions>(options?: T): Promise<OpenDialogReturn<T>>;
105105
homeDirectory(): Promise<string>;
106106
getAppInfo: () => Promise<AppInfo>;
107+
writeTextToClipboard: (text: string) => Promise<void>;
108+
readTextFromClipboard: () => Promise<string>;
107109
}
Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
1+
import { InjectionToken } from '@gitbutler/shared/context';
12
import { chipToasts } from '@gitbutler/ui';
2-
import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager';
3+
import type { IBackend } from '$lib/backend/backend';
34

4-
/**
5-
* Copy the provided text into the the system clipboard. Upon completion, a toast will be displayed which contains
6-
* information about the success of this operation.
7-
*
8-
* @param text text to be copied into the system clipboard.
9-
* @param errorMessage optional custom error message which will be displayed if the operation failes. If this is
10-
* not provided, a default generic message will be used.
11-
*/
12-
export async function writeClipboard(
13-
text: string,
14-
opt: {
15-
errorMessage?: string;
16-
message?: string;
17-
} = {}
18-
) {
19-
const { errorMessage, message } = opt;
20-
await writeText(text)
21-
.then(() => {
22-
chipToasts.success(message || 'Copied to clipboard');
23-
})
24-
.catch((err) => {
25-
chipToasts.error(errorMessage || 'Failed to copy');
26-
console.error(errorMessage, err);
27-
});
28-
}
5+
export const CLIPBOARD_SERVICE = new InjectionToken<ClipboardService>('ClipboardService');
6+
export default class ClipboardService {
7+
constructor(private backend: IBackend) {}
8+
9+
/**
10+
* Copy the provided text into the the system clipboard. Upon completion, a toast will be displayed which contains
11+
* information about the success of this operation.
12+
*
13+
* @param text text to be copied into the system clipboard.
14+
* @param errorMessage optional custom error message which will be displayed if the operation failes. If this is
15+
* not provided, a default generic message will be used.
16+
*/
17+
async write(
18+
text: string,
19+
opt: {
20+
errorMessage?: string;
21+
message?: string;
22+
} = {}
23+
) {
24+
const { errorMessage, message } = opt;
25+
await this.backend
26+
.writeTextToClipboard(text)
27+
.then(() => {
28+
chipToasts.success(message || 'Copied to clipboard');
29+
})
30+
.catch((err) => {
31+
chipToasts.error(errorMessage || 'Failed to copy');
32+
console.error(errorMessage, err);
33+
});
34+
}
2935

30-
export async function readClipboard() {
31-
return await readText();
36+
async read() {
37+
return await this.backend.readTextFromClipboard();
38+
}
3239
}

0 commit comments

Comments
 (0)