diff options
| author | bobzel <zzzman@gmail.com> | 2023-05-10 10:08:50 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-05-10 10:08:50 -0400 |
| commit | ebb846116af9c7e65a9d674c765c71c0bf0a7d29 (patch) | |
| tree | d8278e7c27f7577eb5b4706604144940d12a718e /src/client/documents | |
| parent | f912a233a89c8772b22b71d34830ff4b0ba82310 (diff) | |
| parent | 719da9462f02fd3afda9b0b65de19de9405ab4fc (diff) | |
Merge branch 'master' into UI_Update_Eric_Ma
Diffstat (limited to 'src/client/documents')
| -rw-r--r-- | src/client/documents/Documents.ts | 161 | ||||
| -rw-r--r-- | src/client/documents/Gitlike.ts | 226 |
2 files changed, 198 insertions, 189 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 457811e26..5a7894c08 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -10,8 +10,8 @@ import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; -import { Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; -import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; +import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; import { inheritParentAcls, SharingPermissions } from '../../fields/util'; import { Upload } from '../../server/SharedMediaTypes'; import { aggregateBounds, OmitKeys, Utils } from '../../Utils'; @@ -21,6 +21,7 @@ import { Networking } from '../Network'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager, dropActionType } from '../util/DragManager'; import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox'; +import { FollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { undoBatch, UndoManager } from '../util/UndoManager'; @@ -36,7 +37,7 @@ import { FontIconBox } from '../views/nodes/button/FontIconBox'; import { ColorBox } from '../views/nodes/ColorBox'; import { ComparisonBox } from '../views/nodes/ComparisonBox'; import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; -import { DocFocusOptions, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { OpenWhereMod } from '../views/nodes/DocumentView'; import { EquationBox } from '../views/nodes/EquationBox'; import { FieldViewProps } from '../views/nodes/FieldView'; import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; @@ -67,14 +68,16 @@ class EmptyBox { return ''; } } -export abstract class FInfo { +export class FInfo { description: string = ''; - fieldType?: string; + readOnly: boolean = false; + fieldType?: string = ''; values?: Field[]; // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string - constructor(d: string) { + constructor(d: string, readOnly?: boolean) { this.description = d; + this.readOnly = readOnly ?? false; } } class BoolInfo extends FInfo { @@ -84,16 +87,16 @@ class BoolInfo extends FInfo { class NumInfo extends FInfo { fieldType? = 'number'; values?: number[] = []; - constructor(d: string, values?: number[]) { - super(d); + constructor(d: string, readOnly?: boolean, values?: number[]) { + super(d, readOnly); this.values = values; } } class StrInfo extends FInfo { fieldType? = 'string'; values?: string[] = []; - constructor(d: string, values?: string[]) { - super(d); + constructor(d: string, readOnly?: boolean, values?: string[]) { + super(d, readOnly); this.values = values; } } @@ -101,21 +104,28 @@ class DocInfo extends FInfo { fieldType? = 'Doc'; values?: Doc[] = []; constructor(d: string, values?: Doc[]) { - super(d); + super(d, true); this.values = values; } } class DimInfo extends FInfo { fieldType? = 'DimUnit'; values? = [DimUnit.Pixel, DimUnit.Ratio]; + readOnly = true; } class PEInfo extends FInfo { fieldType? = 'pointerEvents'; values? = ['all', 'none']; + readOnly = true; } class DAInfo extends FInfo { fieldType? = 'dropActionType'; values? = ['alias', 'copy', 'move', 'same', 'proto', 'none']; + readOnly = true; +} +class DateInfo extends FInfo { + fieldType? = 'date'; + values?: DateField[] = []; } type BOOLt = BoolInfo | boolean; type NUMt = NumInfo | number; @@ -124,15 +134,17 @@ type DOCt = DocInfo | Doc; type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; type PEVt = PEInfo | 'none' | 'all'; type DROPt = DAInfo | dropActionType; +type DATEt = DateInfo | number; export class DocumentOptions { x?: NUMt = new NumInfo('x coordinate of document in a freeform view'); y?: NUMt = new NumInfo('y coordinage of document in a freeform view'); - z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', [1, 0]); - system?: BOOLt = new BoolInfo('is this a system created/owned doc'); - type?: STRt = new StrInfo('type of document', Array.from(Object.keys(DocumentType))); + z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, [1, 0]); + system?: BOOLt = new BoolInfo('is this a system created/owned doc', true); + type?: STRt = new StrInfo('type of document', true, Array.from(Object.keys(DocumentType))); title?: string; + creationDate?: DATEt = new DateInfo('date the document was created', true); _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); - allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?'); + allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true); 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 ??? '); userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); @@ -146,6 +158,8 @@ export class DocumentOptions { _panY?: NUMt = new NumInfo('vertical pan location of a freeform view'); _width?: NUMt = new NumInfo('displayed width of a document'); _height?: NUMt = new NumInfo('displayed height of document'); + 'data-nativeWidth'?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)'); + 'data-nativeHeight'?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)'); _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)'); _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)'); _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers'); @@ -159,6 +173,7 @@ export class DocumentOptions { _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) + _showAltContentUI?: boolean; // whether to show alternate content button _isLightbox?: boolean; // whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs @@ -166,6 +181,7 @@ export class DocumentOptions { _chromeHidden?: boolean; // whether the editing chrome for a document is hidden _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) + enableDragWhenActive?: boolean; // allow dragging even if document contentts are active (e.g., tree, groups) _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 @@ -195,11 +211,11 @@ export class DocumentOptions { _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'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection'); - '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection'); - 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view'); - 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view'); - 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)'); + '_carousel-caption-xMargin'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection', true); + '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection', true); + 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view', true); + 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view', true); + 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true); openFactoryLocation?: string; // an OpenWhere value to place the factory created document openFactoryAsDelegate?: boolean; // lat?: number; @@ -221,6 +237,8 @@ export class DocumentOptions { recording?: boolean; // whether WebCam is recording or not autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. + linkSource?: Doc; // the source document for a collection of backlinks + updateContentsScript?: ScriptField; // reactive script invoked when viewing a document that can update contents of a collection (or do anything) toolTip?: string; // tooltip to display on hover toolType?: string; // type of pen tool expertMode?: boolean; // something available only in expert (not novice) mode @@ -228,7 +246,8 @@ export class DocumentOptions { contextMenuScripts?: List<ScriptField>; contextMenuLabels?: List<string>; contextMenuIcons?: List<string>; - allowClickBeforeDoubleClick?: boolean; // whether a click function can fire before the timeout for a double click has expired + defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen + waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait 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 layout?: string | Doc; // default layout string for a document @@ -245,6 +264,7 @@ export class DocumentOptions { childContextMenuIcons?: List<string>; followLinkZoom?: boolean; // whether to zoom to the target of a link hideLinkButton?: boolean; // whether the blue link counter button should be hidden + disableDocBrushing?: boolean; // whether to suppress border highlighting hideDecorationTitle?: boolean; hideOpenButton?: boolean; hideResizeHandles?: boolean; @@ -258,8 +278,8 @@ export class DocumentOptions { caption?: RichTextField; opacity?: number; defaultBackgroundColor?: string; - _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint + hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed isFolder?: boolean; 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 @@ -309,6 +329,7 @@ export class DocumentOptions { linearViewExpandable?: boolean; // can linear view be expanded linearViewToggleButton?: string; // button to open close linear view group linearViewSubMenu?: boolean; + linearBtnWidth?: number; flexGap?: number; // Linear view flex gap flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse'; @@ -407,6 +428,7 @@ export namespace Docs { nativeDimModifiable: true, nativeHeightUnfrozen: true, forceReflow: true, + defaultDoubleClick: 'ignore', }, }, ], @@ -435,7 +457,7 @@ export namespace Docs { DocumentType.WEB, { layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true }, + options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, waitForDoubleClickToClick: 'always' }, }, ], [ @@ -463,7 +485,7 @@ export namespace Docs { DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, forceReflow: true, nativeDimModifiable: true }, + options: { _height: 100, fitWidth: true, forceReflow: true, nativeDimModifiable: true }, }, ], [ @@ -500,12 +522,13 @@ export namespace Docs { layout: { view: LinkBox, dataField: defaultDataKey }, options: { childDontRegisterViews: true, - _isLinkButton: true, + onClick: FollowLinkScript(), + hideLinkAnchors: true, _height: 150, description: '', showCaption: 'description', backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area - _removeDropProperties: new List(['isLinkButton']), + _removeDropProperties: new List(['onClick']), }, }, ], @@ -577,14 +600,14 @@ export namespace Docs { DocumentType.PRES, { layout: { view: PresBox, dataField: defaultDataKey }, - options: {}, + options: { defaultDoubleClick: 'ignore', hideLinkAnchors: true }, }, ], [ DocumentType.FONTICON, { - layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%' }, + layout: { view: FontIconBox, dataField: 'icon' }, + options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', enableDragWhenActive: true, hideLinkButton: true, _width: 40, _height: 40 }, }, ], [ @@ -761,7 +784,6 @@ export namespace Docs { ...(template.options || {}), layout: layout.view?.LayoutString(layout.dataField), data: template.data, - layout_keyValue: KeyValueBox.LayoutString(''), }; Object.entries(options).map(pair => { if (typeof pair[1] === 'string' && pair[1].startsWith('@')) { @@ -927,13 +949,13 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } - export function LinkDocument(source: { doc: Doc; ctx?: Doc }, target: { doc: Doc; ctx?: Doc }, options: DocumentOptions = {}, id?: string) { + export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) { const linkDoc = InstanceFromProto( Prototypes.get(DocumentType.LINK), undefined, { - anchor1: source.doc, - anchor2: target.doc, + anchor1: source, + anchor2: target, ...options, }, id @@ -983,6 +1005,7 @@ export namespace Docs { I.author = Doc.CurrentUserEmail; I.rotation = 0; I.data = new InkField(points); + I.defaultDoubleClick = 'click'; I.creationDate = new DateField(); I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; //I['acl-Override'] = SharingPermissions.Unset; @@ -1020,9 +1043,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), { lat, lng, infoWindowOpen, ...options }, id); } - export function KVPDocument(document: Doc, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options }); - } + // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView) + // export function KVPDocument(document: Doc, options: DocumentOptions = {}) { + // return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options }); + // } export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id); @@ -1054,7 +1078,7 @@ export namespace Docs { } export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', enableDragWhenActive: true, _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { @@ -1184,11 +1208,10 @@ export namespace DocUtils { * @param docs * @param docFilters * @param docRangeFilters - * @param viewSpecScript + * @param parentCollection * Given a list of docs and docFilters, @returns the list of Docs that match those filters */ - export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField, parentCollection?: Doc) { - const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + export function FilterDocs(childDocs: Doc[], docFilters: string[], docRangeFilters: string[], parentCollection?: Doc) { if (!docFilters?.length && !docRangeFilters?.length) { return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one } @@ -1275,26 +1298,19 @@ export namespace DocUtils { return rangeFilteredDocs; } - export function DefaultFocus(doc: Doc, options: DocFocusOptions) { - return undefined; - } - export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1)); return DocUtils.ActiveRecordings.map(audio => { const sourceDoc = getSourceDoc(); - const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor(true) || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline'); - link && (link.followLinkLocation = OpenWhere.addRight); - return link; + return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { linkDisplay: false, linkRelationship: 'recording annotation:linked recording', description: 'recording timeline' }); }); } - export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = '', description: string = '', id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { - if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; - const sv = DocumentManager.Instance.getDocumentView(source.doc); - if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return; + export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, showPopup?: number[]) { + if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; + const sv = DocumentManager.Instance.getDocumentView(source); if (target.doc === Doc.UserDoc()) return undefined; const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { @@ -1334,16 +1350,16 @@ export namespace DocUtils { target, { title: ComputedField.MakeFunction('generateLinkTitle(self)') as any, - 'anchor1-useLinkSmallAnchor': source.doc.useLinkSmallAnchor ? true : undefined, - 'anchor2-useLinkSmallAnchor': target.doc.useLinkSmallAnchor ? true : undefined, + 'anchor1-useLinkSmallAnchor': source.useLinkSmallAnchor ? true : undefined, + 'anchor2-useLinkSmallAnchor': target.useLinkSmallAnchor ? true : undefined, 'acl-Public': SharingPermissions.Augment, '_acl-Public': SharingPermissions.Augment, - linkDisplay: true, + linkDisplay: linkSettings.linkDisplay, _linkAutoMove: true, - linkRelationship, + linkRelationship: linkSettings.linkRelationship, _showCaption: 'description', _showTitle: 'linkRelationship', - description, + description: linkSettings.description, }, id ), @@ -1363,7 +1379,6 @@ export namespace DocUtils { value: 'any', _readOnly_: 'boolean', scriptContext: 'any', - thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', @@ -1411,41 +1426,33 @@ export namespace DocUtils { export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { let created: Doc | undefined; - let layout: ((fieldKey: string) => string) | undefined; const field = target[fieldKey]; - const resolved = options || {}; + const resolved = options ?? {}; if (field instanceof ImageField) { created = Docs.Create.ImageDocument(field.url.href, resolved); - layout = ImageBox.LayoutString; + created.layout = ImageBox.LayoutString(fieldKey); } else if (field instanceof Doc) { created = field; } else if (field instanceof VideoField) { created = Docs.Create.VideoDocument(field.url.href, resolved); - layout = VideoBox.LayoutString; + created.layout = VideoBox.LayoutString(fieldKey); } else if (field instanceof PdfField) { created = Docs.Create.PdfDocument(field.url.href, resolved); - layout = PDFBox.LayoutString; + created.layout = PDFBox.LayoutString(fieldKey); } else if (field instanceof AudioField) { created = Docs.Create.AudioDocument(field.url.href, resolved); - layout = AudioBox.LayoutString; - } else if (field instanceof RecordingField) { - created = Docs.Create.RecordingDocument(field.url.href, resolved); - layout = RecordingBox.LayoutString; + created.layout = AudioBox.LayoutString(fieldKey); } else if (field instanceof InkField) { created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved); - layout = InkingStroke.LayoutString; + created.layout = InkingStroke.LayoutString(fieldKey); } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); - layout = CollectionView.LayoutString; - } else if (field instanceof MapField) { - created = Docs.Create.MapDocument(DocListCast(field), resolved); - layout = MapBox.LayoutString; + created.layout = CollectionView.LayoutString(fieldKey); } else { created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved }); - layout = FormattedTextBox.LayoutString; + created.layout = FormattedTextBox.LayoutString(fieldKey); } if (created) { - created.layout = layout?.(fieldKey); created.title = fieldKey; proto && created.proto && (created.proto = Doc.GetProto(proto)); } @@ -1541,7 +1548,7 @@ export namespace DocUtils { const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) .filter(btnDoc => !btnDoc.hidden) .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) - .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail) + .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyDataViz) .map((dragDoc, i) => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), event: undoBatch((args: { x: number; y: number }) => { @@ -1685,14 +1692,15 @@ export namespace DocUtils { x: Cast(doc.x, 'number', null), y: Cast(doc.y, 'number', null), backgroundColor: '#ACCEF7', + hideAllLinks: true, _width: 15, _height: 15, _xPadding: 0, - _isLinkButton: true, + onClick: FollowLinkScript(), _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); Doc.AddDocToList(context, annotationField, pushpin); - const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, 'pushpin', ''); + const pushpinLink = DocUtils.MakeLink(pushpin, doc, { linkRelationship: 'pushpin' }, ''); doc._timecodeToShow = undefined; return pushpin; } @@ -1787,6 +1795,7 @@ export namespace DocUtils { y: y, _fitWidth: true, _autoHeight: true, + _showAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards), title, }); const template = Doc.UserDoc().defaultTextLayout; diff --git a/src/client/documents/Gitlike.ts b/src/client/documents/Gitlike.ts index 575c984f5..5e2baf924 100644 --- a/src/client/documents/Gitlike.ts +++ b/src/client/documents/Gitlike.ts @@ -1,118 +1,118 @@ -import { Doc, DocListCast, DocListCastAsync, Field } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { Cast, DateCast } from "../../fields/Types"; -import { DateField } from "../../fields/DateField"; -import { Id } from "../../fields/FieldSymbols"; +// import { Doc, DocListCast, DocListCastAsync, Field } from "../../fields/Doc"; +// import { List } from "../../fields/List"; +// import { Cast, DateCast } from "../../fields/Types"; +// import { DateField } from "../../fields/DateField"; +// import { Id } from "../../fields/FieldSymbols"; -// synchs matching documents on the two branches that are being merged/pulled -// currently this just synchs the main 'fieldKey' component of the data since -// we don't have individual timestamps for all fields -- this is a problematic design issue. -function GitlikeSynchDocs(bd: Doc, md: Doc) { - const fieldKey = Doc.LayoutFieldKey(md); - const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date; - const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date; - const bdproto = bd && Doc.GetProto(bd); - if (bdate !== mdate && bdate <= mdate) { - if (bdproto && md) { - bdproto[fieldKey] = Field.Copy(md[fieldKey]); - bdproto[`${fieldKey}-lastModified`] = new DateField(); - } - } - const bldate = DateCast(bd._lastModified)?.date; - const mldate = DateCast(md._lastModified)?.date; - if (bldate === mldate || bldate > mldate) return; - if (bdproto && md) { - bd.x = Field.Copy(md.x); - bd.y = Field.Copy(md.y); - bd.width = Field.Copy(md.width); - bd.height = Field.Copy(md.height); - bdproto._lastModified = new DateField(); - } -} +// // synchs matching documents on the two branches that are being merged/pulled +// // currently this just synchs the main 'fieldKey' component of the data since +// // we don't have individual timestamps for all fields -- this is a problematic design issue. +// function GitlikeSynchDocs(bd: Doc, md: Doc) { +// const fieldKey = Doc.LayoutFieldKey(md); +// const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date; +// const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date; +// const bdproto = bd && Doc.GetProto(bd); +// if (bdate !== mdate && bdate <= mdate) { +// if (bdproto && md) { +// bdproto[fieldKey] = Field.Copy(md[fieldKey]); +// bdproto[`${fieldKey}-lastModified`] = new DateField(); +// } +// } +// const bldate = DateCast(bd._lastModified)?.date; +// const mldate = DateCast(md._lastModified)?.date; +// if (bldate === mldate || bldate > mldate) return; +// if (bdproto && md) { +// bd.x = Field.Copy(md.x); +// bd.y = Field.Copy(md.y); +// bd.width = Field.Copy(md.width); +// bd.height = Field.Copy(md.height); +// bdproto._lastModified = new DateField(); +// } +// } -// pulls documents onto a branch from the branch's master -// if a document exists on master but not on the branch, it is branched and added -// NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp. -async function GitlikePullFromMaster(branch: Doc, suffix = "") { - const masterMain = Cast(branch.branchOf, Doc, null); - // get the set of documents on both the branch and master - const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]); - const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); - // get the master documents that correspond to the branch documents - const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd); - const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc)); - // get documents on master that don't have a corresponding master doc (form a branch doc), and ... - const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md))); - const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md))); - oldDocsFromMaster?.forEach(md => { - const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md); - bd && GitlikeSynchDocs(bd, md); - }); - const cloneMap = new Map<string, Doc>(); cloneMap.set(masterMain[Id], branch); - // make branch clones of them, then add them to the branch - const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true, cloneMap)).clone) || []); - newlyBranchedDocs.forEach(nd => { - Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd); - nd.context = branch; - }); - // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted. - const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context); - // so then remove all the deleted main docs from this branch. - remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd)); -} +// // pulls documents onto a branch from the branch's master +// // if a document exists on master but not on the branch, it is branched and added +// // NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp. +// async function GitlikePullFromMaster(branch: Doc, suffix = "") { +// const masterMain = Cast(branch.branchOf, Doc, null); +// // get the set of documents on both the branch and master +// const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]); +// const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); +// // get the master documents that correspond to the branch documents +// const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd); +// const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc)); +// // get documents on master that don't have a corresponding master doc (form a branch doc), and ... +// const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md))); +// const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md))); +// oldDocsFromMaster?.forEach(md => { +// const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md); +// bd && GitlikeSynchDocs(bd, md); +// }); +// const cloneMap = new Map<string, Doc>(); cloneMap.set(masterMain[Id], branch); +// // make branch clones of them, then add them to the branch +// const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true, cloneMap)).clone) || []); +// newlyBranchedDocs.forEach(nd => { +// Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd); +// nd.context = branch; +// }); +// // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted. +// const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context); +// // so then remove all the deleted main docs from this branch. +// remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd)); +// } -// merges all branches from the master branch by first merging the top-level collection of documents, -// and then merging all the annotations on those documents. -// TODO: need to add an incrementing timestamp whenever anything merges. don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp. -async function GitlikeMergeWithMaster(master: Doc, suffix = "") { - const branches = await DocListCastAsync(master.branches); - branches?.map(async branch => { - const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); - branchChildren && await Promise.all(branchChildren.map(async bd => { - const cloneMap = new Map<string, Doc>(); cloneMap.set(master[Id], branch); - // see if the branch's child exists on master. - const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true, cloneMap)).clone; - // if the branch's child didn't exist on master, we make a branch clone of the child to add to master. - // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields - // on the branch child and master clone. - if (masterChild.branchOf) { - const branchDocProto = Doc.GetProto(bd); - const masterChildProto = Doc.GetProto(masterChild); - const branchTitle = bd.title; - branchDocProto.title = masterChildProto.title; - masterChildProto.title = branchTitle; - masterChildProto.branchOf = masterChild.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf' - masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child - Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto); // the branch child should not have the master child in its branch list. - branchDocProto.branchOf = masterChild; // the branch child is now a branch of the master child - } - Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op) - masterChild.context = master; - GitlikeSynchDocs(masterChild, bd);//Doc.GetProto(masterChild), bd); - })); - const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]); - masterChildren?.forEach(mc => { // see if any master children - if (!branchChildren?.find(bc => bc.branchOf === mc)) { // are not in the list of children for this branch. - Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it. - mc.context = undefined; // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp - } - }); - }); -} +// // merges all branches from the master branch by first merging the top-level collection of documents, +// // and then merging all the annotations on those documents. +// // TODO: need to add an incrementing timestamp whenever anything merges. don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp. +// async function GitlikeMergeWithMaster(master: Doc, suffix = "") { +// const branches = await DocListCastAsync(master.branches); +// branches?.map(async branch => { +// const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); +// branchChildren && await Promise.all(branchChildren.map(async bd => { +// const cloneMap = new Map<string, Doc>(); cloneMap.set(master[Id], branch); +// // see if the branch's child exists on master. +// const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true, cloneMap)).clone; +// // if the branch's child didn't exist on master, we make a branch clone of the child to add to master. +// // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields +// // on the branch child and master clone. +// if (masterChild.branchOf) { +// const branchDocProto = Doc.GetProto(bd); +// const masterChildProto = Doc.GetProto(masterChild); +// const branchTitle = bd.title; +// branchDocProto.title = masterChildProto.title; +// masterChildProto.title = branchTitle; +// masterChildProto.branchOf = masterChild.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf' +// masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child +// Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto); // the branch child should not have the master child in its branch list. +// branchDocProto.branchOf = masterChild; // the branch child is now a branch of the master child +// } +// Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op) +// masterChild.context = master; +// GitlikeSynchDocs(masterChild, bd);//Doc.GetProto(masterChild), bd); +// })); +// const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]); +// masterChildren?.forEach(mc => { // see if any master children +// if (!branchChildren?.find(bc => bc.branchOf === mc)) { // are not in the list of children for this branch. +// Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it. +// mc.context = undefined; // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp +// } +// }); +// }); +// } -// performs a "git"-like task: pull or merge -// if pull, then target is a specific branch document that will be updated from its associated master -// if merge, then target is the master doc that will merge in all branches associated with it. -// TODO: parameterize 'merge' to specify which branch(es) should be merged. -// extend 'merge' to allow a specific branch to be merge target (not just master); -// make pull/merge be recursive (ie, this func currently just operates on the main doc and its children) -export async function BranchTask(target: Doc, action: "pull" | "merge") { - const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster; - await func(target, ""); - await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations")); - await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-sidebar")); -} +// // performs a "git"-like task: pull or merge +// // if pull, then target is a specific branch document that will be updated from its associated master +// // if merge, then target is the master doc that will merge in all branches associated with it. +// // TODO: parameterize 'merge' to specify which branch(es) should be merged. +// // extend 'merge' to allow a specific branch to be merge target (not just master); +// // make pull/merge be recursive (ie, this func currently just operates on the main doc and its children) +// export async function BranchTask(target: Doc, action: "pull" | "merge") { +// const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster; +// await func(target, ""); +// await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations")); +// await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-sidebar")); +// } -export async function BranchCreate(target: Doc) { - return (await Doc.MakeClone(target, false, true)).clone; -}
\ No newline at end of file +// export async function BranchCreate(target: Doc) { +// return (await Doc.MakeClone(target, false, true)).clone; +// } |
