Skip to content

Commit ab64b8f

Browse files
authored
Merge pull request #1079 from writer/AB-196
feat(ui): implement a new WdsCopyClipboardButton component - AB-196
2 parents 3db6179 + 4119230 commit ab64b8f

22 files changed

+747
-567
lines changed

package-lock.json

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@tiptap/extension-text": "^3.0.7",
3131
"@tiptap/extensions": "^3.0.7",
3232
"@tiptap/vue-3": "^3.0.7",
33+
"@vueuse/core": "^13.6.0",
3334
"ajv": "^8.17.1",
3435
"ajv-errors": "^3.0.0",
3536
"arquero": "^5.2.0",

src/ui/src/builder/settings/BuilderSettings.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ import BuilderSettingsMain from "./BuilderSettingsMain.vue";
9696
import WdsButton from "@/wds/WdsButton.vue";
9797
import WdsIcon from "@/wds/WdsIcon.vue";
9898
import { SelectionStatus } from "../builderManager";
99-
import { useButtonClipboard } from "../useButtonClipboard";
99+
import { useClipboard } from "@vueuse/core";
100100
import SharedMoreDropdown from "@/components/shared/SharedMoreDropdown.vue";
101101
import { useWriterTracking } from "@/composables/useWriterTracking";
102102
import BuilderSettingsActions from "./BuilderSettingsActions.vue";
@@ -117,8 +117,11 @@ const { component, definition: componentDefinition } = useComponentInformation(
117117
);
118118
const resultId = useBlueprintComponentResultId(component, componentDefinition);
119119
120-
const { copyText: copyComponentId, isCopied: isComponentIdCopied } =
121-
useButtonClipboard(resultId);
120+
const { copy, copied: isComponentIdCopied } = useClipboard();
121+
122+
function copyComponentId() {
123+
copy(resultId.value);
124+
}
122125
123126
const tracking = useWriterTracking(wf);
124127

