diff options
Diffstat (limited to 'src')
60 files changed, 913 insertions, 855 deletions
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 080657fd8..20dbc6f25 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -21,6 +21,7 @@ export enum DocumentType { PDFANNO = "pdfanno", // pdf text selection (could be just a collection?) DATE = "date", // calendar view of a date SCRIPTING = "script", // script editor + EQUATION = "equation", // equation edtior // special purpose wrappers that either take no data or are compositions of lower level types LINK = "link", // link (view of a document that acts as a link) diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 518370e7b..f87c7185c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -25,6 +25,7 @@ import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox"; import { LinkManager } from "../util/LinkManager"; import { Scripting } from "../util/Scripting"; import { undoBatch, UndoManager } from "../util/UndoManager"; +import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { ContextMenu } from "../views/ContextMenu"; @@ -56,34 +57,78 @@ import { PresElementBox } from "../views/presentationview/PresElementBox"; import { SearchBox } from "../views/search/SearchBox"; import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; +import { EquationBox } from "../views/nodes/EquationBox"; const path = require('path'); const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); -export interface DocumentOptions { - system?: boolean; - _autoHeight?: boolean; - _headerHeight?: number; // height of header of custom notes - _headerFontSize?: number; // font size of header of custom notes - _headerPointerEvents?: string; // types of events the header of a custom text document can consume - _panX?: number; - _panY?: number; - _width?: number; - _height?: number; - _nativeWidth?: number; - _nativeHeight?: number; - _dimMagnitude?: number; // magnitude of collectionMulti{row,col} view element - _dimUnit?: string; // "px" or "*" (default = "*") - _fitWidth?: boolean; +class EmptyBox { + public static LayoutString() { + return ""; + } +} +abstract class FInfo { + description: string = ""; + type?: string; + values?: Field[]; + layoutField?: boolean; // is this field a layout (or datadoc) field + // format?: string; // format to display values (e.g, decimal places, $, etc) + // parse?: ScriptField; // parse a value from a string + constructor(d: string, l: boolean = false) { this.description = d; this.layoutField = l; } +} +class BoolInfo extends FInfo { type?= "boolean"; values?: boolean[] = [true, false]; } +class NumInfo extends FInfo { type?= "number"; values?: number[] = []; } +class StrInfo extends FInfo { type?= "string"; values?: string[] = []; } +class DocInfo extends FInfo { type?= "Doc"; values?: Doc[] = []; } +class DimInfo extends FInfo { type?= "DimUnit"; values?= [DimUnit.Pixel, DimUnit.Ratio]; } +class PEInfo extends FInfo { type?= "pointerEvents"; values?= ["all", "none"]; } +class DAInfo extends FInfo { type?= "dropActionType"; values?= ["alias", "copy", "move", "same", "proto", "none"]; } +type BOOLt = BoolInfo | boolean; +type NUMt = NumInfo | number; +type STRt = StrInfo | string; +type DOCt = DocInfo | Doc; +type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; +type PEVt = PEInfo | "none" | "all"; +type DROPt = DAInfo | dropActionType; +export class DocumentOptions { + system?: BOOLt = new BoolInfo("is this a system created/owned doc"); + dropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto this doc "); + childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); + targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); + color?: string; // foreground color data doc + backgroundColor?: STRt = new StrInfo("background color for data doc"); + _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true); + _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents", true); + _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title", true); + _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes", true); + _headerPointerEvents?: PEVt = new PEInfo("types of events the header of a custom text document can consume", true); + _panX?: NUMt = new NumInfo("horizontal pan location of a freeform view", true); + _panY?: NUMt = new NumInfo("vertical pan location of a freeform view", true); + _width?: NUMt = new NumInfo("displayed width of a document", true); + _height?: NUMt = new NumInfo("displayed height of document", true); + _nativeWidth?: NUMt = new NumInfo("native width of document contents (e.g., the pixel width of an image)", true); + _nativeHeight?: NUMt = new NumInfo("native height of document contents (e.g., the pixel height of an image)", true); + _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", true); + _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units", true); + _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)", true); _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents + _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged + _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed _freeformLOD?: boolean; // whether to use LOD to render a freeform document _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged _chromeStatus?: string; + _layerTags?: List<string>; // layer tags a document has (used for tab filtering "layers" in document tab) _searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view) + _forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active) + _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere + _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged. + _hideContextMenu?: boolean; // whether the context menu can be shown _viewType?: string; // sub type of a collection _gridGap?: number; // gap between items in masonry view + _viewScale?: number; // how much a freeform view has been scaled (zoomed) + _overflow?: string; // set overflow behavior _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts _xPadding?: number; @@ -91,31 +136,33 @@ export interface DocumentOptions { _itemIndex?: number; // which item index the carousel viewer is showing _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts _singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document) + _columnWidth?: number; + _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden + _fontSize?: string; + _fontWeight?: number; + _fontFamily?: string; + _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views + _curPage?: number; // current page of a PDF or other? paginated document + _currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds + _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide) + _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) + _timecodeToHide?: number; // the time that a document should be hidden + _timelineLabel?: boolean; // whether the document exists on a timeline "_carousel-caption-xMargin"?: number; "_carousel-caption-yMargin"?: number; x?: number; y?: number; z?: number; author?: string; - _hideContextMenu?: boolean; // whether the context menu can be shown - dropAction?: dropActionType; - childDropAction?: dropActionType; - targetDropAction?: dropActionType; layoutKey?: string; type?: string; title?: string; version?: string; // version identifier for a document label?: string; hidden?: boolean; - userDoc?: Doc; // the userDocument toolTip?: string; // tooltip to display on hover - style?: string; - page?: number; dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links - _viewScale?: number; - _overflow?: string; - forceActive?: boolean; layout?: string | Doc; // default layout string for a document contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents childLimitHeight?: number; // whether to limit the height of colleciton children. 0 - means height can be no bigger than width @@ -123,39 +170,18 @@ export interface DocumentOptions { childLayoutString?: string; // template string for collection to use to render its children hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden - _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; watchedDocuments?: Doc; // list of documents to "watch" in an icon doc to display a badge targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List<string>; hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) - backgroundColor?: string | ScriptField; // background color for data doc - _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor ) - color?: string; // foreground color data doc - _color?: string; // foreground color for each template layout doc (overrides color) - _clipWidth?: number; // percent transition from before to after in comparisonBox caption?: RichTextField; - ignoreClick?: boolean; - lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged - _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed isAnnotating?: boolean; // whether we web document is annotation mode where links can't be clicked to allow annotations to be created opacity?: number; defaultBackgroundColor?: string; - _layers?: List<string>; - _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged. isLinkButton?: boolean; isFolder?: boolean; - _columnWidth?: number; - _fontSize?: string; - _fontWeight?: number; - _fontFamily?: string; - _curPage?: number; - _currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds - _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide) - _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) - _timecodeToHide?: number; // the time that a document should be hidden - _timelineLabel?: boolean; // whether the document exists on a timeline lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection appearFrame?: number; // the frame in which the document appears @@ -173,17 +199,16 @@ export interface DocumentOptions { "onClick-rawScript"?: string; // onClick script in raw text form "onCheckedClick-rawScript"?: string; // onChecked script in raw text form "onCheckedClick-params"?: List<string>; // parameter list for onChecked treeview functions - _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views - _columnHeaders?: List<SchemaHeaderField>; // headers for stacking views - _schemaHeaders?: List<SchemaHeaderField>; // headers for schema view + columnHeaders?: List<SchemaHeaderField>; // headers for stacking views + schemaHeaders?: List<SchemaHeaderField>; // headers for schema view + clipWidth?: number; // percent transition from before to after in comparisonBox dockingConfig?: string; annotationOn?: Doc; + isPushpin?: boolean; removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document - dbDoc?: Doc; iconShape?: string; // shapes of the fonticon border linkRelationship?: string; // type of relatinoship a link represents - ischecked?: ScriptField; // returns whether a font icon box is checked - activeInkPen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) + ignoreClick?: boolean; onClick?: ScriptField; onDoubleClick?: ScriptField; onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked @@ -194,7 +219,7 @@ export interface DocumentOptions { dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop - clipboard?: Doc; + cloneFieldFilter?: List<string>; // fields not to copy when the document is clonedclipboard?: Doc; useCors?: boolean; icon?: string; target?: Doc; // available for use in scripts as the primary target document @@ -202,8 +227,6 @@ export interface DocumentOptions { targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script searchFileTypes?: List<string>; // file types allowed in a search query strokeWidth?: number; - cloneFieldFilter?: List<string>; // fields not to copy when the document is cloned - _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view @@ -218,29 +241,47 @@ export interface DocumentOptions { treeViewDefaultExpandedView?: string; // default expanded view sidebarColor?: string; // background color of text sidebar sidebarViewType?: string; // collection type of text sidebar - limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents - // [key: string]: Opt<Field>; - pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown + docMaxAutoHeight?: number; // maximum height for newly created (eg, from pasting) text documents + text?: string; textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; - selectedIndex?: number; - syntaxColor?: string; // can be applied to text for syntax highlighting all matches in the text + selectedIndex?: number; // which item in a linear view has been selected using the "thumb doc" ui + clipboard?: Doc; searchQuery?: string; // for quersyBox linearViewIsExpanded?: boolean; // is linear view expanded - isLabel?: boolean; // whether the document is a label or not (video / audio) useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox border?: string; //for searchbox - hovercolor?: string; + hoverBackgroundColor?: string; // background color of a label when hovered } +export namespace Docs { -class EmptyBox { - public static LayoutString() { - return ""; + const _docOptions = new DocumentOptions(); + + export async function setupFieldInfos() { + return await DocServer.GetRefField("FieldInfos7") as Doc ?? + runInAction(() => { + const infos = new Doc("FieldInfos7", true); + const keys = Object.keys(new DocumentOptions()); + for (const key of keys) { + const options = (_docOptions as any)[key] as FInfo; + const finfo = new Doc(); + finfo.name = key; + switch (options.type) { + case "boolean": finfo.options = new List<boolean>(options.values as any as boolean[]); break; + case "number": finfo.options = new List<number>(options.values as any as number[]); break; + case "Doc": finfo.options = new List<Doc>(options.values as any as Doc[]); break; + default: // string, pointerEvents, dimUnit, dropActionType + finfo.options = new List<string>(options.values as any as string[]); break; + } + finfo.layoutField = options.layoutField; + finfo.description = options.description; + finfo.type = options.type; + infos[key] = finfo; + } + return infos; + }); } -} - -export namespace Docs { export let newAccount: boolean = false; @@ -335,6 +376,9 @@ export namespace Docs { [DocumentType.LABEL, { layout: { view: LabelBox, dataField: defaultDataKey }, }], + [DocumentType.EQUATION, { + layout: { view: EquationBox, dataField: defaultDataKey }, + }], [DocumentType.BUTTON, { layout: { view: LabelBox, dataField: "onClick" }, }], @@ -475,7 +519,7 @@ export namespace Docs { // whatever options pertain to this specific prototype const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; options.layout = layout.view?.LayoutString(layout.dataField); - const doc = Doc.assign(new Doc(prototypeId, true), { system: true, layoutKey: "layout", ...options }); + const doc = Doc.assign(new Doc(prototypeId, true), { system: true, layoutKey: "layout", ...options } as any); doc.data = template.data; doc.layout_keyValue = KeyValueBox.LayoutString(""); return doc; @@ -610,7 +654,6 @@ export namespace Docs { // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. dataDoc[fieldKey + "-annotations"] = new List<Doc>(); - dataDoc.aliases = new List<Doc>([viewDoc]); proto.links = ComputedField.MakeFunction("links(self)"); @@ -620,6 +663,9 @@ export namespace Docs { viewDoc["acl-Public"] = dataDoc["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; viewDoc["acl-Override"] = dataDoc["acl-Override"] = "None"; + !Doc.IsSystem(dataDoc) && ![DocumentType.PDFANNO, DocumentType.LINK, DocumentType.LINKANCHOR, DocumentType.TEXTANCHOR].includes(proto.type as any) && + !protoProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc); + return Doc.assign(viewDoc, delegateProps, true); } @@ -638,7 +684,7 @@ export namespace Docs { if (value !== undefined) { deleg[fieldKey] = value; } - return Doc.assign(deleg, options); + return Doc.assign(deleg, options as any); } export function ImageDocument(url: string, options: DocumentOptions = {}) { @@ -678,7 +724,7 @@ export namespace Docs { } export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) { - return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", { _clipWidth: 50, _backgroundColor: "gray", targetDropAction: "alias", ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", { clipWidth: 50, _backgroundColor: "gray", targetDropAction: "alias", ...options }); } export function AudioDocument(url: string, options: DocumentOptions = {}) { @@ -720,7 +766,7 @@ export namespace Docs { const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { dontRegisterChildViews: true, isLinkButton: true, treeViewHideTitle: true, backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area - treeViewExpandedView: "fields", removeDropProperties: new List(["_layers", "isLinkButton"]), ...options + treeViewExpandedView: "fields", removeDropProperties: new List(["_layerTags", "isLinkButton"]), ...options }, id); const linkDocProto = Doc.GetProto(doc); linkDocProto.treeViewOpen = true;// setting this in the instance creator would set it on the view document. @@ -755,8 +801,8 @@ export namespace Docs { I.x = options.x; I.y = options.y; I._backgroundColor = "transparent"; - I._width = options._width; - I._height = options._height; + I._width = options._width as number; + I._height = options._height as number; I._fontFamily = "cursive"; I.author = Doc.CurrentUserEmail; I.rotation = 0; @@ -784,19 +830,21 @@ export namespace Docs { } export function KVPDocument(document: Doc, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { _fitWidth: true, title: document.title + ".kvp", ...options }); } export function DocumentDocument(document?: Doc, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", targetDropAction: "move", ...options }); } - export function TextanchorDocument(options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), undefined, { targetDropAction: "move", ...options }); + export function TextanchorDocument(options: DocumentOptions = {}, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), undefined, { targetDropAction: "move", ...options }, id); } export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id); + const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id); + documents.map(d => d.context = inst); + return inst; } export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { @@ -812,23 +860,23 @@ export namespace Docs { } export function CarouselDocument(documents: Array<Doc>, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Carousel }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, ...options, _viewType: CollectionViewType.Carousel }); } export function Carousel3DDocument(documents: Array<Doc>, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Carousel3D }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, ...options, _viewType: CollectionViewType.Carousel3D }); } export function SchemaDocument(schemaHeaders: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _schemaHeaders: schemaHeaders.length ? new List(schemaHeaders) : undefined, ...options, _viewType: CollectionViewType.Schema }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, schemaHeaders: schemaHeaders.length ? new List(schemaHeaders) : undefined, ...options, _viewType: CollectionViewType.Schema }); } export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Tree }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Tree }, id); } export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Stacking }, id, undefined, protoId); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, ...options, _viewType: CollectionViewType.Stacking }, id, undefined, protoId); } export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) { @@ -847,6 +895,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.LABEL), undefined, { ...(options || {}) }); } + export function EquationDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.EQUATION), undefined, { ...(options || {}) }); + } + export function ButtonDocument(options?: DocumentOptions) { // const btn = InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), "onClick-rawScript": "-script-" }); // btn.layoutKey = "layout_onClick"; @@ -874,7 +926,7 @@ export namespace Docs { export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) { const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); - const tabs = TreeDocument(documents, { title: "On-Screen Tabs", freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true }); + const tabs = TreeDocument(documents, { title: "On-Screen Tabs", freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", _fitWidth: true, system: true }); const all = TreeDocument([], { title: "Off-Screen Tabs", freezeChildren: "add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true }); Doc.GetProto(inst).data = new List<Doc>([tabs, all]); return inst; @@ -1126,7 +1178,7 @@ export namespace DocUtils { if (type.indexOf("video") !== -1) { ctor = Docs.Create.VideoDocument; if (!options._width) options._width = 600; - if (!options._height) options._height = options._width * 2 / 3; + if (!options._height) options._height = (options._width as number) * 2 / 3; } if (type.indexOf("audio") !== -1) { ctor = Docs.Create.AudioDocument; @@ -1134,7 +1186,7 @@ export namespace DocUtils { if (type.indexOf("pdf") !== -1) { ctor = Docs.Create.PdfDocument; if (!options._width) options._width = 400; - if (!options._height) options._height = options._width * 1200 / 927; + if (!options._height) options._height = (options._width as number) * 1200 / 927; } if (type.indexOf("html") !== -1) { if (path.includes(window.location.hostname)) { @@ -1145,8 +1197,8 @@ export namespace DocUtils { const alias = Doc.MakeAlias(field); alias.x = options.x || 0; alias.y = options.y || 0; - alias._width = options._width || 300; - alias._height = options._height || options._width || 300; + alias._width = (options._width as number) || 300; + alias._height = (options._height as number) || (options._width as number) || 300; return alias; } return undefined; @@ -1177,6 +1229,20 @@ export namespace DocUtils { icon: "eye" }); ContextMenu.Instance.addItem({ + description: ":math", event: () => { + const created = Docs.Create.EquationDocument(); + if (created) { + created.author = Doc.CurrentUserEmail; + created.x = x; + created.y = y; + created.width = 300; + created.height = 35; + EquationBox.SelectOnLoad = created[Id]; + docAdder?.(created); + } + }, icon: "compress-arrows-alt" + }); + ContextMenu.Instance.addItem({ description: "Add Template Doc ...", subitems: DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ description: ":" + StrCast(dragDoc.title), diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e8ecef231..79a493dc9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -224,9 +224,9 @@ export class CurrentUserUtils { if (doc["template-buttons"] === undefined) { doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", - hidden: ComputedField.MakeFunction("self.userDoc.noviceMode") as any, - userDoc: doc, _stayInCollection: true, _hideContextMenu: true, - _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", + hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, + _stayInCollection: true, _hideContextMenu: true, + _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled", dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true })); } else { @@ -242,23 +242,23 @@ export class CurrentUserUtils { // setup the different note type skins static setupNoteTemplates(doc: Doc) { if (doc["template-note-Note"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow", system: true }); + const noteView = Docs.Create.TextDocument("", { title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); doc["template-note-Note"] = new PrefetchProxy(noteView); } if (doc["template-note-Idea"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Idea", backgroundColor: "pink", system: true }); + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "pink", system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); doc["template-note-Idea"] = new PrefetchProxy(noteView); } if (doc["template-note-Topic"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Topic", backgroundColor: "lightblue", system: true }); + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "lightblue", system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); doc["template-note-Topic"] = new PrefetchProxy(noteView); } if (doc["template-note-Todo"] === undefined) { const noteView = Docs.Create.TextDocument("", { - title: "text", style: "Todo", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", + title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); @@ -361,7 +361,7 @@ export class CurrentUserUtils { static creatorBtnDescriptors(doc: Doc): { title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, - click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc + click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc }[] { if (doc.emptyPresentation === undefined) { doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(), @@ -428,7 +428,7 @@ export class CurrentUserUtils { ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", system: true, cloneFieldFilter: new List<string>(["system"]) }); + doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, cloneFieldFilter: new List<string>(["system"]) }); ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyImage === undefined) { @@ -481,7 +481,7 @@ export class CurrentUserUtils { } } const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); - const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ + const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, icon, title, @@ -490,23 +490,20 @@ export class CurrentUserUtils { dropAction: "alias", onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, onClick: click ? ScriptField.MakeScript(click) : undefined, - ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined, - activeInkPen, backgroundColor, _hideContextMenu: true, removeDropProperties: new List<string>(["_stayInCollection"]), _stayInCollection: true, dragFactory, clickFactory, - userDoc: noviceMode ? undefined as any : doc, - hidden: noviceMode ? undefined as any : ComputedField.MakeFunction("self.userDoc.noviceMode"), + hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, system: true })); if (dragCreatorSet === undefined) { doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, - _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", + _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled", dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true })); } else { @@ -535,7 +532,7 @@ export class CurrentUserUtils { if (doc.mySearchPanelDoc === undefined) { doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({ _width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true, - childDropAction: "alias", lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", system: true + childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", system: true })) as any as Doc; } } @@ -560,9 +557,8 @@ export class CurrentUserUtils { watchedDocuments, onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) })); - const userDoc = menuBtns[menuBtns.length - 1]; - userDoc.userDoc = doc; - userDoc.hidden = ComputedField.MakeFunction("self.userDoc.noviceMode"); + // hack -- last button is assumed to be the userDoc + menuBtns[menuBtns.length - 1].hidden = ComputedField.MakeFunction("IsNoviceMode()"); doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, { title: "menuItemPanel", @@ -571,7 +567,7 @@ export class CurrentUserUtils { _backgroundColor: "black", ignoreClick: true, _gridGap: 0, _yMargin: 0, - _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, lockedPosition: true, _chromeStatus: "disabled", system: true + _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, _chromeStatus: "disabled", system: true })); } // this resets all sidebar buttons to being deactivated @@ -604,14 +600,14 @@ export class CurrentUserUtils { // Sets up mobileMenu stacking document static setupMobileMenu() { const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), { - _width: 980, ignoreClick: true, lockedPosition: false, _chromeStatus: "disabled", title: "home", _yMargin: 100, system: true + _width: 980, ignoreClick: true, _lockedPosition: false, _chromeStatus: "disabled", title: "home", _yMargin: 100, system: true })); return menu; } // SEts up mobile buttons for inside mobile menu static setupMobileButtons(doc?: Doc, buttons?: string[]) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " }, { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." }, { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." }, @@ -623,7 +619,7 @@ export class CurrentUserUtils { return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => this.mobileButton({ title: data.title, - lockedPosition: true, + _lockedPosition: true, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, _backgroundColor: data.backgroundColor, system: true }, @@ -659,12 +655,12 @@ export class CurrentUserUtils { static setupThumbButtons(doc: Doc) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ + { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" }, + { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" }, + { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange" }, + { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" }, + { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, @@ -672,7 +668,6 @@ export class CurrentUserUtils { onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, clipboard: data.clipboard, onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activeInkPen: data.activeInkPen, pointerHack: true, backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory, system: true })); } @@ -680,7 +675,7 @@ export class CurrentUserUtils { static setupThumbDoc(userDoc: Doc) { if (!userDoc.thumbDoc) { const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { - _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", + _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true }); thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { @@ -698,13 +693,13 @@ export class CurrentUserUtils { static setupMobileUploadDoc(userDoc: Doc) { // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" }) const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", { - title: "Upload Images From the Web", _chromeStatus: "enabled", lockedPosition: true, system: true + title: "Upload Images From the Web", _chromeStatus: "enabled", _lockedPosition: true, system: true }); const uploadDoc = Docs.Create.StackingDocument([], { - title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true, system: true + title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true }); return Docs.Create.StackingDocument([webDoc, uploadDoc], { - _width: screen.width, lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true + _width: screen.width, _lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true }); } @@ -724,20 +719,20 @@ export class CurrentUserUtils { if (doc.myCreators === undefined) { doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, - _width: 500, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", system: true + _width: 500, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled", system: true })); } // setup a color picker if (doc.myColorPicker === undefined) { const color = Docs.Create.ColorDocument({ - title: "color picker", _width: 300, dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, forceActive: true, removeDropProperties: new List<string>(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true + title: "color picker", _width: 300, dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, removeDropProperties: new List<string>(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true }); doc.myColorPicker = new PrefetchProxy(color); } if (doc.myTools === undefined) { const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", forceActive: true, system: true, _stayInCollection: true, _hideContextMenu: true, + title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled", _forceActive: true, system: true, _stayInCollection: true, _hideContextMenu: true, })) as any as Doc; doc.myTools = toolsStack; @@ -750,9 +745,9 @@ export class CurrentUserUtils { if (doc.myDashboards === undefined) { doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Dashboards", _height: 400, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); (doc.myDashboards as any as Doc).contextMenuScripts = new List<ScriptField>([newDashboard!]); @@ -766,9 +761,9 @@ export class CurrentUserUtils { if (doc.myPresentations === undefined) { doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Presentations", _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); (doc.myPresentations as any as Doc).contextMenuScripts = new List<ScriptField>([newPresentations!]); @@ -785,10 +780,10 @@ export class CurrentUserUtils { doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true }); doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileRoot as Doc, doc.myFileOrphans as Doc], { title: "My Documents", _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, isFolder: true, treeViewType: "fileSystem", - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "proto", system: true })); } return doc.myFilesystem as any as Doc; @@ -796,13 +791,12 @@ export class CurrentUserUtils { static setupRecentlyClosedDocs(doc: Doc) { // setup Recently Closed library item - doc.myRecentlyClosedDocs === undefined; if (doc.myRecentlyClosedDocs === undefined) { doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "Recently Closed", _height: 500, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]); @@ -830,9 +824,9 @@ export class CurrentUserUtils { doc.treeViewOpen = true; doc.treeViewExpandedView = "fields"; doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "My UserDoc", + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } } @@ -860,9 +854,9 @@ export class CurrentUserUtils { } static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { - ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", forceActive: true, + ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", _forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - backgroundColor: "black", treeViewPreventOpen: true, lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true, system: true + backgroundColor: "black", treeViewPreventOpen: true, _lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true, system: true })) as any as Doc static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ @@ -916,7 +910,7 @@ export class CurrentUserUtils { if (!sharedDocs) { sharedDocs = Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, - _showTitle: "title", ignoreClick: true, lockedPosition: true, + _showTitle: "title", ignoreClick: true, _lockedPosition: true, }, sharingDocumentId + "outer", sharingDocumentId); (sharedDocs as Doc)["acl-Public"] = Doc.GetProto(sharedDocs as Doc)["acl-Public"] = SharingPermissions.Add; } @@ -931,14 +925,14 @@ export class CurrentUserUtils { static setupImportSidebar(doc: Doc) { if (doc.myImportDocs === undefined) { doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { - title: "My ImportDocuments", forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, - childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, lockedPosition: true, _chromeStatus: "disabled", system: true + title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, + childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, _chromeStatus: "disabled", system: true })); } if (doc.myImportPanel === undefined) { const uploads = Cast(doc.myImportDocs, Doc, null); const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true }); - doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, lockedPosition: true, system: true })); + doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true })); } } @@ -1003,7 +997,6 @@ export class CurrentUserUtils { doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; doc._raiseWhenDragged = true; - doc.activeInkPen = doc; doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); doc.activeInkWidth = StrCast(doc.activeInkWidth, "1"); doc.activeInkBezier = StrCast(doc.activeInkBezier, "0"); @@ -1051,6 +1044,7 @@ export class CurrentUserUtils { // } // }); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); + doc.fieldInfos = await Docs.setupFieldInfos(); return doc; } @@ -1203,9 +1197,9 @@ export class CurrentUserUtils { CurrentUserUtils.openDashboard(userDoc, dashboardDoc); } - public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean) { + public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number) { const tbox = Docs.Create.TextDocument("", { - _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, + _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, _width: width || 200, _height: height || 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), _fontFamily: StrCast(Doc.UserDoc().fontFamily), title }); @@ -1235,6 +1229,8 @@ Scripting.addGlobal(function openDragFactory(dragFactory: Doc) { view && SelectionManager.SelectView(view, false); } }); +Scripting.addGlobal(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, + "is Dash in novice mode"); Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 637e219d5..969eed701 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -221,12 +221,13 @@ export class DocumentManager { } } -Scripting.addGlobal(function DocFocus(doc: any) { +Scripting.addGlobal(function DocFocusOrOpen(doc: any) { const dv = DocumentManager.Instance.getDocumentView(doc); if (dv && dv?.props.Document === doc) dv.props.focus(doc, { willZoom: true }); else { - const context = Cast(doc.context, Doc, null); - CollectionDockingView.AddSplit(context || doc, "right") && context && + const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null); + const showDoc = context || doc; + CollectionDockingView.AddSplit(showDoc === Doc.GetProto(showDoc) ? Doc.MakeAlias(showDoc) : showDoc, "right") && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc)); } });
\ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 19f1f8d15..437fea0ea 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -13,7 +13,7 @@ import * as globalCssVariables from "../views/globalCssVariables.scss"; import { UndoManager } from "./UndoManager"; import { SnappingManager } from "./SnappingManager"; -export type dropActionType = "alias" | "copy" | "move" | "same" | "none" | undefined; // undefined = move, "same" = move but don't call removeDropProperties +export type dropActionType = "alias" | "copy" | "move" | "same" | "proto" | "none" | undefined; // undefined = move, "same" = move but don't call removeDropProperties export function SetupDrag( _reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc> | undefined, @@ -138,13 +138,13 @@ export namespace DragManager { isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts } export class LinkDragData { - constructor(linkSourceDoc: Doc) { - this.linkSourceDocument = linkSourceDoc; + constructor(dragDoc: Doc, linkSourceGetAnchor: () => Doc) { + this.dragDocument = dragDoc; + this.linkSourceGetAnchor = linkSourceGetAnchor; } - droppedDocuments: Doc[] = []; - linkSourceDocument: Doc; - dontClearTextBox?: boolean; - linkDropCallback?: (data: { linkDocument: Doc }) => void; + dragDocument: Doc; + linkSourceGetAnchor: () => Doc; + linkSourceDoc?: Doc; } export class ColumnDragData { constructor(colKey: SchemaHeaderField) { @@ -154,23 +154,18 @@ export namespace DragManager { } // used by PDFs,Text,Image,Video,Web to conditionally (if the drop completes) create a text annotation when dragging the annotate button from the AnchorMenu when a text/region selection has been made. // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag - export class AnchorAnnoDragData { - constructor(dragDoc: Doc, annotationDocCreator: () => Doc, dropDocCreator: () => Doc) { - this.dragDocument = dragDoc; + export class AnchorAnnoDragData extends LinkDragData { + constructor(dragDoc: Doc, linkSourceGetAnchor: () => Doc, dropDocCreator: (annotationOn: Doc | undefined) => Doc) { + super(dragDoc, linkSourceGetAnchor); this.dropDocCreator = dropDocCreator; - this.annotationDocCreator = annotationDocCreator; this.offset = [0, 0]; } - targetContext: Doc | undefined; - dragDocument: Doc; - annotationDocCreator: () => Doc; - dropDocCreator: () => Doc; + linkSourceDoc?: Doc; + dropDocCreator: (annotationOn: Doc | undefined) => Doc; dropDocument?: Doc; - annotationDocument?: Doc; offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; - linkDropCallback?: (data: { linkDocument: Doc }) => void; } export function MakeDropTarget( @@ -221,8 +216,9 @@ export namespace DragManager { docDragData.droppedDocuments = dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : docDragData.dropAction === "alias" ? Doc.MakeAlias(d) : - docDragData.dropAction === "copy" ? Doc.MakeClone(d) : d); - docDragData.dropAction !== "same" && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { + docDragData.dropAction === "proto" ? Doc.GetProto(d) : + docDragData.dropAction === "copy" ? Doc.MakeClone(d) : d); + !["same", "proto"].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []); const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); remProps.map(prop => drop[prop] = undefined); @@ -257,8 +253,8 @@ export namespace DragManager { } // drags a linker button and creates a link on drop - export function StartLinkDrag(ele: HTMLElement, sourceDoc: Doc, downX: number, downY: number, options?: DragOptions) { - StartDrag([ele], new DragManager.LinkDragData(sourceDoc), downX, downY, options); + export function StartLinkDrag(ele: HTMLElement, sourceDoc: Doc, sourceDocGetAnchor: undefined | (() => Doc), downX: number, downY: number, options?: DragOptions) { + StartDrag([ele], new DragManager.LinkDragData(sourceDoc, () => sourceDocGetAnchor?.() ?? sourceDoc), downX, downY, options); } // drags a column from a schema view diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 00ac6e521..2d598c1ac 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -24,7 +24,8 @@ export namespace SerializationHelper { serializing++; if (!(obj.constructor.name in reverseMap)) { - throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`); + serializing--; + throw Error("Error: " + `type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`); } const json = serialize(obj); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index ef1228976..128ba858f 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -11,7 +11,7 @@ import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail } /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) -interface DocComponentProps { +export interface DocComponentProps { Document: Doc; LayoutTemplate?: () => Opt<Doc>; LayoutTemplateString?: string; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 209e750d6..a9cd7b4a9 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -312,7 +312,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return false; } - _ref = React.createRef<TemplateMenu>(); + _ref = React.createRef<HTMLDivElement>(); @observable _tooltipOpen: boolean = false; @computed get templateButton() { @@ -321,11 +321,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !view0 ? (null) : <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom"> <div className="documentButtonBar-linkFlyout" ref={this._dragRef} - onPointerEnter={action(() => !(this._ref.current as any as HTMLElement)?.getBoundingClientRect().width && (this._tooltipOpen = true))} > + onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} > <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} content={!this._aliasDown ? (null) : - <TemplateMenu ref={this._ref} docViews={views.filter(v => v).map(v => v as DocumentView)} />}> + <div ref={this._ref}> <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} /></div>}> <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} > {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />} </div> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2391a21e6..1e3c0ba92 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -292,10 +292,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b this._rotateUndo = undefined; } - - - _initialAutoHeight = false; - _dragHeights = new Map<Doc, number>(); + _dragHeights = new Map<Doc, { start: number, lowest: number }>(); @action onPointerDown = (e: React.PointerEvent): void => { @@ -323,12 +320,9 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b } this._snapX = e.pageX; this._snapY = e.pageY; - this._initialAutoHeight = true; DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc); - SelectionManager.Views().map(dv => { - this._dragHeights.set(dv.layoutDoc, NumCast(dv.layoutDoc._height)); - dv.layoutDoc._delayAutoHeight = dv.layoutDoc._height; - }); + SelectionManager.Views().map(dv => + this._dragHeights.set(dv.layoutDoc, { start: NumCast(dv.layoutDoc._height), lowest: NumCast(dv.layoutDoc._height) })); } onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { @@ -369,8 +363,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b let dX = 0, dY = 0, dW = 0, dH = 0; const unfreeze = () => SelectionManager.Views().forEach(action((element: DocumentView) => - ((element.rootDoc.type === DocumentType.RTF || - element.rootDoc.type === DocumentType.COMPARISON || + ((element.rootDoc.type === DocumentType.COMPARISON || (element.rootDoc.type === DocumentType.WEB && Doc.LayoutField(element.rootDoc) instanceof HtmlField)) && Doc.NativeHeight(element.layoutDoc)) && element.toggleNativeDimensions())); switch (this._resizeHdlId) { @@ -421,8 +414,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions(); if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { const doc = Document(docView.rootDoc); - let nwidth = docView.nativeWidth; - let nheight = docView.nativeHeight; + const nwidth = docView.nativeWidth; + const nheight = docView.nativeHeight; const width = (doc._width || 0); let height = (doc._height || (nheight / nwidth * width)); height = !height || isNaN(height) ? 20 : height; @@ -431,7 +424,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b if (nwidth / nheight !== width / height && !dragBottom) { height = nheight / nwidth * width; } - if (e.ctrlKey || (!dragBottom || !docView.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction + if (e.ctrlKey && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; else dW = dH * nwidth / nheight; } @@ -441,10 +434,6 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); const fixedAspect = (nwidth && nheight); - if (fixedAspect && (!nwidth || !nheight)) { - doc._nativeWidth = nwidth = doc._width || 0; - doc._nativeHeight = nheight = doc._height || 0; - } if (e.ctrlKey && [DocumentType.IMG, DocumentType.SCREENSHOT, DocumentType.VID].includes(doc.type as DocumentType)) { dW !== 0 && runInAction(() => { const dataDoc = doc[DataSym]; @@ -454,29 +443,33 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b Doc.SetNativeHeight(dataDoc, nh + (dW > 0 ? 10 : -10) * nh / nw); }); } - else if (nwidth > 0 && nheight > 0) { - if (Math.abs(dW) > Math.abs(dH) || dragRight) { - if (!fixedAspect || (dragRight && e.ctrlKey)) { + else if (fixedAspect) { + if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !e.ctrlKey)) || dragRight) { + if (dragRight && e.ctrlKey) { doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc); + } else { + if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW; + else if (!e.ctrlKey) doc._height = actualdH; } doc._width = actualdW; - if (fixedAspect && !doc._fitWidth) doc._height = nheight / nwidth * doc._width; - else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH; } else { - if (!fixedAspect || (dragBottom && (e.ctrlKey || docView.layoutDoc._fitWidth))) { + if (dragBottom && (e.ctrlKey || docView.layoutDoc._fitWidth)) { // frozen web pages and others that fitWidth can't grow horizontally to match a vertical resize so the only choice is to change the nativeheight even if the ctrl key isn't used doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc); + } else { + if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH; + else if (!e.ctrlKey) doc._width = actualdW; } doc._height = actualdH; - if (fixedAspect && !doc._fitWidth) doc._width = nwidth / nheight * doc._height; - else if (!fixedAspect || !e.ctrlKey) doc._width = actualdW; } } else { dW && (doc._width = actualdW); dH && (doc._height = actualdH); - dH && this._initialAutoHeight && (doc._autoHeight = this._initialAutoHeight = false); + dH && (doc._autoHeight = false); } } + const val = this._dragHeights.get(docView.layoutDoc); + if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) }); })); return false; } @@ -484,11 +477,12 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b @action onPointerUp = (e: PointerEvent): void => { SelectionManager.Views().map(dv => { - if (NumCast(dv.layoutDoc._delayAutoHeight) < this._dragHeights.get(dv.layoutDoc)!) { - dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.ContentScale(), dv.props.PanelWidth(), dv.props.PanelHeight()); - dv.layoutDoc._autoHeight = true; + const hgts = this._dragHeights.get(dv.layoutDoc); + if (hgts && hgts.lowest < hgts.start && hgts.lowest <= 20) { + dv.effectiveNativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.ContentScale(), dv.props.PanelWidth(), dv.props.PanelHeight()); + if (dv.layoutDoc._autoHeight) dv.layoutDoc._autoHeight = false; + setTimeout(() => dv.layoutDoc._autoHeight = true); } - dv.layoutDoc._delayAutoHeight = undefined; }); this._resizeHdlId = ""; this.Interacting = false; @@ -617,8 +611,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> {closeIcon} - {titleArea} - {seldoc.props.hideResizeHandles ? (null) : + {seldoc.props.Document.type === DocumentType.EQUATION ? (null) : titleArea} + {seldoc.props.hideResizeHandles || seldoc.props.Document.type === DocumentType.EQUATION ? (null) : <> {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) : <Tooltip key="i" title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top"> diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 19b23af13..462d78865 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -233,7 +233,7 @@ export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInk export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); } export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); } export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); } -export function ActiveInkPen(): Doc { return Cast(Doc.UserDoc().activeInkPen, Doc, null); } +export function ActiveInkPen(): Doc { return Doc.UserDoc(); } export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); } export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); } export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index babc518ff..3fc72c45c 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -46,7 +46,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { this._docFilters && (this._docFilters.length = 0); this._future = this._history = []; } else { - TabDocView.PinDoc(doc, { hidePresBox: true }); + //TabDocView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }]; if (doc !== LightboxView.LightboxDoc) { this._savedState = { @@ -247,6 +247,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { ContainingCollectionDoc={undefined} renderDepth={0} /> </div> + {this.navBtn(0, undefined, this.props.PanelHeight / 2 - 12.50, "chevron-left", () => LightboxView.LightboxDoc && LightboxView._history?.length ? "" : "none", e => { e.stopPropagation(); @@ -258,6 +259,10 @@ export class LightboxView extends React.Component<LightboxViewProps> { LightboxView.Next(); })} <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} /> + <div className="lightboxView-navBtn" title={"toggle fit width"} style={{ position: "absolute", right: 10, top: 10, color: "white" }} + onClick={e => { e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth }}> + <FontAwesomeIcon icon={"arrows-alt-h"} size="2x" /> + </div> </div>; } } @@ -276,6 +281,6 @@ export class LightboxTourBtn extends React.Component<LightboxTourBtnProps> { this.props.stepInto(); }, StrCast(this.props.tourMap()?.lastElement()?.TourMap) - ) + ); } }
\ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6010eb518..c356aa0f5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -102,7 +102,7 @@ export class MainView extends React.Component { } new InkStrokeProperties(); this._sidebarContent.proto = undefined; - DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's + DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); @@ -230,7 +230,7 @@ export class MainView extends React.Component { createNewPresentation = async () => { if (!await this.userDoc.myPresentations) { this.userDoc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "PRESENTATION TRAILS", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true, system: true + title: "PRESENTATION TRAILS", _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true })); } const pres = Docs.Create.PresDocument(new List<Doc>(), @@ -321,8 +321,8 @@ export class MainView extends React.Component { <div className={`styleProvider-treeView-hide${doc.hidden ? "-active" : ""}`} onClick={e => toggleField(e, doc, "hidden")}> <FontAwesomeIcon icon={doc.hidden ? "eye-slash" : "eye"} size="sm" /> </div> - <div className={`styleProvider-treeView-lock${doc.lockedPosition ? "-active" : ""}`} onClick={e => toggleField(e, doc, "lockedPosition")}> - <FontAwesomeIcon icon={doc.lockedPosition ? "lock" : "unlock"} size="sm" /> + <div className={`styleProvider-treeView-lock${doc._lockedPosition ? "-active" : ""}`} onClick={e => toggleField(e, doc, "_lockedPosition")}> + <FontAwesomeIcon icon={doc._lockedPosition ? "lock" : "unlock"} size="sm" /> </div> </>; } @@ -484,6 +484,7 @@ export class MainView extends React.Component { DataDoc={undefined} fieldKey={"data"} dropAction={"alias"} + setHeight={returnFalse} parentActive={returnFalse} styleProvider={DefaultStyleProvider} layerProvider={undefined} @@ -554,6 +555,7 @@ export class MainView extends React.Component { isSelected={returnTrue} active={returnTrue} select={returnTrue} + setHeight={returnFalse} addDocument={undefined} addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} @@ -591,6 +593,7 @@ export class MainView extends React.Component { styleProvider={undefined} isSelected={returnFalse} select={returnFalse} + setHeight={returnFalse} rootSelected={returnFalse} renderDepth={0} parentActive={returnFalse} @@ -663,6 +666,7 @@ export class MainView extends React.Component { select={returnFalse} rootSelected={returnFalse} renderDepth={0} + setHeight={returnFalse} layerProvider={undefined} styleProvider={undefined} addDocTab={returnFalse} diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 6e8f9a2df..f03e1dd9d 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -64,27 +64,24 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. */ - AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { + AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); - const targetCreator = () => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100); - FormattedTextBox.SelectOnLoad = target[Id]; - return target; - }; - const anchorCreator = () => { - const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color - annoDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing? + const sourceAnchorCreator = () => { + const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color this.props.addDocument(annoDoc); return annoDoc; }; - DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, anchorCreator, targetCreator), e.pageX, e.pageY, { + const targetCreator = (annotationOn: Doc | undefined) => { + const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); + FormattedTextBox.SelectOnLoad = target[Id]; + return target; + }; + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { dragComplete: e => { - if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { - e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "Annotation"); - e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc; + if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { + e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; } - e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument } }); }); @@ -141,11 +138,12 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { } } @action - highlight = (color: string) => { + highlight = (color: string, isLinkButton: boolean) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color); annotationDoc && this.props.addDocument(annotationDoc); + annotationDoc && (annotationDoc.isLinkButton = isLinkButton); return annotationDoc as Doc ?? undefined; } @@ -197,7 +195,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up - this.highlight("rgba(245, 230, 95, 0.75)"); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) + this.highlight("rgba(245, 230, 95, 0.75)", false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) } } else { runInAction(() => this._width = this._height = 0); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 5b57ad19f..679a4b81e 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -79,7 +79,7 @@ export class PreviewCursor extends React.Component<{}> { else { // creates text document FormattedTextBox.PasteOnLoad = e; - UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(CurrentUserUtils.GetNewTextDoc("-pasted text-", newPoint[0], newPoint[1], 500)), "paste"); + UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(CurrentUserUtils.GetNewTextDoc("-pasted text-", newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), "paste"); } } else //pasting in images diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 8ad5f3f2b..e418a6f3c 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -194,12 +194,12 @@ export class PropertiesButtons extends React.Component<{}, {}> { get lockButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<div className="dash-tooltip">{`${this.selectedDoc?.lockedPosition ? "Unlock" : "Lock"} Position`}</div>} placement="top"> + title={<div className="dash-tooltip">{`${this.selectedDoc?._lockedPosition ? "Unlock" : "Lock"} Position`}</div>} placement="top"> <div> - <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc.lockedPosition ? "on" : "off"}`} onPointerDown={this.onLock} > + <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._lockedPosition ? "on" : "off"}`} onPointerDown={this.onLock} > <FontAwesomeIcon className="documentdecorations-icon" size="lg" - color={this.selectedDoc?.lockedPosition ? "black" : "white"} - icon={this.selectedDoc?.lockedPosition ? "unlock" : "lock"} /> + color={this.selectedDoc?._lockedPosition ? "black" : "white"} + icon={this.selectedDoc?._lockedPosition ? "unlock" : "lock"} /> </div> <div className="propertiesButtons-title" >Position </div> @@ -260,7 +260,10 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action setCaption = () => { - SelectionManager.Views().forEach(dv => dv.rootDoc._showCaption = dv.rootDoc._showCaption === undefined ? "caption" : undefined); + SelectionManager.Views().forEach(dv => { + dv.rootDoc._showCaption = dv.rootDoc._showCaption === undefined ? "caption" : undefined; + console.log("caption = " + dv.rootDoc._showCaption); + }); } @computed @@ -427,6 +430,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { } @action @undoBatch + changeFitWidth = () => { + if (this.selectedDoc) { + if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => dv.rootDoc._fitWidth = !dv.rootDoc._fitWidth); + else this.selectedDoc._fitWidth = !this.selectedDoc._fitWidth; + } + } + + @action @undoBatch changeClusters = () => { if (this.selectedDoc) { if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => dv.rootDoc._useClusters = !dv.rootDoc._useClusters); @@ -448,6 +459,20 @@ export class PropertiesButtons extends React.Component<{}, {}> { </Tooltip>; } + @computed + get fitWidthButton() { + const targetDoc = this.selectedDoc; + return !targetDoc ? (null) : <Tooltip + title={<><div className="dash-tooltip">{this.selectedDoc?._fitWidth ? "Stop Fitting Width" : "Fit Width"}</div></>} placement="top"> + <div> + <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._fitWidth ? "on" : "off"}`} onPointerDown={this.changeFitWidth}> + <FontAwesomeIcon className="documentdecorations-icon" icon="arrows-alt-h" size="lg" /> + </div> + <div className="propertiesButtons-title"> {this.selectedDoc?._fitWidth ? "unfit" : "fit"} </div> + </div> + </Tooltip>; + } + @undoBatch @action private makeMask = () => { @@ -521,6 +546,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button" style={{ display: !isFreeForm && !isText ? "none" : "" }}> {this.fitContentButton} </div> + <div className="propertiesButtons-button"> + {this.fitWidthButton} + </div> <div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}> {this.maskButton} </div> diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index e272fbaa6..91667fd64 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -47,9 +47,9 @@ function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkSc function toggleBackground(doc: Doc) { UndoManager.RunInBatch(() => runInAction(() => { - const layers = StrListCast(doc.layers); + const layers = StrListCast(doc._layerTags); if (!layers.includes(StyleLayers.Background)) { - if (!layers.length) doc.layers = new List<string>([StyleLayers.Background]); + if (!layers.length) doc._layerTags = new List<string>([StyleLayers.Background]); else layers.push(StyleLayers.Background); } else layers.splice(layers.indexOf(StyleLayers.Background), 1); @@ -68,16 +68,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | const selected = property.includes(":selected"); const isCaption = property.includes(":caption"); const isAnchor = property.includes(":anchor"); - const isFooter = property.includes(":footer"); - - const isBackground = () => StrListCast(doc?.layers).includes(StyleLayers.Background); + const isAnnotated = property.includes(":annotated"); + const isBackground = () => StrListCast(doc?._layerTags).includes(StyleLayers.Background); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); switch (property.split(":")[0]) { case StyleProp.TreeViewIcon: return doc ? Doc.toIcon(doc) : "question"; case StyleProp.DocContents: return undefined; - case StyleProp.WidgetColor: return darkScheme() ? "lightgrey" : "dimgrey"; + case StyleProp.WidgetColor: return isAnnotated ? "lightBlue" : darkScheme() ? "lightgrey" : "dimgrey"; case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null)); case StyleProp.HideLinkButton: return isAnchor || props?.dontRegisterView || (!selected && (doc?.isLinkButton || doc?.hideLinkButton)); case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle, @@ -90,15 +89,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | const colsum = (col.red() + col.green() + col.blue()); if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return "black"; return "white"; - case StyleProp.Hidden: return BoolCast(doc?._hidden, BoolCast(doc?.hidden)); - case StyleProp.BorderRounding: return StrCast(doc?._borderRounding, StrCast(doc?.borderRounding)); + case StyleProp.Hidden: return BoolCast(doc?._hidden); + case StyleProp.BorderRounding: return StrCast(doc?._borderRounding); case StyleProp.TitleHeight: return 15; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && doc?._showTitle && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (isAnchor && docProps) return "transparent"; if (isCaption) return "rgba(0,0,0 ,0.4)"; if (Doc.UserDoc().renderStyle === "comic") return "transparent"; - let docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor)); + let docColor: Opt<string> = StrCast(doc?._backgroundColor); if (!docProps) { if (MainView.Instance.LastButton === doc) return darkScheme() ? "dimgrey" : "lightgrey"; switch (doc?.type) { @@ -115,6 +114,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | case DocumentType.FILTER: docColor = docColor || (darkScheme() ? "#2d2d2d" : "rgba(105, 105, 105, 0.432)"); break; case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break; case DocumentType.SLIDER: break; + case DocumentType.EQUATION: docColor = docColor || "transparent"; break; case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break; case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break; case DocumentType.LINK: return "transparent"; @@ -163,7 +163,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | if (isAnchor && docProps) return "none"; if (props?.pointerEvents === "none") return "none"; const layer = doc && props?.layerProvider?.(doc); - if (opacity() === 0 || doc?.type === DocumentType.INK || doc?.isInkMask) return "none"; + if (opacity() === 0 || (doc?.type === DocumentType.INK && !docProps?.treeViewDoc) || doc?.isInkMask) return "none"; if (layer === false && !selected && !SnappingManager.GetIsDragging()) return "none"; if (doc?.type !== DocumentType.INK && layer === true) return "all"; return undefined; @@ -271,15 +271,15 @@ export function DefaultLayerProvider(thisDoc: Doc) { if (assign) { const activeLayer = StrCast(thisDoc?.activeLayer); if (activeLayer) { - const layers = Cast(doc.layers, listSpec("string"), []); + const layers = Cast(doc._layerTags, listSpec("string"), []); if (layers.length && !layers.includes(activeLayer)) layers.push(activeLayer); - else if (!layers.length) doc.layers = new List<string>([activeLayer]); + else if (!layers.length) doc._layerTags = new List<string>([activeLayer]); if (activeLayer === "red" || activeLayer === "green" || activeLayer === "blue") doc._backgroundColor = activeLayer; } return true; } else { if (Doc.AreProtosEqual(doc, thisDoc)) return true; - const layers = StrListCast(doc.layers); + const layers = StrListCast(doc._layerTags); if (!layers.length && !thisDoc?.activeLayer) return true; if (layers.includes(StrCast(thisDoc?.activeLayer))) return true; return false; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index d2e0cb265..f39d5ee4c 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -140,6 +140,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { rootSelected={returnFalse} onCheckedClick={this.scriptField} onChildClick={this.scriptField} + setHeight={returnFalse} dropAction={undefined} active={returnTrue} parentActive={returnFalse} diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 4bdd39194..077e50dd2 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -16,6 +16,8 @@ import { EditableView } from "../EditableView"; import { CollectionStackingView } from "./CollectionStackingView"; import "./CollectionStackingView.scss"; import { SnappingManager } from "../../util/SnappingManager"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; +import { Id } from "../../../fields/FieldSymbols"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -142,8 +144,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr if (!value && !forceEmptyNote) return false; this._createAliasSelected = false; const key = StrCast(this.props.parent.props.Document._pivotField); - const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _width: 200, title: value }); + const newDoc = Docs.Create.TextDocument("", { _autoHeight: true, _width: 200, title: value }); const onLayoutDoc = this.onLayoutDoc(key); + FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SelectOnLoadChar = value; (onLayoutDoc ? newDoc : newDoc[DataSym])[key] = this.getValue(this.props.heading); const docs = this.props.parent.childDocList; return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument?.(newDoc) || false; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 504cf32a0..a265045b8 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -553,7 +553,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { </div>; return <div className={"collectionSchemaView" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")} style={{ - overflow: this.props.overflow === true ? "scroll" : undefined, backgroundColor: "white", + overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white", pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 77046f5ea..7152f4272 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -29,6 +29,7 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; +import { LightboxView } from "../LightboxView"; const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -140,8 +141,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, () => this.pivotField, () => this.layoutDoc._columnHeaders = new List() ); - this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, this.forceAutoHeight); + this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, + () => this.props.setHeight(this.headerMargin + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))); } + componentWillUnmount() { super.componentWillUnmount(); this._pivotFieldDisposer?.(); @@ -202,10 +205,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); - let dref: Opt<HTMLDivElement>; + let dref: Opt<DocumentView>; const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); - return <DocumentView ref={r => dref = r?.ContentDiv ? r.ContentDiv : undefined} + return <DocumentView ref={r => dref = r || undefined} Document={doc} DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} renderDepth={this.props.renderDepth + 1} @@ -245,18 +248,24 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, />; } + getDocTransform(doc: Doc, dref?: DocumentView) { + const y = this._scroll; // required for document decorations to update when the text box container is scrolled + const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(- translateX - (dref?.centeringX || 0), - translateY - (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale * (this.props.scaling?.() || 1)); + } getDocWidth(d?: Doc) { if (!d) return 0; - const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const nw = Doc.NativeWidth(layoutDoc); - return Math.min(nw && !this.layoutDoc._columnsFill ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); + const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const nw = Doc.NativeWidth(childLayoutDoc) || (childLayoutDoc._fitWidth ? 0 : d[WidthSym]()); + return Math.min(nw && !this.layoutDoc._columnsFill ? (this.props.scaling?.() || 1) * d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); } getDocHeight(d?: Doc) { if (!d) return 0; const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc; const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc); - const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc); + const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (childLayoutDoc._fitWidth ? 0 : d[WidthSym]()); + const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (childLayoutDoc._fitWidth ? 0 : d[HeightSym]()); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); if (!this.layoutDoc._columnsFill) wid = Math.min(wid, childLayoutDoc[WidthSym]()); const hllimit = NumCast(this.layoutDoc.childLimitHeight, -1); @@ -266,7 +275,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, return Math.min(hllimit === 0 ? this.props.PanelWidth() : hllimit === -1 ? 10000 : hllimit, wid * aspect); } return childLayoutDoc._fitWidth ? - (!nh ? this.props.PanelHeight() - 2 * this.yMargin : + (!nh ? Math.min(NumCast(childLayoutDoc.height, 10000) * (this.props.scaling?.() || 1), this.props.PanelHeight() - 2 * this.yMargin) : Math.min(wid * nh / (nw || 1), this.layoutDoc._autoHeight ? 100000 : this.props.PanelHeight() - 2 * this.yMargin)) : Math.min(hllimit === 0 ? this.props.PanelWidth() : hllimit === -1 ? 10000 : hllimit, Math.max(20, childLayoutDoc[HeightSym]())); } @@ -359,8 +368,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, const cols = () => this.isStackingView ? 1 : Math.max(1, Math.min(this.filteredChildren.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); return <CollectionStackingViewFieldColumn - unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)} - observeHeight={(ref) => { + unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { if (ref) { this.refList.push(ref); const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; @@ -369,10 +378,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => NumCast(Doc.Layout(doc)._viewScale, 1) * Number(getComputedStyle(r).height.replace("px", ""))))); - if (this.props.isAnnotationOverlay) { - doc[this.props.fieldKey + "-height"] = height; - } else { - Doc.Layout(doc)._height = height * NumCast(Doc.Layout(doc)._viewScale, 1); + if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { + this.props.setHeight(height * NumCast(Doc.Layout(doc)._viewScale, 1)); } } })); @@ -392,17 +399,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, />; } - getDocTransform(doc: Doc, dref?: HTMLDivElement) { - const y = this._scroll; // required for document decorations to update when the text box container is scrolled - const { scale, translateX, translateY } = Utils.GetScreenTransform(dref); - return new Transform(-translateX, -translateY, 1).scale(this.props.ScreenToLocalTransform().Scale); - } - - forceAutoHeight = () => { - const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; - Doc.Layout(doc)._height = this.headerMargin + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); - } - sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; @@ -422,7 +418,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, this.observer = new _global.ResizeObserver(action((entries: any) => { if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); - Doc.Layout(doc)._height = this.headerMargin + Math.max(height, NumCast(doc[this.props.fieldKey + "-height"])); + this.props.setHeight(this.headerMargin + height); } })); this.observer.observe(ref); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index a9438f8f7..639650b94 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -224,11 +224,14 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: e.stopPropagation(); return added; } - else if (de.complete.annoDragData && (!this.props.isAnnotationOverlay || de.complete.annoDragData.dragDocument === this.props.Document)) { - e.stopPropagation(); - de.complete.annoDragData.annotationDocument = de.complete.annoDragData.annotationDocCreator(); - de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(); - return de.complete.annoDragData.dropDocument && this.addDocument(de.complete.annoDragData.dropDocument); + else if (de.complete.annoDragData) { + const dropCreator = de.complete.annoDragData.dropDocCreator; + de.complete.annoDragData.dropDocCreator = () => { + const dropped = dropCreator(this.props.isAnnotationOverlay ? this.rootDoc : undefined); + this.addDocument(dropped); + return dropped; + } + return true; } return false; } @@ -253,11 +256,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: e.stopPropagation(); e.preventDefault(); - const addDocument = (doc: Doc | Doc[]) => { - const docs = doc instanceof Doc ? [doc] : doc; - docs.forEach(doc => Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", doc)); - return this.addDocument(doc); - }; + const addDocument = (doc: Doc | Doc[]) => this.addDocument(doc); if (html) { if (FormattedTextBox.IsFragment(html)) { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f305174f1..a217de193 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -153,12 +153,10 @@ export class CollectionView extends Touchable<CollectionViewProps> { const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { const pushpin = Docs.Create.FontIconDocument({ - title: "pushpin", label: "", + title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true, icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7", _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null) }); - pushpin.isPushpin = true; - Doc.GetProto(pushpin).annotationOn = doc.annotationOn; Doc.SetInPlace(doc, "annotationOn", undefined, true); Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin); const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", ""); @@ -191,6 +189,7 @@ export class CollectionView extends Touchable<CollectionViewProps> { const ind = (targetDataDoc[this.props.fieldKey] as List<Doc>).indexOf(doc); if (ind !== -1) { Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc); + doc.context = undefined; recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); } }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 3f8794665..f7e067399 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -382,7 +382,8 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { } } @computed get renderBounds() { - const bounds = this.props.tabView()?.ComponentView?.freeformData?.(true)?.bounds; + const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView; + const bounds = compView?.freeformData?.(true)?.bounds; if (!bounds) return undefined; const xbounds = bounds.r - bounds.x; const ybounds = bounds.b - bounds.y; @@ -419,6 +420,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { docViewPath={returnEmptyDoclist} childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. noOverlay={true} // don't render overlay Docs since they won't scale + setHeight={returnFalse} active={returnTrue} select={emptyFunction} dropAction={undefined} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 0852b2a46..e383cf176 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -101,13 +101,13 @@ export class TreeView extends React.Component<TreeViewProps> { @observable _dref: DocumentView | undefined | null; get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; } - get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.fileSysMode ? "data" : Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields"); } + get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.fileSysMode ? (this.doc.isFolder ? "data" : "layout") : Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields"); } get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs && !this.fileSysMode ? this.fieldKey : this.defaultExpandedView); } @computed get doc() { TraceMobx(); return this.props.document; } @computed get outlineMode() { return this.props.treeView.doc.treeViewType === "outline"; } @computed get fileSysMode() { return this.props.treeView.doc.treeViewType === "fileSystem"; } - @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; } + @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._overrideTreeViewOpen; } @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } @computed get dataDoc() { return this.doc[DataSym]; } @@ -123,7 +123,7 @@ export class TreeView extends React.Component<TreeViewProps> { const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined; return ((this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCastOrNull(this.doc[field])); // otherwise use the document's data field + DocListCastOrNull(this.doc[field]))?.filter(doc => !this.fileSysMode || field !== "aliases");// || Doc.GetT(doc, "context", Doc, true)); // otherwise use the document's data field } @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc); @@ -254,7 +254,7 @@ export class TreeView extends React.Component<TreeViewProps> { const before = pt[1] < rect.top + rect.height / 2; const inside = this.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); if (de.complete.linkDragData) { - const sourceDoc = de.complete.linkDragData.linkSourceDocument; + const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", ""); e.stopPropagation(); @@ -477,8 +477,8 @@ export class TreeView extends React.Component<TreeViewProps> { <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={action(() => { if (this.fileSysMode) { - this.doc.treeViewExpandedView = this.doc.isFolder ? this.fieldKey : this.treeViewExpandedView === "layout" ? "fields" : - this.treeViewExpandedView === "fields" ? "aliases" : "layout"; + this.doc.treeViewExpandedView = this.doc.isFolder ? this.fieldKey : this.treeViewExpandedView === "layout" ? "aliases" : + this.treeViewExpandedView === "aliases" && !Doc.UserDoc().noviceMode ? "fields" : "layout"; } else if (this.treeViewOpen) { this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView : this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields") : @@ -495,7 +495,11 @@ export class TreeView extends React.Component<TreeViewProps> { } showContextMenu = (e: React.MouseEvent) => simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); - contextMenuItems = () => this.doc.isFolder ? [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] : Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; + contextMenuItems = () => this.doc.isFolder ? + [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] : Doc.IsSystem(this.doc) ? [] : + this.fileSysMode && this.doc === Doc.GetProto(this.doc) ? + [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }] : + [{ script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }] truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, this.props.panelWidth()); onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4040362d8..af8ccb9c5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -264,7 +264,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)]; layoutDoc._width = NumCast(layoutDoc._width, 300); layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300); - !StrListCast(d.layers).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront + !StrListCast(d._layerTags).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront } this.updateGroupBounds(); @@ -275,16 +275,23 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @undoBatch internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) { - annoDragData.dropDocument!.x = xp - annoDragData.offset[0]; - annoDragData.dropDocument!.y = yp - annoDragData.offset[1]; - this.bringToFront(annoDragData.dropDocument!); + const dropCreator = annoDragData.dropDocCreator; + annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { + const dropDoc = dropCreator(annotationOn); + if (dropDoc) { + dropDoc.x = xp - annoDragData.offset[0]; + dropDoc.y = yp - annoDragData.offset[1]; + this.bringToFront(dropDoc); + } + return dropDoc || this.rootDoc; + } return true; } @undoBatch internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) { - if (linkDragData.linkSourceDocument === this.props.Document || this.props.Document.annotationOn) return false; - if (!linkDragData.linkSourceDocument.context || StrCast(Cast(linkDragData.linkSourceDocument.context, Doc, null)?.type) === DocumentType.COL) { + if (linkDragData.dragDocument === this.props.Document || this.props.Document.annotationOn) return false; + if (!linkDragData.dragDocument.context || StrCast(Cast(linkDragData.dragDocument.context, Doc, null)?.type) === DocumentType.COL) { // const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); // this.props.addDocument(source); // linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed @@ -292,7 +299,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } else { const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); this.props.addDocument?.(source); - de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed + de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed e.stopPropagation(); return true; } @@ -300,9 +307,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P onInternalDrop = (e: Event, de: DragManager.DropEvent) => { const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); - if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp); - if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); + else if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); + else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); return false; } @@ -429,8 +436,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P styleProp = colors[cluster % colors.length]; const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.filter(s => !StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); - set?.filter(s => StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); + set?.filter(s => !StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); + set?.filter(s => StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); } } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; return styleProp; @@ -869,7 +876,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action bringToFront = (doc: Doc, sendToBack?: boolean) => { - if (sendToBack || StrListCast(doc.layers).includes(StyleLayers.Background)) { + if (sendToBack || StrListCast(doc._layerTags).includes(StyleLayers.Background)) { doc.zIndex = 0; } else if (doc.isInkMask) { doc.zIndex = 5000; @@ -1363,7 +1370,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect); const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - let snappableDocs = activeDocs.filter(doc => !StrListCast(doc.layers).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + let snappableDocs = activeDocs.filter(doc => !StrListCast(doc._layerTags).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 9e442a8c8..2a30e5fd0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -167,7 +167,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.childLayoutString ? e.key : ""; FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch"); - this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0)); + this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0, this.props.isAnnotationOverlay ? this.props.Document : undefined)); e.stopPropagation(); } } @@ -359,11 +359,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { Doc.GetProto(doc).data = new List<Doc>(selected); Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; + !this.props.isAnnotationOverlay && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(doc)); doc._panX = doc._panY = 0; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newCollection.system = undefined; - newCollection.layers = new List<string>(layers); + newCollection._layerTags = new List<string>(layers); newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; newCollection._isGroup = makeGroup; @@ -652,7 +653,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque render() { return <div className="marqueeView" style={{ - overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : + overflow: //(!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : StrCast(this.props.Document._overflow), cursor: "hand" }} diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 58db080ad..e2feff5ed 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -235,7 +235,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { i, y, h, x: x + w > this.numCols ? 0 : x, // handles wrapping around of nodes when numCols decreases w: Math.min(w, this.numCols), // reduces width if greater than numCols - static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout.lockedPosition, false) // checks if the lock position item has been selected in the context menu + static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout._lockedPosition, false) // checks if the lock position item has been selected in the context menu })) : this.savedLayoutList.map((layout, index) => { Object.assign(layout, this.unflexedPosition(index)); return layout; }); } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index e51417f64..d23f3309e 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -217,8 +217,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={undefined} - docViewPath={returnEmptyDoclist} + layerProvider={this.props.layerProvider} + docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} @@ -257,7 +257,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)); + const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1)); const width = () => this.lookupPixels(layout); const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push( diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index d61a2bb72..641ae6ce1 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -217,8 +217,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={undefined} - docViewPath={returnEmptyDoclist} + layerProvider={this.props.layerProvider} + docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} @@ -257,19 +257,13 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)); + const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1)); const height = () => this.lookupPixels(layout); const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push( - <div - className={"document-wrapper"} - key={"wrapper" + i} - > + <div className={"document-wrapper"} key={"wrapper" + i} > {this.getDisplayDoc(layout, dxf, width, height)} - <HeightLabel - layout={layout} - collectionDoc={Document} - /> + <HeightLabel layout={layout} collectionDoc={Document} /> </div>, <ResizeBar height={resizerHeight} diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index d5b6a269e..715ec92f8 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -11,7 +11,7 @@ import { StrCast } from "../../../fields/Types"; import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch } from "../../util/UndoManager"; import { ViewBoxBaseComponent } from "../DocComponent"; -import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox } from "../InkingStroke"; +import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox, ActiveInkColor } from "../InkingStroke"; import "./ColorBox.scss"; import { FieldView, FieldViewProps } from './FieldView'; import { DocumentType } from "../../documents/DocumentTypes"; @@ -60,8 +60,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument style={{ width: `${100}%`, height: `${100}%` }} > <SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} - color={StrCast(ActiveInkPen()?.backgroundColor, - StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} /> + color={StrCast(selDoc?._backgroundColor, ActiveInkColor())} /> <div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}> <div> {ActiveInkWidth() ?? 2}</div> diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 7a8eb5628..02c112745 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -11,6 +11,7 @@ import { CollectionView } from "../collections/CollectionView"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; import { LabelBox } from "./LabelBox"; +import { EquationBox } from "./EquationBox"; import { SliderBox } from "./SliderBox"; import { LinkBox } from "./LinkBox"; import { ScriptingBox } from "./ScriptingBox"; @@ -109,6 +110,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo isSelected: (outsideReaction: boolean) => boolean, select: (ctrl: boolean) => void, scaling?: () => number, + setHeight: (height: number) => void, layoutKey: string, hideOnLeave?: boolean, }> { @@ -219,7 +221,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo blacklistedAttrs={emptyPath} renderInWrapper={false} components={{ - FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView, + FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 18cabc309..085ffae26 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -51,16 +51,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp if (this.props.InMenu && this.props.StartLink) { if (this._linkButton.current !== null) { const linkDrag = UndoManager.StartBatch("Drag Link"); - this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, { + this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, { dragComplete: dropEv => { if (this.props.View && dropEv.linkDocument) {// dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = "hyperlink"); - - // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks) - // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures - // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView - // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it. - dropEv.linkDragData?.linkDropCallback?.(dropEv as { linkDocument: Doc }); // bcz: typescript can't figure out that this is valid even though we tested dropEv.linkDocument above } linkDrag?.end(); }, @@ -106,7 +100,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp const alias = Doc.MakeAlias(otherdoc); alias.x = wid; alias.y = 0; - alias.lockedPosition = false; + alias._lockedPosition = false; wid += otherdoc[WidthSym](); Doc.AddDocToList(Doc.GetProto(target), "data", alias); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f07cbfbf5..cd61d20b1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; +import { action, computed, observable, runInAction, reaction, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; @@ -275,7 +275,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this._downX = touch.clientX; this._downY = touch.clientY; if (!e.nativeEvent.cancelBubble) { - if ((this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation(); + if ((this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation(); this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -290,7 +290,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (e.cancelBubble && this.active) { this.removeMoveListeners(); } - else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { const touch = me.touchEvent.changedTouches.item(0); if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { @@ -429,7 +429,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { let stopPropagate = true; let preventDefault = true; - !StrListCast(this.props.Document.layers).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); + !StrListCast(this.props.Document._layerTags).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (this._timeout) { clearTimeout(this._timeout); @@ -528,7 +528,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart) && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); @@ -596,7 +596,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document); @undoBatch toggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`); - @undoBatch toggleLockPosition = () => this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; + @undoBatch toggleLockPosition = () => this.Document._lockedPosition = this.Document._lockedPosition ? undefined : true; @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { @@ -607,13 +607,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps "Linking to document tabs not yet supported. Drop link on document content."); return; } - if (de.complete.annoDragData) de.complete.annoDragData.annotationDocument = de.complete.annoDragData.annotationDocCreator(); - const linkSource = de.complete.annoDragData ? de.complete.annoDragData.annotationDocument : de.complete.linkDragData ? de.complete.linkDragData.linkSourceDocument : undefined; - if (linkSource && linkSource !== this.props.Document) { + const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData; + if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor(); + if (linkdrag?.linkSourceDoc) { e.stopPropagation(); - const dropDoc = this._componentView?.getAnchor?.() || this.rootDoc; - if (de.complete.annoDragData) de.complete.annoDragData.dropDocument = dropDoc; - de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]); + if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) { + de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined); + } + if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) { + const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document; + de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]); + } } } @@ -687,7 +691,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (!this.Document.annotationOn) { const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); + this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document._lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document._lockedPosition) ? "unlock" : "lock" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); @@ -755,6 +759,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; + setHeight = (height: number) => this.rootDoc._height = height; setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; @observable contentsActive: () => boolean = returnFalse; @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive; @@ -780,6 +785,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps setContentView={this.setContentView} scaling={this.contentScaling} PanelHeight={this.panelHeight} + setHeight={this.setHeight} contentsActive={this.setContentsActive} parentActive={this.parentActive} ScreenToLocalTransform={this.screenToLocal} @@ -953,7 +959,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] : ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"])[highlightIndex]; const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex]; - let highlighting = !this.props.cantBrush && highlightIndex && ![DocumentType.FONTICON, DocumentType.INK].includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; + const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON]; + let highlighting = !this.props.cantBrush && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` : @@ -982,6 +989,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { public static ROOT_DIV = "documentView-effectsWrapper"; public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive public ContentRef = React.createRef<HTMLDivElement>(); + private _disposers: { [name: string]: IReactionDisposer } = {}; @observable public docView: DocumentViewInternal | undefined | null; @@ -997,30 +1005,40 @@ export class DocumentView extends React.Component<DocumentViewProps> { @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; } @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); } - @computed get nativeWidth() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); } - @computed get nativeHeight() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); } + @computed get nativeWidth() { + return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : + returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + } + @computed get nativeHeight() { + return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : + returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + } + shouldNotScale = () => this.layoutDoc._fitWidth || [CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._viewType as any); + @computed get effectiveNativeWidth() { return this.nativeWidth || (this.shouldNotScale() ? 0 : NumCast(this.layoutDoc.width)); } + @computed get effectiveNativeHeight() { return this.nativeHeight || (this.shouldNotScale() ? 0 : NumCast(this.layoutDoc.height)); } @computed get nativeScaling() { - if (this.nativeWidth && (this.layoutDoc?._fitWidth || this.props.PanelHeight() / this.nativeHeight > this.props.PanelWidth() / this.nativeWidth)) { - return this.props.PanelWidth() / this.nativeWidth; // width-limited or fitWidth + const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0; + if (this.effectiveNativeWidth && (this.layoutDoc?._fitWidth || this.props.PanelHeight() / this.effectiveNativeHeight > this.props.PanelWidth() / this.effectiveNativeWidth)) { + return Math.max(minTextScale, this.props.PanelWidth() / this.effectiveNativeWidth); // width-limited or fitWidth } - return this.nativeWidth && this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; // height-limited or unscaled + return this.effectiveNativeWidth && this.effectiveNativeHeight ? Math.max(minTextScale, this.props.PanelHeight() / this.effectiveNativeHeight) : 1; // height-limited or unscaled } - @computed get panelWidth() { return this.nativeWidth ? this.nativeWidth * this.nativeScaling : this.props.PanelWidth(); } + @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); } @computed get panelHeight() { - if (this.nativeHeight) { + if (this.effectiveNativeHeight) { return Math.min(this.props.PanelHeight(), this.props.Document._fitWidth ? - Math.max(NumCast(this.props.Document._height), NumCast(((this.props.Document.scrollHeight || 0) as number) * this.props.PanelWidth() / this.nativeWidth, this.props.PanelHeight())) : - this.nativeHeight * this.nativeScaling + Math.max(NumCast(this.props.Document._height), NumCast(((this.props.Document.scrollHeight || 0) as number) * this.props.PanelWidth() / this.effectiveNativeWidth, this.props.PanelHeight())) : + this.effectiveNativeHeight * this.nativeScaling ); } return this.props.PanelHeight(); } - @computed get Xshift() { return this.nativeWidth ? (this.props.PanelWidth() - this.nativeWidth * this.nativeScaling) / 2 : 0; } - @computed get YShift() { return this.nativeWidth && this.nativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.nativeScaling) / 2 : 0; } + @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } + @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } - @computed get centeringY() { return this.props.Document._fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.YShift; } + @computed get centeringY() { return this.props.Document._fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); contentsActive = () => this.docView?.contentsActive(); @@ -1067,8 +1085,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { docViewPathFunc = () => this.docViewPath; isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection); - NativeWidth = () => this.nativeWidth; - NativeHeight = () => this.nativeHeight; + NativeWidth = () => this.effectiveNativeWidth; + NativeHeight = () => this.effectiveNativeHeight; PanelWidth = () => this.panelWidth; PanelHeight = () => this.panelHeight; ContentScale = () => this.nativeScaling; @@ -1076,41 +1094,49 @@ export class DocumentView extends React.Component<DocumentViewProps> { screenToLocalTransform = () => { return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling); } - componentDidMount() { - !BoolCast(this.props.Document?.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this); + this._disposers.height = reaction( + () => NumCast(this.layoutDoc._height), + action(height => { + const docMax = NumCast(this.layoutDoc.docMaxAutoHeight); + if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height; + }) + ); + !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this); } componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); !this.props.dontRegisterView && DocumentManager.Instance.RemoveView(this); } render() { TraceMobx(); - const internalProps = { - ...this.props, - DocumentView: this.selfView, - viewPath: this.docViewPathFunc, - PanelWidth: this.PanelWidth, - PanelHeight: this.PanelHeight, - NativeWidth: this.NativeWidth, - NativeHeight: this.NativeHeight, - isSelected: this.isSelected, - select: this.select, - ContentScaling: this.ContentScale, - ScreenToLocalTransform: this.screenToLocalTransform, - focus: this.props.focus || emptyFunction, - bringToFront: emptyFunction, - }; + const xshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined; + const yshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; return (<div className="contentFittingDocumentView"> {!this.props.Document || !this.props.PanelWidth() ? (null) : ( <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef} style={{ position: this.props.Document.isInkMask ? "absolute" : undefined, transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) > 0.001 ? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth(), - height: this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.YShift) > 0.001 ? this.props.Document._fitWidth ? `${this.panelHeight}px` : `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(), + width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, + height: yshift ?? this.props.Document._fitWidth ? `${this.panelHeight}px` : + `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`, }}> - <DocumentViewInternal {...this.props} {...internalProps} ref={action((r: DocumentViewInternal | null) => this.docView = r)} /> + <DocumentViewInternal {...this.props} + DocumentView={this.selfView} + viewPath={this.docViewPathFunc} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} + NativeWidth={this.NativeWidth} + NativeHeight={this.NativeHeight} + isSelected={this.isSelected} + select={this.select} + ContentScaling={this.ContentScale} + ScreenToLocalTransform={this.screenToLocalTransform} + focus={this.props.focus || emptyFunction} + bringToFront={emptyFunction} + ref={action((r: DocumentViewInternal | null) => this.docView = r)} /> </div>)} </div>); } diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/client/views/nodes/EquationBox.scss diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx new file mode 100644 index 000000000..5bc73d5d9 --- /dev/null +++ b/src/client/views/nodes/EquationBox.tsx @@ -0,0 +1,82 @@ +import EquationEditor from 'equation-editor-react'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { documentSchema } from '../../../fields/documentSchemas'; +import { createSchema, makeInterface } from '../../../fields/Schema'; +import { StrCast, NumCast } from '../../../fields/Types'; +import { ViewBoxBaseComponent } from '../DocComponent'; +import { FieldView, FieldViewProps } from './FieldView'; +import './LabelBox.scss'; +import { Id } from '../../../fields/FieldSymbols'; +import { simulateMouseClick } from '../../../Utils'; +import { TraceMobx } from '../../../fields/util'; +import { reaction, action } from 'mobx'; +import { Docs } from '../../documents/Documents'; +import { LightboxView } from '../LightboxView'; + +const EquationSchema = createSchema({}); + +type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSchema]>; +const EquationDocument = makeInterface(EquationSchema, documentSchema); + +@observer +export class EquationBox extends ViewBoxBaseComponent<FieldViewProps, EquationDocument>(EquationDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); } + public static SelectOnLoad: string = ""; + _ref: React.RefObject<EquationEditor> = React.createRef(); + componentDidMount() { + if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { + this.props.select(false); + + this._ref.current!.mathField.focus(); + this._ref.current!.mathField.select(); + } + reaction(() => this.props.isSelected(), + selected => { + if (this._ref.current) { + if (selected) this._ref.current.element.current.children[0].addEventListener("keydown", this.keyPressed, true); + else this._ref.current.element.current.children[0].removeEventListener("keydown", this.keyPressed); + } + }, { fireImmediately: true }); + } + @action + keyPressed = (e: KeyboardEvent) => { + const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace("px", "")); + const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace("px", "")); + if (e.key === "Enter" || e.key === "Tab") { + const nextEq = Docs.Create.EquationDocument({ + title: "# math", text: StrCast(this.dataDoc.text), _width, _height: 25, + x: NumCast(this.layoutDoc.x) + (e.key === "Tab" ? _width + 10 : 0), y: NumCast(this.layoutDoc.y) + (e.key === "Enter" ? _height + 10 : 0) + }); + EquationBox.SelectOnLoad = nextEq[Id]; + this.props.addDocument?.(nextEq); + e.stopPropagation(); + } + if (e.key === "Backspace" && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc); + } + onChange = (str: string) => { + this.dataDoc.text = str; + const style = this._ref.current && getComputedStyle(this._ref.current.element.current); + if (style) { + const _height = Number(style.height.replace("px", "")); + const _width = Number(style.width.replace("px", "")); + this.layoutDoc._width = Math.max(35, _width); + this.layoutDoc._height = Math.max(25, _height); + } + } + render() { + TraceMobx(); + return (<div onPointerDown={e => !e.ctrlKey && e.stopPropagation()} + style={{ + pointerEvents: !this.props.isSelected() ? "none" : undefined, + }} + > + <EquationEditor ref={this._ref} + value={this.dataDoc.text || "y"} + spaceBehavesLikeTab={true} + onChange={this.onChange} + autoCommands="pi theta sqrt sum prod alpha beta gamma rho" + autoOperatorNames="sin cos tan" /></div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 1b4119210..465c18309 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -16,12 +16,13 @@ import { VideoBox } from "./VideoBox"; export interface FieldViewProps extends DocumentViewSharedProps { // FieldView specific props that are not part of DocumentView props fieldKey: string; - overflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further + scrollOverflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further active: (outsideReaction?: boolean) => boolean; select: (isCtrlPressed: boolean) => void; isSelected: (outsideReaction?: boolean) => boolean; scaling?: () => number; + setHeight: (height: number) => void; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) pointerEvents?: string; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index a3a3ec662..b25d78a1e 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -190,8 +190,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc } }); let newFacet: Opt<Doc>; - if (facetHeader === "text") {//} || facetValues.rtFields / allCollectionDocs.length > 0.1) { - newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, system: true, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true }); + if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) { + newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, system: true, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, _forceActive: true, ignoreClick: true }); Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox newFacet._textBoxPadding = 4; const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`; @@ -352,6 +352,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc fieldKey={this.props.fieldKey} CollectionView={undefined} cantBrush={true} + setHeight={returnFalse} // if the tree view can trigger the height of the filter box to change, then this needs to be filled in. onChildClick={this.suppressChildClick} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 121b9f26c..56c79cde9 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -50,7 +50,6 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />; const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu} style={{ - boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "", }}> <div className="menuButton-wrap"> diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4c3031ae2..240aa1659 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -62,13 +62,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD componentDidMount() { this._disposers.selection = reaction(() => this.props.isSelected(), - selected => { - if (!selected) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - } - }, - { fireImmediately: true }); + selected => !selected && setTimeout(() => { + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + })); this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), action(({ nativeSize, width }) => { if (!this.layoutDoc._height) { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index ebb953dad..83a49a393 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -67,6 +67,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { fieldKey: this.props.keyName, rootSelected: returnFalse, isSelected: returnFalse, + setHeight: returnFalse, select: emptyFunction, dropAction: "alias", bringToFront: emptyFunction, diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 87d5b07a2..6a7793ff0 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -59,9 +59,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument } @observable _mouseOver = false; - @computed get backColor() { return this.clicked || this._mouseOver ? StrCast(this.layoutDoc.hovercolor) : "unset"; } + @computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; } - @observable clicked = false; // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); @@ -70,15 +69,12 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument const label = typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); return ( <div className="labelBox-outerDiv" - onClick={action(() => this.clicked = !this.clicked)} onMouseLeave={action(() => this._mouseOver = false)} onMouseOver={action(() => this._mouseOver = true)} ref={this.createDropTarget} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}> <div className="labelBox-mainButton" style={{ - background: StrCast(this.layoutDoc.backgroundColor), - backgroundColor: this.backColor, - color: StrCast(this.layoutDoc.color, "inherit"), + backgroundColor: this.hoverColor, fontSize: StrCast(this.layoutDoc._fontSize) || "inherit", fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit", letterSpacing: StrCast(this.layoutDoc.letterSpacing), diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index d76b61502..392565402 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -94,7 +94,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch openLinkTargetOnRight = (e: React.MouseEvent) => { const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); alias.isLinkButton = undefined; - alias.layers = undefined; + alias._layerTags = undefined; alias.layoutKey = "layout"; this.props.addDocTab(alias, "add:right"); } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ac67949f9..6afc2258a 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -204,13 +204,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._disposers.selection = reaction(() => this.props.isSelected(), - selected => { - if (!selected) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - } - }, - { fireImmediately: true }); + selected => !selected && setTimeout(() => { + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + })); this._disposers.triggerVideo = reaction( () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined, time => time !== undefined && setTimeout(() => { @@ -485,7 +482,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD playing = () => this._playing; isActiveChild = () => this._isChildActive; - timelineWhenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(runInAction(() => this._isChildActive = isActive)); + timelineWhenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); timelineScreenToLocal = () => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight()); setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time; timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 2cd6f5f33..0cf052501 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -140,13 +140,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum runInAction(() => this._url = urlField?.url.toString() || ""); this._disposers.selection = reaction(() => this.props.isSelected(), - selected => { - if (!selected) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - } - }, - { fireImmediately: true }); + selected => !selected && setTimeout(() => { + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + })); document.addEventListener("pointerup", this.onLongPressUp); document.addEventListener("pointermove", this.onLongPressMove); diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index b39a845db..62f65cdae 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,18 +1,18 @@ -import { IReactionDisposer, observable, computed, action } from "mobx"; -import { Doc, DocListCast, Field, DataSym } from "../../../../fields/Doc"; +import { action, computed, IReactionDisposer, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as ReactDOM from 'react-dom'; +import { DataSym, Doc, DocListCast, Field } from "../../../../fields/Doc"; import { List } from "../../../../fields/List"; import { listSpec } from "../../../../fields/Schema"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { ComputedField } from "../../../../fields/ScriptField"; import { Cast, StrCast } from "../../../../fields/Types"; import { DocServer } from "../../../DocServer"; +import { DocUtils } from "../../../documents/Documents"; import { CollectionViewType } from "../../collections/CollectionView"; +import "./DashFieldView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import React = require("react"); -import * as ReactDOM from 'react-dom'; -import "./DashFieldView.scss"; -import { observer } from "mobx-react"; -import { DocUtils } from "../../../documents/Documents"; export class DashFieldView { _fieldWrapper: HTMLDivElement; // container for label and value diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx new file mode 100644 index 000000000..eff018635 --- /dev/null +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -0,0 +1,75 @@ +import EquationEditor from "equation-editor-react"; +import { IReactionDisposer } from "mobx"; +import { observer } from "mobx-react"; +import * as ReactDOM from 'react-dom'; +import { Doc } from "../../../../fields/Doc"; +import { StrCast } from "../../../../fields/Types"; +import "./DashFieldView.scss"; +import { FormattedTextBox } from "./FormattedTextBox"; +import React = require("react"); + +export class EquationView { + _fieldWrapper: HTMLDivElement; // container for label and value + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._fieldWrapper = document.createElement("div"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.fontWeight = "bold"; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline-block"; + this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + + ReactDOM.render(<EquationViewInternal + fieldKey={node.attrs.fieldKey} + width={node.attrs.width} + height={node.attrs.height} + tbox={tbox} + />, this._fieldWrapper); + (this as any).dom = this._fieldWrapper; + } + destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } + selectNode() { } +} + +interface IEquationViewInternal { + fieldKey: string; + tbox: FormattedTextBox; + width: number; + height: number; +} + +@observer +export class EquationViewInternal extends React.Component<IEquationViewInternal> { + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + _fieldKey: string; + + constructor(props: IEquationViewInternal) { + super(props); + this._fieldKey = this.props.fieldKey; + this._textBoxDoc = this.props.tbox.props.Document; + } + + componentWillUnmount() { this._reactionDisposer?.(); } + + render() { + return <div className="equationView" style={{ + position: "relative", + display: "inline-block", + width: this.props.width, + height: this.props.height, + }}> + <EquationEditor + value={StrCast(this._textBoxDoc[this._fieldKey], "y=")} + onChange={str => this._textBoxDoc[this._fieldKey] = str} + autoCommands="pi theta sqrt sum prod alpha beta gamma rho" + autoOperatorNames="sin cos tan" + spaceBehavesLikeTab={true} + /> + </div >; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b4fbda9f4..104d60fff 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -40,6 +40,7 @@ import { DashDocView } from "./RichTextSchema"; import { DashDocCommentView } from "./DashDocCommentView"; import { DashFieldView } from "./DashFieldView"; +import { EquationView } from "./EquationView"; import { SummaryView } from "./SummaryView"; import { OrderedListView } from "./OrderedListView"; import { FootnoteView } from "./FootnoteView"; @@ -47,7 +48,7 @@ import { FootnoteView } from "./FootnoteView"; import { schema } from "./schema_rts"; import { SelectionManager } from "../../../util/SelectionManager"; import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { CollectionFreeFormView, collectionFreeformViewProps } from '../../collections/collectionFreeForm/CollectionFreeFormView'; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from "../../DocComponent"; @@ -58,16 +59,13 @@ import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, findLinkMark } from './FormattedTextBoxComment'; import React = require("react"); import { CollectionStackingView } from '../../collections/CollectionStackingView'; -import { CollectionViewType } from '../../collections/CollectionView'; import { SnappingManager } from '../../../util/SnappingManager'; import { LinkDocPreview } from '../LinkDocPreview'; -import { SubCollectionViewProps } from '../../collections/CollectionSubView'; import { StyleProp } from '../../StyleProvider'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from '../../../util/DocumentManager'; import { LightboxView } from '../../LightboxView'; -import { DocAfterFocusFunc } from '../DocumentView'; const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { @@ -89,24 +87,38 @@ type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, da export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); + public static get DefaultLayout() { + return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); + } public static CanAnnotate = true; public static Instance: FormattedTextBox; public ProseRef?: HTMLDivElement; public get EditorView() { return this._editorView; } public get SidebarKey() { return this.fieldKey + "-sidebar"; } - private _boxRef: React.RefObject<HTMLDivElement> = React.createRef(); private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; private _applyingChange: string = ""; private _searchIndex = 0; + private _lastTimedMark: Mark | undefined = undefined; private _cachedLinks: Doc[] = []; private _undoTyping?: UndoManager.Batch; private _disposers: { [name: string]: IReactionDisposer } = {}; private _dropDisposer?: DragManager.DragDropDisposer; private _recordingStart: number = 0; private _ignoreScroll = false; + private _lastText = ""; + private _focusSpeed: Opt<number>; + private _keymap: any = undefined; + private _rules: RichTextRules | undefined; + @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } + @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } + @computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; } + @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); } + @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); } + @computed get sidebarHeight() { return NumCast(this.rootDoc[this.SidebarKey + "-height"]); } + @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } @computed get _recording() { return this.dataDoc?.audioState === "recording"; } set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } @@ -114,9 +126,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public static SelectOnLoad = ""; public static PasteOnLoad: ClipboardEvent | undefined; public static SelectOnLoadChar = ""; - public static IsFragment(html: string) { - return html.indexOf("data-pm-slice") !== -1; - } + public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } public static GetHref(html: string): string { const parser = new DOMParser(); const parsedHtml = parser.parseFromString(html, 'text/html'); @@ -127,24 +137,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return ""; } public static GetDocFromUrl(url: string) { - if (url.startsWith(document.location.origin)) { - const split = new URL(url).pathname.split("doc/"); - const docid = split[split.length - 1]; - return docid; - } - return ""; - } - - @undoBatch - public setFontColor(color: string) { - const view = this._editorView!; - if (view.state.selection.from === view.state.selection.to) return false; - if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { - this.layoutDoc.color = color; - } - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); - view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); - return true; + return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid } constructor(props: any) { @@ -154,8 +147,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._recordingStart = Date.now(); } - public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - // removes all hyperlink anchors for the removed linkDoc // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing. @@ -192,35 +183,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection"); - linkOnDeselect: Map<string, string> = new Map(); - - doLinkOnDeselect() { - - Array.from(this.linkOnDeselect.entries()).map(entry => { - const key = entry[0]; - const value = entry[1]; - - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - DocServer.GetRefField(value).then(doc => { - DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); - DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); - if (linkDoc) { - (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; - } else { - DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id); - } - }); - }); - }); - - this.linkOnDeselect.clear(); - } - @action setupAnchorMenu = () => { AnchorMenu.Instance.Status = "marquee"; - AnchorMenu.Instance.Highlight = action((color: string) => { + AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => { this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch); return undefined; }); @@ -231,55 +197,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); - const targetCreator = () => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100); + const targetCreator = (annotationOn?: Doc) => { + const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); FormattedTextBox.SelectOnLoad = target[Id]; return target; }; - DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, { - dragComplete: e => { - const anchor = this.makeLinkAnchor(undefined, "add:right", undefined, "a link"); - if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { - e.linkDocument = DocUtils.MakeLink({ doc: anchor }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note"); - e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.rootDoc; - } - e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument - } - }); + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, this.getAnchor, targetCreator), e.pageX, e.pageY); }); const coordsT = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom)); } - _lastText = ""; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); - if (metadata) { - const range = tx.selection.$from.blockRange(tx.selection.$to); - let text = range ? tx.doc.textBetween(range.start, range.end) : ""; - let textEndSelection = tx.selection.to; - for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } - text = text.substr(0, textEndSelection - range!.start); - text = text.split(" ")[text.split(" ").length - 1]; - const split = text.split("::"); - if (split.length > 1 && split[1]) { - const key = split[0]; - const value = split[split.length - 1]; - this.linkOnDeselect.set(key, value); - - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - const allAnchors = [{ href: Utils.prepend("/doc/" + id), title: value, anchorId: id }]; - const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, location: "add:right", title: value }); - const mval = this._editorView.state.schema.marks.metadataVal.create(); - const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); - tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); - this.dataDoc[key] = value; - } - } - const state = this._editorView.state.apply(tx); this._editorView.updateState(state); @@ -290,14 +222,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - let unchanged = true; const effectiveAcl = GetEffectiveAcl(this.dataDoc); - const removeSelection = (json: string | undefined) => { - return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); - }; + const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ? + json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { + let unchanged = true; if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { this._applyingChange = this.fieldKey; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); @@ -309,9 +240,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; - } - } else { // if we've deleted all the text in a note driven by a template, then restore the template data this.dataDoc[this.props.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); @@ -321,7 +250,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._applyingChange = ""; if (!unchanged) { this.updateTitle(); - this.tryUpdateHeight(); + this.tryUpdateScrollHeight(); } } } else { @@ -381,25 +310,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let node = this._editorView.state.doc; while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; - const titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); + this.dataDoc.title = "-" + str.substr(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); } } // needs a better API for taking in a set of words with target documents instead of just one target public hyperlinkTerms = (terms: string[], target: Doc) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - const tr = this._editorView.state.tr; - const flattened: TextSelection[] = []; - res.map(r => r.map(h => flattened.push(h))); - const lastSel = Math.min(flattened.length - 1, this._searchIndex); - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - const anchor = new Doc(); - const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; - const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }]; - const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, title: "a link", location }); - this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); + const res1 = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + let tr = this._editorView.state.tr; + const flattened1: TextSelection[] = []; + res1.map(r => r.map(h => flattened1.push(h))); + flattened1.forEach((flat, i) => { + const flattened: TextSelection[] = []; + const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + res.map(r => r.map(h => flattened.push(h))); + this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + const anchor = Docs.Create.TextanchorDocument(); + const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; + const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }]; + const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location }); + tr = tr.addMark(flattened[i].from, flattened[i].to, link); + }); + this._editorView.dispatch(tr); } } public highlightSearchTerms = (terms: string[], backward: boolean) => { @@ -460,7 +393,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @undoBatch @action - drop = async (e: Event, de: DragManager.DropEvent) => { + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = this.getAnchor; const dragData = de.complete.docDragData; if (dragData) { const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0]; @@ -483,22 +417,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); - this.tryUpdateHeight(); e.stopPropagation(); // } } // otherwise, fall through to outer collection to handle drop - } else if (de.complete.linkDragData) { - de.complete.linkDragData.linkDropCallback = this.linkDrop; - } - else if (de.complete.annoDragData) { - de.complete.annoDragData.linkDropCallback = this.linkDrop; } } - linkDrop = (data: { linkDocument: Doc }) => { - const anchor1Title = data.linkDocument.anchor1 instanceof Doc ? StrCast(data.linkDocument.anchor1.title) : "-untitled-"; - const anchor1 = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1 : undefined; - this.makeLinkAnchor(anchor1, "add:right", undefined, anchor1Title); - } getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { let offset = 0; @@ -523,7 +446,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - //Recursively finds matches within a given node findInNode(pm: EditorView, node: Node, find: string) { let ret: TextSelection[] = []; @@ -544,8 +466,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } return ret; } - static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; updateHighlights = () => { clearStyleSheetRules(FormattedTextBox._userStyleSheet); if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) { @@ -591,22 +513,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp })), false); } sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { - const bounds = this.CurrentDiv.getBoundingClientRect(); + const bounds = this._ref.current!.getBoundingClientRect(); this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; e.preventDefault(); return false; } - @undoBatch - @action - toggleNativeDimensions = () => { - alert("need to fix"); - // Doc.toggleNativeDimensions(this.layoutDoc, 1, this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0); - } - public static get DefaultLayout(): Doc | string | undefined { - return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); - } specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; @@ -680,78 +593,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); - !Doc.UserDoc().noviceMode && appearanceItems.push({ description: "Create progressivized slide...", event: this.progressivizeText, icon: "desktop" }); cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); const options = cm.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; - !Doc.UserDoc().noviceMode && optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); + optionItems.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); + optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - optionItems.push({ description: `${!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); this._downX = this._downY = Number.NaN; } - progressivizeText = () => { - const list = this.ProseRef?.getElementsByTagName("li"); - const mainBulletText: string[] = []; - const mainBulletList: Doc[] = []; - if (list) { - const newBullets: Doc[] = this.recursiveProgressivize(1, list)[0]; - mainBulletList.push.apply(mainBulletList, newBullets); - } - const title = Docs.Create.TextDocument(StrCast(this.rootDoc.title), { title: "Title", _width: 800, _height: 70, x: 20, y: -10, _fontSize: '20pt', backgroundColor: "rgba(0,0,0,0)", appearFrame: 0, _fontWeight: 700 }); - mainBulletList.push(title); - const doc = Docs.Create.FreeformDocument(mainBulletList, { - title: StrCast(this.rootDoc.title), - x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, - _width: 400, _height: 225, _fitToBox: true, - }); - this.props.addDocument?.(doc); - } - - recursiveProgressivize = (nestDepth: number, list: HTMLCollectionOf<HTMLLIElement>, d?: number, y?: number, before?: string): [Doc[], number] => { - const mainBulletList: Doc[] = []; - let b = d ? d : 0; - let yLoc = y ? y : 0; - let nestCount = 0; - let count: string = before ? before : ''; - const fontSize: string = (16 - (nestDepth * 2)) + 'pt'; - const xLoc: number = (nestDepth * 20); - const width: number = 390 - xLoc; - const height: number = 55 - (nestDepth * 5); - Array.from(list).forEach(listItem => { - const mainBullets: number = Number(listItem.getAttribute("data-bulletstyle")); - if (mainBullets === nestDepth) { - if (listItem.childElementCount > 1) { - b++; - nestCount++; - yLoc += height; - count = before ? count + nestCount + "." : nestCount + "."; - const text = listItem.getElementsByTagName("p")[0].innerText; - const length = text.length; - const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); - // yLoc += NumCast(bullet1._height); - mainBulletList.push(bullet1); - const newList = this.recursiveProgressivize(nestDepth + 1, listItem.getElementsByTagName("li"), b, yLoc, count); - mainBulletList.push.apply(mainBulletList, newList[0]); - yLoc += newList.length * (55 - ((nestDepth + 1) * 5)); - } else { - b++; - nestCount++; - yLoc += height; - count = before ? count + nestCount + "." : nestCount + "."; - const text = listItem.innerText; - const length = text.length; - const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); - // yLoc += NumCast(bullet1._height); - mainBulletList.push(bullet1); - } - } - }); - return [mainBulletList, yLoc]; - } - recordDictation = () => { DictationManager.Controls.listen({ interimHandler: this.setDictationContent, @@ -791,8 +643,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - _keymap: any = undefined; - _rules: RichTextRules | undefined; @computed get config() { this._keymap = buildKeymap(schema, this.props); this._rules = new RichTextRules(this.props.Document, this); @@ -846,7 +696,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; } - focusSpeed: Opt<number>; scrollFocus = (doc: Doc, smooth: boolean) => { const anchorId = doc[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { @@ -878,7 +727,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const ret = findAnchorFrag(editor.state.doc.content, editor); if (ret.frag.size > 2 && ret.start >= 0) { - smooth && (this.focusSpeed = 500); + smooth && (this._focusSpeed = 500); let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected @@ -886,23 +735,32 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); const escAnchorId = anchorId[0] >= '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId; addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" }); - setTimeout(() => this.focusSpeed = undefined, this.focusSpeed); - setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this.focusSpeed || 0, 1500)); + setTimeout(() => this._focusSpeed = undefined, this._focusSpeed); + setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 1500)); } } - return this.focusSpeed; + return this._focusSpeed; + } + + // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. + // Since we also monitor all component height changes, this will update the document's height. + resetNativeHeight = (scrollHeight: number) => { + const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); + this.rootDoc[this.fieldKey + "-height"] = scrollHeight + this.titleHeight; + if (nh) this.layoutDoc._nativeHeight = scrollHeight; } componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this.props.contentsActive?.(this.IsActive); this._cachedLinks = DocListCast(this.Document.links); - this._disposers.sidebarheight = reaction( - () => ({ annoHeight: NumCast(this.rootDoc[this.annotationKey + "-height"]), textHeight: NumCast(this.rootDoc[this.fieldKey + "-height"]) }), - ({ annoHeight, textHeight }) => { - this.layoutDoc._autoHeight && (this.rootDoc._height = Math.max(annoHeight, textHeight)); - }); + this._disposers.autoHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), + ({ width, autoHeight, scrollHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight) + ); + this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight }), + ({ sidebarHeight, textHeight, autoHeight }) => autoHeight && this.props.setHeight(Math.max(sidebarHeight, textHeight))); this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); @@ -930,7 +788,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const updatedState = JSON.parse(incomingValue.data.Data); if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); + this.tryUpdateScrollHeight(); } } else if (incomingValue?.str) { selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); @@ -957,21 +815,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - this._disposers.autoHeight = reaction( - () => ({ - width: NumCast(this.layoutDoc._width), - autoHeight: this.layoutDoc?._autoHeight - }), - ({ width, autoHeight }) => width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0) - ); - this._disposers.height = reaction( - () => Cast(this.layoutDoc._height, "number", null), - action(height => { - if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) { - this.layoutDoc._delayAutoHeight = height; - } - }) - ); this.setupEditor(this.config, this.props.fieldKey); @@ -1016,8 +859,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); quickScroll = undefined; - - setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight))); + this.tryUpdateScrollHeight(); } pushToGoogleDoc = async () => { @@ -1114,10 +956,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return text; } - sliceSingleNode(slice: Slice) { - return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null; - } - handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { const cbe = event as ClipboardEvent; const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin"); @@ -1180,7 +1018,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - private isActiveTab(el: Element | null | undefined) { + isActiveTab(el: Element | null | undefined) { while (el && el !== document.body) { if (getComputedStyle(el).display === "none") return false; el = el.parentNode as any; @@ -1188,7 +1026,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return true; } - private setupEditor(config: any, fieldKey: string) { + richTextMenuPlugin() { + const self = this; + return new Plugin({ + view(newView) { + runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); + return new RichTextMenuPlugin({ editorProps: this.props }); + } + }); + } + setupEditor(config: any, fieldKey: string) { const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); const rtfField = Cast((!curText?.Text && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField); if (this.ProseRef) { @@ -1202,8 +1049,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const scrollRef = self._scrollRef.current; if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) { const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale; - if (this.focusSpeed !== undefined) { - scrollPos && smoothScroll(this.focusSpeed, scrollRef, scrollPos); + if (this._focusSpeed !== undefined) { + scrollPos && smoothScroll(this._focusSpeed, scrollRef, scrollPos); } else { scrollRef.scrollTo({ top: scrollPos }); } @@ -1215,6 +1062,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, + equation(node, view, getPos) { return new EquationView(node, view, getPos, self); }, summary(node, view, getPos) { return new SummaryView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(); }, footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } @@ -1254,18 +1102,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; } } - getFont(font: string) { - switch (font) { - case "Arial": return schema.marks.arial.create(); - case "Times New Roman": return schema.marks.timesNewRoman.create(); - case "Georgia": return schema.marks.georgia.create(); - case "Comic Sans MS": return schema.marks.comicSans.create(); - case "Tahoma": return schema.marks.tahoma.create(); - case "Impact": return schema.marks.impact.create(); - case "ACrimson Textrial": return schema.marks.crimson.create(); - } - return schema.marks.arial.create(); - } componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); @@ -1308,7 +1144,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } this._downX = e.clientX; this._downY = e.clientY; - this.doLinkOnDeselect(); this._downEvent = true; FormattedTextBoxComment.textBox = this; if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { @@ -1331,7 +1166,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onPointerUp = (e: React.PointerEvent): void => { - FormattedTextBox.CanAnnotate = true; if (!this._editorView?.state.selection.empty && FormattedTextBox.CanAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; @@ -1339,7 +1173,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); - const target = (e.target as any).parentElement; // hrefs are store don the database of the <a> node that wraps the hyerlink <span> + let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> + while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs); } (e.nativeEvent as any).formattedHandled = true; @@ -1351,7 +1186,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action onDoubleClick = (e: React.MouseEvent): void => { - this.doLinkOnDeselect(); FormattedTextBoxComment.textBox = this; if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar @@ -1373,7 +1207,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action onFocused = (e: React.FocusEvent): void => { FormattedTextBox.FocusedBox = this; - this.tryUpdateHeight(); //applyDevTools.applyDevTools(this._editorView); // see if we need to preserve the insertion point @@ -1495,18 +1328,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - menuPlugin: any; - - richTextMenuPlugin() { - const self = this; - return new Plugin({ - view(newView) { - runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); - return self.menuPlugin = new RichTextMenuPlugin({ editorProps: this.props }); - } - }); - } - public startUndoTypingBatch() { !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); } @@ -1525,7 +1346,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; this.endUndoTypingBatch(); - this.doLinkOnDeselect(); FormattedTextBox.LiveTextUndo?.end(); FormattedTextBox.LiveTextUndo = undefined; @@ -1544,7 +1364,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - _lastTimedMark: Mark | undefined = undefined; onKeyDown = (e: React.KeyboardEvent) => { // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container) if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) { @@ -1573,9 +1392,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); } else { if (e.key === "Tab" || e.key === "Enter") { - if (e.key === "Enter") { - this.insertTime(); - } + if (e.key === "Enter") this.insertTime(); e.preventDefault(); } if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) { @@ -1598,99 +1415,73 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } - - @action - tryUpdateHeight(limitHeight?: number) { - let scrollHeight = this.ProseRef?.scrollHeight || 0; - if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - scrollHeight = scrollHeight * NumCast(this.layoutDoc._viewScale, 1); - if (limitHeight && scrollHeight > limitHeight) { - scrollHeight = limitHeight; - this.layoutDoc.limitHeight = undefined; - this.layoutDoc._autoHeight = false; - } - const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); - const dh = NumCast(this.rootDoc._height); - const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + this.titleHeight); - if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) { - // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... - console.log("Delayed height adjustment..."); - setTimeout(() => { - this.rootDoc._height = newHeight; - this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined; - }, 10); - } else { - try { - const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", "")) * NumCast(this.Document._viewScale, 1); - const outer = this.rootDoc[HeightSym]() - boxHeight - this.titleHeight; - this.rootDoc[this.fieldKey + "-height"] = newHeight + Math.max(0, outer); - } catch (e) { console.log("Error in tryUpdateHeight"); } - } - } //else this.rootDoc[this.fieldKey + "-height"] = 0; + tryUpdateScrollHeight() { + const proseHeight = this.ProseRef?.scrollHeight || 0; + const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); + if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; + if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { + setScrollHeight(); + } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... + } } + fitToBox = () => this.props.Document._fitToBox; + sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + sidebarAddDocument = (doc: Doc | Doc[]) => this.addDocument(doc, this.SidebarKey); + sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); + sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); + setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height; + sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); + sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); + @computed get audioHandle() { - return !this.layoutDoc._showAudio ? (null) : - <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} > - <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" /> - </div>; + return <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} > + <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" /> + </div>; } - @computed get sidebarHandle() { const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; - return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) : - <div className="formattedTextBox-sidebar-handle" - style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }} - onPointerDown={this.sidebarDown} - />; + return <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} + style={{ + left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, + background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")) + }} />; } - - sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); - fitToBox = () => this.props.Document._fitToBox; - sidebarAddDocument = (doc: Doc | Doc[]) => this.addDocument(doc, this.SidebarKey); - sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); - sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); @computed get sidebarCollection() { - const collectionProps: SubCollectionViewProps & collectionFreeformViewProps = { - ...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit, - NativeWidth: returnZero, - NativeHeight: returnZero, - PanelHeight: this.props.PanelHeight, - PanelWidth: this.sidebarWidth, - xMargin: 0, - yMargin: 0, - chromeStatus: "enabled", - scaleField: this.SidebarKey + "-scale", - isAnnotationOverlay: false, - fieldKey: this.SidebarKey, - fitContentsToDoc: this.fitToBox, - select: emptyFunction, - active: this.annotationsActive, - scaling: this.sidebarContentScaling, - whenActiveChanged: this.whenActiveChanged, - removeDocument: this.sidebarRemDocument, - moveDocument: this.sidebarMoveDocument, - addDocument: this.sidebarAddDocument, - CollectionView: undefined, - ScreenToLocalTransform: this.sidebarScreenToLocal, - renderDepth: this.props.renderDepth + 1, + const renderComponent = (tag: string) => { + const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView; + return <ComponentTag + {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} + NativeWidth={returnZero} + NativeHeight={returnZero} + PanelHeight={this.props.PanelHeight} + PanelWidth={this.sidebarWidth} + xMargin={0} + yMargin={0} + chromeStatus={"enabled"} + scaleField={this.SidebarKey + "-scale"} + isAnnotationOverlay={false} + setHeight={this.setSidebarHeight} + fitContentsToDoc={this.fitToBox} + select={emptyFunction} + active={this.annotationsActive} + scaling={this.sidebarContentScaling} + whenActiveChanged={this.whenActiveChanged} + removeDocument={this.sidebarRemDocument} + moveDocument={this.sidebarMoveDocument} + addDocument={this.sidebarAddDocument} + CollectionView={undefined} + ScreenToLocalTransform={this.sidebarScreenToLocal} + renderDepth={this.props.renderDepth + 1} + noSidebar={true} + fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : this.SidebarKey} />; }; - return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : - <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} - style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - {this.layoutDoc.sidebarViewType === "translation" ? - <FormattedTextBox {...collectionProps} noSidebar={true} fieldKey={`${this.fieldKey}-translation`} /> : - this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? - <CollectionFreeFormView {...collectionProps} /> : - <CollectionStackingView {...collectionProps} />} - </div>; + return <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} + style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> + {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} + </div>; } - - @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); - @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } render() { TraceMobx(); const selected = this.props.isSelected(); @@ -1705,19 +1496,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0); const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : ""; return ( - <div className="formattedTextBox-cont" ref={this._boxRef} + <div className="formattedTextBox-cont" style={{ transform: `scale(${scale})`, transformOrigin: "top left", width: `${100 / scale}%`, height: `${100 / scale}%`, - overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, + // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, ...this.styleFromLayoutString(scale) // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > }}> <div className={`formattedTextBox-cont`} ref={this._ref} style={{ - overflow: this.layoutDoc._autoHeight ? "hidden" : undefined, - height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined), + overflow: this.autoHeight ? "hidden" : undefined, + height: this.props.height || (this.autoHeight && this.props.renderDepth ? "max-content" : undefined), background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)), color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)), pointerEvents: interactive ? undefined : "none", @@ -1751,9 +1542,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }} /> </div> - {this.sidebarCollection} - {this.sidebarHandle} - {this.audioHandle} + {(this.props.noSidebar || this.Document._noSidebar) || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection} + {(this.props.noSidebar || this.Document._noSidebar) || this.Document._singleLine ? (null) : this.sidebarHandle} + {!this.layoutDoc._showAudio ? (null) : this.audioHandle} </div> </div> ); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 15c669338..6821935a0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -85,7 +85,7 @@ export class FormattedTextBoxComment { static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "") { FormattedTextBoxComment.textBox = textBox; if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { - FormattedTextBoxComment.setupPreview(view, textBox, hrefs ? hrefs.trim().split(" ") : undefined); + FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ")); } } diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 243cfc6de..aa51a3a64 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -144,6 +144,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind("Alt-\\", setBlockType(schema.nodes.paragraph)); bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block)); + bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { + dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))); + }) + for (let i = 1; i <= 6; i++) { bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i })); } @@ -255,36 +259,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey // bind("Mod-Enter", cmd); bind("Shift-Enter", cmd); - - bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { - const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { - return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); - }); - - const path = (state.doc.resolve(state.selection.from - 1) as any).path; - - const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; - - const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator; - - if (anchor >= 0) { - - const textsel = TextSelection.create(state.doc, anchor, range!.end); - - const text = range ? state.doc.textBetween(textsel.from, textsel.to) : ""; - - let whitespace = text.length - 1; - - for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } - if (text.endsWith(":")) { - dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any). - addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any)); - } - } - - return false; - }); - return keys; } diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 5d9c8b56d..3bd41fa7d 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -242,6 +242,19 @@ export const nodes: { [index: string]: NodeSpec } = { } }, + equation: { + inline: true, + attrs: { + fieldKey: { default: "" }, + }, + group: "inline", + draggable: false, + toDOM(node) { + const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; + return ["div", { ...node.attrs, ...attrs }]; + } + }, + video: { inline: true, attrs: { diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d1fdc6c44..e3d14d620 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -43,7 +43,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @observable public Status: "marquee" | "annotation" | "" = ""; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined; + public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined; public Delete: () => void = unimplementedFunction; public AddTag: (key: string, value: string) => boolean = returnFalse; public PinToPres: () => void = unimplementedFunction; @@ -76,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Highlight(this.highlightColor) && this.Pinned) { + if (!this.Highlight(this.highlightColor, false) && this.Pinned) { this.Highlighting = !this.Highlighting; } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index ca6dc87ae..720d2d92e 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -123,6 +123,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this.props.startupLive && this.setupPdfJsViewer(); this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0); + this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, + () => { + this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.fieldKey + "-nativeHeight"]); + this.props.setHeight(NumCast(this.props.Document[this.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); + }); + this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc), m => { if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), m.searchMatch > 0); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 553443931..c82d03fce 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -565,7 +565,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc removeDocument={returnFalse} PanelHeight={this.open ? this.returnHeight : returnZero} PanelWidth={this.open ? this.returnLength : returnZero} - overflow={length > window.innerWidth || this.children > 6 ? true : false} + scrollOverflow={length > window.innerWidth || this.children > 6 ? true : false} focus={this.selectElement} ScreenToLocalTransform={Transform.Identity} /> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 69d51e545..05baacf80 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -496,12 +496,10 @@ export namespace Doc { Doc.SetLayout(alias, Doc.MakeAlias(layout)); } alias.aliasOf = doc; - if (doc !== Doc.GetProto(doc)) { - alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`); - } + alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`); alias.author = Doc.CurrentUserEmail; - Doc.AddDocToList(doc[DataSym], "aliases", alias); + Doc.AddDocToList(Doc.GetProto(doc)[DataSym], "aliases", alias); return alias; } @@ -762,6 +760,13 @@ export namespace Doc { } }); copy.author = Doc.CurrentUserEmail; + if (copyProto) { + Doc.GetProto(copy).context = undefined; + Doc.GetProto(copy).aliases = new List<Doc>([copy]); + } else { + Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy); + } + copy.context = undefined; Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); return copy; } @@ -774,6 +779,7 @@ export namespace Doc { const delegate = new Doc(id, true); delegate.proto = doc; delegate.author = Doc.CurrentUserEmail; + if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate); title && (delegate.title = title); return delegate; } @@ -1114,14 +1120,12 @@ export namespace Doc { export function isDocPinned(doc: Doc) { //add this new doc to props.Document const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; - if (curPres) { - return DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; - } - return false; + return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; } export function copyDragFactory(dragFactory: Doc) { const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); + ndoc && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(ndoc)); if (ndoc && dragFactory["dragFactory-count"] !== undefined) { dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts index e0fc5902b..78f8a6bfb 100644 --- a/src/fields/Schema.ts +++ b/src/fields/Schema.ts @@ -3,6 +3,7 @@ import { Doc, Field } from "./Doc"; import { ObjectField } from "./ObjectField"; import { RefField } from "./RefField"; import { SelfProxy } from "./FieldSymbols"; +import { List } from "./List"; type AllToInterface<T extends Interface[]> = { 1: ToInterface<Head<T>> & AllToInterface<Tail<T>>, @@ -67,9 +68,9 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu return function (doc?: Doc | Doc[]) { if (doc instanceof Doc || doc === undefined) { return fn(doc || new Doc); - } else { - if (!doc instanceof Promise) return doc.map(fn); - } + } else if (doc instanceof List) { + return doc.map(fn); + } else return {}; }; } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index c949e0791..9345ecde5 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -55,8 +55,8 @@ async function deserializeScript(script: ScriptField) { if (script.script.originalScript === 'convertToButtons(dragData)') { return (script as any).script = (ScriptField.ConvertToButtons ?? (ScriptField.ConvertToButtons = ComputedField.MakeFunction('convertToButtons(dragData)', { dragData: "DocumentDragData" })))?.script; } - if (script.script.originalScript === 'self.userDoc.noviceMode') { - return (script as any).script = (ScriptField.NoviceMode ?? (ScriptField.NoviceMode = ComputedField.MakeFunction('self.userDoc.noviceMode')))?.script; + if (script.script.originalScript === 'IsNoviceMode()') { + return (script as any).script = (ScriptField.NoviceMode ?? (ScriptField.NoviceMode = ComputedField.MakeFunction('IsNoviceMode()')))?.script; } if (script.script.originalScript === `selectMainMenu(self)`) { return (script as any).script = (ScriptField.SelectMenu ?? (ScriptField.SelectMenu = ComputedField.MakeFunction('selectMainMenu(self)')))?.script; diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 6973079b0..4d8b6b55c 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -21,7 +21,6 @@ export const documentSchema = createSchema({ _currentTimecode: "number", // current play back time of a temporal document (video / audio) _timecodeToShow: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) _timecodeToHIde: "number", // the time that a document should be hidden - isLabel: "boolean", // whether the document is a label or not (video / audio) markers: listSpec(Doc), // list of markers for audio / video x: "number", // x coordinate when in a freeform view y: "number", // y coordinate when in a freeform view @@ -96,7 +95,7 @@ export const documentSchema = createSchema({ isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked layers: listSpec("string"), // which layers the document is part of - lockedPosition: "boolean", // whether the document can be moved (dragged) + _lockedPosition: "boolean", // whether the document can be moved (dragged) _lockedTransform: "boolean",// whether a freeformview can pan/zoom // drag drop properties diff --git a/src/fields/util.ts b/src/fields/util.ts index b9c5a13c1..36c765dd0 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -280,16 +280,16 @@ export function setter(target: any, in_prop: string | symbol | number, value: an let prop = in_prop; const effectiveAcl = getPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true; - // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "None"].includes(value))) return true; // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true; - if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { - if (!prop.startsWith("_")) { - console.log(prop + " is deprecated - switch to _" + prop); - prop = "_" + prop; - } + if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) { + // if (!prop.startsWith("_")) { + // console.log(prop + " is deprecated - switch to _" + prop); + // prop = "_" + prop; + // } + if (!prop.startsWith("__")) prop = prop.substring(1); if (target.__LAYOUT__) { target.__LAYOUT__[prop] = value; return true; @@ -308,12 +308,14 @@ export function getter(target: any, in_prop: string | symbol | number, receiver: if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop]; if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; if (prop === LayoutSym) return target.__LAYOUT__; - if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { - if (!prop.startsWith("_")) { - console.log(prop + " is deprecated - switch to _" + prop); - prop = "_" + prop; - } - if (target.__LAYOUT__) return target.__LAYOUT__[prop]; + let search = false; + if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) { + // if (!prop.startsWith("_")) { + // console.log(prop + " is deprecated - switch to _" + prop); + // prop = "_" + prop; + // } + if (!prop.startsWith("__")) search = true; + if (target.__LAYOUT__) return target.__LAYOUT__[prop] ?? (search ? target.__LAYOUT__[prop.substring(1)] : undefined); } if (prop === "then") {//If we're being awaited return undefined; @@ -324,7 +326,7 @@ export function getter(target: any, in_prop: string | symbol | number, receiver: if (SerializationHelper.IsSerializing()) { return target[prop]; } - return getFieldImpl(target, prop, receiver); + return (search ? getFieldImpl(target, (prop as any as string).substring(1), receiver) : undefined) ?? getFieldImpl(target, prop, receiver); } function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any { diff --git a/src/server/database.ts b/src/server/database.ts index 41bf8b3da..2a55c14de 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -163,7 +163,7 @@ export namespace Database { } public async insert(value: any, collectionName = DocumentsCollection) { - if (this.db) { + if (this.db && value !== null) { if ("id" in value) { value._id = value.id; delete value.id; @@ -185,7 +185,7 @@ export namespace Database { newProm = prom ? prom.then(run) : run(); this.currentWrites[id] = newProm; return newProm; - } else { + } else if (value !== null) { this.onConnect.push(() => this.insert(value, collectionName)); } } diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 7e3c4da82..4ae97913f 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -277,7 +277,11 @@ export namespace WebSocket { function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void { diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet;// convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; - const newListItems = diff.diff.$set[updatefield].fields; + const newListItems = diff.diff.$set[updatefield]?.fields; + if (!newListItems) { + console.log("Error: addToListField - no new list items"); + return; + } const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || []; diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; |