Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to copy to clipboard feature #2365

Merged
merged 1 commit into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 40 additions & 19 deletions src/components/modals/share-cite-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import { LogActions } from '@isrd-isi-edu/chaise/src/models/log';
// services
import { ConfigService } from '@isrd-isi-edu/chaise/src/services/config';
import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
import $log from '@isrd-isi-edu/chaise/src/services/logger';

// utils
import { resolvePermalink } from '@isrd-isi-edu/chaise/src/utils/uri-utils';
import { getVersionDate, humanizeTimestamp } from '@isrd-isi-edu/chaise/src/utils/date-time-utils';

import { copyToClipboard } from '@isrd-isi-edu/chaise/src/utils/ui-utils';

export type ShareCiteModalProps = {
/**
Expand Down Expand Up @@ -75,32 +76,52 @@ const ShareCiteModal = ({
logStackPath,
}: ShareCiteModalProps): JSX.Element => {

const DEFAULT_COPY_TOOLTIP = 'Copy link URL to clipboard.';
const [versionLinkCopyTooltip, setVersionLinkCopyTooltip] = useState(DEFAULT_COPY_TOOLTIP);
const [liveLinkCopyTooltip, setLiveLinkCopyTooltip] = useState(DEFAULT_COPY_TOOLTIP);

const logCitationDownload = () => {
LogService.logClientAction({
action: LogService.getActionString(LogActions.CITE_BIBTEXT_DOWNLOAD, logStackPath),
stack: logStack ? logStack : LogService.getStackObject()
}, reference.defaultLogInfo);
};

const copyToClipboard = (text: string, action: string) => {
/**
* set the tooltip of the copy button
* @param isVersionLink whether this is for the version link or live link
* @param str the tooltip
*/
const setLinkCopyTooltip = (isVersionLink: boolean, str: string) => {
if (isVersionLink) {
setVersionLinkCopyTooltip(str)
} else {
setLiveLinkCopyTooltip(str);
}
}

/**
* the callback for clicking on the copy link button
* @param isVersionLink whether this is for the version link or live link
*/
const onCopyToClipboard = (isVersionLink: boolean) => {
const action = isVersionLink ? LogActions.SHARE_VERSIONED_LINK_COPY : LogActions.SHARE_LIVE_LINK_COPY;
const text = isVersionLink ? versionLink : liveLink;

LogService.logClientAction({
action: LogService.getActionString(action, logStackPath),
stack: logStack ? logStack : LogService.getStackObject()
}, reference.defaultLogInfo);

// Create a dummy input to put the text string into it, select it, then copy it
// this has to be done because of HTML security and not letting scripts just copy stuff to the clipboard
// it has to be a user initiated action that is done through the DOM object
const dummy = document.createElement('input');
dummy.setAttribute('visibility', 'hidden');
dummy.setAttribute('display', 'none');
document.body.appendChild(dummy);
// dummy.setAttribute('id', 'copy_id');
// document.getElementById('copy_id')!.value = text;
dummy.value = text;
dummy.select();
document.execCommand('copy');
document.body.removeChild(dummy);
copyToClipboard(text).then(() => {
setLinkCopyTooltip(isVersionLink, 'Copied!');
setTimeout(() => {
setLinkCopyTooltip(isVersionLink, DEFAULT_COPY_TOOLTIP);
}, 1000);
}).catch((err) => {
$log.warn('failed to copy with the following error:');
$log.warn(err);
})
}

const citationReady = !!citation && citation.isReady;
Expand Down Expand Up @@ -183,10 +204,10 @@ const ShareCiteModal = ({
<ChaiseTooltip placement='bottom' tooltip={`Data snapshotted at ${versionDate}`}>
<small>({versionDateRelative}) </small>
</ChaiseTooltip>
<ChaiseTooltip placement='bottom' tooltip='Copy link URL to clipboard.'>
<ChaiseTooltip placement='bottom' tooltip={versionLinkCopyTooltip} dynamicTooltipString>
<span
className='fa-solid fa-clipboard chaise-copy-to-clipboard-btn'
onClick={() => copyToClipboard(versionLink, LogActions.SHARE_VERSIONED_LINK_COPY)}
onClick={() => onCopyToClipboard(true)}
/>
</ChaiseTooltip>
</h3>
Expand All @@ -195,10 +216,10 @@ const ShareCiteModal = ({
}
<h3 className='share-item-header'>
<span>Live Link </span>
<ChaiseTooltip placement='bottom' tooltip='Copy link URL to clipboard.'>
<ChaiseTooltip placement='bottom' tooltip={liveLinkCopyTooltip} dynamicTooltipString>
<span
className='fa-solid fa-clipboard chaise-copy-to-clipboard-btn'
onClick={() => copyToClipboard(liveLink, LogActions.SHARE_LIVE_LINK_COPY)}
onClick={() => onCopyToClipboard(false)}
/>
</ChaiseTooltip>
</h3>
Expand Down
25 changes: 21 additions & 4 deletions src/components/recordset/recordset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Title from '@isrd-isi-edu/chaise/src/components/title';
import TableHeader from '@isrd-isi-edu/chaise/src/components/recordset/table-header';

// hooks
import { useEffect, useRef, useState } from 'react';
import React, { AnchorHTMLAttributes, useEffect, useRef, useState } from 'react';
import useError from '@isrd-isi-edu/chaise/src/hooks/error';
import useRecordset from '@isrd-isi-edu/chaise/src/hooks/recordset';

Expand Down Expand Up @@ -162,6 +162,8 @@ const RecordsetInner = ({

const [savedQueryUpdated, setSavedQueryUpdated] = useState<boolean>(false);

const [permalinkTooltip, setPermalinkTooltip] = useState(MESSAGE_MAP.tooltip.permalink);

const mainContainer = useRef<HTMLDivElement>(null);
const topRightContainer = useRef<HTMLDivElement>(null);
const topLeftContainer = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -529,11 +531,26 @@ const RecordsetInner = ({

const recordsetLink = getRecordsetLink();

const copyPermalink = () => {
/**
* the callback for when permalink button is clicked
*/
const copyPermalink = (e: React.MouseEvent) => {
// avoid the navigation
e.preventDefault();

// log the action
logRecordsetClientAction(LogActions.PERMALINK_LEFT);

copyToClipboard(recordsetLink);
// copy to the clipboard
copyToClipboard(recordsetLink).then(() => {
setPermalinkTooltip('Copied!');
setTimeout(() => {
setPermalinkTooltip(MESSAGE_MAP.tooltip.permalink);
}, 1000);
}).catch((err) => {
$log.warn('failed to copy with the following error:')
$log.warn(err);
})
}

/**
Expand Down Expand Up @@ -823,7 +840,7 @@ const RecordsetInner = ({
reference={reference}
disabled={isLoading || !page || page.length === 0}
/>
<ChaiseTooltip placement='bottom' tooltip={MESSAGE_MAP.tooltip.permalink}>
<ChaiseTooltip placement='bottom' tooltip={permalinkTooltip} dynamicTooltipString>
<a
id='permalink'
className='chaise-btn chaise-btn-primary'
Expand Down
1 change: 1 addition & 0 deletions src/components/share-cite-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const ShareCiteButton = ({
<ChaiseTooltip
placement='bottom-start'
tooltip={waitingForModal ? 'Opening the share and cite links...' : 'Click here to show the share and cite links.'}
dynamicTooltipString
>
<button className='share-cite-btn chaise-btn chaise-btn-primary' onClick={onButtonClick} disabled={waitingForModal}>
{!waitingForModal && <span className='chaise-btn-icon fa fa-share-square'></span>}
Expand Down
46 changes: 30 additions & 16 deletions src/components/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,25 @@ type ChaiseTooltipProps = {
* The class name that will be attached to the tooltip
* (can be used for applying custom styles to the tooltip)
*/
className?: string,
/**
* if you want to manually control the tooltip, use
* this property in combination with `onToggle`.
* You should make sure `show` is set to proper value in that callback.
*/
show?: boolean,
/**
* if you want to manually control the tooltip, use
* this property in combination with `show`.
* You should make sure `show` is set to proper value in this callback.
*/
onToggle?: (nextShow: boolean) => void
className?: string,
/**
* if you want to manually control the tooltip, use
* this property in combination with `onToggle`.
* You should make sure `show` is set to proper value in that callback.
*/
show?: boolean,
/**
* if you want to manually control the tooltip, use
* this property in combination with `show`.
* You should make sure `show` is set to proper value in this callback.
*/
onToggle?: (nextShow: boolean) => void,
/**
* whether the text is dynamic and we should remount the tooltip when its content changes.
* This must be used with dynamic tooltips that change while user is still seeing the tooltip. without it the tooltip
* will be misaligned and might even go over the edge of the window.
*/
dynamicTooltipString?: boolean
}


Expand All @@ -42,7 +48,8 @@ const ChaiseTooltip = ({
placement,
className,
show,
onToggle
onToggle,
dynamicTooltipString
}: ChaiseTooltipProps): JSX.Element => {
/**
* - in react-bootstrap, the focus on the buttons remains even in cases where
Expand All @@ -60,7 +67,7 @@ const ChaiseTooltip = ({
* TODO we might want to explore better ways to handle this. ideally we should find
* a way to automatically blur the buttons, or manually do it for the cases where we also have tooltip
*/
const trigger : OverlayTriggerType[] = ['hover'];
const trigger: OverlayTriggerType[] = ['hover'];
if (IS_DEV_MODE) {
trigger.push('focus');
}
Expand All @@ -72,7 +79,14 @@ const ChaiseTooltip = ({
onToggle={onToggle}
placement={placement}
overlay={
<Tooltip className={className}>
<Tooltip
className={className}
/**
* adding the tooltip as key will make sure we're remounting when tooltip changes.
* we have to remount so the tooltip position is updated properly
*/
{...(dynamicTooltipString && typeof tooltip === 'string' && { key: tooltip })}
>
{tooltip}
</Tooltip>
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/message-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const MESSAGE_MAP = {
tooltip: {
versionTime: 'You are looking at data that was snapshotted on ',
downloadCSV: 'Click to download all matched results',
permalink: 'Click to copy the current url to clipboard.',
permalink: 'Click to copy the current URL to clipboard.',
actionCol: 'Click on the action buttons to view, edit, or delete each record',
viewCol: 'Click on the icon to view the detailed page associated with each record',
null: 'Search for any record with no value assigned',
Expand Down
31 changes: 12 additions & 19 deletions src/utils/ui-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,26 +243,19 @@ export function addTopHorizontalScroll(parent: HTMLElement, fixedPos = false, ex
return sensors;
}

export function copyToClipboard(text: string) {
if (document.execCommand) {
const dummy = document.createElement('input');
dummy.setAttribute('visibility', 'hidden');
dummy.setAttribute('display', 'none');

document.body.appendChild(dummy);
dummy.setAttribute('id', 'permalink_copy');
dummy.value = text;
dummy.select();
document.execCommand('copy');

document.body.removeChild(dummy);
}
else if (navigator && navigator.clipboard) {
navigator.clipboard.writeText(text).catch((err) => {
$log.warn('failed to copy with the following error:')
$log.warn(err);
/**
* add the given text to clipboard
* @param text the text that should be copied to clipboard
*/
export function copyToClipboard(text: string): Promise<void> {
return new Promise((resolve, reject) => {
navigator.clipboard.writeText(text).then(() => {
resolve();
}).catch((err) => {
reject(err);
});
}
});

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ var testParams = {
},
tooltip: {
exportDropdown: "Click to choose an export format.",
permalink: "Click to copy the current url to clipboard.",
permalink: "Click to copy the current URL to clipboard.",
actionCol: "Click on the action buttons to view, edit, or delete each record"
},
activeList: {
Expand Down
Loading