Skip to content

Commit 3299224

Browse files
committed
Introduce ClipboardService and migrate clipboard access
Introduce ClipboardService and the CLIPBOARD_SERVICE token. Update all UI and service clipboard access to go through ClipboardService, which uses the backend abstraction for clipboard operations. Provide user toasts for copy feedback and ensure dependency injection across the app.
1 parent 5b7999a commit 3299224

10 files changed

+69
-50
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>
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
}

apps/desktop/src/routes/+layout.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { EVENT_CONTEXT } from '$lib/analytics/eventContext';
2121
import { POSTHOG_WRAPPER } from '$lib/analytics/posthog';
2222
import { BACKEND } from '$lib/backend';
23+
import ClipboardService, { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
2324
import BaseBranchService, { BASE_BRANCH_SERVICE } from '$lib/baseBranch/baseBranchService.svelte';
2425
import { BranchService, BRANCH_SERVICE } from '$lib/branches/branchService.svelte';
2526
import CLIManager, { CLI_MANAGER } from '$lib/cli/cli';
@@ -199,6 +200,7 @@
199200
);
200201
201202
const urlService = new URLService(data.backend);
203+
const clipboardService = new ClipboardService(data.backend);
202204
203205
const projectsService = new ProjectsService(clientState, data.homeDir, data.backend);
204206
provide(PROJECTS_SERVICE, projectsService);
@@ -284,6 +286,7 @@
284286
provide(RESIZE_SYNC, new ResizeSync());
285287
provide(GIT_SERVICE, new GitService(data.backend));
286288
provide(URL_SERVICE, urlService);
289+
provide(CLIPBOARD_SERVICE, clipboardService);
287290
288291
provide(BACKEND, data.backend);
289292

0 commit comments

Comments
 (0)