diff options
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r-- | src/fields/Doc.ts | 226 |
1 files changed, 157 insertions, 69 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 5a8a6e4b6..56b97e42f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -7,9 +7,8 @@ import { DocServer } from '../client/DocServer'; import { DocumentType } from '../client/documents/DocumentTypes'; import { LinkManager } from '../client/util/LinkManager'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; -import { SelectionManager } from '../client/util/SelectionManager'; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; -import { undoable, UndoManager } from '../client/util/UndoManager'; +import { undoable } from '../client/util/UndoManager'; import { decycle } from '../decycler/decycler'; import * as JSZipUtils from '../JSZipUtils'; import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; @@ -21,6 +20,7 @@ import { AclPrivate, AclReadonly, Animation, + AudioPlay, CachedUpdates, DirectLinks, DocAcl, @@ -48,9 +48,9 @@ import { FieldId, RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { listSpec } from './Schema'; import { ComputedField, ScriptField } from './ScriptField'; -import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; +import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; -import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, containedFieldChangedHandler } from './util'; +import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions } from './util'; import JSZip = require('jszip'); export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -157,7 +157,26 @@ export function updateCachedAcls(doc: Doc) { @scriptingGlobal @Deserializable('Doc', updateCachedAcls, ['id']) export class Doc extends RefField { - @observable public static CurrentlyLoading: Doc[]; + @observable public static RecordingEvent = 0; + + // this isn't really used at the moment, but is intended to indicate whether ink stroke are passed through a gesture recognizer + static GetRecognizeGestures() { + return BoolCast(Doc.UserDoc()._recognizeGestures); + } + static SetRecognizeGestures(show: boolean) { + Doc.UserDoc()._recognizeGestures = show; + } + + // + // This controls whether fontIconButtons will display labels under their icons or not + // + static GetShowIconLabels() { + return BoolCast(Doc.UserDoc()._showLabel); + } + static SetShowIconLabels(show: boolean) { + Doc.UserDoc()._showLabel = show; + } + @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor // removes from currently loading display @action public static removeCurrentlyLoading(doc: Doc) { @@ -170,9 +189,6 @@ export class Doc extends RefField { // adds doc to currently loading display @action public static addCurrentlyLoading(doc: Doc) { - if (!Doc.CurrentlyLoading) { - Doc.CurrentlyLoading = []; - } if (Doc.CurrentlyLoading.indexOf(doc) === -1) { Doc.CurrentlyLoading.push(doc); } @@ -205,6 +221,9 @@ export class Doc extends RefField { public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } + public static get MyTopBarBtns() { + return DocCast(Doc.UserDoc().myTopBarBtns); + } public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } @@ -238,22 +257,6 @@ export class Doc extends RefField { public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } - public static get MyFileOrphans() { - return DocCast(Doc.UserDoc().myFileOrphans); - } - public static AddFileOrphan(doc: Doc) { - if ( - doc && - Doc.MyFileOrphans instanceof Doc && - Doc.IsDataProto(doc) && - !Doc.IsSystem(doc) && - ![DocumentType.CONFIG, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(doc.type as any) && - !doc.isFolder && - !doc.annotationOn - ) { - Doc.AddDocToList(Doc.MyFileOrphans, undefined, doc); - } - } public static get MyTools() { return DocCast(Doc.UserDoc().myTools); } @@ -347,6 +350,7 @@ export class Doc extends RefField { @observable public [DocAcl]: { [key: string]: symbol } = {}; @observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout @observable public [DirectLinks] = new ObservableSet<Doc>(); + @observable public [AudioPlay]: any; // meant to store sound object from Howl @observable public [Animation]: Opt<Doc>; @observable public [Highlight]: boolean = false; static __Anim(Doc: Doc) { @@ -388,7 +392,7 @@ export class Doc extends RefField { } else { return Cast(layoutField, Doc, null); } - return Cast(self[renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; + return Cast(self[renderFieldKey + '_layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; } return undefined; } @@ -400,6 +404,12 @@ export class Doc extends RefField { public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } + public static get IsSharingEnabled() { + return Doc.UserDoc().isSharingEnabled as boolean; + } + public static set IsSharingEnabled(val) { + Doc.UserDoc().isSharingEnabled = val; + } public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } @@ -526,6 +536,13 @@ export namespace Doc { export function IsDelegateField(doc: Doc, fieldKey: string) { return doc && Get(doc, fieldKey, true) !== undefined; } + // + // this will write the value to the key on either the data doc or the embedding doc. The choice + // of where to write it is based on: + // 1) if the embedding Doc already has this field defined on it, then it will be written to the embedding + // 2) if the data doc has the field, then it's written there. + // 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding) + // export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { if (key.startsWith('_')) key = key.substring(1); const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined; @@ -573,7 +590,7 @@ export namespace Doc { // compare whether documents or their protos match export function AreProtosEqual(doc?: Doc, other?: Doc) { - return doc && other && Doc.GetProto(doc) === Doc.GetProto(other); + return doc && other && (doc === other || Doc.GetProto(doc) === Doc.GetProto(other)); } // Gets the data document for the document. Note: this is mis-named -- it does not specifically @@ -701,7 +718,8 @@ export namespace Doc { } export function BestEmbedding(doc: Doc) { - const bestEmbedding = Doc.GetProto(doc) ? DocListCast(doc.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail) : doc; + const bestEmbedding = Doc.GetProto(doc) ? [doc, ...DocListCast(doc.proto_embeddings)].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail) : doc; + bestEmbedding && Doc.AddDocToList(Doc.GetProto(doc), 'protoEmbeddings', doc); return bestEmbedding ?? Doc.MakeEmbedding(doc); } @@ -782,7 +800,6 @@ export namespace Doc { copy.cloneOf = doc; cloneMap.set(doc[Id], copy); - Doc.AddFileOrphan(copy); return copy; } export function repairClone(clone: Doc, cloneMap: Map<string, Doc>, visited: Set<Doc>) { @@ -917,7 +934,7 @@ export namespace Doc { // If it doesn't find the expanded layout, then it makes a delegate of the template layout and // saves it on the data doc indexed by the template layout's id. // - const expandedLayoutFieldKey = templateField + '-layout[' + templateLayoutDoc[Id] + ']'; + const expandedLayoutFieldKey = templateField + '_layout[' + templateLayoutDoc[Id] + ']'; let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; if (templateLayoutDoc.resolvedDataDoc instanceof Promise) { @@ -985,6 +1002,59 @@ export namespace Doc { return overwrite; } + export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) { + if (infield instanceof List<any>) { + infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system)); + return; + } + const doc = infield as Doc; + if (references.has(doc)) { + references.add(doc); + return; + } + const excludeLists = ['My Recently Closed', 'My Header Bar', 'My Dashboards'].includes(StrCast(doc.title)); + if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return; + references.add(doc); + Object.keys(doc).forEach(key => { + if (key === 'proto') { + if (doc.proto instanceof Doc) { + Doc.FindReferences(doc.proto, references, system); + } + } else { + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]); + if (field instanceof RefField) { + if (field instanceof Doc) { + if (key === 'myLinkDatabase') { + field instanceof Doc && references.add(field); + // skip docs that have been closed and are scheduled for garbage collection + } else { + Doc.FindReferences(field, references, system); + } + } + } else if (cfield instanceof ComputedField) { + } else if (field instanceof ObjectField) { + if (field instanceof Doc) { + Doc.FindReferences(field, references, system); + } else if (field instanceof List) { + !excludeLists && Doc.FindReferences(field, references, system); + } else if (field instanceof ProxyField) { + if (key === 'myLinkDatabase') { + field instanceof Doc && references.add(field); + // skip docs that have been closed and are scheduled for garbage collection + } else { + Doc.FindReferences(field.value, references, system); + } + } else if (field instanceof PrefetchProxy) { + Doc.FindReferences(field.value, references, system); + } + } else if (field instanceof Promise) { + debugger; //This shouldn't happend... + } + } + }); + } + export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc { const copy = new Doc(copyProtoId, true); updateCachedAcls(copy); @@ -1025,7 +1095,6 @@ export namespace Doc { if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } - Doc.AddFileOrphan(copy); return copy; } @@ -1044,7 +1113,6 @@ export namespace Doc { if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate); title && (delegate.title = title); delegate[Initializing] = false; - Doc.AddFileOrphan(delegate); return delegate; } return undefined; @@ -1177,7 +1245,7 @@ export namespace Doc { // the document containing the view layout information - will be the Document itself unless the Document has // a layout field or 'layout' is given. export function Layout(doc: Doc, layout?: Doc): Doc { - const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}-layout[` + layout[Id] + ']'], Doc, null); + const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}_layout[` + layout[Id] + ']'], Doc, null); return overrideLayout || doc[DocLayout] || doc; } export function SetLayout(doc: Doc, layout: Doc | string) { @@ -1309,8 +1377,13 @@ export namespace Doc { } export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { - if (linkDoc.link_anchor_2 === anchorDoc || (linkDoc.link_anchor_2 as Doc).annotationOn) return '2'; - return Doc.AreProtosEqual(anchorDoc, (linkDoc.link_anchor_1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.link_anchor_1 as Doc) ? '1' : '2'; + const linkAnchor2 = DocCast(linkDoc.link_anchor_2); + const linkAnchor1 = DocCast(linkDoc.link_anchor_1); + if (linkDoc.link_matchEmbeddings) { + return [linkAnchor2, linkAnchor2.annotationOn].includes(anchorDoc) ? '2' : '1'; + } + if (Doc.AreProtosEqual(linkAnchor2, anchorDoc) || Doc.AreProtosEqual(linkAnchor2.annotationOn as Doc, anchorDoc)) return '2'; + return Doc.AreProtosEqual(linkAnchor1, anchorDoc) || Doc.AreProtosEqual(linkAnchor1.annotationOn as Doc, anchorDoc) ? '1' : '2'; } export function linkFollowUnhighlight() { @@ -1328,9 +1401,9 @@ export namespace Doc { UnhighlightWatchers.push(watcher); } else watcher(); } - export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) { + export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) { linkFollowUnhighlight(); - (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect)); + (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect)); document.removeEventListener('pointerdown', linkFollowUnhighlight); document.addEventListener('pointerdown', linkFollowUnhighlight); if (UnhighlightTimer) clearTimeout(UnhighlightTimer); @@ -1345,11 +1418,11 @@ export namespace Doc { if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false; return doc[Highlight] || Doc.GetProto(doc)[Highlight]; } - export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) { + export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) { runInAction(() => { highlightedDocs.add(doc); doc[Highlight] = true; - doc[Animation] = presEffect; + doc[Animation] = presentation_effect; if (dataAndDisplayDocs) { highlightedDocs.add(Doc.GetProto(doc)); Doc.GetProto(doc)[Highlight] = true; @@ -1390,18 +1463,36 @@ export namespace Doc { const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1; return isTransparent(StrCast(doc[key])); } + if (key === '-linkedTo') { + // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) + const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); + const matchLink = (value: string, anchor: Doc) => { + const linkedToExp = value?.split('='); + if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value; + return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; + }; + // prettier-ignore + return (value === Doc.FilterNone && !allLinks.length) || + (value === Doc.FilterAny && !!allLinks.length) || + (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || + matchLink(value,DocCast(link.link_anchor_2)) )); + } if (typeof value === 'string') { value = value.replace(`,${Utils.noRecursionHack}`, ''); } const fieldVal = doc[key]; + // prettier-ignore + if ((value === Doc.FilterAny && fieldVal !== undefined) || + (value === Doc.FilterNone && fieldVal === undefined)) { + return true; + } if (Cast(fieldVal, listSpec('string'), []).length) { - const vals = Cast(fieldVal, listSpec('string'), []); + const vals = StrListCast(fieldVal); const docs = vals.some(v => (v as any) instanceof Doc); if (docs) return value === Field.toString(fieldVal as Field); return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } - const fieldStr = Field.toString(fieldVal as Field); - return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { @@ -1414,24 +1505,40 @@ export namespace Doc { prevLayout === 'icon' && (doc.deiconifyLayout = undefined); doc.layout_fieldKey = deiconify || 'layout'; } - export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: number[]) { + export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: readonly number[], modifiers?: 'remove') { + //, modifiers: 'remove' | 'set' if (!container) return; + const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []); + for (let i = 0; i < childFiltersByRanges.length; i += 3) { if (childFiltersByRanges[i] === key) { + console.log('this is key inside childfilters by range ' + key); childFiltersByRanges.splice(i, 3); + console.log('this is child filters by range ' + childFiltersByRanges); break; } } if (range !== undefined) { + console.log('in doc.ts in set range filter'); childFiltersByRanges.push(key); childFiltersByRanges.push(range[0].toString()); childFiltersByRanges.push(range[1].toString()); container._childFiltersByRanges = new List<string>(childFiltersByRanges); + console.log('this is child filters by range ' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]); + console.log('this is new list ' + container._childFiltersByRange); } + + if (modifiers) { + childFiltersByRanges.splice(0, 3); + container._childFiltersByRanges = new List<string>(childFiltersByRanges); + } + console.log('this is child filters by range END' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]); } export const FilterSep = '::'; + export const FilterAny = '--any--'; + export const FilterNone = '--undefined--'; // filters document in a container collection: // all documents with the specified value for the specified key are included/excluded @@ -1443,8 +1550,8 @@ export namespace Doc { runInAction(() => { for (let i = 0; i < childFilters.length; i++) { const fields = childFilters[i].split(FilterSep); // split key:value:modifier - if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { - if (fields[2] === modifiers && modifiers && fields[1] === value) { + if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { + if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) { if (toggle) modifiers = 'remove'; else return; } @@ -1542,7 +1649,7 @@ export namespace Doc { case DocumentType.COMPARISON: return 'columns'; case DocumentType.RTF: return 'sticky-note'; case DocumentType.COL: - const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : 'question'; + const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : doc?.title==='Untitled Collection'? 'object-group': 'chalkboard'; const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question'; return !doc?.isFolder ? folder : chevron; case DocumentType.WEB: return 'globe-asia'; @@ -1558,6 +1665,10 @@ export namespace Doc { case DocumentType.PDF: return 'file-pdf'; case DocumentType.LINK: return 'link'; case DocumentType.MAP: return 'map-marker-alt'; + case DocumentType.DATAVIZ: return 'chart-bar'; + case DocumentType.EQUATION: return 'calculator'; + case DocumentType.SIMULATION: return 'rocket'; + case DocumentType.CONFIG: return 'question-circle'; default: return 'question'; } } @@ -1765,24 +1876,6 @@ ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -ScriptingGlobals.add(function undo() { - SelectionManager.DeselectAll(); - return UndoManager.Undo(); -}); - -export function ShowUndoStack() { - SelectionManager.DeselectAll(); - var buffer = ''; - UndoManager.undoStack.forEach((batch, i) => { - buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n'; - ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n')); - }); - alert(buffer); -} -ScriptingGlobals.add(function redo() { - SelectionManager.DeselectAll(); - return UndoManager.Redo(); -}); ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return 'invalid'; @@ -1797,12 +1890,7 @@ ScriptingGlobals.add(function activePresentationItem() { const curPres = Doc.ActivePresentation; return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; }); -ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { - const docs = SelectionManager.Views() - .map(dv => dv.props.Document) - .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); - return docs.length ? new List(docs) : prevValue; -}); + ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') { Doc.setDocFilter(container, key, value, modifiers); }); |