11<script setup lang="ts">
22import { isPluginContextEvent , type PluginContextEvent , portRange } from ' @figwright/shared' ;
3- import { tryOnScopeDispose , useDocumentVisibility , useEventListener , useNow } from ' @vueuse/core' ;
3+ import {
4+ tryOnScopeDispose ,
5+ useClipboard ,
6+ useDocumentVisibility ,
7+ useEventListener ,
8+ useNow ,
9+ } from ' @vueuse/core' ;
410import { computed , onMounted , ref , watch } from ' vue' ;
511
612import { createSandboxBridge } from ' ./bridge/sandbox.js' ;
@@ -62,7 +68,6 @@ const errorEntries = computed(() => state.value.activity.filter(e => e.status ==
6268
6369// Which Activity rows are expanded to show the payload fed to the LLM, and which just got copied.
6470const expanded = ref <Set <string >>(new Set ());
65- const copiedId = ref <string | null >(null );
6671
6772const toggle = (id : string ): void => {
6873 const next = new Set (expanded .value );
@@ -74,29 +79,14 @@ const toggle = (id: string): void => {
7479const formatSize = (bytes : number ): string =>
7580 bytes < 1024 ? ` ${bytes } B ` : ` ${(bytes / 1024 ).toFixed (1 )} KB ` ;
7681
77- // Copy the shown payload. navigator.clipboard is often blocked inside the Figma plugin iframe, so
78- // fall back to a transient textarea + execCommand. Either way, flash a "copied" label.
82+ // Copy the shown payload. `legacy: true` makes useClipboard fall back to execCommand when the async
83+ // Clipboard API isn't available — which is the case inside the Figma plugin iframe. `copied` flips
84+ // true for ~1.5s after a copy; we pair it with the last-copied id so only that row's button flashes.
85+ const { copy : clipboardCopy, copied } = useClipboard ({ legacy: true });
86+ const copiedId = ref <string | null >(null );
7987const copy = (text : string , id : string ): void => {
80- const flash = (): void => {
81- copiedId .value = id ;
82- setTimeout (() => {
83- if (copiedId .value === id ) copiedId .value = null ;
84- }, 1500 );
85- };
86- void Promise .resolve (navigator .clipboard ?.writeText (text ))
87- .catch (() => {
88- const ta = document .createElement (' textarea' );
89- ta .value = text ;
90- document .body .appendChild (ta );
91- ta .select ();
92- try {
93- document .execCommand (' copy' );
94- } catch {
95- /* nothing else to try */
96- }
97- ta .remove ();
98- })
99- .finally (flash );
88+ copiedId .value = id ;
89+ void clipboardCopy (text );
10090};
10191
10292const formatAgo = (ts : number ): string => {
@@ -214,10 +204,7 @@ const runInBackground = (): void => {
214204 <span class =" min-w-0 truncate" :class =" e.status === 'error' ? 'text-fig-danger' : ''" >
215205 {{ e.method }}
216206 </span >
217- <span v-if =" e.payload" class =" ml-auto shrink-0 text-fig-muted" >
218- {{ formatSize(e.payload.bytes) }}
219- </span >
220- <span :class =" ['shrink-0 text-fig-muted', e.payload ? '' : 'ml-auto']" >
207+ <span class =" ml-auto shrink-0 text-fig-muted" >
221208 {{ e.durationMs === undefined ? '' : `${e.durationMs}ms` }}
222209 </span >
223210 <span class =" w-9 shrink-0 text-right text-fig-muted" >{{
@@ -234,7 +221,7 @@ const runInBackground = (): void => {
234221 class =" ml-auto shrink-0 rounded border border-white/15 px-1.5 py-0.5 hover:bg-white/10 hover:text-fig-fg"
235222 @click =" copy(e.payload.preview, e.id)"
236223 >
237- {{ copiedId === e.id ? 'copied' : 'copy' }}
224+ {{ copied && copiedId === e.id ? 'copied' : 'copy' }}
238225 </button >
239226 </div >
240227 <pre
0 commit comments