diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/CurrentUserUtils.ts | 57 | ||||
| -rw-r--r-- | src/client/util/DocumentManager.ts | 8 | ||||
| -rw-r--r-- | src/client/util/DragManager.ts | 20 | ||||
| -rw-r--r-- | src/client/util/InteractionUtils.tsx | 79 | ||||
| -rw-r--r-- | src/client/util/SettingsManager.tsx | 22 | ||||
| -rw-r--r-- | src/client/util/request-image-size.js | 26 | ||||
| -rw-r--r-- | src/client/util/type_decls.d | 3 |
7 files changed, 109 insertions, 106 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fdac3c00a..114dbac93 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -12,6 +12,7 @@ import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, DateCast, DocCast, PromiseValue, StrCast } from "../../fields/Types"; import { nullAudio } from "../../fields/URLField"; import { SetCachedGroups, SharingPermissions } from "../../fields/util"; +import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; @@ -45,7 +46,7 @@ interface Button { buttonText?: string; // fields that do not correspond to DocumentOption fields - scripts?: { script?: string; onClick?: string; } + scripts?: { script?: string; onClick?: string; onDoubleClick?: string } funcs?: { [key:string]: string }; subMenu?: Button[]; } @@ -462,28 +463,33 @@ export class CurrentUserUtils { static setupDashboards(doc: Doc, field:string) { var myDashboards = DocCast(doc[field]); + const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; const newDashboard = `createNewDashboard()`; + const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true }; const reqdBtnScript = {onClick: newDashboard,} const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); + const contextMenuScripts = [/*newDashboard*/] as string[]; + const contextMenuLabels = [/*"Create New Dashboard"*/] as string[]; + const contextMenuIcons = [/*"plus"*/] as string[]; + const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters + const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts + const childContextMenuLabels = ["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters + const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters const reqdOpts:DocumentOptions = { title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true, - targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true, + targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 350, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias", _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true, - contextMenuLabels: new List<string>(["Create New Dashboard"]), - contextMenuIcons: new List<string>(["plus"]), - childContextMenuLabels: new List<string>(["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]),// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters - childContextMenuIcons: new List<string>(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters + contextMenuLabels:new List<string>(contextMenuLabels), + contextMenuIcons:new List<string>(contextMenuIcons), + childContextMenuLabels:new List<string>(childContextMenuLabels), + childContextMenuIcons:new List<string>(childContextMenuIcons), explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." }; myDashboards = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); - const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; - const contextMenuScripts = [newDashboard]; - const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters - const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts if (Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { myDashboards.contextMenuScripts = new List<ScriptField>(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } @@ -512,7 +518,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true, isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, - treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "alias", childContextMenuLabels: new List<string>(["Create new folder"]), childContextMenuIcons: new List<string>(["plus"]), explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." @@ -529,7 +535,7 @@ export class CurrentUserUtils { static setupRecentlyClosed(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true, - treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", + treeViewTruncateTitleWidth: 350, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", contextMenuLabels: new List<string>(["Empty recently closed"]), contextMenuIcons:new List<string>(["trash"]), explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." @@ -555,7 +561,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view", boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, - treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 + treeViewHideTitle: true, treeViewTruncateTitleWidth: 350 }; if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }); return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]); @@ -576,20 +582,22 @@ export class CurrentUserUtils { /// initializes the required buttons in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc, field="myDockedBtns") { const dockedBtns = DocCast(doc[field]); - const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}, funcs?: {[key:string]:string}) => + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) => DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }}, + { scripts: { }, opts: { title: "linker", icon: "linkui", toolTip: "link started"}}, + { scripts: { }, opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing audio"}}, // { scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, opts: { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, width: 20}}, // { scripts: { onClick:""}, opts: { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, width: 20}, funcs: { buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}}, // { scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, opts:{title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, width: 20,} }, ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts = { - title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, + title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); @@ -622,13 +630,13 @@ export class CurrentUserUtils { static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", _readOnly_);}' }}, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", false, _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }}, // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} }, - // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:'{ return setActiveTool("line", _readOnly_);}' }}, + { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} }, + { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} }, + { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} }, { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} }, { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, @@ -657,9 +665,9 @@ export class CurrentUserUtils { CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, - { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_)'}}, + { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}, width: 20, scripts: {}}, + { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: {}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, @@ -684,7 +692,7 @@ export class CurrentUserUtils { }; const reqdFuncs:{[key:string]:any} = { ...params.funcs, - backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally + backgroundColor: params.btnType === ButtonType.ToggleButton ? params.scripts?.onClick:undefined /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally } return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } @@ -792,6 +800,7 @@ export class CurrentUserUtils { doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); doc._showLabel ?? (doc._showLabel = true); doc.textAlign ?? (doc.textAlign = "left"); + doc.activeTool = InkTool.None; doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");; doc.activeInkWidth ?? (doc.activeInkWidth = 1); doc.activeInkBezier ?? (doc.activeInkBezier = "0"); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3a6c43773..b046d950f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -228,7 +228,7 @@ export class DocumentManager { ); return; } else if (!docView && targetDoc.type !== DocumentType.MARKER) { - annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below + annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below } } if (focusView) { @@ -244,7 +244,7 @@ export class DocumentManager { res(ViewAdjustment.doNothing); }), }); - if (focusView.props.Document.layoutKey === 'layout_icon') { + if (focusView.props.Document.layoutKey === 'layout_icon' && focusView.rootDoc.type !== DocumentType.SCRIPTING) { focusView.iconify(() => doFocus(true)); } else { doFocus(false); @@ -335,9 +335,7 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { } else { const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; - const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc; - - CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc)); + CollectionDockingView.AddSplit(Doc.BestAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {})); } } ScriptingGlobals.add(DocFocusOrOpen); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 664933de0..36e5a65fb 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -24,7 +24,7 @@ export type dropActionType = 'alias' | 'copy' | 'move' | 'same' | 'proto' | 'non * @param dropAction: What to do with the document when it is dropped * @param dragStarted: Method to call when the drag is started */ -export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc> | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, dragStarted?: () => void) { +export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc | undefined> | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, dragStarted?: () => void) { const onRowMove = async (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); @@ -206,7 +206,7 @@ export namespace DragManager { !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : docDragData.dropAction === 'alias' - ? Doc.MakeAlias(d) + ? Doc.BestAlias(d) : docDragData.dropAction === 'proto' ? Doc.GetProto(d) : docDragData.dropAction === 'copy' @@ -260,10 +260,6 @@ export namespace DragManager { StartDrag([ele], dragData, downX, downY, options); } - export function StartImgDrag(ele: HTMLElement, downX: number, downY: number) { - StartDrag([ele], {}, downX, downY); - } - export function SetSnapLines(horizLines: number[], vertLines: number[]) { SnappingManager.setSnapLines(horizLines, vertLines); } @@ -325,7 +321,7 @@ export namespace DragManager { export let DocDragData: DocumentDragData | undefined; export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { if (dragData.dropAction === 'none') return; - DocDragData = dragData instanceof DocumentDragData ? dragData : undefined; + DocDragData = dragData as DocumentDragData; const batch = UndoManager.StartBatch('dragging'); eles = eles.filter(e => e); CanEmbed = dragData.canEmbed || false; @@ -357,15 +353,15 @@ export namespace DragManager { let rot = 0; const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : []; const dragElements = eles.map(ele => { - let useDim = false; + // bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element + let useDim = false; // if doc is rotated by freeformview, then the dragged elements width and height won't reflect the unrotated dimensions, so we need to rely on the element knowing its own width/height. \ + // if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu) if (ele?.parentElement?.parentElement?.parentElement?.className === 'collectionFreeFormDocumentView-container') { ele = ele.parentElement.parentElement.parentElement; - const rotStr = ele.style.transform.replace(/.*rotate\(([-0-9.]*)deg\).*/, '$1'); - if (rotStr) rot = Number(rotStr); + rot = Number(ele.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0); } else { useDim = true; } - if (rot < 0) rot += 360; if (!ele.parentNode) dragDiv.appendChild(ele); const dragElement = ele.parentNode === dragDiv ? ele : (ele.cloneNode(true) as HTMLElement); const children = Array.from(dragElement.children); @@ -387,7 +383,7 @@ export namespace DragManager { const rect = ele.getBoundingClientRect(); const w = ele.offsetWidth || rect.width; const h = ele.offsetHeight || rect.height; - const rotR = -(rot / 180) * Math.PI; + const rotR = -((rot < 0 ? rot + 360 : rot) / 180) * Math.PI; const tl = [0, 0]; const tr = [Math.cos(rotR) * w, Math.sin(-rotR) * w]; const bl = [Math.sin(rotR) * h, Math.cos(-rotR) * h]; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 4af51b9a0..3cdf4dbd2 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,4 +1,5 @@ import React = require('react'); +import { GestureUtils } from '../../pen-gestures/GestureUtils'; import { Utils } from '../../Utils'; import './InteractionUtils.scss'; @@ -186,7 +187,7 @@ export namespace InteractionUtils { export function makePolygon(shape: string, points: { X: number; Y: number }[]) { if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) { //pointer is up (first and last points are the same) - if (shape === 'arrow' || shape === 'line' || shape === 'circle') { + if (shape === GestureUtils.Gestures.Arrow || shape === GestureUtils.Gestures.Line || shape === GestureUtils.Gestures.Circle) { //if arrow or line, the two end points should be the starting and the ending point var left = points[0].X; var top = points[0].Y; @@ -208,7 +209,7 @@ export namespace InteractionUtils { left = points[0].X; bottom = points[points.length - 1].Y; top = points[0].Y; - if (shape !== 'arrow' && shape !== 'line' && shape !== 'circle') { + if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) { //switch left/right and top/bottom if needed if (left > right) { const temp = right; @@ -224,64 +225,39 @@ export namespace InteractionUtils { } points = []; switch (shape) { - case 'rectangle': + case GestureUtils.Gestures.Rectangle: points.push({ X: left, Y: top }); points.push({ X: right, Y: top }); points.push({ X: right, Y: bottom }); points.push({ X: left, Y: bottom }); points.push({ X: left, Y: top }); - case 'triangle': - // points.push({ X: left, Y: bottom }); - // points.push({ X: right, Y: bottom }); - // points.push({ X: (right + left) / 2, Y: top }); - // points.push({ X: left, Y: bottom }); - - points.push({ X: left, Y: bottom }); + break; + case GestureUtils.Gestures.Triangle: points.push({ X: left, Y: bottom }); - - points.push({ X: right, Y: bottom }); - points.push({ X: right, Y: bottom }); points.push({ X: right, Y: bottom }); - points.push({ X: right, Y: bottom }); - - points.push({ X: (right + left) / 2, Y: top }); - points.push({ X: (right + left) / 2, Y: top }); points.push({ X: (right + left) / 2, Y: top }); - points.push({ X: (right + left) / 2, Y: top }); - - points.push({ X: left, Y: bottom }); points.push({ X: left, Y: bottom }); - case 'circle': + break; + case GestureUtils.Gestures.Circle: const centerX = (Math.max(left, right) + Math.min(left, right)) / 2; const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); - if (centerX - Math.min(left, right) < centerY - Math.min(top, bottom)) { - for (var y = Math.min(top, bottom); y < Math.max(top, bottom); y++) { - const x = Math.sqrt(Math.pow(radius, 2) - Math.pow(y - centerY, 2)) + centerX; - points.push({ X: x, Y: y }); - } - for (var y = Math.max(top, bottom); y > Math.min(top, bottom); y--) { - const x = Math.sqrt(Math.pow(radius, 2) - Math.pow(y - centerY, 2)) + centerX; - const newX = centerX - (x - centerX); - points.push({ X: newX, Y: y }); - } - points.push({ X: Math.sqrt(Math.pow(radius, 2) - Math.pow(Math.min(top, bottom) - centerY, 2)) + centerX, Y: Math.min(top, bottom) }); - } else { - for (var x = Math.min(left, right); x < Math.max(left, right); x++) { - const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; - points.push({ X: x, Y: y }); - } - for (var x = Math.max(left, right); x > Math.min(left, right); x--) { - const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; - const newY = centerY - (y - centerY); - points.push({ X: x, Y: newY }); - } - points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(Math.min(left, right) - centerX, 2)) + centerY }); + for (var x = centerX - radius; x < centerX + radius; x++) { + const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; + points.push({ X: x, Y: y }); } - case 'line': + for (var x = centerX + radius; x > centerX - radius; x--) { + const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; + const newY = centerY - (y - centerY); + points.push({ X: x, Y: newY }); + } + points.push({ X: centerX - radius, Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(-radius, 2)) + centerY }); + break; + + case GestureUtils.Gestures.Line: points.push({ X: left, Y: top }); points.push({ X: right, Y: bottom }); - return points; + break; } return points; } @@ -291,17 +267,14 @@ export namespace InteractionUtils { * @param type - InteractionUtils.(PENTYPE | ERASERTYPE | MOUSETYPE | TOUCHTYPE) */ export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { + // prettier-ignore switch (type) { // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 - case PENTYPE: - return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); - case ERASERTYPE: - return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); - case TOUCHTYPE: - return e.pointerType === TOUCHTYPE; - default: - return e.pointerType === type; + case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); + case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); + case TOUCHTYPE: return e.pointerType === TOUCHTYPE; } + return e.pointerType === type; } /** diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index a185c8936..5c1c836f7 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -26,6 +26,11 @@ export enum ColorScheme { System = '-MatchSystem', } +export enum freeformScrollMode { + Pan = 'pan', + Zoom = 'zoom' +} + @observer export class SettingsManager extends React.Component<{}> { public static Instance: SettingsManager; @@ -186,6 +191,10 @@ export class SettingsManager extends React.Component<{}> { <input type="checkbox" onChange={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} /> <div className="preferences-check">Show button labels</div> </div> + <div> + <input type="checkbox" onChange={e => FontIconBox.SetRecognizeGesturs(!FontIconBox.GetRecognizeGestures())} checked={FontIconBox.GetRecognizeGestures()} /> + <div className="preferences-check">Recognize ink Gesturs</div> + </div> </div> ); } @@ -298,6 +307,12 @@ export class SettingsManager extends React.Component<{}> { ); } + + + setFreeformScrollMode = (mode: freeformScrollMode) => { + Doc.UserDoc().freeformScrollMode = mode; + } + @computed get modesContent() { return ( <div className="tab-content modes-content"> @@ -319,6 +334,13 @@ export class SettingsManager extends React.Component<{}> { <div className="playground-text">Playground Mode</div> </div> </div> + <div className="tab-column-title">Freeform scroll mode</div> + <div> + <button onClick={() => this.setFreeformScrollMode(freeformScrollMode.Pan)}>Scroll to pan</button> + <div>Scrolling pans around the freeform, holding shift and scrolling zooms in and out.</div> + <button onClick={() => this.setFreeformScrollMode(freeformScrollMode.Zoom)}>Scroll to zoom</button> + <div>Scrolling zooms in and out of canvas</div> + </div> </div> <div className="tab-column"> <div className="tab-column-title">Permissions</div> diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js index beb030635..502e0fbac 100644 --- a/src/client/util/request-image-size.js +++ b/src/client/util/request-image-size.js @@ -15,15 +15,18 @@ const HttpError = require('standard-http-error'); module.exports = function requestImageSize(options) { let opts = { - encoding: null + encoding: null, }; if (options && typeof options === 'object') { opts = Object.assign(options, opts); } else if (options && typeof options === 'string') { - opts = Object.assign({ - uri: options - }, opts); + opts = Object.assign( + { + uri: options, + }, + opts + ); } else { return Promise.reject(new Error('You should provide an URI string or a "request" options object.')); } @@ -38,9 +41,8 @@ module.exports = function requestImageSize(options) { return reject(new HttpError(res.statusCode, res.statusMessage)); } - let buffer = new Buffer.from([]); + let buffer = Buffer.from([]); let size; - let imageSizeError; res.on('data', chunk => { buffer = Buffer.concat([buffer, chunk]); @@ -48,8 +50,8 @@ module.exports = function requestImageSize(options) { try { size = imageSize(buffer); } catch (err) { - imageSizeError = err; - return; + reject(err); + return req.abort(); } if (size) { @@ -58,11 +60,11 @@ module.exports = function requestImageSize(options) { } }); - res.on('error', err => reject(err)); + res.on('error', reject); res.on('end', () => { if (!size) { - return reject(imageSizeError); + return reject(new Error('Image has no size')); } size.downloaded = buffer.length; @@ -70,6 +72,6 @@ module.exports = function requestImageSize(options) { }); }); - req.on('error', err => reject(err)); + req.on('error', reject); }); -};
\ No newline at end of file +}; diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 9063dc894..1a93bbe59 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -67,6 +67,9 @@ interface RegExp { readonly sticky: boolean; readonly unicode: boolean; } +interface Date { + now() : string; +} interface String { codePointAt(pos: number): number | undefined; includes(searchString: string, position?: number): boolean; |
