diff --git a/package.json b/package.json index 8e71a9ea..2d5183ff 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "axios": "^0.21.1", "browser-or-node": "^2.0.0", "classnames": "^2.3.2", - "clipboard": "2.0.8", "color": "^3.2.1", "combokeys": "^3.0.1", "copy-to-clipboard": "^3.3.1", diff --git a/packages/ove/src/AlignmentView/index.js b/packages/ove/src/AlignmentView/index.js index 9b58635d..21266a46 100644 --- a/packages/ove/src/AlignmentView/index.js +++ b/packages/ove/src/AlignmentView/index.js @@ -4,7 +4,6 @@ import { Droppable, Draggable as DndDraggable } from "@hello-pangea/dnd"; -import Clipboard from "clipboard"; import React from "react"; import { connect } from "react-redux"; import { @@ -104,6 +103,7 @@ export class AlignmentView extends React.Component { this.handleAlignmentCopy ); } + getMaxLength = () => { const { alignmentTracks } = this.props; const { sequenceData = { sequence: "" }, alignmentData } = @@ -175,6 +175,7 @@ export class AlignmentView extends React.Component { this.onShortcutCopy && document.removeEventListener("keydown", this.handleAlignmentCopy); } + handleAlignmentCopy = event => { if ( event.key === "c" && @@ -196,6 +197,7 @@ export class AlignmentView extends React.Component { event.preventDefault(); } }; + getAllAlignmentsFastaText = () => { const selectionLayer = this.props.store.getState().VectorEditor.__allEditorsOptions.alignments[ @@ -214,6 +216,7 @@ export class AlignmentView extends React.Component { }); return seqDataOfAllTracksToCopy.join(""); }; + state = { alignmentName: this.props.alignmentName, isTrackDragging: false, @@ -222,6 +225,7 @@ export class AlignmentView extends React.Component { width: 0, nameDivWidth: 140 }; + easyStore = store({ selectionLayer: { start: -1, end: -1 }, caretPosition: -1, @@ -280,6 +284,7 @@ export class AlignmentView extends React.Component { }, 5000); } } + componentDidMount() { const updateAlignmentSelection = newRangeOrCaret => { this.updateSelectionOrCaret(false, newRangeOrCaret, { @@ -1194,6 +1199,44 @@ export class AlignmentView extends React.Component { const alignmentData = track.alignmentData; const { name } = alignmentData; + + const copySpecificAlignmentFasta = async () => { + const { selectionLayer } = + this.props.store.getState().VectorEditor + .__allEditorsOptions.alignments[ + this.props.id + ] || {}; + const seqDataToCopy = getSequenceDataBetweenRange( + alignmentData, + selectionLayer + ).sequence; + const seqDataToCopyAsFasta = `>${name}\r\n${seqDataToCopy}\r\n`; + await navigator.clipboard.writeText( + seqDataToCopyAsFasta + ); + }; + + const copySpecificAlignment = async () => { + const { selectionLayer } = + this.props.store.getState().VectorEditor + .__allEditorsOptions.alignments[ + this.props.id + ] || {}; + const seqDataToCopy = getSequenceDataBetweenRange( + alignmentData, + selectionLayer + ).sequence; + await navigator.clipboard.writeText( + seqDataToCopy + ); + }; + + const getAllAlignmentsFastaText = async () => { + await navigator.clipboard.writeText( + this.getAllAlignmentsFastaText() + ); + }; + showContextMenu( [ ...(additionalSelectionLayerRightClickedOptions @@ -1207,24 +1250,8 @@ export class AlignmentView extends React.Component { className: "copyAllAlignmentsFastaClipboardHelper", hotkey: "cmd+c", - willUnmount: () => { - this - .copyAllAlignmentsFastaClipboardHelper && - this.copyAllAlignmentsFastaClipboardHelper.destroy(); - }, - didMount: () => { - this.copyAllAlignmentsFastaClipboardHelper = - new Clipboard( - `.copyAllAlignmentsFastaClipboardHelper`, - { - action: "copyAllAlignmentsFasta", - text: () => { - return this.getAllAlignmentsFastaText(); - } - } - ); - }, onClick: () => { + getAllAlignmentsFastaText(); window.toastr.success("Selection Copied"); } }, @@ -1232,36 +1259,8 @@ export class AlignmentView extends React.Component { text: `Copy Selection of ${name} as Fasta`, className: "copySpecificAlignmentFastaClipboardHelper", - willUnmount: () => { - this - .copySpecificAlignmentFastaClipboardHelper && - this.copySpecificAlignmentFastaClipboardHelper.destroy(); - }, - didMount: () => { - this.copySpecificAlignmentFastaClipboardHelper = - new Clipboard( - `.copySpecificAlignmentFastaClipboardHelper`, - { - action: "copySpecificAlignmentFasta", - text: () => { - const { selectionLayer } = - this.props.store.getState() - .VectorEditor - .__allEditorsOptions.alignments[ - this.props.id - ] || {}; - const seqDataToCopy = - getSequenceDataBetweenRange( - alignmentData, - selectionLayer - ).sequence; - const seqDataToCopyAsFasta = `>${name}\r\n${seqDataToCopy}\r\n`; - return seqDataToCopyAsFasta; - } - } - ); - }, onClick: () => { + copySpecificAlignmentFasta(); window.toastr.success( "Selection Copied As Fasta" ); @@ -1271,35 +1270,8 @@ export class AlignmentView extends React.Component { text: `Copy Selection of ${name}`, className: "copySpecificAlignmentAsPlainClipboardHelper", - willUnmount: () => { - this - .copySpecificAlignmentAsPlainClipboardHelper && - this.copySpecificAlignmentAsPlainClipboardHelper.destroy(); - }, - didMount: () => { - this.copySpecificAlignmentAsPlainClipboardHelper = - new Clipboard( - `.copySpecificAlignmentAsPlainClipboardHelper`, - { - action: "copySpecificAlignmentFasta", - text: () => { - const { selectionLayer } = - this.props.store.getState() - .VectorEditor - .__allEditorsOptions.alignments[ - this.props.id - ] || {}; - const seqDataToCopy = - getSequenceDataBetweenRange( - alignmentData, - selectionLayer - ).sequence; - return seqDataToCopy; - } - } - ); - }, onClick: () => { + copySpecificAlignment(); window.toastr.success("Selection Copied"); } } @@ -1313,7 +1285,7 @@ export class AlignmentView extends React.Component { sequenceLength={sequenceLength} charWidth={this.getCharWidthInLinearView()} row={{ start: 0, end: sequenceLength - 1 }} - > + /> + /> ; }; } + componentWillUnmount() { this.combokeys && this.combokeys.detach(); } @@ -183,6 +183,7 @@ function VectorInteractionHOC(Component /* options */) { omitIcons: true }); } + updateSelectionOrCaret = (shiftHeld, newRangeOrCaret) => { const { selectionLayer, @@ -257,7 +258,8 @@ function VectorInteractionHOC(Component /* options */) { e.preventDefault(); }; - handleCutOrCopy = isCut => e => { + handleCutOrCopy = isCut => async e => { + e.preventDefault(); const { onCopy = noop, sequenceData, @@ -303,7 +305,6 @@ function VectorInteractionHOC(Component /* options */) { }` ); - const clipboardData = e.clipboardData; const textToCopy = (this.sequenceDataToCopy || {}).textToCopy !== undefined ? this.sequenceDataToCopy.textToCopy @@ -312,9 +313,16 @@ function VectorInteractionHOC(Component /* options */) { : seqData.sequence; seqData.textToCopy = textToCopy; - clipboardData.setData("text/plain", textToCopy); - clipboardData.setData("application/json", JSON.stringify(seqData)); - e.preventDefault(); + await navigator.clipboard.writeText(textToCopy); + // application/json is not supported by clipboard api on browser + // await navigator.clipboard.write([ + // new ClipboardItem({ + // "application/json": new Blob([JSON.stringify(seqData)], { + // type: "application/json" + // }), + // "text/plain": new Blob([textToCopy], { type: "text/plain" }) + // }) + // ]); if (isCut && !(readOnly || disableBpEditing) && !disableBpEditing) { this.handleDnaDelete(false); @@ -326,10 +334,8 @@ function VectorInteractionHOC(Component /* options */) { }), this.props ); - document.body.removeEventListener("cut", this.handleCut); } else { onCopy(e, seqData, this.props); - document.body.removeEventListener("copy", this.handleCopy); } window.toastr.success( `Selection ${ @@ -340,6 +346,7 @@ function VectorInteractionHOC(Component /* options */) { ); this.sequenceDataToCopy = undefined; }; + handleCut = this.handleCutOrCopy(true); handleCopy = this.handleCutOrCopy(); @@ -358,6 +365,7 @@ function VectorInteractionHOC(Component /* options */) { } }; }; + createDisableBpEditingMsg = () => { window.toastr.warning( typeof this.props.disableBpEditing === "string" @@ -366,6 +374,7 @@ function VectorInteractionHOC(Component /* options */) { this.getDuplicateAction() ); }; + createReadOnlyMsg = () => { window.toastr.warning( this.props.readOnly === "string" @@ -486,6 +495,7 @@ function VectorInteractionHOC(Component /* options */) { //we only call caretPositionUpdate if we're actually changing something this.props.caretPositionUpdate(position); }; + selectionLayerUpdate = newSelection => { const { selectionLayer = { start: -1, end: -1 }, ignoreGapsOnHighlight } = this.props; @@ -582,6 +592,7 @@ function VectorInteractionHOC(Component /* options */) { annotationDeselectAll(undefined); annotationSelect(annotation); }; + insertHelper = { onClick: (e, ctxInfo) => { this.handleDnaInsert({ @@ -593,75 +604,55 @@ function VectorInteractionHOC(Component /* options */) { } }; - // eslint-disable-next-line no-unused-vars getCopyOptions = annotation => { const { sequenceData, readOnly, disableBpEditing, selectionLayer } = this.props; const { isProtein } = sequenceData; - const makeTextCopyable = (transformFunc, className, action = "copy") => { - return new Clipboard(`.${className}`, { - action: () => action, - text: () => { - if (action === "copy") { - document.body.addEventListener("copy", this.handleCopy); - } else { - document.body.addEventListener("cut", this.handleCut); - } - const { editorName, store } = this.props; - const { sequenceData, copyOptions, selectionLayer } = - store.getState().VectorEditor[editorName]; + const makeTextCopyable = transformFunc => { + return async () => { + const { editorName, store } = this.props; + const { sequenceData, copyOptions, selectionLayer } = + store.getState().VectorEditor[editorName]; - const selectedSeqData = getSequenceDataBetweenRange( - sequenceData, - getSelFromWrappedAddon( - selectionLayer, - sequenceData.sequence.length - ), - { - excludePartial: { - features: !copyOptions.partialFeatures, - parts: !copyOptions.partialParts - }, - exclude: { - features: !copyOptions.features, - parts: !copyOptions.parts - } + const selectedSeqData = getSequenceDataBetweenRange( + sequenceData, + getSelFromWrappedAddon( + selectionLayer, + sequenceData.sequence.length + ), + { + excludePartial: { + features: !copyOptions.partialFeatures, + parts: !copyOptions.partialParts + }, + exclude: { + features: !copyOptions.features, + parts: !copyOptions.parts } - ); - const sequenceDataToCopy = transformFunc( - selectedSeqData, - sequenceData - ); - this.sequenceDataToCopy = sequenceDataToCopy; - - if (window.Cypress) { - window.Cypress.textToCopy = sequenceDataToCopy.textToCopy; - window.Cypress.seqDataToCopy = sequenceDataToCopy; } - return sequenceDataToCopy.textToCopy || sequenceDataToCopy.sequence; + ); + const sequenceDataToCopy = transformFunc( + selectedSeqData, + sequenceData + ); + this.sequenceDataToCopy = sequenceDataToCopy; + + if (window.Cypress) { + window.Cypress.textToCopy = sequenceDataToCopy.textToCopy; + window.Cypress.seqDataToCopy = sequenceDataToCopy; } - }); + }; }; const aaCopy = { text: "Copy AA Sequence", className: "openVeCopyAA", - willUnmount: () => { - this.openVeCopyAA && this.openVeCopyAA.destroy(); - }, - didMount: ({ className }) => { - this.openVeCopyAA = makeTextCopyable(selectedSeqData => { - const textToCopy = isProtein - ? selectedSeqData.proteinSequence.toUpperCase() - : getAminoAcidStringFromSequenceString(selectedSeqData.sequence); - return { - ...selectedSeqData, - textToCopy - }; - }, className); - } + onClick: makeTextCopyable(selectedSeqData => ({ + ...selectedSeqData, + textToCopy: isProtein + ? selectedSeqData.proteinSequence.toUpperCase() + : getAminoAcidStringFromSequenceString(selectedSeqData.sequence) + })) }; - // TODO: maybe stop using Clipboard.js and unify clipboard handling with - // a more versatile approach return [ ...(readOnly || disableBpEditing ? [] @@ -673,20 +664,7 @@ function VectorInteractionHOC(Component /* options */) { { text: "Cut", className: "openVeCut", - willUnmount: () => { - this.openVeCut && this.openVeCut.destroy(); - }, - didMount: ({ className }) => { - // TODO: Maybe use a cut action instead - this.openVeCut = makeTextCopyable( - s => ({ - ...s, - textToCopy: isProtein ? s.proteinSequence : s.sequence - }), - className, - "cut" - ); - } + onClick: this.handleCut } ]), { @@ -699,80 +677,45 @@ function VectorInteractionHOC(Component /* options */) { { text: isProtein ? "Copy DNA Bps" : "Copy", className: "openVeCopy2", - willUnmount: () => { - this.openVeCopy2 && this.openVeCopy2.destroy(); - }, - didMount: ({ className }) => { - this.openVeCopy2 = makeTextCopyable( - s => ({ ...s, textToCopy: s.sequence }), - className - ); - } + onClick: makeTextCopyable(s => ({ + ...s, + textToCopy: s.sequence + })) }, { text: "Copy Genbank For Selection", className: "openVeCopyGenbankForSelection", - willUnmount: () => { - this.openVeCopyGenbankForSelection && - this.openVeCopyGenbankForSelection.destroy(); - }, - didMount: ({ className }) => { - this.openVeCopyGenbankForSelection = makeTextCopyable( - getGenbankFromSelection, - className - ); - } + onClick: makeTextCopyable(getGenbankFromSelection) }, { text: isProtein ? "Copy Reverse Complement DNA Bps" : "Copy Reverse Complement", className: "openVeCopyReverse", - willUnmount: () => { - this.openVeCopyReverse && this.openVeCopyReverse.destroy(); - }, - didMount: ({ className }) => { - this.openVeCopyReverse = makeTextCopyable( - getReverseComplementSequenceAndAnnotations, - className - ); - } + onClick: makeTextCopyable( + getReverseComplementSequenceAndAnnotations + ) }, ...(isProtein ? [] : [aaCopy]), { text: "Copy Reverse Complement AA Sequence", className: "openVeCopyAAReverse", - willUnmount: () => { - this.openVeCopyAAReverse && this.openVeCopyAAReverse.destroy(); - }, - didMount: ({ className }) => { - this.openVeCopyAAReverse = makeTextCopyable(selectedSeqData => { - const revSeqData = - getReverseComplementSequenceAndAnnotations(selectedSeqData); - const textToCopy = isProtein + onClick: makeTextCopyable(selectedSeqData => { + const revSeqData = + getReverseComplementSequenceAndAnnotations(selectedSeqData); + return { + ...revSeqData, + textToCopy: isProtein ? revSeqData.proteinSequence.toUpperCase() - : getAminoAcidStringFromSequenceString(revSeqData.sequence); - return { - ...revSeqData, - textToCopy - }; - }, className); - } + : getAminoAcidStringFromSequenceString(revSeqData.sequence) + }; + }) }, { text: isProtein ? "Copy Complement DNA Bps" : "Copy Complement", className: "openVeCopyComplement", - willUnmount: () => { - this.openVeCopyComplement && - this.openVeCopyComplement.destroy(); - }, - didMount: ({ className }) => { - this.openVeCopyComplement = makeTextCopyable( - getComplementSequenceAndAnnotations, - className - ); - } + onClick: makeTextCopyable(getComplementSequenceAndAnnotations) }, copyOptionsMenu ] diff --git a/yarn.lock b/yarn.lock index 8e2ffab9..8912d71f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5273,15 +5273,6 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -clipboard@2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba" - integrity sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ== - dependencies: - good-listener "^1.2.2" - select "^1.1.2" - tiny-emitter "^2.0.0" - cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -6087,11 +6078,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -delegate@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" - integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== - dequal@^2.0.0, dequal@^2.0.2, dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" @@ -7534,13 +7520,6 @@ globrex@^0.1.2: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== -good-listener@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" - integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw== - dependencies: - delegate "^3.1.2" - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -11610,11 +11589,6 @@ secure-compare@3.0.1: resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== -select@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" - integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA== - semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -12266,11 +12240,6 @@ throttleit@^1.0.0: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -tiny-emitter@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" - integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== - tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"