handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs

feat: copy plc audit entry to clipboard

mary.my.id 2fa56343 b0d1f544

verified
+62 -5
+5
src/components/ic-icons/baseline-check.tsx
··· 1 + import { createIcon } from './_icon'; 2 + 3 + const CheckIcon = createIcon([['M9 16.17L4.83 12l-1.42 1.41L9 19L21 7l-1.41-1.41z']]); 4 + 5 + export default CheckIcon;
+9
src/components/ic-icons/baseline-content-copy.tsx
··· 1 + import { createIcon } from './_icon'; 2 + 3 + const ContentCopyIcon = createIcon([ 4 + [ 5 + 'M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m0 16H8V7h11z', 6 + ], 7 + ]); 8 + 9 + export default ContentCopyIcon;
+48 -5
src/views/identity/plc-oplogs.tsx
··· 1 - import { JSX, Match, Switch } from 'solid-js'; 1 + import { createSignal, JSX, Match, onCleanup, Switch } from 'solid-js'; 2 2 import * as v from 'valibot'; 3 3 4 4 import { At } from '@atcute/client/lexicons'; ··· 15 15 import CircularProgressView from '~/components/circular-progress-view'; 16 16 import DiffTable from '~/components/diff-table'; 17 17 import ErrorView from '~/components/error-view'; 18 + import ContentCopyIcon from '~/components/ic-icons/baseline-content-copy'; 19 + import CheckIcon from '~/components/ic-icons/baseline-check'; 18 20 19 21 const PlcOperationLogPage = () => { 20 22 const [params, setParams] = useSearchParams({ ··· 301 303 </div> 302 304 303 305 <div class="flex min-w-0 grow flex-col py-4"> 304 - <p class="font-mono text-[0.8125rem] leading-5 text-gray-600"> 305 - <span class={!nullified ? `` : `line-through`}>{/* @once */ entry.createdAt}</span> 306 - {nullified && <span> (nullified)</span>} 307 - </p> 306 + <div class="flex justify-between gap-4"> 307 + <div class="min-w-0 break-words font-mono text-[0.8125rem] leading-5 text-gray-600"> 308 + <p> 309 + <span class={!nullified ? `` : `line-through`}> 310 + {/* @once */ entry.createdAt} 311 + </span> 312 + {nullified && <span> (nullified)</span>} 313 + </p> 314 + </div> 315 + 316 + <div> 317 + <CopyButton text={JSON.stringify(entry, null, 2)} /> 318 + </div> 319 + </div> 308 320 309 321 {node} 310 322 </div> ··· 321 333 }; 322 334 323 335 export default PlcOperationLogPage; 336 + 337 + const CopyButton = (props: { text: string }) => { 338 + const [copied, setCopied] = createSignal(false); 339 + let timeout: number | undefined; 340 + 341 + onCleanup(() => clearTimeout(timeout)); 342 + 343 + const copy = () => { 344 + navigator.clipboard.writeText(props.text).then(() => { 345 + clearTimeout(timeout); 346 + 347 + setCopied(true); 348 + timeout = setTimeout(() => setCopied(false), 500); 349 + }); 350 + }; 351 + 352 + return ( 353 + <button 354 + title="Copy to clipboard" 355 + onClick={copy} 356 + class={ 357 + `grid h-6 w-6 place-items-center rounded text-base` + 358 + (!copied() 359 + ? ` text-gray-600 hover:bg-gray-200 hover:text-black active:bg-gray-200` 360 + : ` bg-green-600 text-white`) 361 + } 362 + > 363 + {!copied() ? <ContentCopyIcon /> : <CheckIcon />} 364 + </button> 365 + ); 366 + }; 324 367 325 368 const groupBy = <K, T>(items: T[], keyFn: (item: T, index: number) => K): Map<K, T[]> => { 326 369 const map = new Map<K, T[]>();