src/ui/src/builder/settings/BuilderSettingsArtifactAPITriggerDetails.vue

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
<WdsButton
4747
variant="tertiary"
4848
size="small"
49-
@click="copyCreate"
49+
@click="copyCreate()"
5050
>
5151
<WdsIcon
5252
:name="isCreateCopied ? 'check' : 'clipboard'"
@@ -83,7 +83,7 @@
8383
variant="tertiary"
8484
size="small"
8585
:disabled="isPollDisabled"
86-
@click="copyPoll"
86+
@click="copyPoll()"
8787
>
8888
<WdsIcon :name="isPollCopied ? 'check' : 'clipboard'" />
8989
Copy poll async
@@ -103,7 +103,7 @@ import WdsTabs, { WdsTabOptions } from "@/wds/WdsTabs.vue";
103103
import WdsTextareaInput from "@/wds/WdsTextareaInput.vue";
104104
import WdsFieldWrapper from "@/wds/WdsFieldWrapper.vue";
105105
import BuilderFieldsCode from "./BuilderFieldsCode.vue";
106-
import { useButtonClipboard } from "../useButtonClipboard";
106+
import { useClipboard } from "@vueuse/core";
107107
import WdsTitle2 from "@/wds/WdsTitle2.vue";
108108
import { FieldType } from "@/writerTypes";
109109
@@ -153,8 +153,9 @@ const parsedOutput = computed(() => {
153153
}
154154
});
155155
156-
const { copyText: copyCreate, isCopied: isCreateCopied } =
157-
useButtonClipboard(curlCreate);
156+
const { copy: copyCreate, copied: isCreateCopied } = useClipboard({
157+
source: curlCreate,
158+
});
158159
159160
const isPollDisabled = computed(() => {
160161
return !parsedOutput.value?.poll_url?.trim();
@@ -168,8 +169,9 @@ const curlPoll = computed(() => {
168169
);
169170
});
170171
171-
const { copyText: copyPoll, isCopied: isPollCopied } =
172-
useButtonClipboard(curlPoll);
172+
const { copy: copyPoll, copied: isPollCopied } = useClipboard({
173+
source: curlPoll,
174+
});
173175
</script>
174176

175177
<style scoped>

src/ui/src/builder/settings/BuilderSettingsMain.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ import BuilderSettingsBinding from "./BuilderSettingsBinding.vue";
7373
import BuilderSettingsVisibility from "./BuilderSettingsVisibility.vue";
7474
import WdsButton from "@/wds/WdsButton.vue";
7575
import WdsIcon from "@/wds/WdsIcon.vue";
76-
import { useButtonClipboard } from "../useButtonClipboard";
76+
import { useClipboard } from "@vueuse/core";
7777
import { artifactRegistry } from "./artifacts";
7878
import { defineAsyncComponentWithLoader } from "@/utils/defineAsyncComponentWithLoader";
7979
@@ -108,8 +108,11 @@ watch(component, (newComponent) => {
108108
if (!newComponent) ssbm.setSelection(null);
109109
});
110110
111-
const { copyText: copyComponentId, isCopied: isComponentIdCopied } =
112-
useButtonClipboard(computed(() => ssbm.firstSelectedId.value));
111+
const { copy, copied: isComponentIdCopied } = useClipboard();
112+
113+
function copyComponentId() {
114+
copy(ssbm.firstSelectedId.value);
115+
}
113116
114117
const isBindable = computed(() =>
115118
Object.values(componentDefinition.value?.events ?? {}).some(

src/ui/src/builder/settings/BuilderSettingsProperties.vue

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
<template>
2-
<div
3-
v-if="ssbm.isSingleSelectionActive && fields"
4-
class="BuilderSettingsProperties"
5-
>
2+
<div v-if="ssbm.isSingleSelectionActive" class="BuilderSettingsProperties">
63
<WdsTabs
74
v-if="fieldCategories.length > 1"
85
v-model="selectedCategoryTab"
@@ -344,24 +341,42 @@ const fieldCategories = computed(() => {
344341
].filter((c) => fieldsByCategory.value[c]?.length);
345342
});
346343
347-
const fieldsByCategory = computed(() => {
348-
const entries = Object.entries(fields.value);
349-
const result = {
350-
[FieldCategory.General]: entries.filter(
351-
([_, fieldValue]) =>
352-
!fieldValue.category ||
353-
fieldValue.category == FieldCategory.General,
354-
),
355-
[FieldCategory.Style]: entries.filter(
356-
([_, fieldValue]) => fieldValue.category == FieldCategory.Style,
357-
),
358-
[FieldCategory.Tools]: entries.filter(
359-
([_, fieldValue]) => fieldValue.category == FieldCategory.Tools,
360-
),
344+
type FieldEntry = [string, WriterComponentDefinitionField];
345+
346+
const fieldsByCategory = computed<Record<FieldCategory, FieldEntry[]>>(() => {
347+
const result: Record<FieldCategory, FieldEntry[]> = {
348+
[FieldCategory.General]: [],
349+
[FieldCategory.Style]: [],
350+
[FieldCategory.Tools]: [],
351+
};
352+
353+
Object.entries(fields.value).forEach(([k, v]) => {
354+
const category = v.category || FieldCategory.General;
355+
356+
if (result[category]) {
357+
result[category].push([k, v]);
358+
}
359+
});
360+
361+
return {
362+
[FieldCategory.General]: sortByOrder(result[FieldCategory.General]),
363+
[FieldCategory.Style]: sortByOrder(result[FieldCategory.Style]),
364+
[FieldCategory.Tools]: sortByOrder(result[FieldCategory.Tools]),
361365
};
362-
return result;
363366
});
364367
368+
function sortByOrder(fieldEntries: FieldEntry[]): FieldEntry[] {
369+
return fieldEntries
370+
.map((item, itemIndex) => [item, itemIndex] as const)
371+
.sort(([a, aIndex], [b, bIndex]) => {
372+
const aOrder = a[1].order ?? 0;
373+
const bOrder = b[1].order ?? 0;
374+
375+
return bOrder - aOrder || aIndex - bIndex;
376+
})
377+
.map(([item]) => item);
378+
}
379+
365380
function handleExpand(fieldKey: string) {
366381
expandedFields.value.add(fieldKey);
367382
}

src/ui/src/builder/useButtonClipboard.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/ui/src/components/blueprints/abstract/BlueprintsNodeActions.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { computed, inject } from "vue";
33
import injectionKeys from "@/injectionKeys";
44
import { useComponentInformation } from "@/composables/useComponentInformation";
55
import { useBlueprintComponentResultId } from "@/composables/useBlueprintComponentResultId";
6-
import { useButtonClipboard } from "@/builder/useButtonClipboard";
6+
import { useClipboard } from "@vueuse/core";
77
import WdsButton from "@/wds/WdsButton.vue";
88
import WdsIcon from "@/wds/WdsIcon.vue";
99
import SharedMoreDropdown from "@/components/shared/SharedMoreDropdown.vue";
@@ -33,8 +33,11 @@ const { component, definition: def } = useComponentInformation(wf, componentId);
3333
const resultId = useBlueprintComponentResultId(component, def);
3434
const tracking = useWriterTracking(wf);
3535
36-
const { copyText: copyComponentId, isCopied: isComponentIdCopied } =
37-
useButtonClipboard(resultId);
36+
const { copy, copied: isComponentIdCopied } = useClipboard();
37+
38+
function copyComponentId() {
39+
copy(resultId.value);
40+
}
3841
3942
const settingsActions = useBuilderSettingsActions(
4043
wf,

src/ui/src/components/core/content/CoreAnnotatedText.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import CoreAnnotatedText from "./CoreAnnotatedText.vue";
44
import VueDOMPurifyHTML from "vue-dompurify-html";
55
import injectionKeys from "@/injectionKeys";
66
import { buildMockCore, mockProvides } from "@/tests/mocks";
7-
import { flushPromises, shallowMount } from "@vue/test-utils";
7+
import { flushPromises, mount } from "@vue/test-utils";
88
import { ref } from "vue";
99
import { WdsColor } from "@/wds/tokens";
1010

@@ -23,7 +23,7 @@ describe("CoreAnnotatedText", async () => {
2323
it("should render in non-markdown mode", async () => {
2424
const { core } = buildMockCore();
2525

26-
const wrapper = shallowMount(CoreAnnotatedText, {
26+
const wrapper = mount(CoreAnnotatedText, {
2727
global: {
2828
plugins: [VueDOMPurifyHTML],
2929
provide: {
@@ -37,7 +37,6 @@ describe("CoreAnnotatedText", async () => {
3737
rotateHue: ref(true),
3838
referenceColor: ref(WdsColor.Blue5),
3939
copyButtons: ref(true),
40-
quickCopy: ref(false),
4140
},
4241
},
4342
},
@@ -64,7 +63,7 @@ describe("CoreAnnotatedText", async () => {
6463
it("should render in markdown mode", async () => {
6564
const { core } = buildMockCore();
6665

67-
const wrapper = shallowMount(CoreAnnotatedText, {
66+
const wrapper = mount(CoreAnnotatedText, {
6867
global: {
6968
plugins: [VueDOMPurifyHTML],
7069
provide: {
@@ -78,7 +77,6 @@ describe("CoreAnnotatedText", async () => {
7877
rotateHue: ref(true),
7978
referenceColor: ref(WdsColor.Blue5),
8079
copyButtons: ref(true),
81-
quickCopy: ref(false),
8280
},
8381
},
8482
},

src/ui/src/components/core/content/CoreAnnotatedText.vue

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div v-if="shouldDisplay" class="CoreAnnotatedText">
2+
<SharedControlBar v-if="shouldDisplay" class="CoreAnnotatedText">
33
<BaseMarkdownRaw
44
v-if="useMarkdown"
55
:raw-markdown="markdown"
@@ -26,12 +26,13 @@
2626
</span>
2727
</span>
2828
</template>
29-
<SharedControlBar
30-
v-if="fields.copyButtons.value"
31-
:copy-raw-content="copyRawContent"
32-
:copy-structured-content="copyStructuredContent"
33-
/>
34-
</div>
29+
<template #actions>
30+
<SharedCopyClipboardButton
31+
v-if="fields.copyButtons.value"
32+
:options="copyButtonOptions"
33+
/>
34+
</template>
35+
</SharedControlBar>
3536
</template>
3637

3738
<script lang="ts">
@@ -41,6 +42,7 @@ import {
4142
buttonTextColor,
4243
cssClasses,
4344
primaryTextColor,
45+
createEnableCopyButtonField,
4446
} from "@/renderer/sharedStyleFields";
4547
import SharedControlBar from "@/components/shared/SharedControlBar.vue";
4648
import { WdsColor } from "@/wds/tokens";
@@ -83,11 +85,8 @@ export default {
8385
desc: "If active, the output will be sanitized; unsafe elements will be removed.",
8486
default: "no",
8587
}),
86-
copyButtons: createBooleanField({
87-
name: "Enable copy buttons",
88-
desc: "If active, adds a control bar with both copy text and JSON buttons.",
88+
copyButtons: createEnableCopyButtonField({
8989
default: "no",
90-
category: FieldCategory.Style,
9190
}),
9291
buttonColor,
9392
buttonTextColor,
@@ -105,6 +104,11 @@ import { computed, inject, readonly, ref, watch } from "vue";
105104
import chroma, { Color } from "chroma-js";
106105
import BaseEmptiness from "../base/BaseEmptiness.vue";
107106
import BaseMarkdownRaw from "../base/BaseMarkdownRaw.vue";
107+
import { defineAsyncComponentWithLoader } from "@/utils/defineAsyncComponentWithLoader";
108+
109+
const SharedCopyClipboardButton = defineAsyncComponentWithLoader({
110+
loader: () => import("@/components/shared/SharedCopyClipboardButton.vue"),
111+
});
108112
109113
type AnnotatedTextElementArray = [content: string, tag: string, color?: string];
110114
type AnnotatedTextElement = string | AnnotatedTextElementArray;
@@ -261,6 +265,11 @@ const copyStructuredContent = computed(() => {
261265
return text.value.join("");
262266
}
263267
});
268+
269+
const copyButtonOptions = computed(() => [
270+
{ label: "Copy text", value: copyRawContent.value },
271+
{ label: "Copy JSON", value: copyStructuredContent.value },
272+
]);
264273
</script>
265274

266275
<style scoped>

0 commit comments

Comments
 (0)