From db3d00a079803335803b2b417ea9e7b638d3f83d Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Sun, 20 Aug 2023 15:50:36 +0300 Subject: [PATCH 01/12] define onHover, onClick, OnDoubleClick, onKeyPress events handlers --- src/Linear/Linear.tsx | 1 + src/Linear/SeqBlock.tsx | 1 + src/SeqViewerContainer.tsx | 4 +++ src/SeqViz.tsx | 56 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/src/Linear/Linear.tsx b/src/Linear/Linear.tsx index 3ad8e9e3a..39478cf6a 100644 --- a/src/Linear/Linear.tsx +++ b/src/Linear/Linear.tsx @@ -163,6 +163,7 @@ export default class Linear extends React.Component { const firstBase = i * bpsPerBlock; seqBlocks.push( { * wrapping it in a textSpan with that color as a fill */ seqTextSpan = (bp: string, i: number) => { + console.log(this.props); const { bpColors, charWidth, firstBase, id } = this.props; let color: string | undefined; diff --git a/src/SeqViewerContainer.tsx b/src/SeqViewerContainer.tsx index 64c89033e..b520e45c3 100644 --- a/src/SeqViewerContainer.tsx +++ b/src/SeqViewerContainer.tsx @@ -41,6 +41,10 @@ interface SeqViewerContainerProps { highlights: Highlight[]; name: string; onSelection: (selection: Selection) => void; + onHover: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + onClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + onDoubleClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + onKeyPress: (event: KeyboardEvent, selection: Selection) => void; refs?: SeqVizChildRefs; rotateOnScroll: boolean; search: NameRange[]; diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index 5b97b8fb8..967af48ea 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -92,6 +92,18 @@ export interface SeqVizProps { /** a callback that's executed on each click of the sequence viewer. Selection includes meta about the selected element */ onSelection?: (selection: Selection) => void; + /** a callback that's executed on hover on emements in the viewer. */ + onHover?: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + + /** a callback that's executed on click on elements in the viewer. */ + onClick?: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + + /** a callback that's executed on double click on elements on the viewer. */ + onDoubleClick?: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + + /** a callback that's executed on each click of the sequence viewer. */ + onKeyPress?: (event: KeyboardEvent, selection: Selection) => void; + /** Refs associated with custom children. */ refs?: SeqVizChildRefs; @@ -186,6 +198,10 @@ export default class SeqViz extends React.Component { name: "", onSearch: (_: Range[]) => null, onSelection: (_: Selection) => null, + onHover: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, + onClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, + onDoubleClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, + onKeyPress: (event: KeyboardEvent, selection: Selection) => null, rotateOnScroll: true, search: { mismatch: 0, query: "" }, selectAllEvent: e => e.key === "a" && (e.metaKey || e.ctrlKey), @@ -401,6 +417,26 @@ export default class SeqViz extends React.Component { start: a.start % (seq.length + 1), })); + handleHoverEvent = (element, hover, view, container) => { + const { onHover } = this.props; + onHover!(element, hover, view, container); + }; + + handleClickEvent = (element, circular, linear, event) => { + const { onClick } = this.props; + onClick!(element, circular, linear, event); + }; + + handleDoubleClickEvent = (element, circular, linear, event) => { + const { onDoubleClick } = this.props; + onDoubleClick!(element, circular, linear, event); + }; + + handleKeyPressEvent = (event, selection) => { + const { onKeyPress } = this.props; + onKeyPress!(event, selection); + }; + render() { const { highlightedRegions, highlights, showComplement, showIndex, style, zoom } = this.props; let { translations } = this.props; @@ -436,6 +472,26 @@ export default class SeqViz extends React.Component { (() => { // do nothing }), + onHover: + this.props.onHover || + (() => { + // do nothing + }), + onClick: + this.props.onClick || + (() => { + // do nothing + }), + onDoubleClick: + this.props.onDoubleClick || + (() => { + // do nothing + }), + onKeyPress: + this.props.onKeyPress || + (() => { + // do nothing + }), rotateOnScroll: !!this.props.rotateOnScroll, showComplement: (!!compSeq && (typeof showComplement !== "undefined" ? showComplement : true)) || false, showIndex: !!showIndex, From 3da0b659f1291107895f35d2e13df8f4a4917f19 Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Mon, 21 Aug 2023 18:08:31 +0300 Subject: [PATCH 02/12] use onKeyPress enent handler --- demo/lib/App.tsx | 1 + src/EventHandler.tsx | 5 ++++- src/SeqViewerContainer.tsx | 3 ++- src/SeqViz.tsx | 12 ++++++------ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx index b10b7dcfd..9b5014c30 100644 --- a/demo/lib/App.tsx +++ b/demo/lib/App.tsx @@ -219,6 +219,7 @@ export default class App extends React.Component { highlights={[{ start: 0, end: 10 }]} name={this.state.name} onSelection={selection => this.setState({ selection })} + // onKeyPress={(e, selection) => console.log(e, selection)} refs={{ circular: this.circularRef, linear: this.linearRef }} search={this.state.search} selection={this.state.selection} diff --git a/src/EventHandler.tsx b/src/EventHandler.tsx index aff47a38f..f898ff705 100644 --- a/src/EventHandler.tsx +++ b/src/EventHandler.tsx @@ -13,6 +13,7 @@ export interface EventsHandlerProps { selection: Selection; seq: string; setSelection: (selection: Selection) => void; + onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; } /** @@ -30,12 +31,14 @@ export class EventHandler extends React.PureComponent { * action handler for a keyboard keypresses. */ handleKeyPress = (e: React.KeyboardEvent) => { + const { onKeyPress, selection } = this.props; const keyType = this.keypressMap(e); - if (!keyType) { + if (!keyType && !onKeyPress) { return; // not recognized key } e.preventDefault(); this.handleSeqInteraction(keyType); + onKeyPress(e, selection); }; /** diff --git a/src/SeqViewerContainer.tsx b/src/SeqViewerContainer.tsx index b520e45c3..3b22c1750 100644 --- a/src/SeqViewerContainer.tsx +++ b/src/SeqViewerContainer.tsx @@ -44,7 +44,7 @@ interface SeqViewerContainerProps { onHover: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; onClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; onDoubleClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; - onKeyPress: (event: KeyboardEvent, selection: Selection) => void; + onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; refs?: SeqVizChildRefs; rotateOnScroll: boolean; search: NameRange[]; @@ -284,6 +284,7 @@ class SeqViewerContainer extends React.Component void; - /** a callback that's executed on each click of the sequence viewer. */ - onKeyPress?: (event: KeyboardEvent, selection: Selection) => void; + /** a callback that's executed on press keyboard buttons on the viewer. */ + onKeyPress?: (event: React.KeyboardEvent, selection: Selection) => void; /** Refs associated with custom children. */ refs?: SeqVizChildRefs; @@ -198,10 +198,10 @@ export default class SeqViz extends React.Component { name: "", onSearch: (_: Range[]) => null, onSelection: (_: Selection) => null, - onHover: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, - onClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, - onDoubleClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, - onKeyPress: (event: KeyboardEvent, selection: Selection) => null, + onHover: (element: any, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, + onClick: (element: any, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, + onDoubleClick: (element: any, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, + onKeyPress: (event: React.KeyboardEvent, selection: Selection) => {}, rotateOnScroll: true, search: { mismatch: 0, query: "" }, selectAllEvent: e => e.key === "a" && (e.metaKey || e.ctrlKey), From 7f33dadd956044d7e91a7fa48cbfe04ad3a0da22 Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Thu, 31 Aug 2023 14:34:51 +0300 Subject: [PATCH 03/12] adding onHover to bases with example in the demo --- demo/lib/App.tsx | 19 ++++++++++++++++++- src/Linear/SeqBlock.tsx | 29 ++++++++++++++++++++++++++--- src/SeqViewerContainer.tsx | 6 +++--- src/SeqViz.tsx | 12 ++++++------ src/seqvizElementsTypes.ts | 10 ++++++++++ src/viewerTypes.ts | 4 ++++ 6 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 src/seqvizElementsTypes.ts create mode 100644 src/viewerTypes.ts diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx index 9b5014c30..b22487ce0 100644 --- a/demo/lib/App.tsx +++ b/demo/lib/App.tsx @@ -13,11 +13,13 @@ import { Sidebar, } from "semantic-ui-react"; import seqparse from "seqparse"; +import { TextSpan } from "typescript"; import Circular from "../../src/Circular/Circular"; import Linear from "../../src/Linear/Linear"; import SeqViz from "../../src/SeqViz"; import { AnnotationProp } from "../../src/elements"; +import { SEQVIZ_ELEMENTS_TYPES } from "../../src/seqvizElementsTypes"; import Header from "./Header"; import file from "./file"; @@ -33,6 +35,7 @@ interface AppState { customChildren: boolean; enzymes: any[]; name: string; + hoveredBase: number; search: { query: string }; searchResults: any; selection: any; @@ -51,6 +54,7 @@ export default class App extends React.Component { annotations: [], customChildren: true, enzymes: ["PstI", "EcoRI", "XbaI", "SpeI"], + hoveredBase: 0, name: "", search: { query: "ttnnnaat" }, searchResults: {}, @@ -73,7 +77,7 @@ export default class App extends React.Component { componentDidMount = async () => { const seq = await seqparse(file); - this.setState({ annotations: seq.annotations, name: seq.name, seq: seq.seq }); + this.setState({ annotations: seq.annotations, name: seq.name, seq: seq.seq, hoveredBase: 0 }); }; toggleSidebar = () => { @@ -218,6 +222,16 @@ export default class App extends React.Component { enzymes={this.state.enzymes} highlights={[{ start: 0, end: 10 }]} name={this.state.name} + onHover={(element, hover, view, container) => { + // console.log({ element, hover, view, container }); + if (element.type === SEQVIZ_ELEMENTS_TYPES.base) { + if (hover) { + this.setState({ hoveredBase: element.start + element.index + 1 }); + } else { + this.setState({ hoveredBase: 0 }); + } + } + }} onSelection={selection => this.setState({ selection })} // onKeyPress={(e, selection) => console.log(e, selection)} refs={{ circular: this.circularRef, linear: this.linearRef }} @@ -234,6 +248,9 @@ export default class App extends React.Component { )} +
+
Over base {this.state.hoveredBase > 0 ? this.state.hoveredBase : "-"}
+
diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx index da2604f2f..74dd582f7 100644 --- a/src/Linear/SeqBlock.tsx +++ b/src/Linear/SeqBlock.tsx @@ -2,7 +2,9 @@ import * as React from "react"; import { InputRefFunc } from "../SelectionHandler"; import { Annotation, CutSite, Highlight, NameRange, Range, SeqType, Size, Translation } from "../elements"; +import { SEQVIZ_ELEMENTS_TYPES } from "../seqvizElementsTypes"; import { seqBlock, svgText } from "../style"; +import { VIEWER_TYPES } from "../viewerTypes"; import AnnotationRows from "./Annotations"; import { CutSites } from "./CutSites"; import Find from "./Find"; @@ -43,6 +45,9 @@ interface SeqBlockProps { key: string; lineHeight: number; onUnmount: (a: string) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: HTMLElement) => null; + onClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null; searchRows: Range[]; seq: string; seqFontSize: number; @@ -191,8 +196,7 @@ export class SeqBlock extends React.PureComponent { * wrapping it in a textSpan with that color as a fill */ seqTextSpan = (bp: string, i: number) => { - console.log(this.props); - const { bpColors, charWidth, firstBase, id } = this.props; + const { bpColors, bpsPerBlock, charWidth, firstBase, id, onHover, onClick, onDoubleClick } = this.props; let color: string | undefined; if (bpColors) { @@ -204,10 +208,29 @@ export class SeqBlock extends React.PureComponent { undefined; } + const key = i + bp + id; + + const element = { + key: key, + start: firstBase, + end: firstBase + bpsPerBlock, + index: i, + type: SEQVIZ_ELEMENTS_TYPES.base, + viewer: VIEWER_TYPES.linear, + }; + return ( // the +0.2 here and above is to offset the characters they're not right on the left edge. When they are, // other elements look like they're shifted too far to the right. - + onHover(element, true, "LINEAR", e.target as HTMLElement)} + onMouseLeave={e => onHover(element, false, "LINEAR", e.target as HTMLElement)} + onClick={e => onClick(element, false, true, e.target as HTMLElement)} + onDoubleClick={e => onDoubleClick(element, false, true, e.target as HTMLElement)} + > {bp} ); diff --git a/src/SeqViewerContainer.tsx b/src/SeqViewerContainer.tsx index 3b22c1750..8020185da 100644 --- a/src/SeqViewerContainer.tsx +++ b/src/SeqViewerContainer.tsx @@ -41,9 +41,9 @@ interface SeqViewerContainerProps { highlights: Highlight[]; name: string; onSelection: (selection: Selection) => void; - onHover: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; - onClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; - onDoubleClick: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + onHover: (element: Selection, hover: boolean, view: "LINEAR" | "CIRCULAR", container: HTMLElement) => void; + onClick: (element: Selection, circular: boolean, linear: boolean, container: HTMLElement) => void; + onDoubleClick: (element: Selection, circular: boolean, linear: boolean, container: HTMLElement) => void; onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; refs?: SeqVizChildRefs; rotateOnScroll: boolean; diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index a94ee1e05..3e0fba7e4 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -93,13 +93,13 @@ export interface SeqVizProps { onSelection?: (selection: Selection) => void; /** a callback that's executed on hover on emements in the viewer. */ - onHover?: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; /** a callback that's executed on click on elements in the viewer. */ - onClick?: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; /** a callback that's executed on double click on elements on the viewer. */ - onDoubleClick?: (element: Selection, hover: boolean, view: "linear" | "circular", container: HTMLElement) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; /** a callback that's executed on press keyboard buttons on the viewer. */ onKeyPress?: (event: React.KeyboardEvent, selection: Selection) => void; @@ -198,9 +198,9 @@ export default class SeqViz extends React.Component { name: "", onSearch: (_: Range[]) => null, onSelection: (_: Selection) => null, - onHover: (element: any, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, - onClick: (element: any, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, - onDoubleClick: (element: any, hover: boolean, view: "linear" | "circular", container: HTMLElement) => null, + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: HTMLElement) => null, + onClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null, + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null, onKeyPress: (event: React.KeyboardEvent, selection: Selection) => {}, rotateOnScroll: true, search: { mismatch: 0, query: "" }, diff --git a/src/seqvizElementsTypes.ts b/src/seqvizElementsTypes.ts new file mode 100644 index 000000000..b7f55035d --- /dev/null +++ b/src/seqvizElementsTypes.ts @@ -0,0 +1,10 @@ +export const SEQVIZ_ELEMENTS_TYPES = { + seq: "SEQ", + annotation: "ANNOTATION", + find: "FIND", + enzyme: "ENZYME", + highlight: "HIGHLIGHT", + base: "BASE", + translation: "TRANSLATION", + aminoacid: "AMINOACID", +}; diff --git a/src/viewerTypes.ts b/src/viewerTypes.ts new file mode 100644 index 000000000..5beac2b92 --- /dev/null +++ b/src/viewerTypes.ts @@ -0,0 +1,4 @@ +export const VIEWER_TYPES = { + linear: "LINEAR", + circular: "CIRCULAR", +}; From a9c5a783b01f03b0f2ddc9eb888a22cde65dc7e3 Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Fri, 1 Sep 2023 00:27:32 +0300 Subject: [PATCH 04/12] adding onClick, onDoubleClick and onHover to translations and annotations --- demo/lib/App.tsx | 34 ++++++++++++++++++++++++++ demo/package-lock.json | 17 +++++++++++++ demo/package.json | 1 + src/Linear/Annotations.tsx | 35 +++++++++++++++++++++++++-- src/Linear/Linear.tsx | 9 +++++++ src/Linear/SeqBlock.tsx | 15 +++++++++--- src/Linear/Translations.tsx | 48 ++++++++++++++++++++++++++++++------- src/SelectionHandler.tsx | 2 +- src/SeqViewerContainer.tsx | 6 ++--- src/SeqViz.tsx | 6 ++--- 10 files changed, 153 insertions(+), 20 deletions(-) diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx index b22487ce0..2636454f2 100644 --- a/demo/lib/App.tsx +++ b/demo/lib/App.tsx @@ -13,6 +13,8 @@ import { Sidebar, } from "semantic-ui-react"; import seqparse from "seqparse"; +import tippy from "tippy.js"; +import "tippy.js/dist/tippy.css"; import { TextSpan } from "typescript"; import Circular from "../../src/Circular/Circular"; @@ -222,6 +224,12 @@ export default class App extends React.Component { enzymes={this.state.enzymes} highlights={[{ start: 0, end: 10 }]} name={this.state.name} + onClick={(element, circular, linear, container) => { + // console.log({ element, circular, linear, container }); + }} + onDoubleClick={(element, circular, linear, container) => { + // console.log({ element, circular, linear, container }); + }} onHover={(element, hover, view, container) => { // console.log({ element, hover, view, container }); if (element.type === SEQVIZ_ELEMENTS_TYPES.base) { @@ -230,6 +238,32 @@ export default class App extends React.Component { } else { this.setState({ hoveredBase: 0 }); } + } else if (element.type == SEQVIZ_ELEMENTS_TYPES.annotation) { + if (hover) { + if ((container as any)._tippy) { + (container as any)._tippy?.show(); + } else { + // you can pass some details with annotations to display in tippy + tippy(container, { + content: ` +
+
+ Name: ${element.name} +
+
+ position: [${element.start}-${element.end}] +
+
+ Length: ${element.end - element.start + 1} +
+
+ `, + allowHTML: true, + }).show(); + } + } else { + (container as any)._tippy?.hide(); + } } }} onSelection={selection => this.setState({ selection })} diff --git a/demo/package-lock.json b/demo/package-lock.json index 75e57db7c..d78ffd019 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -17,6 +17,7 @@ "react-dom": "^18.2.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.1.3", + "tippy.js": "^6.3.7", "typescript": "^4.8.2" }, "devDependencies": { @@ -1642,6 +1643,14 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -2892,6 +2901,14 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "requires": { + "@popperjs/core": "^2.9.0" + } + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", diff --git a/demo/package.json b/demo/package.json index f199067b9..4249a54e4 100644 --- a/demo/package.json +++ b/demo/package.json @@ -21,6 +21,7 @@ "react-dom": "^18.2.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.1.3", + "tippy.js": "^6.3.7", "typescript": "^4.8.2" }, "browserslist": { diff --git a/src/Linear/Annotations.tsx b/src/Linear/Annotations.tsx index 581e7e277..0e6159a00 100644 --- a/src/Linear/Annotations.tsx +++ b/src/Linear/Annotations.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { InputRefFunc } from "../SelectionHandler"; +import { InputRefFunc, RefSelection } from "../SelectionHandler"; import { COLOR_BORDER_MAP, darkerColor } from "../colors"; import { NameRange } from "../elements"; import { annotation, annotationLabel } from "../style"; @@ -27,6 +27,9 @@ const AnnotationRows = (props: { fullSeq: string; inputRef: InputRefFunc; lastBase: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; width: number; yDiff: number; @@ -43,6 +46,9 @@ const AnnotationRows = (props: { height={props.elementHeight} inputRef={props.inputRef} lastBase={props.lastBase} + onClick={props.onClick} + onDoubleClick={props.onDoubleClick} + onHover={props.onHover} seqBlockRef={props.seqBlockRef} width={props.width} y={props.yDiff + props.elementHeight * i} @@ -66,6 +72,9 @@ const AnnotationRow = (props: { height: number; inputRef: InputRefFunc; lastBase: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; width: number; y: number; @@ -102,8 +111,12 @@ const SingleNamedElement = (props: { index: number; inputRef: InputRefFunc; lastBase: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; }) => { - const { element, elements, findXAndWidth, firstBase, index, inputRef, lastBase } = props; + const { element, elements, findXAndWidth, firstBase, index, inputRef, lastBase, onClick, onDoubleClick, onHover } = + props; const { color, direction, end, name, start } = element; const forward = direction === 1; @@ -186,6 +199,12 @@ const SingleNamedElement = (props: { const nameLength = name.length * 6.75; // aspect ratio of roboto mono is ~0.66 const nameFits = nameLength < width - 15; + const annotationElement = { + ...element, + type: "ANNOTATION", + viewer: "LINEAR", + }; + return ( { // do nothing }} + onClick={e => { + onClick(annotationElement, false, true, e.target as SVGGElement); + }} + onDoubleClick={e => { + onDoubleClick(annotationElement, false, true, e.target as SVGGElement); + }} onFocus={() => { // do nothing }} + onMouseEnter={e => { + onHover(annotationElement, true, "LINEAR", e.target as SVGGElement); + }} + onMouseLeave={e => { + onHover(annotationElement, false, "LINEAR", e.target as SVGGElement); + }} onMouseOut={() => hoverOtherAnnotationRows(element.id, 0.7)} onMouseOver={() => hoverOtherAnnotationRows(element.id, 1.0)} /> diff --git a/src/Linear/Linear.tsx b/src/Linear/Linear.tsx index 39478cf6a..5af201aed 100644 --- a/src/Linear/Linear.tsx +++ b/src/Linear/Linear.tsx @@ -20,6 +20,9 @@ export interface LinearProps { highlights: Highlight[]; inputRef: InputRefFunc; lineHeight: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (id: string) => void; search: NameRange[]; seq: string; @@ -67,6 +70,9 @@ export default class Linear extends React.Component { elementHeight, highlights, lineHeight, + onClick, + onDoubleClick, + onHover, onUnmount, search, seq, @@ -191,6 +197,9 @@ export default class Linear extends React.Component { y={yDiff} zoom={zoom} zoomed={zoomed} + onClick={onClick} + onDoubleClick={onDoubleClick} + onHover={onHover} onUnmount={onUnmount} /> ); diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx index 74dd582f7..822d5a364 100644 --- a/src/Linear/SeqBlock.tsx +++ b/src/Linear/SeqBlock.tsx @@ -44,10 +44,10 @@ interface SeqBlockProps { inputRef: InputRefFunc; key: string; lineHeight: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: string) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: HTMLElement) => null; - onClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null; searchRows: Range[]; seq: string; seqFontSize: number; @@ -252,6 +252,9 @@ export class SeqBlock extends React.PureComponent { id, inputRef, lineHeight, + onClick, + onDoubleClick, + onHover, onUnmount, searchRows, seq, @@ -399,6 +402,9 @@ export class SeqBlock extends React.PureComponent { seqType={seqType} translationRows={translationRows} yDiff={translationYDiff} + onClick={onClick} + onDoubleClick={onDoubleClick} + onHover={onHover} onUnmount={onUnmount} /> )} @@ -412,6 +418,9 @@ export class SeqBlock extends React.PureComponent { fullSeq={fullSeq} inputRef={inputRef} lastBase={lastBase} + onClick={onClick} + onDoubleClick={onDoubleClick} + onHover={onHover} seqBlockRef={this} width={size.width} yDiff={annYDiff} diff --git a/src/Linear/Translations.tsx b/src/Linear/Translations.tsx index 384db0675..6689b3bcd 100644 --- a/src/Linear/Translations.tsx +++ b/src/Linear/Translations.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { InputRefFunc } from "../SelectionHandler"; +import { InputRefFunc, RefSelection } from "../SelectionHandler"; import { borderColorByIndex, colorByIndex } from "../colors"; import { SeqType, Translation } from "../elements"; import { randomID } from "../sequence"; @@ -16,6 +16,9 @@ interface TranslationRowsProps { fullSeq: string; inputRef: InputRefFunc; lastBase: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translationRows: Translation[][]; @@ -32,6 +35,9 @@ export const TranslationRows = ({ fullSeq, inputRef, lastBase, + onClick, + onDoubleClick, + onHover, onUnmount, seqType, translationRows, @@ -49,6 +55,9 @@ export const TranslationRows = ({ height={elementHeight * 0.9} inputRef={inputRef} lastBase={lastBase} + onClick={onClick} + onDoubleClick={onDoubleClick} + onHover={onHover} seqType={seqType} translations={translations} y={yDiff + elementHeight * i} @@ -71,6 +80,9 @@ const TranslationRow = (props: { height: number; inputRef: InputRefFunc; lastBase: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translations: Translation[]; @@ -96,6 +108,9 @@ interface SingleNamedElementProps { height: number; inputRef: InputRefFunc; lastBase: number; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translation: Translation; @@ -143,6 +158,9 @@ class SingleNamedElement extends React.PureComponent { height: h, inputRef, lastBase, + onClick, + onDoubleClick, + onHover, seqType, translation, y, @@ -220,18 +238,32 @@ class SingleNamedElement extends React.PureComponent { // arrow are facing const path = this.genPath(bpCount, direction === 1 ? 1 : -1); + const aaElement: RefSelection = { + end: AAEnd, + parent: { ...translation, type: "TRANSLATION" }, + start: AAStart, + type: "AMINOACID", + viewer: "LINEAR", + }; + return ( { + onClick(aaElement, false, true, e.target as SVGGElement); + }} + onDoubleClick={e => { + onDoubleClick(aaElement, false, true, e.target as SVGGElement); + }} + onMouseEnter={e => { + onHover(aaElement, true, "LINEAR", e.target as SVGGElement); + }} + onMouseLeave={e => { + onHover(aaElement, false, "LINEAR", e.target as SVGGElement); + }} > void; - onHover: (element: Selection, hover: boolean, view: "LINEAR" | "CIRCULAR", container: HTMLElement) => void; - onClick: (element: Selection, circular: boolean, linear: boolean, container: HTMLElement) => void; - onDoubleClick: (element: Selection, circular: boolean, linear: boolean, container: HTMLElement) => void; + onHover: (element: Selection, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onClick: (element: Selection, circular: boolean, linear: boolean, container: Element) => void; + onDoubleClick: (element: Selection, circular: boolean, linear: boolean, container: Element) => void; onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; refs?: SeqVizChildRefs; rotateOnScroll: boolean; diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index 3e0fba7e4..305365422 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -198,9 +198,9 @@ export default class SeqViz extends React.Component { name: "", onSearch: (_: Range[]) => null, onSelection: (_: Selection) => null, - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: HTMLElement) => null, - onClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null, - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: HTMLElement) => null, + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => null, + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => null, + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => null, onKeyPress: (event: React.KeyboardEvent, selection: Selection) => {}, rotateOnScroll: true, search: { mismatch: 0, query: "" }, From 35354e101e51da2075cb3ef25a885db5e656dabc Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Fri, 1 Sep 2023 22:28:05 +0300 Subject: [PATCH 05/12] adding onContextMenu event handler --- src/Linear/Annotations.tsx | 37 +++++++++++++++++++++++++++++++++++-- src/Linear/Linear.tsx | 8 ++++++++ src/Linear/SeqBlock.tsx | 13 ++++++++++++- src/Linear/Translations.tsx | 24 ++++++++++++++++++++++++ src/SeqViewerContainer.tsx | 12 +++++++++--- src/SeqViz.tsx | 25 ++++++++++++++++++++++--- 6 files changed, 110 insertions(+), 9 deletions(-) diff --git a/src/Linear/Annotations.tsx b/src/Linear/Annotations.tsx index 0e6159a00..c2b4cdb36 100644 --- a/src/Linear/Annotations.tsx +++ b/src/Linear/Annotations.tsx @@ -28,6 +28,12 @@ const AnnotationRows = (props: { inputRef: InputRefFunc; lastBase: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; @@ -47,6 +53,7 @@ const AnnotationRows = (props: { inputRef={props.inputRef} lastBase={props.lastBase} onClick={props.onClick} + onContextMenu={props.onContextMenu} onDoubleClick={props.onDoubleClick} onHover={props.onHover} seqBlockRef={props.seqBlockRef} @@ -73,6 +80,12 @@ const AnnotationRow = (props: { inputRef: InputRefFunc; lastBase: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; @@ -112,11 +125,28 @@ const SingleNamedElement = (props: { inputRef: InputRefFunc; lastBase: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; }) => { - const { element, elements, findXAndWidth, firstBase, index, inputRef, lastBase, onClick, onDoubleClick, onHover } = - props; + const { + element, + elements, + findXAndWidth, + firstBase, + index, + inputRef, + lastBase, + onClick, + onContextMenu, + onDoubleClick, + onHover, + } = props; const { color, direction, end, name, start } = element; const forward = direction === 1; @@ -229,6 +259,9 @@ const SingleNamedElement = (props: { onClick={e => { onClick(annotationElement, false, true, e.target as SVGGElement); }} + onContextMenu={e => { + onContextMenu(annotationElement, false, true, e); + }} onDoubleClick={e => { onDoubleClick(annotationElement, false, true, e.target as SVGGElement); }} diff --git a/src/Linear/Linear.tsx b/src/Linear/Linear.tsx index 5af201aed..2eb8b27fc 100644 --- a/src/Linear/Linear.tsx +++ b/src/Linear/Linear.tsx @@ -21,6 +21,12 @@ export interface LinearProps { inputRef: InputRefFunc; lineHeight: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (id: string) => void; @@ -71,6 +77,7 @@ export default class Linear extends React.Component { highlights, lineHeight, onClick, + onContextMenu, onDoubleClick, onHover, onUnmount, @@ -198,6 +205,7 @@ export default class Linear extends React.Component { zoom={zoom} zoomed={zoomed} onClick={onClick} + onContextMenu={onContextMenu} onDoubleClick={onDoubleClick} onHover={onHover} onUnmount={onUnmount} diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx index 822d5a364..9b2525769 100644 --- a/src/Linear/SeqBlock.tsx +++ b/src/Linear/SeqBlock.tsx @@ -45,6 +45,12 @@ interface SeqBlockProps { key: string; lineHeight: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: string) => void; @@ -196,7 +202,8 @@ export class SeqBlock extends React.PureComponent { * wrapping it in a textSpan with that color as a fill */ seqTextSpan = (bp: string, i: number) => { - const { bpColors, bpsPerBlock, charWidth, firstBase, id, onHover, onClick, onDoubleClick } = this.props; + const { bpColors, bpsPerBlock, charWidth, firstBase, id, onHover, onClick, onContextMenu, onDoubleClick } = + this.props; let color: string | undefined; if (bpColors) { @@ -229,6 +236,7 @@ export class SeqBlock extends React.PureComponent { onMouseEnter={e => onHover(element, true, "LINEAR", e.target as HTMLElement)} onMouseLeave={e => onHover(element, false, "LINEAR", e.target as HTMLElement)} onClick={e => onClick(element, false, true, e.target as HTMLElement)} + onContextMenu={e => onContextMenu(element, false, true, e)} onDoubleClick={e => onDoubleClick(element, false, true, e.target as HTMLElement)} > {bp} @@ -253,6 +261,7 @@ export class SeqBlock extends React.PureComponent { inputRef, lineHeight, onClick, + onContextMenu, onDoubleClick, onHover, onUnmount, @@ -403,6 +412,7 @@ export class SeqBlock extends React.PureComponent { translationRows={translationRows} yDiff={translationYDiff} onClick={onClick} + onContextMenu={onContextMenu} onDoubleClick={onDoubleClick} onHover={onHover} onUnmount={onUnmount} @@ -419,6 +429,7 @@ export class SeqBlock extends React.PureComponent { inputRef={inputRef} lastBase={lastBase} onClick={onClick} + onContextMenu={onContextMenu} onDoubleClick={onDoubleClick} onHover={onHover} seqBlockRef={this} diff --git a/src/Linear/Translations.tsx b/src/Linear/Translations.tsx index 6689b3bcd..af753ca20 100644 --- a/src/Linear/Translations.tsx +++ b/src/Linear/Translations.tsx @@ -17,6 +17,12 @@ interface TranslationRowsProps { inputRef: InputRefFunc; lastBase: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; @@ -36,6 +42,7 @@ export const TranslationRows = ({ inputRef, lastBase, onClick, + onContextMenu, onDoubleClick, onHover, onUnmount, @@ -56,6 +63,7 @@ export const TranslationRows = ({ inputRef={inputRef} lastBase={lastBase} onClick={onClick} + onContextMenu={onContextMenu} onDoubleClick={onDoubleClick} onHover={onHover} seqType={seqType} @@ -81,6 +89,12 @@ const TranslationRow = (props: { inputRef: InputRefFunc; lastBase: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; @@ -109,6 +123,12 @@ interface SingleNamedElementProps { inputRef: InputRefFunc; lastBase: number; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; @@ -159,6 +179,7 @@ class SingleNamedElement extends React.PureComponent { inputRef, lastBase, onClick, + onContextMenu, onDoubleClick, onHover, seqType, @@ -255,6 +276,9 @@ class SingleNamedElement extends React.PureComponent { onClick={e => { onClick(aaElement, false, true, e.target as SVGGElement); }} + onContextMenu={e => { + onContextMenu(aaElement, false, true, e); + }} onDoubleClick={e => { onDoubleClick(aaElement, false, true, e.target as SVGGElement); }} diff --git a/src/SeqViewerContainer.tsx b/src/SeqViewerContainer.tsx index 99583c322..78d7778aa 100644 --- a/src/SeqViewerContainer.tsx +++ b/src/SeqViewerContainer.tsx @@ -41,9 +41,15 @@ interface SeqViewerContainerProps { highlights: Highlight[]; name: string; onSelection: (selection: Selection) => void; - onHover: (element: Selection, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; - onClick: (element: Selection, circular: boolean, linear: boolean, container: Element) => void; - onDoubleClick: (element: Selection, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; refs?: SeqVizChildRefs; rotateOnScroll: boolean; diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index 305365422..170616833 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -98,6 +98,14 @@ export interface SeqVizProps { /** a callback that's executed on click on elements in the viewer. */ onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + /** a callback that's executed on right click on elements in the viewer. */ + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; + /** a callback that's executed on double click on elements on the viewer. */ onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; @@ -198,9 +206,15 @@ export default class SeqViz extends React.Component { name: "", onSearch: (_: Range[]) => null, onSelection: (_: Selection) => null, - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => null, - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => null, - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => null, + onClick: (element: any, circular: boolean, linear: boolean, container: Element) => {}, + onContextMenu: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => {}, + onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => {}, + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => {}, onKeyPress: (event: React.KeyboardEvent, selection: Selection) => {}, rotateOnScroll: true, search: { mismatch: 0, query: "" }, @@ -482,6 +496,11 @@ export default class SeqViz extends React.Component { (() => { // do nothing }), + onContextMenu: + this.props.onContextMenu || + (() => { + // do nothing + }), onDoubleClick: this.props.onDoubleClick || (() => { From 0bd6a193e03c3455a77da13aba56332f3208fced Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Fri, 1 Sep 2023 22:29:09 +0300 Subject: [PATCH 06/12] adding seqviz context menu as an example to onContextMenu event handler --- demo/lib/App.tsx | 85 ++++++++++++++++++++++++++++++++++++++++ demo/package-lock.json | 21 ++++++++++ demo/package.json | 1 + src/SelectionHandler.tsx | 5 +++ 4 files changed, 112 insertions(+) diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx index 2636454f2..99e317767 100644 --- a/demo/lib/App.tsx +++ b/demo/lib/App.tsx @@ -1,4 +1,6 @@ import * as React from "react"; +import { Menu as ContextMenu, Item, Separator, Submenu, useContextMenu } from "react-contexify"; +import "react-contexify/dist/ReactContexify.css"; import { Button, Checkbox, @@ -25,6 +27,8 @@ import { SEQVIZ_ELEMENTS_TYPES } from "../../src/seqvizElementsTypes"; import Header from "./Header"; import file from "./file"; +const CONTEXT_MENU_ID = "menu-id"; + const viewerTypeOptions = [ { key: "both", text: "Both", value: "both" }, { key: "circular", text: "Circular", value: "circular" }, @@ -77,11 +81,84 @@ export default class App extends React.Component { linearRef: React.RefObject = React.createRef(); circularRef: React.RefObject = React.createRef(); + showContextMenu: (params: any) => void = () => { + //do nothing + }; + componentDidMount = async () => { const seq = await seqparse(file); + + const { show } = useContextMenu({ + id: CONTEXT_MENU_ID, + }); + + this.showContextMenu = show; + this.setState({ annotations: seq.annotations, name: seq.name, seq: seq.seq, hoveredBase: 0 }); }; + handleContextMenuAddForwardTranslation = ({ event, props, triggerEvent, data }) => { + // console.log(event, props, triggerEvent, data); + console.log(this.state.selection); + if ( + !this.state.selection.start || + !this.state.selection.end || + !this.state.selection.length || + this.state.selection.length === 0 + ) { + this.state.translations.push({ + direction: 1, + end: this.state.seq.length, + start: 0, + }); + } else { + const { start, end, clockwise } = this.state.selection; + this.state.translations.push({ + direction: 1, + end: clockwise ? end : start, + start: clockwise ? start : end, + }); + } + this.setState({ + translations: this.state.translations, + }); + }; + + handleContextMenuAddReverseTranslation = ({ event, props, triggerEvent, data }) => { + // console.log(event, props, triggerEvent, data); + // console.log(this.state.selection); + if ( + !this.state.selection.start || + !this.state.selection.end || + !this.state.selection.length || + this.state.selection.length === 0 + ) { + this.state.translations.push({ + direction: 1, + end: this.state.seq.length, + start: 0, + }); + } else { + const { start, end, clockwise } = this.state.selection; + this.state.translations.push({ + direction: -1, + end: clockwise ? end : start, + start: clockwise ? start : end, + }); + } + this.setState({ + translations: this.state.translations, + }); + }; + + displayContextMenu(e) { + // put whatever custom logic you need + // you can even decide to not display the Menu + this.showContextMenu({ + event: e, + }); + } + toggleSidebar = () => { const { showSidebar } = this.state; this.setState({ showSidebar: !showSidebar }); @@ -227,6 +304,10 @@ export default class App extends React.Component { onClick={(element, circular, linear, container) => { // console.log({ element, circular, linear, container }); }} + onContextMenu={(element, circular, linear, event) => { + // console.log({ element, circular, linear, event }); + this.displayContextMenu(event); + }} onDoubleClick={(element, circular, linear, container) => { // console.log({ element, circular, linear, container }); }} @@ -288,6 +369,10 @@ export default class App extends React.Component { + + Add forward translations + Add reverse translations + ); } diff --git a/demo/package-lock.json b/demo/package-lock.json index d78ffd019..bab072dd9 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -14,6 +14,7 @@ "next": "^12.3.1", "next-with-less": "^2.0.5", "react": "^18.2.0", + "react-contexify": "^6.0.0", "react-dom": "^18.2.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.1.3", @@ -1347,6 +1348,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-contexify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz", + "integrity": "sha512-jMhz6yZI81Jv3UDj7TXqCkhdkCFEEmvwGCPXsQuA2ZUC8EbCuVQ6Cy8FzKMXa0y454XTDClBN2YFvvmoFlrFkg==", + "dependencies": { + "clsx": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -2699,6 +2712,14 @@ "loose-envify": "^1.1.0" } }, + "react-contexify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz", + "integrity": "sha512-jMhz6yZI81Jv3UDj7TXqCkhdkCFEEmvwGCPXsQuA2ZUC8EbCuVQ6Cy8FzKMXa0y454XTDClBN2YFvvmoFlrFkg==", + "requires": { + "clsx": "^1.2.1" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/demo/package.json b/demo/package.json index 4249a54e4..c818b0c2c 100644 --- a/demo/package.json +++ b/demo/package.json @@ -18,6 +18,7 @@ "next": "^12.3.1", "next-with-less": "^2.0.5", "react": "^18.2.0", + "react-contexify": "^6.0.0", "react-dom": "^18.2.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.1.3", diff --git a/src/SelectionHandler.tsx b/src/SelectionHandler.tsx index 6dbfd098f..ad297ca7b 100644 --- a/src/SelectionHandler.tsx +++ b/src/SelectionHandler.tsx @@ -204,6 +204,11 @@ export default class SelectionHandler extends React.PureComponent { + // don't reset selection on right click + if (e.button == 2) { + return; + } + const selection = this.context; const currBase = this.calculateBaseLinear(e, knownRange); From 8489dbc686bc26b152babcdf812c5fd64b443d6b Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Fri, 1 Sep 2023 23:58:10 +0300 Subject: [PATCH 07/12] refactoring --- demo/lib/App.tsx | 4 +++- src/SeqViz.tsx | 62 ++++++++++++++++-------------------------------- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx index 99e317767..07d40dbe2 100644 --- a/demo/lib/App.tsx +++ b/demo/lib/App.tsx @@ -347,8 +347,10 @@ export default class App extends React.Component { } } }} + onKeyPress={(e, selection) => { + // console.log(e, selection) + }} onSelection={selection => this.setState({ selection })} - // onKeyPress={(e, selection) => console.log(e, selection)} refs={{ circular: this.circularRef, linear: this.linearRef }} search={this.state.search} selection={this.state.selection} diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index 170616833..3ea832761 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -86,15 +86,6 @@ export interface SeqVizProps { /** the name of the sequence to show in the middle of the circular viewer */ name?: string; - /** a callback that's executed on each change to the search parameters or sequence */ - onSearch?: (search: Range[]) => void; - - /** a callback that's executed on each click of the sequence viewer. Selection includes meta about the selected element */ - onSelection?: (selection: Selection) => void; - - /** a callback that's executed on hover on emements in the viewer. */ - onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; - /** a callback that's executed on click on elements in the viewer. */ onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; @@ -109,9 +100,18 @@ export interface SeqVizProps { /** a callback that's executed on double click on elements on the viewer. */ onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + /** a callback that's executed on hover on emements in the viewer. */ + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + /** a callback that's executed on press keyboard buttons on the viewer. */ onKeyPress?: (event: React.KeyboardEvent, selection: Selection) => void; + /** a callback that's executed on each change to the search parameters or sequence */ + onSearch?: (search: Range[]) => void; + + /** a callback that's executed on each click of the sequence viewer. Selection includes meta about the selected element */ + onSelection?: (selection: Selection) => void; + /** Refs associated with custom children. */ refs?: SeqVizChildRefs; @@ -204,8 +204,6 @@ export default class SeqViz extends React.Component { enzymes: [], enzymesCustom: {}, name: "", - onSearch: (_: Range[]) => null, - onSelection: (_: Selection) => null, onClick: (element: any, circular: boolean, linear: boolean, container: Element) => {}, onContextMenu: ( element: any, @@ -216,6 +214,8 @@ export default class SeqViz extends React.Component { onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => {}, onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => {}, onKeyPress: (event: React.KeyboardEvent, selection: Selection) => {}, + onSearch: (_: Range[]) => null, + onSelection: (_: Selection) => null, rotateOnScroll: true, search: { mismatch: 0, query: "" }, selectAllEvent: e => e.key === "a" && (e.metaKey || e.ctrlKey), @@ -431,26 +431,6 @@ export default class SeqViz extends React.Component { start: a.start % (seq.length + 1), })); - handleHoverEvent = (element, hover, view, container) => { - const { onHover } = this.props; - onHover!(element, hover, view, container); - }; - - handleClickEvent = (element, circular, linear, event) => { - const { onClick } = this.props; - onClick!(element, circular, linear, event); - }; - - handleDoubleClickEvent = (element, circular, linear, event) => { - const { onDoubleClick } = this.props; - onDoubleClick!(element, circular, linear, event); - }; - - handleKeyPressEvent = (event, selection) => { - const { onKeyPress } = this.props; - onKeyPress!(event, selection); - }; - render() { const { highlightedRegions, highlights, showComplement, showIndex, style, zoom } = this.props; let { translations } = this.props; @@ -481,16 +461,6 @@ export default class SeqViz extends React.Component { start: h.start % (seq.length + 1), }) ), - onSelection: - this.props.onSelection || - (() => { - // do nothing - }), - onHover: - this.props.onHover || - (() => { - // do nothing - }), onClick: this.props.onClick || (() => { @@ -506,11 +476,21 @@ export default class SeqViz extends React.Component { (() => { // do nothing }), + onHover: + this.props.onHover || + (() => { + // do nothing + }), onKeyPress: this.props.onKeyPress || (() => { // do nothing }), + onSelection: + this.props.onSelection || + (() => { + // do nothing + }), rotateOnScroll: !!this.props.rotateOnScroll, showComplement: (!!compSeq && (typeof showComplement !== "undefined" ? showComplement : true)) || false, showIndex: !!showIndex, From 09874beb73031125c98cb228cb3668639399e6af Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Sat, 2 Sep 2023 10:34:40 +0300 Subject: [PATCH 08/12] fixing webpack compiling errors --- src/Linear/Annotations.tsx | 2 +- src/SeqViz.tsx | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Linear/Annotations.tsx b/src/Linear/Annotations.tsx index c2b4cdb36..618376a2a 100644 --- a/src/Linear/Annotations.tsx +++ b/src/Linear/Annotations.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { InputRefFunc, RefSelection } from "../SelectionHandler"; +import { InputRefFunc } from "../SelectionHandler"; import { COLOR_BORDER_MAP, darkerColor } from "../colors"; import { NameRange } from "../elements"; import { annotation, annotationLabel } from "../style"; diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index 3ea832761..8c0261c20 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -204,16 +204,11 @@ export default class SeqViz extends React.Component { enzymes: [], enzymesCustom: {}, name: "", - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => {}, - onContextMenu: ( - element: any, - circular: boolean, - linear: boolean, - event: React.MouseEvent - ) => {}, - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => {}, - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => {}, - onKeyPress: (event: React.KeyboardEvent, selection: Selection) => {}, + onClick: () => {}, + onContextMenu: () => {}, + onDoubleClick: () => {}, + onHover: () => {}, + onKeyPress: () => {}, onSearch: (_: Range[]) => null, onSelection: (_: Selection) => null, rotateOnScroll: true, From b8b7e8be189a498fe2e188767c0bb36ff8e1d7f3 Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Sat, 2 Sep 2023 10:49:03 +0300 Subject: [PATCH 09/12] fixing lint process --- src/EventHandler.tsx | 2 +- src/Linear/Annotations.tsx | 6 +++--- src/Linear/SeqBlock.tsx | 14 +++++++------- src/Linear/Translations.tsx | 6 +++--- src/SeqViewerContainer.tsx | 6 +++--- src/seqvizElementsTypes.ts | 8 ++++---- src/viewerTypes.ts | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/EventHandler.tsx b/src/EventHandler.tsx index f898ff705..a535ef036 100644 --- a/src/EventHandler.tsx +++ b/src/EventHandler.tsx @@ -9,11 +9,11 @@ export interface EventsHandlerProps { children: React.ReactNode; copyEvent: (e: React.KeyboardEvent) => boolean; handleMouseEvent: (e: any) => void; + onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; selectAllEvent: (e: React.KeyboardEvent) => boolean; selection: Selection; seq: string; setSelection: (selection: Selection) => void; - onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; } /** diff --git a/src/Linear/Annotations.tsx b/src/Linear/Annotations.tsx index 618376a2a..373f8949f 100644 --- a/src/Linear/Annotations.tsx +++ b/src/Linear/Annotations.tsx @@ -52,13 +52,13 @@ const AnnotationRows = (props: { height={props.elementHeight} inputRef={props.inputRef} lastBase={props.lastBase} + seqBlockRef={props.seqBlockRef} + width={props.width} + y={props.yDiff + props.elementHeight * i} onClick={props.onClick} onContextMenu={props.onContextMenu} onDoubleClick={props.onDoubleClick} onHover={props.onHover} - seqBlockRef={props.seqBlockRef} - width={props.width} - y={props.yDiff + props.elementHeight * i} /> ))} diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx index 9b2525769..ba89a876e 100644 --- a/src/Linear/SeqBlock.tsx +++ b/src/Linear/SeqBlock.tsx @@ -202,7 +202,7 @@ export class SeqBlock extends React.PureComponent { * wrapping it in a textSpan with that color as a fill */ seqTextSpan = (bp: string, i: number) => { - const { bpColors, bpsPerBlock, charWidth, firstBase, id, onHover, onClick, onContextMenu, onDoubleClick } = + const { bpColors, bpsPerBlock, charWidth, firstBase, id, onClick, onContextMenu, onDoubleClick, onHover } = this.props; let color: string | undefined; @@ -219,9 +219,9 @@ export class SeqBlock extends React.PureComponent { const element = { key: key, - start: firstBase, end: firstBase + bpsPerBlock, index: i, + start: firstBase, type: SEQVIZ_ELEMENTS_TYPES.base, viewer: VIEWER_TYPES.linear, }; @@ -233,11 +233,11 @@ export class SeqBlock extends React.PureComponent { key={key} fill={color || undefined} x={charWidth * i + charWidth * 0.2} - onMouseEnter={e => onHover(element, true, "LINEAR", e.target as HTMLElement)} - onMouseLeave={e => onHover(element, false, "LINEAR", e.target as HTMLElement)} onClick={e => onClick(element, false, true, e.target as HTMLElement)} onContextMenu={e => onContextMenu(element, false, true, e)} onDoubleClick={e => onDoubleClick(element, false, true, e.target as HTMLElement)} + onMouseEnter={e => onHover(element, true, "LINEAR", e.target as HTMLElement)} + onMouseLeave={e => onHover(element, false, "LINEAR", e.target as HTMLElement)} > {bp}
@@ -428,13 +428,13 @@ export class SeqBlock extends React.PureComponent { fullSeq={fullSeq} inputRef={inputRef} lastBase={lastBase} + seqBlockRef={this} + width={size.width} + yDiff={annYDiff} onClick={onClick} onContextMenu={onContextMenu} onDoubleClick={onDoubleClick} onHover={onHover} - seqBlockRef={this} - width={size.width} - yDiff={annYDiff} /> )} {zoomed && seqType !== "aa" ? ( diff --git a/src/Linear/Translations.tsx b/src/Linear/Translations.tsx index af753ca20..7e0f3a7df 100644 --- a/src/Linear/Translations.tsx +++ b/src/Linear/Translations.tsx @@ -62,13 +62,13 @@ export const TranslationRows = ({ height={elementHeight * 0.9} inputRef={inputRef} lastBase={lastBase} + seqType={seqType} + translations={translations} + y={yDiff + elementHeight * i} onClick={onClick} onContextMenu={onContextMenu} onDoubleClick={onDoubleClick} onHover={onHover} - seqType={seqType} - translations={translations} - y={yDiff + elementHeight * i} onUnmount={onUnmount} /> ))} diff --git a/src/SeqViewerContainer.tsx b/src/SeqViewerContainer.tsx index 78d7778aa..ec1ce66d7 100644 --- a/src/SeqViewerContainer.tsx +++ b/src/SeqViewerContainer.tsx @@ -40,8 +40,6 @@ interface SeqViewerContainerProps { height: number; highlights: Highlight[]; name: string; - onSelection: (selection: Selection) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; onContextMenu: ( element: any, @@ -50,7 +48,9 @@ interface SeqViewerContainerProps { event: React.MouseEvent ) => void; onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; + onSelection: (selection: Selection) => void; refs?: SeqVizChildRefs; rotateOnScroll: boolean; search: NameRange[]; @@ -290,11 +290,11 @@ class SeqViewerContainer extends React.Component {this.props.children ? ( this.props.children({ diff --git a/src/seqvizElementsTypes.ts b/src/seqvizElementsTypes.ts index b7f55035d..36e95ff44 100644 --- a/src/seqvizElementsTypes.ts +++ b/src/seqvizElementsTypes.ts @@ -1,10 +1,10 @@ export const SEQVIZ_ELEMENTS_TYPES = { - seq: "SEQ", + aminoacid: "AMINOACID", annotation: "ANNOTATION", - find: "FIND", + base: "BASE", enzyme: "ENZYME", + find: "FIND", highlight: "HIGHLIGHT", - base: "BASE", + seq: "SEQ", translation: "TRANSLATION", - aminoacid: "AMINOACID", }; diff --git a/src/viewerTypes.ts b/src/viewerTypes.ts index 5beac2b92..3d6540ae0 100644 --- a/src/viewerTypes.ts +++ b/src/viewerTypes.ts @@ -1,4 +1,4 @@ export const VIEWER_TYPES = { - linear: "LINEAR", circular: "CIRCULAR", + linear: "LINEAR", }; From f9039dc57fbdd6c45a8a3009a3ef07ee76088f4e Mon Sep 17 00:00:00 2001 From: "Eng. Elias" Date: Sat, 2 Sep 2023 10:53:16 +0300 Subject: [PATCH 10/12] minor fix --- src/Linear/SeqBlock.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx index ba89a876e..10b5b3fbb 100644 --- a/src/Linear/SeqBlock.tsx +++ b/src/Linear/SeqBlock.tsx @@ -218,9 +218,9 @@ export class SeqBlock extends React.PureComponent { const key = i + bp + id; const element = { - key: key, end: firstBase + bpsPerBlock, index: i, + key: key, start: firstBase, type: SEQVIZ_ELEMENTS_TYPES.base, viewer: VIEWER_TYPES.linear, From cf6ad09e968102968011f02d9f8377bbca8e6753 Mon Sep 17 00:00:00 2001 From: Eng-Elias Date: Wed, 6 Aug 2025 18:08:09 +0400 Subject: [PATCH 11/12] lint fix --- demo/lib/App.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx index 612760ad3..330ca8082 100644 --- a/demo/lib/App.tsx +++ b/demo/lib/App.tsx @@ -148,6 +148,7 @@ export default class App extends React.Component { direction: 1, end: this.state.seq.length, start: 0, + name: "", }); } else { const { start, end, clockwise } = this.state.selection; @@ -155,6 +156,7 @@ export default class App extends React.Component { direction: 1, end: clockwise ? end : start, start: clockwise ? start : end, + name: "", }); } this.setState({ @@ -175,6 +177,7 @@ export default class App extends React.Component { direction: 1, end: this.state.seq.length, start: 0, + name: "", }); } else { const { start, end, clockwise } = this.state.selection; @@ -182,6 +185,7 @@ export default class App extends React.Component { direction: -1, end: clockwise ? end : start, start: clockwise ? start : end, + name: "", }); } this.setState({ From ad769e109a411923d79798730edf4a40cf1b3998 Mon Sep 17 00:00:00 2001 From: Eng-Elias Date: Wed, 6 Aug 2025 18:31:12 +0400 Subject: [PATCH 12/12] make the new event handler as optional props to fix the tests --- src/EventHandler.tsx | 4 ++-- src/Linear/Annotations.tsx | 34 +++++++++++++++++----------------- src/Linear/Linear.tsx | 8 ++++---- src/Linear/SeqBlock.tsx | 18 +++++++++--------- src/Linear/Translations.tsx | 34 +++++++++++++++++----------------- src/SeqViewerContainer.tsx | 10 +++++----- 6 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/EventHandler.tsx b/src/EventHandler.tsx index a535ef036..537328fcc 100644 --- a/src/EventHandler.tsx +++ b/src/EventHandler.tsx @@ -9,7 +9,7 @@ export interface EventsHandlerProps { children: React.ReactNode; copyEvent: (e: React.KeyboardEvent) => boolean; handleMouseEvent: (e: any) => void; - onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; + onKeyPress?: (event: React.KeyboardEvent, selection: Selection) => void; selectAllEvent: (e: React.KeyboardEvent) => boolean; selection: Selection; seq: string; @@ -38,7 +38,7 @@ export class EventHandler extends React.PureComponent { } e.preventDefault(); this.handleSeqInteraction(keyType); - onKeyPress(e, selection); + onKeyPress?.(e, selection); }; /** diff --git a/src/Linear/Annotations.tsx b/src/Linear/Annotations.tsx index af98d4f87..dda228ca5 100644 --- a/src/Linear/Annotations.tsx +++ b/src/Linear/Annotations.tsx @@ -27,15 +27,15 @@ const AnnotationRows = (props: { fullSeq: string; inputRef: InputRefFunc; lastBase: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; width: number; yDiff: number; @@ -79,15 +79,15 @@ const AnnotationRow = (props: { height: number; inputRef: InputRefFunc; lastBase: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; width: number; y: number; @@ -123,15 +123,15 @@ const SingleNamedElement = (props: { index: number; inputRef: InputRefFunc; lastBase: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; }) => { const { element, @@ -271,22 +271,22 @@ const SingleNamedElement = (props: { // do nothing }} onClick={e => { - onClick(annotationElement, false, true, e.target as SVGGElement); + onClick?.(annotationElement, false, true, e.target as SVGGElement); }} onContextMenu={e => { - onContextMenu(annotationElement, false, true, e); + onContextMenu?.(annotationElement, false, true, e); }} onDoubleClick={e => { - onDoubleClick(annotationElement, false, true, e.target as SVGGElement); + onDoubleClick?.(annotationElement, false, true, e.target as SVGGElement); }} onFocus={() => { // do nothing }} onMouseEnter={e => { - onHover(annotationElement, true, "LINEAR", e.target as SVGGElement); + onHover?.(annotationElement, true, "LINEAR", e.target as SVGGElement); }} onMouseLeave={e => { - onHover(annotationElement, false, "LINEAR", e.target as SVGGElement); + onHover?.(annotationElement, false, "LINEAR", e.target as SVGGElement); }} onMouseOut={() => hoverOtherAnnotationRows(element.id, 0.7)} onMouseOver={() => hoverOtherAnnotationRows(element.id, 1.0)} diff --git a/src/Linear/Linear.tsx b/src/Linear/Linear.tsx index e132b9ce1..ca919d5a1 100644 --- a/src/Linear/Linear.tsx +++ b/src/Linear/Linear.tsx @@ -20,15 +20,15 @@ export interface LinearProps { highlights: Highlight[]; inputRef: InputRefFunc; lineHeight: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (id: string) => void; primers: Primer[]; search: NameRange[]; diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx index 2e24ca80c..ea8a36095 100644 --- a/src/Linear/SeqBlock.tsx +++ b/src/Linear/SeqBlock.tsx @@ -45,15 +45,15 @@ interface SeqBlockProps { inputRef: InputRefFunc; key: string; lineHeight: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: string) => void; primerFwdRows: Primer[][]; primerRevRows: Primer[][]; @@ -236,11 +236,11 @@ export class SeqBlock extends React.PureComponent { key={key} fill={color || undefined} x={charWidth * i + charWidth * 0.2} - onClick={e => onClick(element, false, true, e.target as HTMLElement)} - onContextMenu={e => onContextMenu(element, false, true, e)} - onDoubleClick={e => onDoubleClick(element, false, true, e.target as HTMLElement)} - onMouseEnter={e => onHover(element, true, "LINEAR", e.target as HTMLElement)} - onMouseLeave={e => onHover(element, false, "LINEAR", e.target as HTMLElement)} + onClick={e => onClick?.(element, false, true, e.target as HTMLElement)} + onContextMenu={e => onContextMenu?.(element, false, true, e)} + onDoubleClick={e => onDoubleClick?.(element, false, true, e.target as HTMLElement)} + onMouseEnter={e => onHover?.(element, true, "LINEAR", e.target as HTMLElement)} + onMouseLeave={e => onHover?.(element, false, "LINEAR", e.target as HTMLElement)} > {bp} diff --git a/src/Linear/Translations.tsx b/src/Linear/Translations.tsx index a0fd926f5..8ebc9c196 100644 --- a/src/Linear/Translations.tsx +++ b/src/Linear/Translations.tsx @@ -25,15 +25,15 @@ interface TranslationRowsProps { fullSeq: string; inputRef: InputRefFunc; lastBase: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translationRows: Translation[][]; @@ -110,15 +110,15 @@ const TranslationRow = (props: { height: number; inputRef: InputRefFunc; lastBase: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translations: Translation[]; @@ -155,15 +155,15 @@ interface SingleNamedElementAminoacidsProps { height: number; inputRef: InputRefFunc; lastBase: number; - onClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translation: Translation; @@ -309,19 +309,19 @@ class SingleNamedElementAminoacids extends React.PureComponent { - onClick(aaElement, false, true, e.target as SVGGElement); + onClick?.(aaElement, false, true, e.target as SVGGElement); }} onContextMenu={e => { - onContextMenu(aaElement, false, true, e); + onContextMenu?.(aaElement, false, true, e); }} onDoubleClick={e => { - onDoubleClick(aaElement, false, true, e.target as SVGGElement); + onDoubleClick?.(aaElement, false, true, e.target as SVGGElement); }} onMouseEnter={e => { - onHover(aaElement, true, "LINEAR", e.target as SVGGElement); + onHover?.(aaElement, true, "LINEAR", e.target as SVGGElement); }} onMouseLeave={e => { - onHover(aaElement, false, "LINEAR", e.target as SVGGElement); + onHover?.(aaElement, false, "LINEAR", e.target as SVGGElement); }} > void; - onContextMenu: ( + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( element: any, circular: boolean, linear: boolean, event: React.MouseEvent ) => void; - onDoubleClick: (element: any, circular: boolean, linear: boolean, container: Element) => void; - onHover: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; - onKeyPress: (event: React.KeyboardEvent, selection: Selection) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onKeyPress?: (event: React.KeyboardEvent, selection: Selection) => void; onSelection: (selection: Selection) => void; primers: Primer[]; refs?: SeqVizChildRefs;