aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-09-18 17:40:01 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-09-18 17:40:01 -0400
commit013f25f01e729feee5db94900c61f4be4dd46869 (patch)
tree765dd5f2e06d6217ca79438e1098cefc8da627bf /src/client/views/pdf
parentf5e765adff1e7b32250eb503c9724a4ac99117f3 (diff)
parent84aa8806a62e2e957e8281d7d492139e3d8225f2 (diff)
Merge branch 'master' into sophie-report-manager
Diffstat (limited to 'src/client/views/pdf')
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx249
-rw-r--r--src/client/views/pdf/Annotation.tsx4
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss66
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx4
-rw-r--r--src/client/views/pdf/PDFViewer.tsx18
5 files changed, 116 insertions, 225 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index b877cc36a..85c1cce8c 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,101 +1,37 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState } from 'react-color';
import { Doc, Opt } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
+import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
+import { DocumentType } from '../../documents/DocumentTypes';
import { SelectionManager } from '../../util/SelectionManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
-import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
-import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
-import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
-import { LightboxView } from '../LightboxView';
-import { EditorView } from 'prosemirror-view';
import './AnchorMenu.scss';
-import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { StrCast } from '../../../fields/Types';
+import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { SettingsManager } from '../../util/SettingsManager';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: AnchorMenu;
private _disposer: IReactionDisposer | undefined;
- private _disposer2: IReactionDisposer | undefined;
- private _commentCont = React.createRef<HTMLButtonElement>();
- private _palette = [
- 'rgba(208, 2, 27, 0.8)',
- 'rgba(238, 0, 0, 0.8)',
- 'rgba(245, 166, 35, 0.8)',
- 'rgba(248, 231, 28, 0.8)',
- 'rgba(245, 230, 95, 0.616)',
- 'rgba(139, 87, 42, 0.8)',
- 'rgba(126, 211, 33, 0.8)',
- 'rgba(65, 117, 5, 0.8)',
- 'rgba(144, 19, 254, 0.8)',
- 'rgba(238, 169, 184, 0.8)',
- 'rgba(224, 187, 228, 0.8)',
- 'rgba(225, 223, 211, 0.8)',
- 'rgba(255, 255, 255, 0.8)',
- 'rgba(155, 155, 155, 0.8)',
- 'rgba(0, 0, 0, 0.8)',
- ];
+ private _commentRef = React.createRef<HTMLDivElement>();
+ private _cropRef = React.createRef<HTMLDivElement>();
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable public Status: 'marquee' | 'annotation' | '' = '';
// GPT additions
- @observable private GPTpopupText: string = '';
- @observable private loadingGPT: boolean = false;
- @observable private showGPTPopup: boolean = false;
- @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY;
@observable private selectedText: string = '';
- @observable private editorView?: EditorView;
- @observable private textDoc?: Doc;
- @observable private highlightRange: number[] | undefined;
- private selectionRange: number[] | undefined;
-
@action
- setGPTPopupVis = (vis: boolean) => {
- this.showGPTPopup = vis;
- };
- @action
- setGPTMode = (mode: GPTPopupMode) => {
- this.GPTMode = mode;
- };
-
- @action
- setGPTPopupText = (txt: string) => {
- this.GPTpopupText = txt;
- };
-
- @action
- setLoading = (loading: boolean) => {
- this.loadingGPT = loading;
- };
-
- @action
- setHighlightRange(r: number[] | undefined) {
- this.highlightRange = r;
- }
-
- @action
- public setSelectedText = (txt: string) => {
- this.selectedText = txt;
- };
-
- @action
- public setEditorView = (editor: EditorView) => {
- this.editorView = editor;
- };
-
- @action
- public setTextDoc = (textDoc: Doc) => {
- this.textDoc = textDoc;
- };
+ public setSelectedText = (txt: string) => (this.selectedText = txt);
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -124,27 +60,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentWillUnmount() {
this._disposer?.();
- this._disposer2?.();
}
componentDidMount() {
- this._disposer2 = reaction(
- () => this._opacity,
- opacity => {
- if (!opacity) {
- this.setGPTPopupVis(false);
- this.setGPTPopupText('');
- }
- },
- { fireImmediately: true }
- );
this._disposer = reaction(
() => SelectionManager.Views().slice(),
- selected => {
- this.setGPTPopupVis(false);
- this.setGPTPopupText('');
- AnchorMenu.Instance.fadeOut(true);
- }
+ selected => AnchorMenu.Instance.fadeOut(true)
);
}
@@ -153,67 +74,18 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* @param e pointer down event
*/
gptSummarize = async (e: React.PointerEvent) => {
- this.setHighlightRange(undefined);
- this.setGPTPopupVis(true);
- this.setGPTMode(GPTPopupMode.SUMMARY);
- this.setLoading(true);
+ // move this logic to gptpopup, need to implement generate again
+ GPTPopup.Instance.setVisible(true);
+ GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
+ GPTPopup.Instance.setLoading(true);
try {
const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
- if (res) {
- this.setGPTPopupText(res);
- } else {
- this.setGPTPopupText('Something went wrong.');
- }
- } catch (err) {
- console.error(err);
- }
-
- this.setLoading(false);
- };
-
- /**
- * Makes a GPT call to edit selected text.
- * @returns nothing
- */
- gptEdit = async () => {
- if (!this.editorView) return;
- this.setHighlightRange(undefined);
- const state = this.editorView.state;
- const sel = state.selection;
- const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n');
- const selectedText = state.doc.textBetween(sel.from, sel.to);
-
- this.setGPTPopupVis(true);
- this.setGPTMode(GPTPopupMode.EDIT);
- this.setLoading(true);
-
- try {
- let res = await gptAPICall(selectedText, GPTCallType.EDIT);
- // let res = await this.mockGPTCall();
- if (!res) return;
- res = res.trim();
- const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1);
-
- if (res) {
- this.setGPTPopupText(resultText);
- this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]);
- } else {
- this.setGPTPopupText('Something went wrong.');
- }
+ GPTPopup.Instance.setText(res || 'Something went wrong.');
} catch (err) {
console.error(err);
}
-
- this.setLoading(false);
- };
-
- /**
- * Replaces text suggestions from GPT.
- */
- replaceText = (replacement: string) => {
- if (!this.editorView || !this.textDoc) return;
- this.textDoc.text = replacement;
+ GPTPopup.Instance.setLoading(false);
};
pointerDown = (e: React.PointerEvent) => {
@@ -221,7 +93,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this,
e,
(e: PointerEvent) => {
- this.StartDrag(e, this._commentCont.current!);
+ this.StartDrag(e, this._commentRef.current!);
return true;
},
returnFalse,
@@ -238,7 +110,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this,
e,
(e: PointerEvent) => {
- this.StartCropDrag(e, this._commentCont.current!);
+ this.StartCropDrag(e, this._cropRef.current!);
return true;
},
returnFalse,
@@ -260,9 +132,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip={'Click to Highlight'}
onClick={this.highlightClicked}
colorPicker={this.highlightColor}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
- <ColorPicker colorPickerType={'github'} selectedColor={this.highlightColor} setSelectedColor={color => this.changeHighlightColor(color)} size={Size.XSMALL} />
+ <ColorPicker selectedColor={this.highlightColor} setFinalColor={this.changeHighlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} />
</Group>
);
}
@@ -284,71 +156,36 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* all selected text available to summarize but its only supported for pdf and web ATM.
* @returns Whether the GPT icon for summarization should appear
*/
- canSummarize = (): boolean => {
- const docs = SelectionManager.Docs();
- if (docs.length > 0) {
- return docs.some(doc => doc.type === 'pdf' || doc.type === 'web');
- }
- return false;
- };
-
- /**
- * Returns whether the selected text can be edited.
- * @returns Whether the GPT icon for summarization should appear
- */
- canEdit = (): boolean => {
- const docs = SelectionManager.Docs();
- if (docs.length > 0) {
- return docs.some(doc => doc.type === 'rtf');
- }
- return false;
- };
+ canSummarize = () => SelectionManager.Docs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
render() {
const buttons =
this.Status === 'marquee' ? (
<>
{this.highlighter}
- <IconButton
- tooltip="Drag to Place Annotation" //
- onPointerDown={this.pointerDown}
- icon={<FontAwesomeIcon icon="comment-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
+ <div ref={this._commentRef}>
+ <IconButton
+ tooltip="Drag to Place Annotation" //
+ onPointerDown={this.pointerDown}
+ icon={<FontAwesomeIcon icon="comment-alt" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
<IconButton
tooltip="Summarize with AI" //
onPointerDown={this.gptSummarize}
icon={<FontAwesomeIcon icon="comment-dots" size="lg" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
- <GPTPopup
- key="gptpopup"
- visible={this.showGPTPopup}
- text={this.GPTpopupText}
- highlightRange={this.highlightRange}
- loading={this.loadingGPT}
- callSummaryApi={this.gptSummarize}
- callEditApi={this.gptEdit}
- replaceText={this.replaceText}
- mode={this.GPTMode}
- />
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
<IconButton
tooltip="Click to Record Annotation" //
onPointerDown={this.audioDown}
icon={<FontAwesomeIcon icon="microphone" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
- )}
- {this.canEdit() && (
- <IconButton
- tooltip="AI edit suggestions" //
- onPointerDown={this.gptEdit}
- icon={<FontAwesomeIcon icon="pencil-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
<Popup
@@ -356,15 +193,17 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
type={Type.PRIM}
icon={<FontAwesomeIcon icon={'search'} />}
popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
- <IconButton
- tooltip="Click/Drag to create cropped image" //
- onPointerDown={this.cropDown}
- icon={<FontAwesomeIcon icon="image" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
+ <div ref={this._cropRef}>
+ <IconButton
+ tooltip="Click/Drag to create cropped image" //
+ onPointerDown={this.cropDown}
+ icon={<FontAwesomeIcon icon="image" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
)}
</>
) : (
@@ -374,7 +213,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip="Remove Link Anchor" //
onPointerDown={this.Delete}
icon={<FontAwesomeIcon icon="trash-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
{this.PinToPres !== returnFalse && (
@@ -382,7 +221,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip="Pin to Presentation" //
onPointerDown={this.PinToPres}
icon={<FontAwesomeIcon icon="map-pin" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
{this.ShowTargetTrail !== returnFalse && (
@@ -390,7 +229,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
tooltip="Show Linked Trail" //
onPointerDown={this.ShowTargetTrail}
icon={<FontAwesomeIcon icon="taxi" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
{this.IsTargetToggler !== returnFalse && (
@@ -401,7 +240,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
toggleStatus={this.IsTargetToggler()}
onClick={this.MakeTargetToggle}
icon={<FontAwesomeIcon icon="thumbtack" />}
- color={StrCast(Doc.UserDoc().userColor)}
+ color={SettingsManager.userColor}
/>
)}
</>
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index db6b1f011..caa72c9dc 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -24,7 +24,7 @@ export class Annotation extends React.Component<IAnnotationProps> {
render() {
return (
<div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}>
- {DocListCast(this.props.anno.textInlineAnnotations).map(a => (
+ {DocListCast(this.props.anno.text_inlineAnnotations).map(a => (
<RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
))}
</div>
@@ -61,7 +61,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
@undoBatch
showTargetTrail = (anchor: Doc) => {
- const trail = DocCast(anchor.presTrail);
+ const trail = DocCast(anchor.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
this.props.addDocTab(trail, OpenWhere.replaceRight);
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 44413ede7..5d966395c 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -6,12 +6,6 @@ $button: #5b97ff;
$highlightedText: #82e0ff;
.summary-box {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- background-color: #ffffff;
- box-shadow: 0 2px 5px #7474748d;
- color: $textgrey;
position: fixed;
bottom: 10px;
right: 10px;
@@ -21,9 +15,16 @@ $highlightedText: #82e0ff;
padding: 15px;
padding-bottom: 0;
z-index: 999;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ background-color: #ffffff;
+ box-shadow: 0 2px 5px #7474748d;
+ color: $textgrey;
.summary-heading {
display: flex;
+ justify-content: space-between;
align-items: center;
border-bottom: 1px solid $greyborder;
padding-bottom: 5px;
@@ -110,6 +111,59 @@ $highlightedText: #82e0ff;
}
}
+.image-content-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ padding-bottom: 16px;
+
+ .img-wrapper {
+ position: relative;
+ cursor: pointer;
+
+ .img-container {
+ pointer-events: none;
+ position: relative;
+
+ img {
+ pointer-events: all;
+ position: relative;
+ }
+ }
+
+ .img-container::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ .btn-container {
+ position: absolute;
+ right: 8px;
+ bottom: 8px;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ &:hover {
+ .img-container::after {
+ opacity: 1;
+ }
+
+ .btn-container {
+ opacity: 1;
+ }
+ }
+ }
+}
+
// Typist CSS
.Typist .Cursor {
display: inline-block;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 9b754588a..038c45582 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -118,15 +118,13 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
try {
let image_urls = await gptImageCall(this.imgDesc);
if (image_urls && image_urls[0]) {
- // need to fix this
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
- console.log('Result', result);
- console.log('Client', result.accessPaths.agnostic.client);
const source = Utils.prepend(result.accessPaths.agnostic.client);
this.setImgUrls([[image_urls[0], source]]);
}
} catch (err) {
console.log(err);
+ return '';
}
GPTPopup.Instance.setLoading(false);
};
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 1319a236d..2a191477b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -46,7 +46,7 @@ interface IViewerProps extends FieldViewProps {
sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean;
loaded?: (nw: number, nh: number, np: number) => void;
setPdfViewer: (view: PDFViewer) => void;
- anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void);
+ anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
}
@@ -67,8 +67,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _pdfViewer: any;
private _styleRule: any; // stylesheet rule for making hyperlinks clickable
private _retries = 0; // number of times tried to create the PDF viewer
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
- private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void);
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+ private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void);
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -95,7 +95,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges());
}
@computed get inlineTextAnnotations() {
- return this.allAnnotations.filter(a => a.textInlineAnnotations);
+ return this.allAnnotations.filter(a => a.text_inlineAnnotations);
}
componentDidMount = async () => {
@@ -195,7 +195,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
return focusSpeed;
};
crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
- brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
+ brushView = (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => this._setBrushViewer?.(view, transTime);
@action
setupPdfJsViewer = async () => {
@@ -375,7 +375,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._downY = e.clientY;
if ((this.props.Document._freeform_scale || 1) !== 1) return;
if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
- this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
+ this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this.props.Document);
}
if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
this.props.select(false);
@@ -464,13 +464,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
onClick = (e: React.MouseEvent) => {
this._scrollStopper?.();
if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
- this._setPreviewCursor(e.clientX, e.clientY, false, false);
+ this._setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
- setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+ setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => (this._setBrushViewer = func);
@action
onZoomWheel = (e: React.WheelEvent) => {