diff options
author | kimdahey <claire_kim1@brown.edu> | 2019-09-21 15:43:22 -0400 |
---|---|---|
committer | kimdahey <claire_kim1@brown.edu> | 2019-09-21 15:43:22 -0400 |
commit | 0b0789816e8a996b32d92e305da84b5922a49f40 (patch) | |
tree | e0a5ae13305c3f4d20413c5930f8281741fade98 /src | |
parent | f520d5a821b515e0c216f055e93f0549bd6733a9 (diff) | |
parent | 1d5dc3eb4095cea017412de9519b8eaee979c16c (diff) |
merge conflicts
Diffstat (limited to 'src')
70 files changed, 1556 insertions, 1581 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 415023ac4..65eb3cffd 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -115,28 +115,23 @@ export class Utils { // Calculate hue // No difference - if (delta == 0) - h = 0; + if (delta === 0) h = 0; // Red is max - else if (cmax == r) - h = ((g - b) / delta) % 6; + else if (cmax === r) h = ((g - b) / delta) % 6; // Green is max - else if (cmax == g) - h = (b - r) / delta + 2; + else if (cmax === g) h = (b - r) / delta + 2; // Blue is max - else - h = (r - g) / delta + 4; + else h = (r - g) / delta + 4; h = Math.round(h * 60); // Make negative hues positive behind 360° - if (h < 0) - h += 360; // Calculate lightness + if (h < 0) h += 360; // Calculate lightness l = (cmax + cmin) / 2; // Calculate saturation - s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); // Multiply l and s by 100 // s = +(s * 100).toFixed(1); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b9c6e5478..b2a320517 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -631,16 +631,16 @@ export namespace DocUtils { LinkManager.Instance.deleteLink(link); LinkManager.Instance.addLink(link); } - }) - }) + }); + }); } } }); } export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) { - if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; + // if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; let sv = DocumentManager.Instance.getDocumentView(source); - if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; + if (sv && sv.props.ContainingCollectionDoc === target) return; if (target === CurrentUserUtils.UserDocument) return undefined; let linkDocProto = new Doc(id, true); @@ -662,10 +662,8 @@ export namespace DocUtils { LinkManager.Instance.addLink(linkDocProto); - let script = `return links(this);`; - let computed = CompileScript(script, { params: { this: "Doc" }, typecheck: false }); - computed.compiled && (Doc.GetProto(source).links = new ComputedField(computed)); - computed.compiled && (Doc.GetProto(target).links = new ComputedField(computed)); + Doc.GetProto(source).links = ComputedField.MakeFunction("links(this)"); + Doc.GetProto(target).links = ComputedField.MakeFunction("links(this)"); }, "make link"); return linkDocProto; } diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index fb3c15cea..c4016d2a5 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -313,7 +313,7 @@ export namespace DictationManager { ["open fields", { action: (target: DocumentView) => { let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); - target.props.addDocTab(kvp, target.dataDoc, "onRight"); + target.props.addDocTab(kvp, target.props.DataDoc, "onRight"); } }], diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ec731da84..a3c7429b9 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -10,6 +10,7 @@ import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { undoBatch, UndoManager } from './UndoManager'; import { Scripting } from './Scripting'; +import { List } from '../../new_fields/List'; export class DocumentManager { @@ -146,6 +147,7 @@ export class DocumentManager { if (!contextDoc) { let docs = docContext ? await DocListCastAsync(docContext.data) : undefined; let found = false; + // bcz: this just searches within the context for the target -- perhaps it should recursively search through all children? docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate)); if (docContext && found) { let targetContextView: DocumentView | null; @@ -154,16 +156,19 @@ export class DocumentManager { docContext.panTransformType = "Ease"; targetContextView.props.focus(docDelegate, willZoom); } else { - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext, undefined); + (dockFunc || CollectionDockingView.AddRightSplit)(docContext, undefined); setTimeout(() => { - this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage); - }, 10); + let dv = DocumentManager.Instance.getDocumentView(docContext); + dv && this.jumpToDocument(docDelegate, willZoom, forceDockFunc, + doc => dv!.props.focus(dv!.props.Document, true, 1, () => dv!.props.addDocTab(doc, undefined, "inPlace")), + linkPage); + }, 1050); } } else { const actualDoc = Doc.MakeAlias(docDelegate); Doc.BrushDoc(actualDoc); if (linkPage !== undefined) actualDoc.curPage = linkPage; - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, undefined); + (dockFunc || CollectionDockingView.AddRightSplit)(actualDoc, undefined); } } else { let contextView: DocumentView | null; @@ -172,7 +177,7 @@ export class DocumentManager { contextDoc.panTransformType = "Ease"; contextView.props.focus(docDelegate, willZoom); } else { - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc, undefined); + (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined); setTimeout(() => { this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage); }, 10); @@ -203,5 +208,34 @@ export class DocumentManager { return 1; } } + + @action + animateBetweenPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { + expandedDocs && expandedDocs.map(expDoc => { + if (expDoc.isMinimized || expDoc.isAnimating === "min") { // MAXIMIZE DOC + if (expDoc.isMinimized) { // docs are never actaully at the minimized location. so when we unminimize one, we have to set our overrides to make it look like it was at the minimize location + expDoc.isMinimized = false; + expDoc.animateToPos = new List<number>([...scrpt, 0]); + expDoc.animateToDimensions = new List<number>([0, 0]); + } + setTimeout(() => { + expDoc.isAnimating = "max"; + expDoc.animateToPos = new List<number>([0, 0, 1]); + expDoc.animateToDimensions = new List<number>([NumCast(expDoc.width), NumCast(expDoc.height)]); + setTimeout(() => expDoc.isAnimating === "max" && (expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined), 600); + }, 0); + } else { // MINIMIZE DOC + expDoc.isAnimating = "min"; + expDoc.animateToPos = new List<number>([...scrpt, 0]); + expDoc.animateToDimensions = new List<number>([0, 0]); + setTimeout(() => { + if (expDoc.isAnimating === "min") { + expDoc.isMinimized = true; + expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined; + } + }, 600); + } + }); + } } Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); });
\ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 252accefa..56496c99b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -66,7 +66,7 @@ export function SetupDrag( function moveLinkedDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { const document = SelectionManager.SelectedDocuments()[0]; - document.props.removeDocument && document.props.removeDocument(doc); + document && document.props.removeDocument && document.props.removeDocument(doc); addDocument(doc); return true; } @@ -204,13 +204,11 @@ export namespace DragManager { constructor(dragDoc: Doc[]) { this.draggedDocuments = dragDoc; this.droppedDocuments = dragDoc; - this.xOffset = 0; - this.yOffset = 0; + this.offset = [0, 0]; } draggedDocuments: Doc[]; droppedDocuments: Doc[]; - xOffset: number; - yOffset: number; + offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; moveDocument?: MoveFunction; @@ -223,14 +221,13 @@ export namespace DragManager { this.dragDocument = dragDoc; this.dropDocument = dropDoc; this.annotationDocument = annotationDoc; - this.xOffset = this.yOffset = 0; + this.offset = [0, 0]; } targetContext: Doc | undefined; dragDocument: Doc; annotationDocument: Doc; dropDocument: Doc; - xOffset: number; - yOffset: number; + offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; } @@ -250,21 +247,13 @@ export namespace DragManager { }); } - export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize?: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { + export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { let dragData = new DragManager.DocumentDragData([]); runInAction(() => StartDragFunctions.map(func => func())); StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : (dropData: { [id: string]: any }) => { let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title }); - let compiled = CompileScript(script, { - params: { doc: Doc.name }, - typecheck: false, - editable: true - }); - if (compiled.compiled) { - let scriptField = new ScriptField(compiled); - bd.onClick = scriptField; - } + bd.onClick = ScriptField.MakeScript(script); params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); initialize && initialize(bd); bd.buttonParams = new List<string>(params); @@ -281,7 +270,8 @@ export namespace DragManager { let droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => { let dvs = DocumentManager.Instance.getDocumentViews(d); if (dvs.length) { - let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView); + let containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined; + let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === containingView); if (inContext.length) { inContext.forEach(dv => droppedDocs.push(dv.props.Document)); } else { diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 75b0b52a7..dc6a0cb7a 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -134,11 +134,10 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps> x: NumCast(doc.x), y: NumCast(doc.y) + offset }; - let parent = this.props.ContainingCollectionView; - if (parent) { + if (this.props.ContainingCollectionDoc) { let importContainer = Docs.Create.StackingDocument(docs, options); importContainer.singleColumn = false; - Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + Doc.AddDocToList(Doc.GetProto(this.props.ContainingCollectionDoc), "data", importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); DocumentManager.Instance.jumpToDocument(importContainer, true); diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 1d2d33800..3e3d3155c 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -97,7 +97,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: tx2.doc.descendants((node: any, offset: any, index: any) => { if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { let path = (tx2.doc.resolve(offset) as any).path; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0); + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) depth++; tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks); } @@ -145,7 +145,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); return tx; - } + }; bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index c727eec73..cd37ea0bb 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -20,7 +20,7 @@ export const inpRules = { /^1\.\s$/, schema.nodes.ordered_list, () => { - return ({ mapStyle: "decimal", bulletStyle: 1 }) + return ({ mapStyle: "decimal", bulletStyle: 1 }); }, (match: any, node: any) => { return node.childCount + node.attrs.order === +match[1]; @@ -33,7 +33,7 @@ export const inpRules = { schema.nodes.ordered_list, // match => { () => { - return ({ mapStyle: "alpha", bulletStyle: 1 }) + return ({ mapStyle: "alpha", bulletStyle: 1 }); // return ({ order: +match[1] }) }, (match: any, node: any) => { @@ -67,7 +67,7 @@ export const inpRules = { (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = Number(match[1]); return state.tr.deleteRange(start, end); } - return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })); }), new InputRule( new RegExp(/^\^\^\s$/), diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 379da112f..ea31671ac 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -350,8 +350,9 @@ export const marks: { [index: string]: MarkSpec } = { let style = getComputedStyle(p); if (style.textDecoration === "underline") return null; if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) + p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { return null; + } } return false; } @@ -372,10 +373,9 @@ export const marks: { [index: string]: MarkSpec } = { getAttrs: (p: any) => { if (typeof (p) !== "string") { let style = getComputedStyle(p); - if (style.textDecoration === "underline") - return null; - if (p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) + if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { return null; + } } return false; } @@ -634,11 +634,11 @@ export class ImageResizeView { DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); } else { DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); - } e.ctrlKey + } } }); } - } + }; this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); @@ -777,7 +777,7 @@ export class FootnoteView { this.innerView.updateState(state); if (!tr.getMeta("fromOutside")) { - let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1) + let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); for (let i = 0; i < transactions.length; i++) { let steps = transactions[i].steps; for (let j = 0; j < steps.length; j++) { diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 1d0916ac0..ff4451824 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -19,6 +19,7 @@ export interface ScriptSucccess { export interface ScriptError { success: false; error: any; + result: any; } export type ScriptResult = ScriptSucccess | ScriptError; @@ -27,7 +28,7 @@ export interface CompiledScript { readonly compiled: true; readonly originalScript: string; readonly options: Readonly<ScriptOptions>; - run(args?: { [name: string]: any }): ScriptResult; + run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult; } export interface CompileError { @@ -100,7 +101,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an // let params: any[] = [Docs, ...fieldTypes]; let compiledFunction = new Function(...paramNames, `return ${script}`); let { capturedVariables = {} } = options; - let run = (args: { [name: string]: any } = {}): ScriptResult => { + let run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => { let argsArray: any[] = []; for (let name of customParams) { if (name === "this") { @@ -127,7 +128,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an if (batch) { batch.end(); } - return { success: false, error }; + onError && onError(error); + return { success: false, error, result: errorVal }; } }; return { compiled: true, run, originalScript, options }; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 9efef888d..4c97a1056 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -24,6 +24,10 @@ export namespace SelectionManager { manager.SelectedDocuments.push(docView); // console.log(manager.SelectedDocuments); docView.props.whenActiveChanged(true); + } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { + manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false)); + manager.SelectedDocuments = [docView]; + FormattedTextBox.InputBoxOverlay = undefined; } } @action diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 50aec3005..a2653855c 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -58,10 +58,6 @@ export class TooltipTextMenu { private _collapsed: boolean = false; - @observable - private _storedMarks: Mark<any>[] | null | undefined; - - constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) { this.view = view; this.editorProps = editorProps; @@ -84,8 +80,6 @@ export class TooltipTextMenu { this.dragElement(dragger); - this._storedMarks = this.view.state.storedMarks; - // this.createCollapse(); // if (this._collapseBtn) { // this.tooltip.appendChild(this._collapseBtn.render(this.view).dom); @@ -280,7 +274,7 @@ export class TooltipTextMenu { if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); } - else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f, undefined); + else this.editorProps.addDocTab(f, undefined, "onRight"); } })); } @@ -311,8 +305,8 @@ export class TooltipTextMenu { let linkDoc = dragData.linkDocument; let guid = Utils.GenerateGuid(); let proto = Doc.GetProto(linkDoc); - if (proto && docView && docView.props.ContainingCollectionView) { - proto.sourceContext = docView.props.ContainingCollectionView.props.Document; + if (proto && docView) { + proto.sourceContext = docView.props.ContainingCollectionDoc; } linkDoc.guid = guid; linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab", guid); @@ -525,12 +519,12 @@ export class TooltipTextMenu { tx2.doc.descendants((node: any, offset: any, index: any) => { if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { let path = (tx2.doc.resolve(offset) as any).path; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0); + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) depth++; tx2.setNodeMarkup(offset, node.type, { mapStyle: style, bulletStyle: depth }, node.marks); } }); - }; + } //remove all node typeand apply the passed-in one to the selected text changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { //remove oldif (nodeType) { //add new diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss new file mode 100644 index 000000000..8cd419bbe --- /dev/null +++ b/src/client/views/DocumentButtonBar.scss @@ -0,0 +1,129 @@ +@import "globalCssVariables"; + +$linkGap : 3px; + +.linkFlyout { + grid-column: 2/4; +} + +.linkButton-empty:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; +} + +.linkButton-nonempty:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; +} + +.documentButtonBar { + margin-top: $linkGap; + grid-column: 1/4; + width: max-content; + height: auto; + display: flex; + flex-direction: row; +} + +.linkButtonWrapper { + pointer-events: auto; + padding-right: 5px; + width: 25px; +} + +.linkButton-linker { + height: 20px; + width: 20px; + text-align: center; + border-radius: 50%; + pointer-events: auto; + color: $dark-color; + border: $dark-color 1px solid; +} + +.linkButton-linker:hover { + cursor: pointer; + transform: scale(1.05); +} + +.linkButton-empty, +.linkButton-nonempty { + height: 20px; + width: 20px; + border-radius: 50%; + opacity: 0.9; + pointer-events: auto; + background-color: $dark-color; + color: $light-color; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + + &:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; + } +} + +.templating-menu { + position: absolute; + pointer-events: auto; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.templating-button, +.docDecs-tagButton { + width: 20px; + height: 20px; + border-radius: 50%; + opacity: 0.9; + font-size: 14; + background-color: $dark-color; + color: $light-color; + text-align: center; + cursor: pointer; + + &:hover { + background: $main-accent; + transform: scale(1.05); + } +} + +#template-list { + position: absolute; + top: 25px; + left: 0px; + width: max-content; + font-family: $sans-serif; + font-size: 12px; + background-color: $light-color-secondary; + padding: 2px 12px; + list-style: none; + + .templateToggle, .chromeToggle { + text-align: left; + } + + input { + margin-right: 10px; + } +} + +@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } +@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } +@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }
\ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx new file mode 100644 index 000000000..b482e3298 --- /dev/null +++ b/src/client/views/DocumentButtonBar.tsx @@ -0,0 +1,368 @@ +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Doc } from "../../new_fields/Doc"; +import { RichTextField } from '../../new_fields/RichTextField'; +import { NumCast } from "../../new_fields/Types"; +import { URLField } from '../../new_fields/URLField'; +import { emptyFunction } from "../../Utils"; +import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; +import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; +import { LinkManager } from '../util/LinkManager'; +import { UndoManager } from "../util/UndoManager"; +import './DocumentButtonBar.scss'; +import './collections/ParentDocumentSelector.scss'; +import { LinkMenu } from "./linking/LinkMenu"; +import { MetadataEntryMenu } from './MetadataEntryMenu'; +import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; +import { TemplateMenu } from "./TemplateMenu"; +import { Template, Templates } from "./Templates"; +import React = require("react"); +import { DocumentView } from './nodes/DocumentView'; +import { ParentDocSelector } from './collections/ParentDocumentSelector'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; + +library.add(faLink); +library.add(faTag); +library.add(faTimes); +library.add(faArrowAltCircleDown); +library.add(faArrowAltCircleUp); +library.add(faStopCircle); +library.add(faCheckCircle); +library.add(faCloudUploadAlt); +library.add(faSyncAlt); +library.add(faShare); + +const cloud: IconProp = "cloud-upload-alt"; +const fetch: IconProp = "sync-alt"; + +@observer +export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> { + private _linkButton = React.createRef<HTMLDivElement>(); + private _linkerButton = React.createRef<HTMLDivElement>(); + private _embedButton = React.createRef<HTMLDivElement>(); + private _tooltipoff = React.createRef<HTMLDivElement>(); + private _textDoc?: Doc; + private _linkDrag?: UndoManager.Batch; + public static Instance: DocumentButtonBar; + + constructor(props: { views: DocumentView[] }) { + super(props); + DocumentButtonBar.Instance = this; + } + + @observable public pushIcon: IconProp = "arrow-alt-circle-up"; + @observable public pullIcon: IconProp = "arrow-alt-circle-down"; + @observable public pullColor: string = "white"; + @observable public isAnimatingFetch = false; + @observable public openHover = false; + public pullColorAnimating = false; + + private pullAnimating = false; + private pushAnimating = false; + + public startPullOutcome = action((success: boolean) => { + if (!this.pullAnimating) { + this.pullAnimating = true; + this.pullIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pullIcon = "arrow-alt-circle-down"; + this.pullAnimating = false; + }), 1000); + } + }); + + public startPushOutcome = action((success: boolean) => { + if (!this.pushAnimating) { + this.pushAnimating = true; + this.pushIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pushIcon = "arrow-alt-circle-up"; + this.pushAnimating = false; + }), 1000); + } + }); + + public setPullState = action((unchanged: boolean) => { + this.isAnimatingFetch = false; + if (!this.pullColorAnimating) { + this.pullColorAnimating = true; + this.pullColor = unchanged ? "lawngreen" : "red"; + setTimeout(this.clearPullColor, 1000); + } + }); + + private clearPullColor = action(() => { + this.pullColor = "white"; + this.pullColorAnimating = false; + }); + + onLinkerButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + document.removeEventListener("pointermove", this.onLinkerButtonMoved); + document.addEventListener("pointermove", this.onLinkerButtonMoved); + document.removeEventListener("pointerup", this.onLinkerButtonUp); + document.addEventListener("pointerup", this.onLinkerButtonUp); + } + + onEmbedButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.addEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + document.addEventListener("pointerup", this.onEmbedButtonUp); + } + + onLinkerButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onLinkerButtonMoved); + document.removeEventListener("pointerup", this.onLinkerButtonUp); + e.stopPropagation(); + } + + onEmbedButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + e.stopPropagation(); + } + + @action + onLinkerButtonMoved = (e: PointerEvent): void => { + if (this._linkerButton.current !== null) { + document.removeEventListener("pointermove", this.onLinkerButtonMoved); + document.removeEventListener("pointerup", this.onLinkerButtonUp); + let selDoc = this.props.views[0]; + let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined; + let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); + FormattedTextBox.InputBoxOverlay = undefined; + this._linkDrag = UndoManager.StartBatch("Drag Link"); + DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { + handlers: { + dragComplete: () => { + if (this._linkDrag) { + this._linkDrag.end(); + this._linkDrag = undefined; + } + }, + }, + hideSource: false + }); + } + e.stopPropagation(); + } + + @action + onEmbedButtonMoved = (e: PointerEvent): void => { + if (this._embedButton.current !== null) { + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + + let dragDocView = this.props.views[0]; + let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + + DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + e.stopPropagation(); + } + + onLinkButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.addEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + document.addEventListener("pointerup", this.onLinkButtonUp); + } + + onLinkButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + e.stopPropagation(); + } + + onLinkButtonMoved = async (e: PointerEvent) => { + if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + DragLinksAsDocuments(this._linkButton.current, e.x, e.y, this.props.views[0].props.Document); + } + e.stopPropagation(); + } + + considerEmbed = () => { + let thisDoc = this.props.views[0].props.Document; + let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; + if (!canEmbed) return (null); + return ( + <div className="linkButtonWrapper"> + <div title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}> + <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" /> + </div> + </div> + ); + } + + private get targetDoc() { + return this.props.views[0].props.Document; + } + + considerGoogleDocsPush = () => { + let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; + if (!canPush) return (null); + let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; + let icon: IconProp = published ? (this.pushIcon as any) : cloud; + return ( + <div className={"linkButtonWrapper"}> + <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => { + DocumentDecorations.hasPushedHack = false; + this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; + }}> + <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} /> + </div> + </div> + ); + } + + considerGoogleDocsPull = () => { + let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; + let dataDoc = Doc.GetProto(this.targetDoc); + if (!canPull || !dataDoc[GoogleRef]) return (null); + let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; + icon = this.openHover ? "share" : icon; + let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; + let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`; + return ( + <div className={"linkButtonWrapper"}> + <div + title={title} + className="linkButton-linker" + style={{ + backgroundColor: this.pullColor, + transition: "0.2s ease all" + }} + onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)} + onPointerLeave={() => runInAction(() => this.openHover = false)} + onClick={e => { + if (e.altKey) { + e.preventDefault(); + window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); + } else { + this.clearPullColor(); + DocumentDecorations.hasPulledHack = false; + this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; + dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); + } + }}> + <FontAwesomeIcon + style={{ + WebkitAnimation: animation, + MozAnimation: animation + }} + className="documentdecorations-icon" + icon={icon} + size="sm" + /> + </div> + </div> + ); + } + + public static hasPushedHack = false; + public static hasPulledHack = false; + + considerTooltip = () => { + let thisDoc = this.props.views[0].props.Document; + let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField; + if (!isTextDoc) return null; + this._textDoc = thisDoc; + return ( + <div className="tooltipwrapper"> + <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}> + {/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */} + </div> + </div> + + ); + } + + onTooltipOff = (e: React.PointerEvent): void => { + e.stopPropagation(); + if (this._textDoc) { + if (this._tooltipoff.current) { + if (this._tooltipoff.current.title === "Hide Tooltip") { + this._tooltipoff.current.title = "Show Tooltip"; + this._textDoc.tooltip = "hi"; + } + else { + this._tooltipoff.current.title = "Hide Tooltip"; + } + } + } + } + + get metadataMenu() { + return ( + <div className="linkButtonWrapper"> + <Flyout anchorPoint={anchorPoints.TOP_LEFT} + content={<MetadataEntryMenu docs={() => this.props.views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */} + <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div> + </Flyout> + </div> + ); + } + + render() { + let linkButton = null; + if (this.props.views.length > 0) { + let selFirst = this.props.views[0]; + + let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length; + linkButton = (<Flyout + anchorPoint={anchorPoints.RIGHT_TOP} + content={<LinkMenu docView={selFirst} + addDocTab={selFirst.props.addDocTab} + changeFlyout={emptyFunction} />}> + <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div> + </Flyout >); + } + + let templates: Map<Template, boolean> = new Map(); + Array.from(Object.values(Templates.TemplateList)).map(template => + templates.set(template, this.props.views.reduce((checked, doc) => checked || doc.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean))); + + return (<div className="documentButtonBar"> + <div className="linkButtonWrapper"> + <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div> + </div> + <div className="linkButtonWrapper"> + <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}> + <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> + </div> + </div> + <div className="linkButtonWrapper"> + <TemplateMenu docs={this.props.views} templates={templates} /> + </div> + {this.metadataMenu} + {this.considerEmbed()} + {this.considerGoogleDocsPush()} + {this.considerGoogleDocsPull()} + <ParentDocSelector Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => { + where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) : this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) : this.props.views[0].props.addDocTab(doc, data, "onRight"); + return true; + }} /> + {/* {this.considerTooltip()} */} + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 4ab5d733f..e68bfc6ad 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -154,7 +154,7 @@ $linkGap : 3px; .link-button-container { margin-top: $linkGap; grid-column: 1/4; - width: auto; + width: max-content; height: auto; display: flex; flex-direction: row; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7829bd7f1..7ec316bf9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,40 +1,29 @@ -import { library, IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt, faShare } from '@fortawesome/free-solid-svg-icons'; +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, reaction, runInAction, trace } from "mobx"; +import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCastAsync } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; +import { ObjectField } from '../../new_fields/ObjectField'; import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { URLField } from '../../new_fields/URLField'; +import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { emptyFunction, Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; -import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; +import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch, UndoManager } from "../util/UndoManager"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { CollectionView } from "./collections/CollectionView"; +import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; -import { DocumentView } from "./nodes/DocumentView"; +import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; +import { DocumentView, swapViews } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; -import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; +import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; -import { LinkMenu } from "./linking/LinkMenu"; -import { TemplateMenu } from "./TemplateMenu"; -import { Template, Templates } from "./Templates"; import React = require("react"); -import { RichTextField } from '../../new_fields/RichTextField'; -import { LinkManager } from '../util/LinkManager'; -import { MetadataEntryMenu } from './MetadataEntryMenu'; -import { ImageBox } from './nodes/ImageBox'; -import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; -import { ObjectField } from '../../new_fields/ObjectField'; -import { DocServer } from '../DocServer'; -import { CompileScript } from '../util/Scripting'; -import { ComputedField } from '../../new_fields/ScriptField'; -import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -50,28 +39,21 @@ library.add(faCloudUploadAlt); library.add(faSyncAlt); library.add(faShare); -const cloud: IconProp = "cloud-upload-alt"; -const fetch: IconProp = "sync-alt"; - @observer export class DocumentDecorations extends React.Component<{}, { value: string }> { static Instance: DocumentDecorations; private _isPointerDown = false; private _resizing = ""; - private keyinput: React.RefObject<HTMLInputElement>; + private _keyinput: React.RefObject<HTMLInputElement>; private _resizeBorderWidth = 16; private _linkBoxHeight = 20 + 3; // link button height + margin private _titleHeight = 20; - private _linkButton = React.createRef<HTMLDivElement>(); - private _linkerButton = React.createRef<HTMLDivElement>(); private _embedButton = React.createRef<HTMLDivElement>(); - private _tooltipoff = React.createRef<HTMLDivElement>(); - private _textDoc?: Doc; private _downX = 0; private _downY = 0; private _iconDoc?: Doc = undefined; private _resizeUndo?: UndoManager.Batch; - private _linkDrag?: UndoManager.Batch; + private _radiusDown = [0, 0]; @observable private _minimizedX = 0; @observable private _minimizedY = 0; @observable private _title: string = ""; @@ -81,58 +63,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @observable private _opacity = 1; @observable private _removeIcon = false; @observable public Interacting = false; - @observable private _isMoving = false; @observable public pushIcon: IconProp = "arrow-alt-circle-up"; @observable public pullIcon: IconProp = "arrow-alt-circle-down"; @observable public pullColor: string = "white"; @observable public isAnimatingFetch = false; @observable public openHover = false; - public pullColorAnimating = false; - - private pullAnimating = false; - private pushAnimating = false; - - public startPullOutcome = action((success: boolean) => { - if (!this.pullAnimating) { - this.pullAnimating = true; - this.pullIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pullIcon = "arrow-alt-circle-down"; - this.pullAnimating = false; - }), 1000); - } - }); - - public startPushOutcome = action((success: boolean) => { - if (!this.pushAnimating) { - this.pushAnimating = true; - this.pushIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pushIcon = "arrow-alt-circle-up"; - this.pushAnimating = false; - }), 1000); - } - }); - - public setPullState = action((unchanged: boolean) => { - this.isAnimatingFetch = false; - if (!this.pullColorAnimating) { - this.pullColorAnimating = true; - this.pullColor = unchanged ? "lawngreen" : "red"; - setTimeout(this.clearPullColor, 1000); - } - }); - - private clearPullColor = action(() => { - this.pullColor = "white"; - this.pullColorAnimating = false; - }); constructor(props: Readonly<{}>) { super(props); DocumentDecorations.Instance = this; - this.keyinput = React.createRef(); + this._keyinput = React.createRef(); reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this._edtingTitle = false); } @@ -155,19 +96,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.DeselectAll(); let fieldTemplate = fieldTemplateView.props.Document; let containerView = fieldTemplateView.props.ContainingCollectionView; - if (containerView) { - let docTemplate = containerView.props.Document; + let docTemplate = fieldTemplateView.props.ContainingCollectionDoc; + if (containerView && docTemplate) { let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length); - let proto = Doc.GetProto(docTemplate); if (metaKey !== containerView.props.fieldKey && containerView.props.DataDoc) { const fd = fieldTemplate.data; fd instanceof ObjectField && (Doc.GetProto(containerView.props.DataDoc)[metaKey] = ObjectField.MakeCopy(fd)); } fieldTemplate.title = metaKey; - Doc.MakeMetadataFieldTemplate(fieldTemplate, proto); + Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate)); if (text.startsWith(">>")) { - proto.detailedLayout = proto.layout; - proto.miniLayout = ImageBox.LayoutString(metaKey); + let layoutNative = Doc.MakeTitled("layoutNative"); + Doc.GetProto(docTemplate).layoutNative = layoutNative; + swapViews(fieldTemplate, "", "layoutNative", layoutNative); + layoutNative.layout = StrCast(fieldTemplateView.props.Document.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`); + layoutNative.backgroundLayout = StrCast(fieldTemplateView.props.Document.backgroundLayout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`); } } } @@ -229,7 +172,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse(); if (transform.TranslateX === 0 && transform.TranslateY === 0) { - setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow... + setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds return this._lastBox; } @@ -254,11 +197,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @action onBackgroundMove = (e: PointerEvent): void => { let dragDocView = SelectionManager.SelectedDocuments()[0]; - const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); - const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top); let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); - dragData.xOffset = xoff; - dragData.yOffset = yoff; + const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); + dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top); dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument; this.Interacting = true; this._hidden = true; @@ -367,7 +308,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (minimizedDoc) { let scrpt = selectedDocs[0].props.ScreenToLocalTransform().scale(selectedDocs[0].props.ContentScaling()).inverse().transformPoint( NumCast(minimizedDoc.x) - NumCast(selectedDocs[0].Document.x), NumCast(minimizedDoc.y) - NumCast(selectedDocs[0].Document.y)); - selectedDocs[0].collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); + SelectionManager.DeselectAll(); + DocumentManager.Instance.animateBetweenPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); } }); } @@ -384,7 +326,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> IconBox.AutomaticTitle(iconDoc); //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined; - iconDoc.proto!.isMinimized = false; iconDoc.width = Number(MINIMIZED_ICON_SIZE); iconDoc.height = Number(MINIMIZED_ICON_SIZE); iconDoc.x = NumCast(doc.x); @@ -413,7 +354,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> iconDoc.y = where[1] + NumCast(selView.props.Document.y); } - _radiusDown = [0, 0]; @action onRadiusDown = (e: React.PointerEvent): void => { e.stopPropagation(); @@ -429,17 +369,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } onRadiusMove = (e: PointerEvent): void => { - this._isMoving = true; let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1])); dist = dist < 3 ? 0 : dist; let usingRule = false; SelectionManager.SelectedDocuments().map(dv => { - let cv = dv.props.ContainingCollectionView; - let ruleProvider = cv && cv.props.ruleProvider; + let ruleProvider = dv.props.ruleProvider; let heading = NumCast(dv.props.Document.heading); ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`); usingRule = usingRule || (ruleProvider && heading ? true : false); - }) + }); !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)). map(d => d.borderRounding = `${Math.min(100, dist)}%`); e.stopPropagation(); @@ -451,15 +389,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.preventDefault(); this._isPointerDown = false; this._resizeUndo && this._resizeUndo.end(); - this._isMoving = false; document.removeEventListener("pointermove", this.onRadiusMove); document.removeEventListener("pointerup", this.onRadiusUp); } + _lastX = 0; + _lastY = 0; @action onPointerDown = (e: React.PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { + this._lastX = e.clientX; + this._lastY = e.clientY; this._isPointerDown = true; this._resizing = e.currentTarget.id; this.Interacting = true; @@ -471,13 +412,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } - onLinkerButtonDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onLinkerButtonMoved); - document.addEventListener("pointermove", this.onLinkerButtonMoved); - document.removeEventListener("pointerup", this.onLinkerButtonUp); - document.addEventListener("pointerup", this.onLinkerButtonUp); - } onEmbedButtonDown = (e: React.PointerEvent): void => { e.stopPropagation(); @@ -487,11 +421,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.addEventListener("pointerup", this.onEmbedButtonUp); } - onLinkerButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onLinkerButtonMoved); - document.removeEventListener("pointerup", this.onLinkerButtonUp); - e.stopPropagation(); - } + onEmbedButtonUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onEmbedButtonMoved); @@ -500,31 +430,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } @action - onLinkerButtonMoved = (e: PointerEvent): void => { - if (this._linkerButton.current !== null) { - document.removeEventListener("pointermove", this.onLinkerButtonMoved); - document.removeEventListener("pointerup", this.onLinkerButtonUp); - let selDoc = SelectionManager.SelectedDocuments()[0]; - let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.proto : undefined; - let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); - FormattedTextBox.InputBoxOverlay = undefined; - this._linkDrag = UndoManager.StartBatch("Drag Link"); - DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { - handlers: { - dragComplete: () => { - if (this._linkDrag) { - this._linkDrag.end(); - this._linkDrag = undefined; - } - }, - }, - hideSource: false - }); - } - e.stopPropagation(); - } - - @action onEmbedButtonMoved = (e: PointerEvent): void => { if (this._embedButton.current !== null) { document.removeEventListener("pointermove", this.onEmbedButtonMoved); @@ -543,29 +448,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.stopPropagation(); } - onLinkButtonDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.addEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - document.addEventListener("pointerup", this.onLinkButtonUp); - } - - onLinkButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - e.stopPropagation(); - } - - onLinkButtonMoved = async (e: PointerEvent) => { - if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - DragLinksAsDocuments(this._linkButton.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document); - } - e.stopPropagation(); - } - onPointerMove = (e: PointerEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -575,42 +457,47 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let dX = 0, dY = 0, dW = 0, dH = 0; + let moveX = e.clientX - this._lastX; // e.movementX; + let moveY = e.clientY - this._lastY; // e.movementY; + this._lastX = e.clientX; + this._lastY = e.clientY; + switch (this._resizing) { case "": break; case "documentDecorations-topLeftResizer": dX = -1; dY = -1; - dW = -(e.movementX); - dH = -(e.movementY); + dW = -moveX; + dH = -moveY; break; case "documentDecorations-topRightResizer": - dW = e.movementX; + dW = moveX; dY = -1; - dH = -(e.movementY); + dH = -moveY; break; case "documentDecorations-topResizer": dY = -1; - dH = -(e.movementY); + dH = -moveY; break; case "documentDecorations-bottomLeftResizer": dX = -1; - dW = -(e.movementX); - dH = e.movementY; + dW = -moveX; + dH = moveY; break; case "documentDecorations-bottomRightResizer": - dW = e.movementX; - dH = e.movementY; + dW = moveX; + dH = moveY; break; case "documentDecorations-bottomResizer": - dH = e.movementY; + dH = moveY; break; case "documentDecorations-leftResizer": dX = -1; - dW = -(e.movementX); + dW = -moveX; break; case "documentDecorations-rightResizer": - dW = e.movementX; + dW = moveX; break; } @@ -694,134 +581,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return "-unset-"; } - changeFlyoutContent = (): void => { - - } - // buttonOnPointerUp = (e: React.PointerEvent): void => { - // e.stopPropagation(); - // } - - considerEmbed = () => { - let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; - let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; - if (!canEmbed) return (null); - return ( - <div className="linkButtonWrapper"> - <div title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}> - <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" /> - </div> - </div> - ); - } - - private get targetDoc() { - return SelectionManager.SelectedDocuments()[0].props.Document; - } - - considerGoogleDocsPush = () => { - let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; - if (!canPush) return (null); - let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; - let icon: IconProp = published ? (this.pushIcon as any) : cloud; - return ( - <div className={"linkButtonWrapper"}> - <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => { - DocumentDecorations.hasPushedHack = false; - this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; - }}> - <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} /> - </div> - </div> - ); - } - - considerGoogleDocsPull = () => { - let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; - let dataDoc = Doc.GetProto(this.targetDoc); - if (!canPull || !dataDoc[GoogleRef]) return (null); - let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; - icon = this.openHover ? "share" : icon; - let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; - let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`; - return ( - <div className={"linkButtonWrapper"}> - <div - title={title} - className="linkButton-linker" - style={{ - backgroundColor: this.pullColor, - transition: "0.2s ease all" - }} - onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)} - onPointerLeave={() => runInAction(() => this.openHover = false)} - onClick={e => { - if (e.altKey) { - e.preventDefault(); - window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); - } else { - this.clearPullColor(); - DocumentDecorations.hasPulledHack = false; - this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; - dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); - } - }}> - <FontAwesomeIcon - style={{ - WebkitAnimation: animation, - MozAnimation: animation - }} - className="documentdecorations-icon" - icon={icon} - size="sm" - /> - </div> - </div> - ); - } - - public static hasPushedHack = false; - public static hasPulledHack = false; - - considerTooltip = () => { - let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; - let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField; - if (!isTextDoc) return null; - this._textDoc = thisDoc; - return ( - <div className="tooltipwrapper"> - <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}> - {/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */} - </div> - </div> - - ); - } - - onTooltipOff = (e: React.PointerEvent): void => { - e.stopPropagation(); - if (this._textDoc) { - if (this._tooltipoff.current) { - if (this._tooltipoff.current.title === "Hide Tooltip") { - this._tooltipoff.current.title = "Show Tooltip"; - this._textDoc.tooltip = "hi"; - } - else { - this._tooltipoff.current.title = "Hide Tooltip"; - } - } - } - } - get metadataMenu() { - return ( - <div className="linkButtonWrapper"> - <Flyout anchorPoint={anchorPoints.TOP_LEFT} - content={<MetadataEntryMenu docs={() => SelectionManager.SelectedDocuments().map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */} - <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div> - </Flyout> - </div> - ); - } render() { var bounds = this.Bounds; @@ -834,27 +594,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} </div>); - let linkButton = null; - if (SelectionManager.SelectedDocuments().length > 0) { - let selFirst = SelectionManager.SelectedDocuments()[0]; - - let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length; - linkButton = (<Flyout - anchorPoint={anchorPoints.RIGHT_TOP} - content={<LinkMenu docView={selFirst} - addDocTab={selFirst.props.addDocTab} - changeFlyout={this.changeFlyoutContent} />}> - <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div> - </Flyout >); - } - - let templates: Map<Template, boolean> = new Map(); - Array.from(Object.values(Templates.TemplateList)).map(template => { - let checked = false; - SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.layoutDoc["show" + template.Name] !== undefined)); - templates.set(template, checked); - }); - bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; bounds.y = Math.max(0, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; const borderRadiusDraggerWidth = 15; @@ -886,7 +625,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> {minimizeIcon} {this._edtingTitle ? - <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> : + <input ref={this._keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> : <div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>} <div className="documentDecorations-closeButton" title="Close Document" onPointerDown={this.onCloseDown}> <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" /> @@ -902,22 +641,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-borderRadius" className="documentDecorations-radius" onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}><span className="borderRadiusTooltip" title="Drag Corner Radius"></span></div> <div className="link-button-container"> - <div className="linkButtonWrapper"> - <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div> - </div> - <div className="linkButtonWrapper"> - <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}> - <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> - </div> - </div> - <div className="linkButtonWrapper"> - <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} /> - </div> - {this.metadataMenu} - {this.considerEmbed()} - {this.considerGoogleDocsPush()} - {this.considerGoogleDocsPull()} - {/* {this.considerTooltip()} */} + <DocumentButtonBar views={SelectionManager.SelectedDocuments()} /> </div> </div > </div> diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index ba125d6e5..2fa03e969 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -88,9 +88,6 @@ export default class KeyManager { }); }, "delete"); break; - case "enter": - SelectionManager.SelectedDocuments().map(selected => Doc.ToggleDetailLayout(selected.props.Document)); - break; } return { @@ -144,7 +141,7 @@ export default class KeyManager { return { stopPropagation: false, preventDefault: false }; } } - MainView.Instance.mainFreeform && CollectionDockingView.Instance.AddRightSplit(MainView.Instance.mainFreeform, undefined); + MainView.Instance.mainFreeform && CollectionDockingView.AddRightSplit(MainView.Instance.mainFreeform, undefined); break; case "arrowleft": if (document.activeElement) { @@ -152,7 +149,7 @@ export default class KeyManager { return { stopPropagation: false, preventDefault: false }; } } - MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform); + MainView.Instance.mainFreeform && CollectionDockingView.CloseRightSplit(MainView.Instance.mainFreeform); break; case "backspace": if (document.activeElement) { @@ -166,7 +163,7 @@ export default class KeyManager { break; case "o": let target = SelectionManager.SelectedDocuments()[0]; - target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target) + target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target); break; case "r": preventDefault = false; diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index 5437b26d6..2d1142f38 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -2,6 +2,7 @@ .inkingCanvas { opacity: 0.99; + touch-action: none; .jsx-parser { position: absolute; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 57dad5e6b..a10df0e75 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -52,16 +52,16 @@ export class InkingControl extends React.Component { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let oldColor = StrCast(targetDoc.backgroundColor); let matchedColor = this._selectedColor; - const cv = view.props.ContainingCollectionView; - let ruleProvider: Doc | undefined; - if (cv) { - if (!cv.props.Document.colorPalette) { - let defaultPalette = ["rg14,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", + const cvd = view.props.ContainingCollectionDoc; + let ruleProvider = view.props.ruleProvider; + if (cvd) { + if (!cvd.colorPalette) { + let defaultPalette = ["rg(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; - let colorPalette = Cast(cv.props.Document.colorPalette, listSpec("string")); - if (!colorPalette) cv.props.Document.colorPalette = new List<string>(defaultPalette); + let colorPalette = Cast(cvd.colorPalette, listSpec("string")); + if (!colorPalette) cvd.colorPalette = new List<string>(defaultPalette); } - let cp = Cast(cv.props.Document.colorPalette, listSpec("string")) as string[]; + let cp = Cast(cvd.colorPalette, listSpec("string")) as string[]; let closest = 0; let dist = 10000000; let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor)); @@ -74,9 +74,9 @@ export class InkingControl extends React.Component { } } cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")"; - cv.props.Document.colorPalette = new List(cp); + cvd.colorPalette = new List(cp); matchedColor = cp[closest]; - ruleProvider = (view.props.Document.heading && cv && cv.props.ruleProvider) ? cv.props.ruleProvider : undefined; + ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } !ruleProvider && (targetDoc.backgroundColor = matchedColor); diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index cd8423a12..335cc609f 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -2,7 +2,7 @@ import { action, observable, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; -import { Doc, DocListCast } from '../../new_fields/Doc'; +import { Doc, DocListCast, Opt } from '../../new_fields/Doc'; import { BoolCast } from '../../new_fields/Types'; import { emptyFunction, returnTrue, returnZero, Utils, returnOne } from '../../Utils'; import { DragManager } from '../util/DragManager'; @@ -78,7 +78,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> this._textTargetDiv = div; this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; if (div) { - this._textBottom = div.parentElement && div.parentElement.style.bottom ? true : false; + this._textBottom = div.parentElement && getComputedStyle(div.parentElement).top !== "0px" ? true : false; this._textColor = (getComputedStyle(div) as any).color; div.style.color = "transparent"; } @@ -104,8 +104,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> document.removeEventListener('pointerup', this.textBoxUp); let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document]); const [left, top] = this._textXf().inverse().transformPoint(0, 0); - dragData.xOffset = e.clientX - left; - dragData.yOffset = e.clientY - top; + dragData.offset = [e.clientX - left, e.clientY - top]; DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, { handlers: { dragComplete: action(emptyFunction), @@ -119,10 +118,8 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> document.removeEventListener('pointerup', this.textBoxUp); } - addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { - if (true) { // location === "onRight") { need to figure out stack to add "inTab" - CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); - } + addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => { + return this._textBox && this._textBox.props.addDocTab(doc, dataDoc, location) ? true : false; } render() { this.TextDoc; this.TextDataDoc; @@ -143,9 +140,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} onClick={undefined} ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined} - ChromeHeight={this.ChromeHeight} - isSelected={returnTrue} select={emptyFunction} renderDepth={0} - ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} + ChromeHeight={this.ChromeHeight} isSelected={returnTrue} select={emptyFunction} renderDepth={0} + ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} + whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} /> </div> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2cec1c052..003919866 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -228,9 +228,10 @@ export class MainView extends React.Component { this.createNewWorkspace(); } } else { - DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => + DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => { field instanceof Doc ? this.openWorkspace(field) : - this.createNewWorkspace(CurrentUserUtils.MainDocId)); + this.createNewWorkspace(CurrentUserUtils.MainDocId) + }); } } @@ -288,6 +289,7 @@ export class MainView extends React.Component { } } }, 100); + return true; } onDrop = (e: React.DragEvent<HTMLDivElement>) => { @@ -319,7 +321,7 @@ export class MainView extends React.Component { <DocumentView Document={mainCont} DataDoc={undefined} addDocument={undefined} - addDocTab={emptyFunction} + addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} onClick={undefined} ruleProvider={undefined} @@ -335,6 +337,7 @@ export class MainView extends React.Component { whenActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} zoomToScale={emptyFunction} getScale={returnOne} />} @@ -367,11 +370,14 @@ export class MainView extends React.Component { document.removeEventListener("pointerup", this.onPointerUp); } flyoutWidthFunc = () => this.flyoutWidth; - addDocTabFunc = (doc: Doc) => { + addDocTabFunc = (doc: Doc, data: Opt<Doc>, where: string) => { + if (where === "close") + return CollectionDockingView.CloseRightSplit(doc); if (doc.dockingConfig) { this.openWorkspace(doc); + return true; } else { - CollectionDockingView.Instance.AddRightSplit(doc, undefined); + return CollectionDockingView.AddRightSplit(doc, undefined); } } @computed @@ -399,6 +405,7 @@ export class MainView extends React.Component { whenActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} zoomToScale={emptyFunction} getScale={returnOne}> </DocumentView>; diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index da4b71e5c..45e0a3562 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -172,6 +172,7 @@ export class OverlayView extends React.Component { ChromeHeight={returnZero} isSelected={returnFalse} select={emptyFunction} + ruleProvider={undefined} layoutKey={"layout"} bringToFront={emptyFunction} addDocument={undefined} @@ -185,9 +186,10 @@ export class OverlayView extends React.Component { whenActiveChanged={emptyFunction} focus={emptyFunction} backgroundColor={returnEmptyString} - addDocTab={emptyFunction} + addDocTab={returnFalse} pinToPres={emptyFunction} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} zoomToScale={emptyFunction} getScale={returnOne} /> </div>; diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 8f06cf770..8ef9f3be6 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -98,7 +98,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> { // tslint:disable-next-line: no-unnecessary-callback-wrapper let params: string[] = []; let setParams = (p: string[]) => params.splice(0, params.length, ...p); - let scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={() => overlayDisposer()} onSave={(text, onError) => { + let scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={overlayDisposer} onSave={(text, onError) => { if (prewrapper) { text = prewrapper + text + (postwrapper ? postwrapper : ""); } diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index e05195ca0..1eb380e0b 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -135,19 +135,17 @@ export class ScriptingRepl extends React.Component { this.commands.push({ command: this.commandString, result: script.errors }); return; } - const result = script.run({ args: this.args }); - if (!result.success) { - this.commands.push({ command: this.commandString, result: result.error.toString() }); - return; - } - this.commands.push({ command: this.commandString, result: result.result }); - this.commandsHistory.push(this.commandString); + const result = script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })); + if (result.success) { + this.commands.push({ command: this.commandString, result: result.result }); + this.commandsHistory.push(this.commandString); - this.maybeScrollToBottom(); + this.maybeScrollToBottom(); - this.commandString = ""; - this.commandBuffer = ""; - this.historyIndex = -1; + this.commandString = ""; + this.commandBuffer = ""; + this.historyIndex = -1; + } break; } case "ArrowUp": { diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index d57a72dad..e4ef8313d 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,6 +1,5 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { DocumentType } from "../documents/DocumentTypes"; import { DocumentManager } from "../util/DocumentManager"; import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; @@ -30,12 +29,12 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool } } @observer -class ChromeToggle extends React.Component<{ checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> { +class OtherToggle extends React.Component<{ checked: boolean, name: string, toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> { render() { return ( <li className="chromeToggle"> <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event)} /> - Chrome + {this.props.name} </li> ); } @@ -51,19 +50,19 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { @observable private _hidden: boolean = true; dragRef = React.createRef<HTMLUListElement>(); - toggleCustom = (e: React.MouseEvent): void => { - this.props.docs.map(dv => dv.toggleCustomView()); + toggleCustom = (e: React.ChangeEvent<HTMLInputElement>): void => { + this.props.docs.map(dv => dv.setCustomView(e.target.checked)); } - toggleFloat = (e: React.MouseEvent): void => { + toggleFloat = (e: React.ChangeEvent<HTMLInputElement>): void => { SelectionManager.DeselectAll(); let topDocView = this.props.docs[0]; let topDoc = topDocView.props.Document; let xf = topDocView.props.ScreenToLocalTransform(); - let ex = e.clientX; - let ey = e.clientY; + let ex = e.target.clientLeft; + let ey = e.target.clientTop; undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))(); - if (!topDoc.z) { + if (e.target.checked) { setTimeout(() => { let newDocView = DocumentManager.Instance.getDocumentView(topDoc); if (newDocView) { @@ -90,16 +89,23 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { @action toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => { if (event.target.checked) { - this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = template.Name.toLowerCase()); + this.props.docs.map(d => d.Document["show" + template.Name] = template.Name.toLowerCase()); } else { - this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = undefined); + this.props.docs.map(d => d.Document["show" + template.Name] = ""); } } @undoBatch @action clearTemplates = (event: React.MouseEvent) => { - Templates.TemplateList.map(template => this.props.docs.map(d => d.layoutDoc["show" + template.Name] = false)); + Templates.TemplateList.forEach(template => this.props.docs.forEach(d => d.Document["show" + template.Name] = undefined)); + ["backgroundColor", "borderRounding", "width", "height"].forEach(field => this.props.docs.forEach(d => { + if (d.Document.isTemplate && d.props.DataDoc) { + d.Document[field] = undefined; + } else if (d.Document["default" + field[0].toUpperCase() + field.slice(1)] !== undefined) { + d.Document[field] = Doc.GetProto(d.Document)[field] = undefined; + } + })); } @action @@ -110,22 +116,26 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { @undoBatch @action toggleChrome = (): void => { - this.props.docs.map(dv => dv.layoutDoc.chromeStatus = (dv.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled")); + this.props.docs.map(dv => { + let layout = dv.Document.layout instanceof Doc ? dv.Document.layout as Doc : dv.Document; + layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled"); + }); } render() { + let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout as Doc : this.props.docs[0].Document; let templateMenu: Array<JSX.Element> = []; this.props.templates.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />)); - templateMenu.push(<ChromeToggle key={"chrome"} checked={this.props.docs[0].Document.chromeStatus !== "disabled"} toggle={this.toggleChrome} />); + templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docs[0].Document.z ? true : false} toggle={this.toggleFloat} />); + templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={typeof this.props.docs[0].Document.layout === "string" ? false : true} toggle={this.toggleCustom} />); + templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />); return ( <div className="templating-menu" > <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div> <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}> {templateMenu} - <button onClick={this.toggleCustom}>{this.props.docs[0].Document.nativeLayout ? "Native" : "Custom"}</button> - <button onClick={this.toggleFloat}>Float</button> - {/* <button onClick={this.clearTemplates}>Clear</button> */} + {<button onClick={this.clearTemplates}>Restore Defaults</button>} </ul> </div> ); diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index a54718e9e..56d12bd84 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -12,7 +12,6 @@ import { ContextMenu } from '../ContextMenu'; import { FieldViewProps } from '../nodes/FieldView'; import './CollectionBaseView.scss'; import { DateField } from '../../../new_fields/DateField'; -import { DocumentType } from '../../documents/DocumentTypes'; export enum CollectionViewType { Invalid, @@ -80,12 +79,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { } } - @computed get dataDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) ? this.props.DataDoc ? this.props.DataDoc : this.props.Document : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; } active = (): boolean => { var isSelected = this.props.isSelected(); - return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary); + return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; } //TODO should this be observable? @@ -95,7 +94,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { this.props.whenActiveChanged(isActive); } - @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } @action.bound addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { @@ -104,7 +103,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } - allowDuplicates = true; let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; const value = Cast(targetDataDoc[targetField], listSpec(Doc)); @@ -127,7 +125,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); + let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); + index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a350cfcc5..8fcba99e3 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,36 +1,35 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faFile } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, Lambda, observable, reaction, trace, computed } from "mobx"; +import { action, computed, Lambda, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; +import { DateField } from '../../../new_fields/DateField'; import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; import { Id } from '../../../new_fields/FieldSymbols'; +import { List } from '../../../new_fields/List'; import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; +import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; -import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { undoBatch } from "../../util/UndoManager"; +import { MainView } from '../MainView'; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; -import { ParentDocSelector } from './ParentDocumentSelector'; import React = require("react"); -import { MainView } from '../MainView'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { Docs } from '../../documents/Documents'; -import { DateField } from '../../../new_fields/DateField'; -import { List } from '../../../new_fields/List'; -import { DocumentType } from '../../documents/DocumentTypes'; +import { ButtonSelector } from './ParentDocumentSelector'; library.add(faFile); @observer @@ -44,7 +43,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp width: width, props: { documentId: document[Id], - dataDocumentId: dataDoc ? dataDoc[Id] : "" + dataDocumentId: dataDoc && dataDoc[Id] !== document[Id] ? dataDoc[Id] : "" //collectionDockingView: CollectionDockingView.Instance } }; @@ -59,7 +58,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp constructor(props: SubCollectionViewProps) { super(props); - if (props.addDocTab === emptyFunction) CollectionDockingView.Instance = this; + !CollectionDockingView.Instance && (CollectionDockingView.Instance = this); //Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; @@ -90,7 +89,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp @action public OpenFullScreen(docView: DocumentView) { let document = Doc.MakeAlias(docView.props.Document); - let dataDoc = docView.dataDoc; + let dataDoc = docView.props.DataDoc; let newItemStackConfig = { type: 'stack', content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)] @@ -121,21 +120,25 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp @undoBatch @action - public CloseRightSplit = (document: Doc): boolean => { + public static CloseRightSplit(document: Doc): boolean { + if (!CollectionDockingView.Instance) return false; + let instance = CollectionDockingView.Instance; let retVal = false; - if (this._goldenLayout.root.contentItems[0].isRow) { - retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { + if (instance._goldenLayout.root.contentItems[0].isRow) { + retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && + DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId) && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { child.contentItems[0].remove(); - this.layoutChanged(document); + instance.layoutChanged(document); return true; } else { Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { - if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { + if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId) && + Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { child.contentItems[j].remove(); child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); - let docs = Cast(this.props.Document.data, listSpec(Doc)); + let docs = Cast(instance.props.Document.data, listSpec(Doc)); docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); return true; } @@ -146,7 +149,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp }); } if (retVal) { - this.stateChanged(); + instance.stateChanged(); } return retVal; } @@ -173,8 +176,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // @undoBatch @action - public AddRightSplit = (document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) => { - let docs = Cast(this.props.Document.data, listSpec(Doc)); + public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) { + if (!CollectionDockingView.Instance) return false; + let instance = CollectionDockingView.Instance; + let docs = Cast(instance.props.Document.data, listSpec(Doc)); if (docs) { docs.push(document); } @@ -183,15 +188,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)] }; - var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); + var newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); - if (this._goldenLayout.root.contentItems.length === 0) { - this._goldenLayout.root.addChild(newContentItem); - } else if (this._goldenLayout.root.contentItems[0].isRow) { - this._goldenLayout.root.contentItems[0].addChild(newContentItem); + if (instance._goldenLayout.root.contentItems.length === 0) { + instance._goldenLayout.root.addChild(newContentItem); + } else if (instance._goldenLayout.root.contentItems[0].isRow) { + instance._goldenLayout.root.contentItems[0].addChild(newContentItem); } else { - var collayout = this._goldenLayout.root.contentItems[0]; - var newRow = collayout.layoutManager.createContentItem({ type: "row" }, this._goldenLayout); + var collayout = instance._goldenLayout.root.contentItems[0]; + var newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout); collayout.parent.replaceChild(collayout, newRow); newRow.addChild(newContentItem, undefined, true); @@ -206,9 +211,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // newContentItem.config.height = 10; } newContentItem.callDownwards('_$init'); - this.layoutChanged(); - - return newContentItem; + instance.layoutChanged(); + return true; } @undoBatch @@ -238,6 +242,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp stack.addChild(docContentConfig, undefined); } this.layoutChanged(); + return true; } setupGoldenLayout() { @@ -396,6 +401,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp dragSpan.style.bottom = "6px"; dragSpan.style.paddingLeft = "4px"; dragSpan.style.paddingRight = "2px"; + let gearSpan = document.createElement("span"); + gearSpan.style.position = "relative"; + gearSpan.style.paddingLeft = "0px"; + gearSpan.style.paddingRight = "12px"; let upDiv = document.createElement("span"); const stack = tab.contentItem.parent; // shifts the focus to this tab when another tab is dragged over it @@ -416,9 +425,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp hideSource: false }); }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan); - ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv); - tab.reactComponents = [dragSpan, upDiv]; + ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan); + // ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, data, where) => { + // where === "onRight" ? CollectionDockingView.AddRightSplit(doc, dataDoc) : CollectionDockingView.Instance.AddTab(stack, doc, dataDoc); + // return true; + // }} />, upDiv); + tab.reactComponents = [dragSpan, gearSpan, upDiv]; tab.element.append(dragSpan); + tab.element.append(gearSpan); tab.element.append(upDiv); tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => { tab.titleElement[0].textContent = doc.title, { fireImmediately: true }; @@ -528,11 +542,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { @observable private _isActive: boolean = false; get _stack(): any { - let parent = (this.props as any).glContainer.parent.parent; - if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) { - return parent.parent.contentItems[1]; - } - return parent; + return (this.props as any).glContainer.parent.parent; } constructor(props: any) { super(props); @@ -606,17 +616,18 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { } return Transform.Identity(); } - get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; } + get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() / this.ScreenToLocalTransform().Scale) / 2 : 0; } - addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { + addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => { if (doc.dockingConfig) { MainView.Instance.openWorkspace(doc); + return true; } else if (location === "onRight") { - CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); + return CollectionDockingView.AddRightSplit(doc, dataDoc); } else if (location === "close") { - CollectionDockingView.Instance.CloseRightSplit(doc); + return CollectionDockingView.CloseRightSplit(doc); } else { - CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); + return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); } } @computed get docView() { @@ -643,6 +654,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { addDocTab={this.addDocTab} pinToPres={this.PinDoc} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} zoomToScale={emptyFunction} getScale={returnOne} />; } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 3452e8702..4dac27e60 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -39,7 +39,7 @@ export interface CellProps { Document: Doc; fieldKey: string; renderDepth: number; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; isFocused: boolean; @@ -151,6 +151,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { fieldExt: "", ruleProvider: undefined, ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.CollectionView.props.Document, isSelected: returnFalse, select: emptyFunction, renderDepth: this.props.renderDepth + 1, @@ -237,13 +238,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> { return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run); }} OnFillDown={async (value: string) => { - let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - if (!script.compiled) { - return; + const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + if (script.compiled) { + DocListCast(this.props.Document[this.props.fieldKey]). + forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, script.run)); } - const run = script.run; - const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]); - val && val.forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, run)); }} /> </div > diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index ec40043cc..39abc41ec 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -201,12 +201,8 @@ export class MovableRow extends React.Component<MovableRowProps> { @action move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => { let targetView = DocumentManager.Instance.getDocumentView(target); - if (targetView) { - let targetContainingColl = targetView.props.ContainingCollectionView; //.props.ContainingCollectionView.props.Document; - if (targetContainingColl) { - let targetContCollDoc = targetContainingColl.props.Document; - return doc !== target && doc !== targetContCollDoc && this.props.removeDoc(doc) && addDoc(doc); - } + if (targetView && targetView.props.ContainingCollectionDoc) { + return doc !== target && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); } return doc !== target && this.props.removeDoc(doc) && addDoc(doc); } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 25d3bd128..7bd2a1971 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -196,6 +196,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { childDocs={this.childDocs} CollectionView={this.props.CollectionView} ContainingCollectionView={this.props.ContainingCollectionView} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} fieldKey={this.props.fieldKey} renderDepth={this.props.renderDepth} moveDocument={this.props.moveDocument} @@ -247,6 +248,7 @@ export interface SchemaTableProps { childDocs?: Doc[]; CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; + ContainingCollectionDoc: Opt<Doc>; fieldKey: string; renderDepth: number; deleteDocument: (document: Doc) => boolean; @@ -254,7 +256,7 @@ export interface SchemaTableProps { ScreenToLocalTransform: () => Transform; active: () => boolean; onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; isSelected: () => boolean; isFocused: (document: Doc) => boolean; @@ -913,7 +915,7 @@ interface CollectionSchemaPreviewProps { removeDocument: (document: Doc) => boolean; active: () => boolean; whenActiveChanged: (isActive: boolean) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; setPreviewScript: (script: string) => void; previewScript?: string; @@ -946,13 +948,12 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - let docDrag = de.data; - let computed = CompileScript("return this.image_data[0]", { params: { this: "Doc" } }); this.props.childDocs && this.props.childDocs.map(otherdoc => { - let doc = docDrag.draggedDocuments[0]; let target = Doc.GetProto(otherdoc); - target.layout = target.detailedLayout = Doc.MakeDelegate(doc); - computed.compiled && (target.miniLayout = new ComputedField(computed)); + let layoutNative = Doc.MakeTitled("layoutNative"); + layoutNative.layout = ComputedField.MakeFunction("this.image_data[0]"); + target.layoutNative = layoutNative; + target.layoutCUstom = target.layout = Doc.MakeDelegate(de.data.draggedDocuments[0]); }); e.stopPropagation(); } @@ -1005,6 +1006,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre moveDocument={this.props.moveDocument} whenActiveChanged={this.props.whenActiveChanged} ContainingCollectionView={this.props.CollectionView} + ContainingCollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} parentActive={this.props.active} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 14a9dc9d9..ccf131797 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -29,7 +29,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); _heightDisposer?: IReactionDisposer; - _childLayoutDisposer?: IReactionDisposer; _sectionFilterDisposer?: IReactionDisposer; _docXfs: any[] = []; _columnStart: number = 0; @@ -87,10 +86,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } componentDidMount() { - this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], - async (args) => args[1] instanceof Doc && - this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc), undefined))); - // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). this._heightDisposer = reaction(() => { if (this.isStackingView && BoolCast(this.props.Document.autoHeight)) { @@ -115,7 +110,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { ); } componentWillUnmount() { - this._childLayoutDisposer && this._childLayoutDisposer(); this._heightDisposer && this._heightDisposer(); this._sectionFilterDisposer && this._sectionFilterDisposer(); } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 34f2652ff..b3b7b40dd 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -178,13 +178,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let key = StrCast(this.props.parent.props.Document.sectionFilter); let value = this.getValue(this._heading); value = typeof value === "string" ? `"${value}"` : value; - let script = `return doc.${key} === ${value}`; - let compiled = CompileScript(script, { params: { doc: Doc.name } }); - if (compiled.compiled) { - let scriptField = new ScriptField(compiled); - alias.viewSpecScript = scriptField; - let dragData = new DragManager.DocumentDragData([alias]); - DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY); + alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name }); + if (alias.viewSpecScript) { + DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY); } e.stopPropagation(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d13c69ecf..774e6b1b9 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,4 +1,4 @@ -import { action, computed } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import * as rp from 'request-promise'; import CursorField from "../../../new_fields/CursorField"; import { Doc, DocListCast } from "../../../new_fields/Doc"; @@ -40,6 +40,8 @@ export interface SubCollectionViewProps extends CollectionViewProps { export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; + private _childLayoutDisposer?: IReactionDisposer; + protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer && this.dropDisposer(); if (ele) { @@ -50,35 +52,35 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { this.createDropTarget(ele); } - @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + componentDidMount() { + this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], + async (args) => args[1] instanceof Doc && + this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)))); + + } + componentWillUnmount() { + this._childLayoutDisposer && this._childLayoutDisposer(); + } + + // The data field for rendeing this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. + // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through + // to its children which may be templates. + // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise. + @computed get dataField() { + return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey]; + } get childLayoutPairs() { return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! })); } - get childDocs() { - //TODO tfs: This might not be what we want? - //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) - let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]); - let viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - if (viewSpecScript) { - let script = viewSpecScript.script; - docs = docs.filter(d => { - let res = script.run({ doc: d }); - if (res.success) { - return res.result; - } - else { - console.log(res.error); - } - }); - } - return docs; - } get childDocList() { - //TODO tfs: This might not be what we want? - //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) - return Cast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc)); + return Cast(this.dataField, listSpec(Doc)); + } + get childDocs() { + let docs = DocListCast(this.dataField); + const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); + return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; } @action @@ -119,7 +121,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) { if (de.mods === "AltKey" && de.data.draggedDocuments.length) { this.childDocs.map(doc => - Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined) + Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc) ); e.stopPropagation(); return true; @@ -265,6 +267,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { promises.push(prom); } } + if (text) { + this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 })); + return; + } if (promises.length) { Promise.all(promises).finally(() => { completed && completed(); batch.end(); }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 40bea2f9d..e5313f68c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -40,7 +40,7 @@ export interface TreeViewProps { ruleProvider: Doc | undefined; moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; - addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; panelWidth: () => number; panelHeight: () => number; @@ -178,7 +178,7 @@ class TreeView extends React.Component<TreeViewProps> { SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true)} OnFillDown={undoBatch((value: string) => { Doc.GetProto(this.dataDoc)[key] = value; - let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined; + let doc = this.props.document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layoutCustom)) : undefined; if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; return this.props.addDocument(doc); @@ -405,7 +405,7 @@ class TreeView extends React.Component<TreeViewProps> { </div>; } public static GetChildElements( - docList: Doc[], + docs: Doc[], treeViewId: string, containingCollection: Doc, dataDoc: Doc | undefined, @@ -414,7 +414,7 @@ class TreeView extends React.Component<TreeViewProps> { remove: ((doc: Doc) => boolean), move: DragManager.MoveFunction, dropAction: dropActionType, - addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void, + addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean, pinToPres: (document: Doc) => void, screenToLocalXf: () => Transform, outerXf: () => { translateX: number, translateY: number }, @@ -425,19 +425,9 @@ class TreeView extends React.Component<TreeViewProps> { preventTreeViewOpen: boolean, renderedIds: string[] ) { - let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0); - let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); + const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); if (viewSpecScript) { - let script = viewSpecScript.script; - docs = docs.filter(d => { - let res = script.run({ doc: d }); - if (res.success) { - return res.result; - } - else { - console.log(res.error); - } - }); + docs = docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } let ascending = Cast(containingCollection.sortAscending, "boolean", null); @@ -548,7 +538,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // excludeFromLibrary means this is the user document + if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" }); ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.props.Document), icon: "minus" }); e.stopPropagation(); @@ -563,8 +553,8 @@ export class CollectionTreeView extends CollectionSubView(Document) { outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {}); openNotifsCol = () => { - if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) { - CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined); + if (CollectionTreeView.NotifsCol) { + this.props.addDocTab(CollectionTreeView.NotifsCol, undefined, "onRight"); } } @@ -614,7 +604,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)} OnFillDown={undoBatch((value: string) => { Doc.GetProto(this.props.Document).title = value; - let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined; + let doc = this.props.Document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layoutCustom)) : undefined; if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 548f663ec..5f4742834 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -99,7 +99,7 @@ export class CollectionView extends React.Component<FieldViewProps> { subItems.push({ description: "Stacking (AutoHeight)", event: () => { this.props.Document.viewType = CollectionViewType.Stacking; - this.props.Document.autoHeight = true + this.props.Document.autoHeight = true; }, icon: "ellipsis-v" }); subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index c897af17e..7510b86a0 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -215,14 +215,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro } } let fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ? - `return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : - `return (${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : - "return true"; + `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : + `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : + "true"; - let compiled = CompileScript(fullScript, { params: { doc: Doc.name }, typecheck: false }); - if (compiled.compiled) { - this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); - } + this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name }); } @action @@ -283,11 +280,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro @action.bound clearFilter = () => { - let compiled = CompileScript("return true", { params: { doc: Doc.name }, typecheck: false }); - if (compiled.compiled) { - this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); - } - + this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction("true", { doc: Doc.name }); this._keyRestrictions = []; this.addKeyRestrictions([]); } diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss index 2dd3e49f2..c186d15f8 100644 --- a/src/client/views/collections/ParentDocumentSelector.scss +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -19,4 +19,13 @@ border-right: 0px; border-left: 0px; } +} +.parentDocumentSelector-button { + pointer-events: all; +} +.buttonSelector { + position: absolute; + display: inline-block; + padding-left: 5px; + padding-right: 5px; }
\ No newline at end of file diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index d8475a467..7f2913214 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -8,8 +8,15 @@ import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; import { NumCast } from "../../../new_fields/Types"; import { CollectionViewType } from "./CollectionBaseView"; +import { DocumentButtonBar } from "../DocumentButtonBar"; +import { DocumentManager } from "../../util/DocumentManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEdit } from "@fortawesome/free-solid-svg-icons"; +import { library } from "@fortawesome/fontawesome-svg-core"; -type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void }; +library.add(faEdit); + +type SelectorProps = { Document: Doc, Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void }; @observer export class SelectorContextMenu extends React.Component<SelectorProps> { @observable private _docs: { col: Doc, target: Doc }[] = []; @@ -83,7 +90,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> { ); } return ( - <span style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }} + <span className="parentDocumentSelector-button" style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <p>^</p> @@ -92,3 +99,38 @@ export class ParentDocSelector extends React.Component<SelectorProps> { ); } } + +@observer +export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any }> { + @observable hover = false; + + @action + onMouseLeave = () => { + this.hover = false; + } + + @action + onMouseEnter = () => { + this.hover = true; + } + + render() { + let flyout; + if (this.hover) { + let view = DocumentManager.Instance.getDocumentView(this.props.Document); + flyout = !view ? (null) : ( + <div className="PDS-flyout" title=" " onMouseLeave={this.onMouseLeave}> + <DocumentButtonBar views={[view]} stack={this.props.Stack} /> + </div> + ); + } + return ( + <span className="buttonSelector" + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave}> + {this.hover ? (null) : <FontAwesomeIcon icon={faEdit} size={"sm"} />} + {flyout} + </span> + ); + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index a25627dd1..a81f5315a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -79,15 +79,15 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP if (containerDoc) { equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!); } - if (view.props.ContainingCollectionView) { - let collid = view.props.ContainingCollectionView.props.Document[Id]; + if (view.props.ContainingCollectionDoc) { + let collid = view.props.ContainingCollectionDoc[Id]; DocListCast(this.props.Document[this.props.fieldKey]). filter(child => child[Id] === collid).map(view => DocumentManager.Instance.getDocumentViews(view).map(view => equalViews.push(view))); } - return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document); + return equalViews.filter(sv => sv.props.ContainingCollectionDoc === this.props.Document); } @computed diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 03ac012b4..36e62842c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -24,9 +24,9 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; +import { DocumentViewProps, documentSchema } from "../../nodes/DocumentView"; import { pageSchema } from "../../nodes/ImageBox"; import { OverlayElementOptions, OverlayView } from "../../OverlayView"; import PDFMenu from "../../pdf/PDFMenu"; @@ -156,6 +156,7 @@ export namespace PivotView { y={pos.y} width={pos.width} height={pos.height} + transition={"transform 1s"} jitterRotation={NumCast(target.props.Document.jitterRotation)} {...target.getChildDocumentViewProps(doc)} />, @@ -176,44 +177,16 @@ export namespace PivotView { } -type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; -const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema); +type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; +const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema); @observer export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _lastX: number = 0; private _lastY: number = 0; - private _inkKey = "ink"; // the document key used to store ink annotation strokes private get _pwidth() { return this.props.PanelWidth(); } private get _pheight() { return this.props.PanelHeight(); } - private _childLayoutDisposer?: IReactionDisposer; - private _childDisposer?: IReactionDisposer; - - componentDidMount() { - this._childDisposer = reaction(() => this.childDocs, - async (childDocs) => { - let childLayout = Cast(this.props.Document.childLayout, Doc) as Doc; - childLayout && childDocs.map(async doc => { - if (!Doc.AreProtosEqual(childLayout, (await doc).layout as Doc)) { - Doc.ApplyTemplateTo(childLayout, doc, undefined); - } - }); - }); - this._childLayoutDisposer = reaction(() => Cast(this.props.Document.childLayout, Doc), - async (childLayout) => { - this.childDocs.map(async doc => { - if (!Doc.AreProtosEqual(childLayout as Doc, (await doc).layout as Doc)) { - Doc.ApplyTemplateTo(childLayout as Doc, doc, undefined); - } - }); - }); - } - componentWillUnmount() { - this._childDisposer && this._childDisposer(); - this._childLayoutDisposer && this._childLayoutDisposer(); - } - - get parentScaling() { + private get parentScaling() { return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1; } @@ -290,7 +263,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @computed get fieldExtensionDoc() { - return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); + return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey); } intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -323,8 +296,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { let z = NumCast(de.data.droppedDocuments[0].z); - let x = (z ? xpo : xp) - de.data.xOffset; - let y = (z ? ypo : yp) - de.data.yOffset; + let x = (z ? xpo : xp) - de.data.offset[0]; + let y = (z ? ypo : yp) - de.data.offset[1]; let dropX = NumCast(de.data.droppedDocuments[0].x); let dropY = NumCast(de.data.droppedDocuments[0].y); de.data.droppedDocuments.forEach(d => { @@ -341,14 +314,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.bringToFront(d); }); - de.data.droppedDocuments.length == 1 && this.updateCluster(de.data.droppedDocuments[0]); + de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); } } else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; - let x = xp - de.data.xOffset; - let y = yp - de.data.yOffset; + let x = xp - de.data.offset[0]; + let y = yp - de.data.offset[1]; let dropX = NumCast(de.data.dropDocument.x); let dropY = NumCast(de.data.dropDocument.y); dragDoc.x = x + NumCast(dragDoc.x) - dropX; @@ -387,10 +360,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let de = new DragManager.DocumentDragData(eles); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); - const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); + de.offset = this.getTransform().transformDirection(e.x - left, e.y - top); de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; - de.xOffset = xoff; - de.yOffset = yoff; DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, { handlers: { dragComplete: action(emptyFunction) }, hideSource: !de.dropAction @@ -473,7 +444,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // choose a cluster color from a palette let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; clusterColor = colors[cluster % colors.length]; - let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor != s.defaultBackgroundColor)) : undefined; + let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)) : undefined; // 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 && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); @@ -536,14 +507,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); } - let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling(), - this._pheight / this.zoomScaling()); - let panelwidth = panelDim[0]; - let panelheight = panelDim[1]; - if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2; - if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2; - if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2; - if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2; + let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; + let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling() * cscale, + this._pheight / this.zoomScaling() * cscale); + if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2; + if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2; + if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2; + if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2; } this.setPan(x - dx, y - dy); this._lastX = e.pageX; @@ -609,7 +579,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } - focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => { + focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => { const state = HistoryUtil.getState(); // TODO This technically isn't correct if type !== "doc", as // currently nothing is done, but we should probably push a new state @@ -630,6 +600,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const newState = HistoryUtil.getState(); newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); + + let px = this.Document.panX; + let py = this.Document.panY; + let s = this.Document.scale; this.setPan(newPanX, newPanY); this.props.Document.panTransformType = "Ease"; @@ -637,6 +611,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (willZoom) { this.setScaleToZoom(doc, scale); } + console.log("Focused " + this.Document.title + " " + s); + afterFocus && setTimeout(() => { + if (afterFocus && afterFocus()) { + console.log("UnFocused " + this.Document.title + " " + s); + this.Document.panX = px; + this.Document.panY = py; + this.Document.scale = s; + } + }, 1000); } setScaleToZoom = (doc: Doc, scale: number = 0.5) => { @@ -676,6 +659,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { PanelHeight: childLayout[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.CollectionView.props.Document, focus: this.focusDocument, backgroundColor: this.getClusterColor, parentActive: this.props.active, @@ -702,6 +686,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { PanelHeight: layoutDoc[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.CollectionView.props.Document, focus: this.focusDocument, backgroundColor: returnEmptyString, parentActive: this.props.active, @@ -714,10 +699,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } - getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } { - const result = script.script.run(params); - return !result.success ? {} : result.result !== undefined ? result.result : - { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; + getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } { + const script = this.Document.arrangeScript; + const result = script && script.script.run(params, console.log); + if (result && result.success) { + return { ...result, transition: "transform 1s" }; + } + return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; } viewDefsToJSX = (views: any[]) => { @@ -759,12 +747,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (this.Document.usePivotLayout) return PivotView.elements(this); let curPage = FieldValue(this.Document.curPage, -1); const initScript = this.Document.arrangeInit; - const script = this.Document.arrangeScript; let state: any = undefined; let pairs = this.childLayoutPairs; let elements: ViewDefResult[] = []; if (initScript) { - const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }); + const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log); if (initResult.success) { const result = initResult.result; const { state: scriptState, views } = result; @@ -774,23 +761,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let docviews = pairs.reduce((prev, pair) => { var page = NumCast(pair.layout.page, -1); - if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { - let minim = BoolCast(pair.layout.isMinimized); - if (minim === undefined || !minim) { - const pos = script ? this.getCalculatedPositions(script, { doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state }) : - { x: Cast(pair.layout.x, "number"), y: Cast(pair.layout.y, "number"), z: Cast(pair.layout.z, "number"), width: Cast(pair.layout.width, "number"), height: Cast(pair.layout.height, "number") }; - state = pos.state === undefined ? state : pos.state; - if (pair.layout && !(pair.data instanceof Promise)) { - prev.push({ - ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} - ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} - jitterRotation={NumCast(this.props.Document.jitterRotation)} - x={script ? pos.x : undefined} y={script ? pos.y : undefined} - width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, - bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } - }); - } - } + if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) { + const pos = this.getCalculatedPositions({ doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state }); + state = pos.state === undefined ? state : pos.state; + prev.push({ + ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} + ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} + jitterRotation={NumCast(this.props.Document.jitterRotation)} + transition={pos.transition} x={pos.x} y={pos.y} width={pos.width} height={pos.height} + {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, + bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 } + }); } return prev; }, elements); @@ -848,11 +829,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor; } }) - ) + ); } analyzeStrokes = async () => { - let data = Cast(this.fieldExtensionDoc[this._inkKey], InkField); + let data = Cast(this.fieldExtensionDoc.ink, InkField); if (data) { CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData); } @@ -903,9 +884,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { description: "Add Note ...", subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({ description: (i + 1) + ": " + StrCast(note.title), - event: ({ x, y }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(x, y)[0], y: this.getTransform().transformPoint(x, y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })), + event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })), icon: "eye" - })), + })) as ContextMenuProps[], icon: "eye" }); ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); @@ -946,12 +927,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } render() { + // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x; this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y; this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x); this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y); + // if fieldExt is set, then children will be stored in the extension document for the fieldKey. + // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document + Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey); const easing = () => this.props.Document.panTransformType === "Ease"; - Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); return ( <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel} onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 8decebe0d..bbea4a555 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,30 +1,24 @@ -import * as htmlToImage from "html-to-image"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Doc, DocListCast } from "../../../../new_fields/Doc"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; import { Utils } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; -import { Templates } from "../../Templates"; import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); -import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; -import { string } from "prop-types"; -import { listSpec } from "../../../../new_fields/Schema"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; -import { CompileScript } from "../../../util/Scripting"; -import { ComputedField } from "../../../../new_fields/ScriptField"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -234,18 +228,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; } - get ink() { - let container = this.props.container.props.Document; - let containerKey = this.props.container.props.fieldKey; - let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); - return Cast(extensionDoc.ink, InkField); + get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. + let cprops = this.props.container.props; + return Cast(Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink, InkField); } set ink(value: InkField | undefined) { - let container = Doc.GetProto(this.props.container.props.Document); - let containerKey = this.props.container.props.fieldKey; - let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); - extensionDoc.ink = value; + let cprops = this.props.container.props; + Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink = value; } @undoBatch @@ -327,16 +317,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps> }); newCollection.chromeStatus = "disabled"; let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - Doc.GetProto(newCollection).summaryDoc = summary; Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]); newCollection.x = bounds.left + bounds.width; - let computed = CompileScript(`return summaryTitle(this);`, { params: { this: "Doc" }, typecheck: false }); - computed.compiled && (Doc.GetProto(newCollection).title = new ComputedField(computed)); + Doc.GetProto(newCollection).summaryDoc = summary; + Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); container.viewType = CollectionViewType.Stacking; container.autoHeight = true; - Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "inPlace", or "onRight" + Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" this.props.addLiveTextDocument(container); } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight" diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 75a4aea26..1280ae28b 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -2,7 +2,7 @@ import { observable, computed, action, runInAction, reaction, IReactionDisposer import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; -import { Doc, DocListCastAsync } from "../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc"; import { undoBatch } from "../../util/UndoManager"; import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types"; import { CollectionViewType } from "../collections/CollectionBaseView"; @@ -86,11 +86,10 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { } async resetPan() { - if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionView) { - let colDoc = this.sourceView.props.ContainingCollectionView.props.Document; - runInAction(() => { this.canPan = false; }); - if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) { - let docs = Cast(colDoc.data, listSpec(Doc), []); + if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionDoc) { + runInAction(() => this.canPan = false); + if (this.sourceView.props.ContainingCollectionDoc.viewType === CollectionViewType.Freeform) { + let docs = Cast(this.sourceView.props.ContainingCollectionDoc.data, listSpec(Doc), []); let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc)); aliases.forEach(alias => { @@ -197,9 +196,9 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { } - _addDocTab: (undefined | ((doc: Doc, dataDoc: Doc | undefined, where: string) => void)); + _addDocTab: (undefined | ((doc: Doc, dataDoc: Opt<Doc>, where: string) => boolean)); - setAddDocTab = (addFunc: (doc: Doc, dataDoc: Doc | undefined, where: string) => void) => { + setAddDocTab = (addFunc: (doc: Doc, dataDoc: Opt<Doc>, where: string) => boolean) => { this._addDocTab = addFunc; } @@ -213,7 +212,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { options.context.panX = newPanX; options.context.panY = newPanY; } - CollectionDockingView.Instance.AddRightSplit(options.context, undefined); + (this._addDocTab || this.props.addDocTab)(options.context, undefined, "onRight"); if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom }); @@ -226,7 +225,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { openLinkRight = () => { if (LinkFollowBox.destinationDoc) { let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); - CollectionDockingView.Instance.AddRightSplit(alias, undefined); + (this._addDocTab || this.props.addDocTab)(alias, undefined, "onRight"); this.highlightDoc(); SelectionManager.DeselectAll(); } @@ -247,7 +246,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { let guid = StrCast(LinkFollowBox.linkDoc.guid); const shouldZoom = options ? options.shouldZoom : false; - let dockingFunc = (document: Doc) => { this._addDocTab && this._addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + let dockingFunc = (document: Doc) => { (this._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) { DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); } @@ -283,7 +283,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { if (LinkFollowBox.destinationDoc) { let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc); // this.prosp.addDocTab is empty -- use the link source's addDocTab - this._addDocTab && this._addDocTab(fullScreenAlias, undefined, "inTab"); + (this._addDocTab || this.props.addDocTab)(fullScreenAlias, undefined, "inTab"); this.highlightDoc(); SelectionManager.DeselectAll(); @@ -300,7 +300,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { options.context.panX = newPanX; options.context.panY = newPanY; } - this._addDocTab && this._addDocTab(options.context, undefined, "inTab"); + (this._addDocTab || this.props.addDocTab)(options.context, undefined, "inTab"); if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom }); this.highlightDoc(); @@ -382,9 +382,9 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { this.shouldUseOnlyParentContext = (this.selectedMode === FollowModes.INPLACE || this.selectedMode === FollowModes.PAN); if (this.shouldUseOnlyParentContext) { - if (this.sourceView && this.sourceView.props.ContainingCollectionView) { - this.selectedContext = this.sourceView.props.ContainingCollectionView.props.Document; - this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionView.props.Document.title)); + if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { + this.selectedContext = this.sourceView.props.ContainingCollectionDoc; + this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionDoc.title)); } } } @@ -405,9 +405,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { @computed get canOpenInPlace() { - if (this.sourceView && this.sourceView.props.ContainingCollectionView) { - let colView = this.sourceView.props.ContainingCollectionView; - let colDoc = colView.props.Document; + if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { + let colDoc = this.sourceView.props.ContainingCollectionDoc; if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) return true; } return false; @@ -468,17 +467,15 @@ export class LinkFollowBox extends React.Component<FieldViewProps> { @computed get parentName() { - if (this.sourceView && this.sourceView.props.ContainingCollectionView) { - let colView = this.sourceView.props.ContainingCollectionView; - return colView.props.Document.title; + if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { + return this.sourceView.props.ContainingCollectionDoc.title; } } @computed get parentID(): string { - if (this.sourceView && this.sourceView.props.ContainingCollectionView) { - let colView = this.sourceView.props.ContainingCollectionView; - return StrCast(colView.props.Document[Id]); + if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { + return StrCast(this.sourceView.props.ContainingCollectionDoc[Id]); } return "col"; } diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 842ce45b1..27af873b5 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -16,7 +16,7 @@ library.add(faTrash); interface Props { docView: DocumentView; changeFlyout: () => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; } @observer diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index d711ac284..1891919ce 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -1,27 +1,25 @@ -import { action, observable } from "mobx"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action } from "mobx"; import { observer } from "mobx-react"; -import { DocumentView } from "../nodes/DocumentView"; -import { LinkMenuItem } from "./LinkMenuItem"; -import { LinkEditor } from "./LinkEditor"; -import './LinkMenu.scss'; -import React = require("react"); -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; -import { LinkManager } from "../../util/LinkManager"; -import { DragLinksAsDocuments, DragManager, SetupDrag } from "../../util/DragManager"; +import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { emptyFunction } from "../../../Utils"; import { Docs } from "../../documents/Documents"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { DragManager, SetupDrag } from "../../util/DragManager"; +import { LinkManager } from "../../util/LinkManager"; import { UndoManager } from "../../util/UndoManager"; -import { StrCast } from "../../../new_fields/Types"; -import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; +import { DocumentView } from "../nodes/DocumentView"; +import './LinkMenu.scss'; +import { LinkMenuItem } from "./LinkMenuItem"; +import React = require("react"); interface LinkMenuGroupProps { sourceDoc: Doc; group: Doc[]; groupType: string; showEditor: (linkDoc: Doc) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; docView: DocumentView; } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index d0c0d184d..835554ac0 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -21,7 +21,7 @@ interface LinkMenuItemProps { sourceDoc: Doc; destinationDoc: Doc; showEditor: (linkDoc: Doc) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; } @observer diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 68d3b8ae1..f08ea4891 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -3,7 +3,7 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons'; import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; +import { Doc, DocListCastAsync, DocListCast } from '../../../new_fields/Doc'; import { List } from '../../../new_fields/List'; import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema'; import { ScriptField } from '../../../new_fields/ScriptField'; @@ -49,7 +49,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt funcs.push({ description: "Clear Script Params", event: () => { let params = Cast(this.props.Document.buttonParams, listSpec("string")); - params && params.map(p => this.props.Document[p] = undefined) + params && params.map(p => this.props.Document[p] = undefined); }, icon: "trash" }); @@ -68,7 +68,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt render() { let params = Cast(this.props.Document.buttonParams, listSpec("string")); let missingParams = params && params.filter(p => this.props.Document[p] === undefined); - params && params.map(async p => await DocListCastAsync(this.props.Document[p])); // bcz: really hacky form of prefetching ... + params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... return ( <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <div className="buttonBox-mainButton" style={{ background: StrCast(this.props.Document.backgroundColor), color: StrCast(this.props.Document.color, "black") }} > diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 19d4a6784..9685f9bca 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,10 +1,10 @@ -import { computed, action, observable, reaction, IReactionDisposer } from "mobx"; +import { computed, action, observable, reaction, IReactionDisposer, trace } from "mobx"; import { observer } from "mobx-react"; import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; import { FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; -import { percent2frac } from "../../../Utils" +import { percent2frac } from "../../../Utils"; import { DocumentView, DocumentViewProps, documentSchema } from "./DocumentView"; import "./CollectionFreeFormDocumentView.scss"; import React = require("react"); @@ -17,8 +17,9 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { width?: number; height?: number; jitterRotation: number; + transition?: string; } -const positionSchema = createSchema({ +export const positionSchema = createSchema({ zIndex: "number", x: "number", y: "number", @@ -32,8 +33,8 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema); export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } - @computed get X() { return this._animx !== undefined ? this._animx : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this._animy !== undefined ? this._animy : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } + @computed get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } + @computed get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } @computed get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.Document[WidthSym](); } @computed get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.Document[HeightSym](); } @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } @@ -59,22 +60,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this._disposer && this._disposer(); } componentDidMount() { - this._disposer = reaction(() => this.props.Document.iconTarget, + this._disposer = reaction(() => [this.props.Document.animateToPos, this.props.Document.isAnimating], () => { - const icon = this.props.Document.iconTarget ? Array.from(Cast(this.props.Document.iconTarget, listSpec("number"))!) : undefined; - if (icon) { - let target = this.props.ScreenToLocalTransform().transformPoint(icon[0], icon[1]); - if (icon[2] === 1) { - this._animx = target[0]; - this._animy = target[1]; - } - setTimeout(action(() => { - this._animx = icon[2] === 1 ? this.Document.x : target[0]; - this._animy = icon[2] === 1 ? this.Document.y : target[1]; - }), 25); - } else { - this._animx = this._animy = undefined; - } + const target = this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined; + this._animPos = !target ? undefined : target[2] ? [this.Document.x || 0, this.Document.y || 0] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]); }, { fireImmediately: true }); } @@ -83,7 +72,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF panelHeight = () => this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth); + .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth) borderRounding = () => { let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; @@ -107,11 +96,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; } - @observable _animx: number | undefined = undefined; - @observable _animy: number | undefined = undefined; + @observable _animPos: number[] | undefined = undefined; render() { - const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return ( <div className="collectionFreeFormDocumentView-container" style={{ @@ -123,7 +110,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF StrCast(this.layoutDoc.boxShadow, ""), borderRadius: this.borderRounding(), transform: this.transform, - transition: this.props.Document.isIconAnimating ? "transform .5s" : hasPosition ? "transform 1s" : StrCast(this.layoutDoc.transition), + transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : StrCast(this.layoutDoc.transition), width: this.width, height: this.height, zIndex: this.Document.zIndex || 0, diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d0e117fe4..3c3cc0d91 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -93,13 +93,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { return { props: list }; } - @computed get templates(): List<string> { - let field = this.props.Document.templates; - if (field && field instanceof List) { - return field; - } - return new List<string>(); - } @computed get finalLayout() { return this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout; } @@ -107,7 +100,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { render() { let self = this; if (this.props.renderDepth > 7) return (null); - if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null); + if (!this.layout && this.props.layoutKey !== "overlayLayout") return (null); return <ObserverJsxParser blacklistedAttrs={[]} components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox }} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 37c38cc04..d8cfff973 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,9 +1,9 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; +import { action, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; -import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; @@ -40,7 +40,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { CompileScript, Scripting } from '../../util/Scripting'; +import { Scripting } from '../../util/Scripting'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -65,19 +65,9 @@ library.add(fa.faUnlock); library.add(fa.faLock); library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone); -// const linkSchema = createSchema({ -// title: "string", -// linkDescription: "string", -// linkTags: "string", -// linkedTo: Doc, -// linkedFrom: Doc -// }); - -// type LinkDoc = makeInterface<[typeof linkSchema]>; -// const LinkDoc = makeInterface(linkSchema); - export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; + ContainingCollectionDoc: Opt<Doc>; Document: Doc; DataDoc?: Doc; fitToBox?: boolean; @@ -92,13 +82,12 @@ export interface DocumentViewProps { ruleProvider: Doc | undefined; PanelWidth: () => number; PanelHeight: () => number; - focus: (doc: Doc, willZoom: boolean, scale?: number) => void; + focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void; parentActive: () => boolean; whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; - addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; - collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void; zoomToScale: (scale: number) => void; backgroundColor: (doc: Doc) => string | undefined; getScale: () => number; @@ -107,34 +96,32 @@ export interface DocumentViewProps { } export const documentSchema = createSchema({ - layout: "string", // should also allow Doc but that can't be expressed in the schema - title: "string", - nativeWidth: "number", - nativeHeight: "number", - backgroundColor: "string", - opacity: "number", - hidden: "boolean", - onClick: ScriptField, - ignoreAspect: "boolean", - autoHeight: "boolean", - isTemplate: "boolean", - isButton: "boolean", - isBackground: "boolean", - ignoreClick: "boolean", - type: "string", - maximizeLocation: "string", - lockedPosition: "boolean", - excludeFromLibrary: "boolean", - width: "number", - height: "number", - borderRounding: "string", - fitToBox: "boolean", - searchFields: "string", - heading: "number", - showCaption: "string", - showTitle: "string" + // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out + title: "string", // document title (can be on either data document or layout) + nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set + nativeHeight: "number", // " + width: "number", // width of document in its container's coordinate system + height: "number", // " + backgroundColor: "string", // background color of document + opacity: "number", // opacity of document + onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document + autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents + isTemplate: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed + isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) + type: "string", // enumerated type of document + maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) + lockedPosition: "boolean", // whether the document can be spatially manipulated + borderRounding: "string", // border radius rounding of document + searchFields: "string", // the search fields to display when this document matches a search in its metadata + heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) + showCaption: "string", // whether editable caption text is overlayed at the bottom of the document + showTitle: "string", // whether an editable title banner is displayed at tht top of the document + isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) + ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) }); + type Document = makeInterface<[typeof documentSchema]>; const Document = makeInterface(documentSchema); @@ -149,52 +136,36 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _dropDisposer?: DragManager.DragDropDisposer; public get ContentDiv() { return this._mainCont.current; } - @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } - @computed get topMost(): boolean { return this.props.renderDepth === 0; } + @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); } + @computed get topMost() { return this.props.renderDepth === 0; } + @computed get nativeWidth() { return this.Document.nativeWidth || 0; } + @computed get nativeHeight() { return this.Document.nativeHeight || 0; } + @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } @action componentDidMount() { - if (this._mainCont.current) { - this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { - handlers: { drop: this.drop.bind(this) } - }); - } + this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); DocumentManager.Instance.DocumentViews.push(this); } @action componentDidUpdate() { this._dropDisposer && this._dropDisposer(); - if (this._mainCont.current) { - this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { - handlers: { drop: this.drop.bind(this) } - }); - } + this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); } + @action componentWillUnmount() { this._dropDisposer && this._dropDisposer(); DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } - get dataDoc() { - // bcz: don't think we need this, but left it in in case strange behavior pops up. DocumentContentsView has this functionality - // if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) { - // // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document - // // has a template layout document, then we will render the template layout but use - // // this document as the data document for the layout. - // return this.props.Document; - // } - return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; - } startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) { if (this._mainCont.current) { - const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); let dragData = new DragManager.DocumentDragData([this.props.Document]); - const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); + const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); + dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); dragData.dropAction = dropAction; - dragData.xOffset = xoff; - dragData.yOffset = yoff; dragData.moveDocument = this.props.moveDocument; dragData.applyAsTemplate = applyAsTemplate; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { @@ -206,151 +177,78 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - @action - public collapseTargetsToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { - SelectionManager.DeselectAll(); - if (expandedDocs) { - let isMinimized: boolean | undefined; - expandedDocs.map(maximizedDoc => { - if (isMinimized === undefined) { - isMinimized = BoolCast(maximizedDoc.isMinimized); - } - let w = NumCast(maximizedDoc.width); - let h = NumCast(maximizedDoc.height); - let iconAnimating = maximizedDoc.isIconAnimating ? Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!) : undefined; - if (isMinimized || (iconAnimating && iconAnimating.length && iconAnimating[0] === 0)) { - // MAXIMIZE DOC - if (maximizedDoc.isMinimized) { - maximizedDoc.isIconAnimating = new List<number>([0, 0]); - maximizedDoc.isMinimized = false; - } - maximizedDoc.iconTarget = new List<number>([...scrpt, 1]); - setTimeout(() => { - maximizedDoc.isIconAnimating = new List<number>([w, h]); - setTimeout(() => { - if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] !== 0) { - maximizedDoc.isIconAnimating = undefined; - } - }, 750); - }, 0); - } else { - maximizedDoc.iconTarget = new List<number>([...scrpt, 0]); - // MINIMIZE DOC - maximizedDoc.isIconAnimating = new List<number>([0, 0]); - setTimeout(() => { - if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] === 0) { - maximizedDoc.isMinimized = true; - maximizedDoc.isIconAnimating = undefined; - } - }, 750); - } - }); - } - } - onClick = async (e: React.MouseEvent) => { - if (e.nativeEvent.cancelBubble) return; // || SelectionManager.IsSelected(this)) -- bcz: needed because EditableView may stopPropagation which won't apparently stop this event from firing. - if (this.onClickHandler && this.onClickHandler.script) { + if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && + (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { e.stopPropagation(); - this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }); e.preventDefault(); - return; + if (this._doubleTap && this.props.renderDepth) { + let fullScreenAlias = Doc.MakeAlias(this.props.Document); + let layoutNative = await PromiseValue(Cast(this.props.Document.layoutNative, Doc)); + if (layoutNative && fullScreenAlias.layout === layoutNative.layout) { + await swapViews(fullScreenAlias, "layoutCustom", "layoutNative"); + } + this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + SelectionManager.DeselectAll(); + Doc.UnBrushDoc(this.props.Document); + } else if (this.onClickHandler && this.onClickHandler.script) { + this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); + } else if (this.Document.isButton) { + SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. + this.buttonClick(e.altKey, e.ctrlKey); + } else SelectionManager.SelectDoc(this, e.ctrlKey); } - let altKey = e.altKey; - let ctrlKey = e.ctrlKey; - if (this._doubleTap && this.props.renderDepth) { - e.stopPropagation(); - let fullScreenAlias = Doc.MakeAlias(this.props.Document); - Doc.UseDetailLayout(fullScreenAlias); - fullScreenAlias.showCaption = "caption"; - this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab"); + } + + buttonClick = async (altKey: boolean, ctrlKey: boolean) => { + let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); + let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); + let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); + let expandedDocs: Doc[] = []; + expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; + expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; + // let expandedDocs = [ ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; + if (expandedDocs.length) { SelectionManager.DeselectAll(); - Doc.UnBrushDoc(this.props.Document); + let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace"); + maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab")); + if (maxLocation === "inPlace") { + expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc, false)); + let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); + DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); + } else { + expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); + } } - else if (!this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && - (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && - Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { - e.stopPropagation(); - SelectionManager.SelectDoc(this, e.ctrlKey); - if (this.Document.isButton || this.Document.type === DocumentType.BUTTON) { - let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); - let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); - let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); - let expandedDocs: Doc[] = []; - expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; - expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; - // let expandedDocs = [ ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; - if (expandedDocs.length) { - SelectionManager.DeselectAll(); - let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace"); - let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); - if (altKey || ctrlKey) { - maxLocation = this.Document.maximizeLocation = (ctrlKey ? maxLocation : (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace")); - if (!maxLocation || maxLocation === "inPlace") { - let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView); - let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !d.isMinimized, false); - expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false); - let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView); - if (!hasView) { - this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false)); - } - expandedDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized); - } - } - if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) { - let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data); - if (dataDocs) { - expandedDocs.forEach(maxDoc => - (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) && - this.props.addDocTab(getDispDoc(maxDoc), undefined, maxLocation))); - } - } else { - let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); - this.collapseTargetsToPoint(scrpt, expandedDocs); - } - } - else if (linkedDocs.length) { - SelectionManager.DeselectAll(); - let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); - let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); - if (firstUnshown.length) first = [firstUnshown[0]]; - let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; - - // @TODO: shouldn't always follow target context - let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined]; - - let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined]; - - if (!linkedFwdDocs.some(l => l instanceof Promise)) { - let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab"); - let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; - DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, - document => { // open up target if it's not already in view ... - let cv = this.props.ContainingCollectionView; // bcz: ugh --- maybe need to have a props.unfocus() method so that we leave things in the state we found them?? - let px = cv && cv.props.Document.panX; - let py = cv && cv.props.Document.panY; - let s = cv && cv.props.Document.scale; - this.props.focus(this.props.Document, true, 1); // by zooming into the button document first - setTimeout(() => { - this.props.addDocTab(document, undefined, maxLocation); - cv && (cv.props.Document.panX = px); - cv && (cv.props.Document.panY = py); - cv && (cv.props.Document.scale = s); - }, 1000); // then after the 1sec animation, open up the target in a new tab - }, - linkedFwdPage[altKey ? 1 : 0], targetContext); - } - } + else if (linkedDocs.length) { + SelectionManager.DeselectAll(); + let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); + let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + if (firstUnshown.length) first = [firstUnshown[0]]; + let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; + + // @TODO: shouldn't always follow target context + let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined]; + let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined]; + + if (!linkedFwdDocs.some(l => l instanceof Promise)) { + let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab"); + let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionDoc) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, + // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards + doc => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)), + linkedFwdPage[altKey ? 1 : 0], targetContext); } } } - onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble) return; this._downX = e.clientX; this._downY = e.clientY; this._hitTemplateDrag = false; + // this whole section needs to move somewhere else. We're trying to initiate a special "template" drag where + // this document is the template and we apply it to whatever we drop it on. for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) { if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") { this._hitTemplateDrag = true; @@ -367,7 +265,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu document.removeEventListener("pointermove", this.onPointerMove); } else if (!e.cancelBubble && this.active) { - if (!this.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) { + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -389,66 +287,41 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } @undoBatch - makeNativeViewClicked = (): void => { - makeNativeView(this.props.Document); - } + makeNativeViewClicked = (): void => { swapViews(this.props.Document, "layoutNative", "layoutCustom"); } + @undoBatch - makeCustomViewClicked = (): void => { - this.props.Document.nativeLayout = this.Document.layout; - this.props.Document.nativeType = this.Document.type; - this.props.Document.nonCustomAutoHeight = this.Document.autoHeight; - this.props.Document.nonCustomWidth = this.Document.width; - this.props.Document.nonCustomHeight = this.Document.height; - this.props.Document.nonCustomNativeWidth = this.Document.nativeWidth; - this.props.Document.nonCustomNativeHeight = this.Document.nativeHeight; - this.props.Document.nonCustomIgnoreAspect = this.Document.ignoreAspect; - PromiseValue(Cast(this.props.Document.customLayout, Doc)).then(custom => { - if (custom) { - this.Document.type = DocumentType.TEMPLATE; - this.props.Document.layout = custom; - !custom.nativeWidth && (this.Document.nativeWidth = 0); - !custom.nativeHeight && (this.Document.nativeHeight = 0); - !custom.nativeWidth && (this.Document.ignoreAspect = true); - this.Document.autoHeight = BoolCast(this.Document.customAutoHeight); - this.Document.width = NumCast(this.props.Document.customWidth); - this.Document.height = NumCast(this.props.Document.customHeight); - this.Document.nativeWidth = NumCast(this.props.Document.customNativeWidth); - this.Document.nativeHeight = NumCast(this.props.Document.customNativeHeight); - this.Document.ignoreAspect = BoolCast(this.Document.customIgnoreAspect); - this.props.Document.customAutoHeight = undefined; - this.props.Document.customWidth = undefined; - this.props.Document.customHeight = undefined; - this.props.Document.customNativeWidth = undefined; - this.props.Document.customNativeHeight = undefined; - this.props.Document.customIgnoreAspect = undefined; - } else { - let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; - let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : - this.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : - Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + makeCustomViewClicked = async () => { + if (this.props.Document.layoutCustom === undefined) { + Doc.GetProto(this.props.DataDoc || this.props.Document).layoutNative = Doc.MakeTitled("layoutNative"); + await swapViews(this.props.Document, "", "layoutNative"); - fieldTemplate.backgroundColor = this.Document.backgroundColor; - fieldTemplate.heading = 1; - fieldTemplate.autoHeight = true; + let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; + let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : + this.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : + Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: this.Document.title + "layout", width: (this.Document.width || 0) + 20, height: Math.max(100, (this.Document.height || 0) + 45) }); - let proto = Doc.GetProto(docTemplate); - Doc.MakeMetadataFieldTemplate(fieldTemplate, proto, true); + fieldTemplate.backgroundColor = this.Document.backgroundColor; + fieldTemplate.heading = 1; + fieldTemplate.autoHeight = true; - Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); - Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.Document.layout; - } - }); + let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: this.Document.title + "_layout", width: (this.Document.width || 0) + 20, height: Math.max(100, (this.Document.height || 0) + 45) }); + + Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true); + Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined); + Doc.GetProto(this.props.DataDoc || this.props.Document).layoutCustom = Doc.MakeTitled("layoutCustom"); + } else { + swapViews(this.props.Document, "layoutCustom", "layoutNative"); + } } @undoBatch makeBtnClicked = (): void => { - let doc = Doc.GetProto(this.props.Document); - if (doc.isButton || doc.onClick) { - doc.isButton = false; - doc.onClick = undefined; + if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) { + this.Document.isButton = false; + this.Document.ignoreClick = false; + this.Document.onClick = undefined; } else { - doc.isButton = true; + this.Document.isButton = true; } } @@ -456,42 +329,28 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @action drop = async (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.AnnotationDragData) { + /// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner e.stopPropagation(); - let annotationDoc = de.data.annotationDocument; - annotationDoc.linkedToDoc = true; - de.data.targetContext = this.props.ContainingCollectionView!.props.Document; + let sourceDoc = de.data.annotationDocument; let targetDoc = this.props.Document; + let annotations = await DocListCastAsync(sourceDoc.annotations); + sourceDoc.linkedToDoc = true; + de.data.targetContext = this.props.ContainingCollectionDoc; targetDoc.targetContext = de.data.targetContext; - let annotations = await DocListCastAsync(annotationDoc.annotations); annotations && annotations.forEach(anno => anno.target = targetDoc); - DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`); + DocUtils.MakeLink(sourceDoc, targetDoc, this.props.ContainingCollectionDoc, `Link from ${StrCast(sourceDoc.title)}`); } if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) { - Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc); + Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document); e.stopPropagation(); } if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc = de.data.linkSourceDocument; - let destDoc = this.props.Document; - e.stopPropagation(); - if (de.mods === "AltKey") { - const protoDest = destDoc.proto; - const protoSrc = sourceDoc.proto; - let src = protoSrc ? protoSrc : sourceDoc; - let dst = protoDest ? protoDest : destDoc; - dst.data = (src.data! as ObjectField)[Copy](); - dst.nativeWidth = src.nativeWidth; - dst.nativeHeight = src.nativeHeight; - } - else { - // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); - // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); - let linkDoc = DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined); - de.data.droppedDocuments.push(destDoc); - de.data.linkDocument = linkDoc; - } + // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); + // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); + de.data.linkSourceDocument !== this.props.Document && + (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc)); } } @@ -499,9 +358,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onDrop = (e: React.DragEvent) => { let text = e.dataTransfer.getData("text/plain"); if (!e.isDefaultPrevented() && text && text.startsWith("<div")) { - let oldLayout = FieldValue(this.Document.layout) || ""; + let oldLayout = StrCast(this.props.Document.layout); let layout = text.replace("{layout}", oldLayout); - this.Document.layout = layout; + this.props.Document.layout = layout; e.stopPropagation(); e.preventDefault(); } @@ -518,46 +377,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu proto.nativeHeight = this.props.PanelHeight(); } } + @undoBatch @action - makeIntoPortal = (): void => { - if (!DocListCast(this.props.Document.links).find(doc => { - if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.Document.title + ".portal") return true; - return false; - })) { + makeIntoPortal = async () => { + let anchors = await Promise.all(DocListCast(this.props.Document.links).map(async (d: Doc) => Cast(d.anchor2, Doc))); + if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) { let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(portalID).then(existingPortal => { let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID }); DocUtils.MakeLink(this.props.Document, portal, undefined, portalID); - Doc.GetProto(this.props.Document).isButton = true; - }) + this.Document.isButton = true; + }); } } + @undoBatch @action - toggleCustomView = (): void => { + setCustomView = (custom: boolean): void => { if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { - Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc) - } else { - if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { - this.makeCustomViewClicked(); - } else if (this.Document.nativeLayout) { - this.makeNativeViewClicked(); - } + Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc); + } else { // bcz: not robust -- for now documents with string layout are native documents, and those with Doc layouts are customized + custom ? this.makeCustomViewClicked() : this.makeNativeViewClicked(); } } @undoBatch @action makeBackground = (): void => { - this.layoutDoc.isBackground = !this.layoutDoc.isBackground; - this.layoutDoc.isBackground && this.props.bringToFront(this.layoutDoc, true); + this.Document.isBackground = !this.Document.isBackground; + this.Document.isBackground && this.props.bringToFront(this.Document, true); } @undoBatch @action toggleLockPosition = (): void => { - this.layoutDoc.lockedPosition = BoolCast(this.layoutDoc.lockedPosition) ? undefined : true; + this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; } listen = async () => { @@ -586,34 +441,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const cm = ContextMenu.Instance; let subitems: ContextMenuProps[] = []; subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this), icon: "desktop" }); - subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.dataDoc, "inTab"), icon: "folder" }); - subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.dataDoc, "onRight"), icon: "caret-square-right" }); - subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "inTab"), icon: "folder" }); - subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" }); + subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab"), icon: "folder" }); + subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight"), icon: "caret-square-right" }); + subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" }); + subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); let existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); - onClicks.push({ - description: "Toggle Detail", event: () => { - let compiled = CompileScript("toggleDetail(this)", { - params: { this: "Doc" }, - typecheck: false, - editable: true, - }); - if (compiled.compiled) { - this.Document.onClick = new ScriptField(compiled); - } - }, icon: "window-restore" - }); - onClicks.push({ description: this.layoutDoc.ignoreClick ? "Select" : "Do Nothing", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); + onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript("toggleDetail(this)"), icon: "window-restore" }); + onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" }); onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); onClicks.push({ description: "Edit onClick Foreach Doc Script", icon: "edit", event: (obj: any) => { - this.props.Document.collectionContext = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; + this.props.Document.collectionContext = this.props.ContainingCollectionDoc; ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n"); } }); @@ -623,20 +467,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" }); if (this.props.DataDoc) { - layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }) + layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }); } - layoutItems.push({ description: `${this.layoutDoc.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.layoutDoc.chromeStatus = (this.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.layoutDoc.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); + layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); + layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.Document.autoHeight = !this.Document.autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); - layoutItems.push({ description: this.layoutDoc.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.layoutDoc.lockedPosition) ? "unlock" : "lock" }); + layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); - if (this.props.Document.detailedLayout && !this.Document.isTemplate) { - layoutItems.push({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" }); - } if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { layoutItems.push({ description: "Use Custom Layout", event: this.makeCustomViewClicked, icon: "concierge-bell" }); - } else if (this.props.Document.nativeLayout) { + } else if (this.props.Document.layoutNative) { layoutItems.push({ description: "Use Native Layout", event: this.makeNativeViewClicked, icon: "concierge-bell" }); } !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); @@ -653,49 +494,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); cm.addItem({ - description: "Download document", icon: "download", event: async () => { - let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { + description: "Download document", icon: "download", event: async () => + console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' } - })); - console.log(y); - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); - } + }))) + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); }); cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); - type User = { email: string, userDocumentId: string }; - let usersMenu: ContextMenuProps[] = []; try { - let stuff = await rp.get(Utils.prepend(RouteStore.getUsers)); - const users: User[] = JSON.parse(stuff); - usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({ - description: email, event: async () => { + type User = { email: string, userDocumentId: string }; + const users: User[] = JSON.parse(await rp.get(Utils.prepend(RouteStore.getUsers))); + let usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({ + description: email, + event: async () => { const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc); - if (!userDocument) { - throw new Error(`Couldn't get user document of user ${email}`); - } - const notifDoc = await Cast(userDocument.optionalRightCollection, Doc); - if (notifDoc instanceof Doc) { - const data = await Cast(notifDoc.data, listSpec(Doc)); - const sharedDoc = Doc.MakeAlias(this.props.Document); - if (data) { - data.push(sharedDoc); - } else { - notifDoc.data = new List([sharedDoc]); + if (userDocument) { + const notifDoc = await Cast(userDocument.optionalRightCollection, Doc); + if (notifDoc) { + const data = await Cast(notifDoc.data, listSpec(Doc)); + const sharedDoc = Doc.MakeAlias(this.props.Document); + if (data) { + data.push(sharedDoc); + } else { + notifDoc.data = new List([sharedDoc]); + } } } - }, icon: "male" - })); + }, + icon: "male" + } as ContextMenuProps)); + cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" }); } catch { } runInAction(() => { - cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" }); if (!ClientUtils.RELEASE) { let setWriteMode = (mode: DocServer.WriteMode) => { DocServer.AclsMode = mode; @@ -731,62 +569,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); } - onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; - onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); }; - isSelected = () => SelectionManager.IsSelected(this); - @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; - @computed get nativeWidth() { return this.Document.nativeWidth || 0; } - @computed get nativeHeight() { return this.Document.nativeHeight || 0; } - @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } - @computed get contents() { - return (<DocumentContentsView {...this.props} - ChromeHeight={this.chromeHeight} - isSelected={this.isSelected} - select={this.select} - onClick={this.onClickHandler} - layoutKey={"layout"} - fitToBox={this.Document.fitToBox ? true : this.props.fitToBox} - DataDoc={this.dataDoc} />); + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document + get layoutDoc(): Document { + return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document); } - chromeHeight = () => { - let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; - let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); - let templates = Cast(this.layoutDoc.templates, listSpec("string")); - if (!showOverlays && templates instanceof List) { - templates.map(str => { - if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title"; - }); - } - return (showTitle ? 25 : 0) + 1;// bcz: why 8?? - } + // does Document set a layout prop + setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; + // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. + getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); + getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); - get layoutDoc(): Document { - // if this document's layout field contains a document (ie, a rendering template), then we will use that - // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. - return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document); + isSelected = () => SelectionManager.IsSelected(this); + select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; + + chromeHeight = () => { + let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; + let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.Document.showTitle); + return (showTitle ? 25 : 0) + 1; } render() { const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined; const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; - const colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor; - const clusterCol = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground; - const backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ? - this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : - ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); + const colorSet = this.setsLayoutProp("backgroundColor"); + const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground; + const backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ? + this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) : + ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document); const nativeWidth = this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%"; const nativeHeight = this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; - const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; - const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.layoutDoc.showTitle; - const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.layoutDoc.showCaption; - const showTextTitle = showTitle && StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined; + const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; + const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle"); + const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); + const showTextTitle = showTitle && StrCast(this.Document.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined; const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); - const borderRounding = this.Document.borderRounding || ruleRounding; + const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding; const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; - const iconAnimating = this.Document.isIconAnimating ? Array.from(Cast(this.Document.isIconAnimating, listSpec("number"))!) : undefined; const searchHighlight = (!this.Document.searchFields ? (null) : <div className="documentView-searchHighlight" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}> {this.Document.searchFields} @@ -794,7 +616,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const captionView = (!showCaption ? (null) : <div className="documentView-captionWrapper" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}> <FormattedTextBox {...this.props} - onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} + onClick={this.onClickHandler} DataDoc={this.props.DataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} /> @@ -807,19 +629,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu transform: `scale(${1 / this.props.ContentScaling()})` }}> <EditableView - contents={(this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle]} + contents={this.Document[showTitle]} display={"block"} height={72} fontSize={12} - GetValue={() => StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle])} - SetValue={(value: string) => ((this.layoutDoc.isTemplate ? this.layoutDoc : Doc.GetProto(this.layoutDoc))[showTitle] = value) ? true : true} + GetValue={() => StrCast(this.Document[showTitle])} + SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true} /> </div>); + const contents = (<DocumentContentsView {...this.props} + ChromeHeight={this.chromeHeight} + isSelected={this.isSelected} + select={this.select} + onClick={this.onClickHandler} + layoutKey={"layout"} + DataDoc={this.props.DataDoc} />); return ( <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} style={{ - transition: iconAnimating ? "transform .5s" : StrCast(this.layoutDoc.transition), - pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all", - color: StrCast(this.layoutDoc.color), + transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), + pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", + color: StrCast(this.Document.color), outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree], outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree], outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px", @@ -831,20 +660,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu opacity: this.Document.opacity }} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} + onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > {!showTitle && !showCaption ? this.Document.searchFields ? (<div className="documentView-searchWrapper"> - {this.contents} + {contents} {searchHighlight} </div>) : - this.contents + contents : <div className="documentView-styleWrapper" > <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? "calc(100% - 29px)" : "100%", top: showTextTitle ? "29px" : undefined }}> - {this.contents} + {contents} </div> {titleView} {captionView} @@ -856,66 +685,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } +export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField: string, oldLayout?: Doc) { + let oldLayoutExt = oldLayout || await Cast(doc[oldLayoutField], Doc); + if (oldLayoutExt) { + oldLayoutExt.autoHeight = doc.autoHeight; + oldLayoutExt.width = doc.width; + oldLayoutExt.height = doc.height; + oldLayoutExt.nativeWidth = doc.nativeWidth; + oldLayoutExt.nativeHeight = doc.nativeHeight; + oldLayoutExt.ignoreAspect = doc.ignoreAspect; + oldLayoutExt.backgroundLayout = doc.backgroundLayout; + oldLayoutExt.type = doc.type; + oldLayoutExt.layout = doc.layout; + } -let makeNativeView = (doc: any): void => { - doc.layout = doc.nativeLayout; - doc.nativeLayout = undefined; - doc.type = doc.nativeType; - - doc.customAutoHeight = doc.autoHeight; - doc.customWidth = doc.width; - doc.customHeight = doc.height; - doc.customNativeWidth = doc.nativeWidth; - doc.customNativeHeight = doc.nativeHeight; - doc.customIgnoreAspect = doc.ignoreAspect; - - doc.autoHeight = doc.nonCustomAutoHeight; - doc.width = doc.nonCustomWidth; - doc.height = doc.nonCustomHeight; - doc.nativeWidth = doc.nonCustomNativeWidth; - doc.nativeHeight = doc.nonCustomNativeHeight; - doc.ignoreAspect = doc.nonCustomIgnoreAspect; - doc.nonCustomAutoHeight = undefined; - doc.nonCustomWidth = undefined; - doc.nonCustomHeight = undefined; - doc.nonCustomNativeWidth = undefined; - doc.nonCustomNativeHeight = undefined; - doc.nonCustomIgnoreAspect = undefined; -} -let makeCustomView = (doc: any): void => { - doc.nativeLayout = doc.layout; - doc.nativeType = doc.type; - doc.nonCustomAutoHeight = doc.autoHeight; - doc.nonCustomWidth = doc.nativeWidth; - doc.nonCustomHeight = doc.nativeHeight; - doc.nonCustomNativeWidth = doc.nativeWidth; - doc.nonCustomNativeHeight = doc.nativeHeight; - doc.nonCustomIgnoreAspect = doc.ignoreAspect; - let custom = doc.customLayout as Doc; - if (custom instanceof Doc) { - doc.type = DocumentType.TEMPLATE; - doc.layout = custom; - !custom.nativeWidth && (doc.nativeWidth = 0); - !custom.nativeHeight && (doc.nativeHeight = 0); - !custom.nativeWidth && (doc.ignoreAspect = true); - doc.autoHeight = doc.autoHeight; - doc.width = doc.customWidth; - doc.height = doc.customHeight; - doc.nativeWidth = doc.customNativeWidth; - doc.nativeHeight = doc.customNativeHeight; - doc.ignoreAspect = doc.ignoreAspect; - doc.customAutoHeight = undefined; - doc.customWidth = undefined; - doc.customHeight = undefined; - doc.customNativeWidth = undefined; - doc.customNativeHeight = undefined; - doc.customIgnoreAspect = undefined; + let newLayoutExt = newLayoutField && await Cast(doc[newLayoutField], Doc); + if (newLayoutExt) { + doc.autoHeight = newLayoutExt.autoHeight; + doc.width = newLayoutExt.width; + doc.height = newLayoutExt.height; + doc.nativeWidth = newLayoutExt.nativeWidth; + doc.nativeHeight = newLayoutExt.nativeHeight; + doc.ignoreAspect = newLayoutExt.ignoreAspect; + doc.backgroundLayout = newLayoutExt.backgroundLayout; + doc.type = newLayoutExt.type; + doc.layout = await newLayoutExt.layout; } } + Scripting.addGlobal(function toggleDetail(doc: any) { - if (doc.type !== DocumentType.COL && doc.type !== DocumentType.TEMPLATE) { - makeCustomView(doc); - } else if (doc.nativeLayout) { - makeNativeView(doc); - } + let native = typeof doc.layout === "string"; + swapViews(doc, native ? "layoutCustom" : "layoutNative", native ? "layoutNative" : "layoutCustom"); });
\ No newline at end of file diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx index 067d47de4..6c3db18c4 100644 --- a/src/client/views/nodes/DragBox.tsx +++ b/src/client/views/nodes/DragBox.tsx @@ -45,17 +45,15 @@ export class DragBox extends DocComponent<FieldViewProps, DragDocument>(DragDocu } onDragMove = (e: MouseEvent) => { - if (!e.cancelBubble && !this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 5 || Math.abs(this._downY - e.clientY) > 5)) { + if (!e.cancelBubble && (Math.abs(this._downX - e.clientX) > 5 || Math.abs(this._downY - e.clientY) > 5)) { document.removeEventListener("pointermove", this.onDragMove); document.removeEventListener("pointerup", this.onDragUp); const onDragStart = this.Document.onDragStart; e.stopPropagation(); e.preventDefault(); - let res = onDragStart ? onDragStart.script.run({ this: this.props.Document }) : undefined; - let doc = res !== undefined && res.success ? - res.result as Doc : - Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" }); - doc && DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); + let res = onDragStart && onDragStart.script.run({ this: this.props.Document }).result; + let doc = (res as Doc) || Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" }); + DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); } e.stopPropagation(); e.preventDefault(); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 943d181d6..49fc2263d 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -30,6 +30,7 @@ export interface FieldViewProps { leaveNativeSize?: boolean; fitToBox?: boolean; ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; + ContainingCollectionDoc: Opt<Doc>; ruleProvider: Doc | undefined; Document: Doc; DataDoc?: Doc; @@ -38,7 +39,7 @@ export interface FieldViewProps { select: (isCtrlPressed: boolean) => void; renderDepth: number; addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; removeDocument?: (document: Doc) => boolean; moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index a3cfe10fc..53f28ac00 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -39,6 +39,7 @@ import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { inputRules } from 'prosemirror-inputrules'; +import { DocumentButtonBar } from '../DocumentButtonBar'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -204,10 +205,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "dummy"); } - - @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } // this should be internal to prosemirror, but is needed // here to make sure that footnote view nodes in the overlay editor @@ -245,9 +245,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true); - }) + }); }); - }) + }); this.linkOnDeselect.clear(); } @@ -258,7 +258,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let 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++) { } + for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } text = text.substr(0, textEndSelection - range!.start); text = text.split(" ")[text.split(" ").length - 1]; let split = text.split("::"); @@ -360,8 +360,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; - if (draggedDoc && draggedDoc.type === DocumentType.TEXT) { - if (!Doc.AreProtosEqual(draggedDoc, this.props.Document)) { + if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) { + if (de.mods === "AltKey") { + if (draggedDoc.data instanceof RichTextField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); + e.stopPropagation(); + } + } else { draggedDoc.isTemplate = true; if (typeof (draggedDoc.layout) === "string") { let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); @@ -487,8 +492,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._pullReactionDisposer = reaction( () => this.props.Document[Pulls], () => { - if (!DocumentDecorations.hasPulledHack) { - DocumentDecorations.hasPulledHack = true; + if (!DocumentButtonBar.hasPulledHack) { + DocumentButtonBar.hasPulledHack = true; let unchanged = this.dataDoc.unchanged; this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); } @@ -498,8 +503,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._pushReactionDisposer = reaction( () => this.props.Document[Pushes], () => { - if (!DocumentDecorations.hasPushedHack) { - DocumentDecorations.hasPushedHack = true; + if (!DocumentButtonBar.hasPushedHack) { + DocumentButtonBar.hasPushedHack = true; this.pushToGoogleDoc(); } } @@ -592,7 +597,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe response && (this.dataDoc[GoogleRef] = response.documentId); let pushSuccess = response !== undefined && !("errors" in response); dataDoc.unchanged = pushSuccess; - DocumentDecorations.Instance.startPushOutcome(pushSuccess); + DocumentButtonBar.Instance.startPushOutcome(pushSuccess); } }; let undo = () => { @@ -637,7 +642,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else { delete dataDoc[GoogleRef]; } - DocumentDecorations.Instance.startPullOutcome(pullSuccess); + DocumentButtonBar.Instance.startPullOutcome(pullSuccess); } checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { @@ -650,7 +655,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let receivedTitle = exportState.title; let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle; dataDoc.unchanged = unchanged; - DocumentDecorations.Instance.setPullState(unchanged); + DocumentButtonBar.Instance.setPullState(unchanged); } } } @@ -698,7 +703,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let annotations = DocListCast(region.annotations); annotations.forEach(anno => anno.target = this.props.Document); - let fieldExtDoc = Doc.resolvedFieldDataDoc(doc, "data", "true"); + let fieldExtDoc = Doc.fieldExtensionDoc(doc, "data"); let targetAnnotations = DocListCast(fieldExtDoc.annotations); if (targetAnnotations) { targetAnnotations.push(region); @@ -973,7 +978,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe tryUpdateHeight() { const ChromeHeight = this.props.ChromeHeight; let sh = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.isOverlay && this.props.Document.autoHeight && sh !== 0) { + if (!this.props.isOverlay && !this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) { let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); @@ -985,7 +990,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground - //|| (this.props.Document.isButton && !this.props.isSelected()) ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); return ( @@ -1011,7 +1015,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} > - <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: (this.props.Document.isButton && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} /> + <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.props.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} /> </div> ); } diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 92cb5a9c9..63a504d1a 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -12,7 +12,7 @@ import { IconField } from "../../../new_fields/IconField"; import { ContextMenu } from "../ContextMenu"; import Measure from "react-measure"; import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss"; -import { Scripting, CompileScript } from "../../util/Scripting"; +import { Scripting } from "../../util/Scripting"; import { ComputedField } from "../../../new_fields/ScriptField"; @@ -45,8 +45,7 @@ export class IconBox extends React.Component<FieldViewProps> { } public static AutomaticTitle(doc: Doc) { - let computed = CompileScript(`return iconTitle(this);`, { params: { this: "Doc" }, typecheck: false }); - computed.compiled && (Doc.GetProto(doc).title = new ComputedField(computed)); + Doc.GetProto(doc).title = ComputedField.MakeFunction('iconTitle(this);'); } public static DocumentIcon(layout: string) { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index beccce9dd..624593245 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -17,13 +17,12 @@ import { Utils } from '../../../Utils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; -import { CompileScript } from '../../util/Scripting'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from '../DocComponent'; import { InkingControl } from '../InkingControl'; -import { positionSchema } from './DocumentView'; +import { documentSchema } from './DocumentView'; import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; @@ -50,8 +49,8 @@ declare class MediaRecorder { constructor(e: any); } -type ImageDocument = makeInterface<[typeof pageSchema, typeof positionSchema]>; -const ImageDocument = makeInterface(pageSchema, positionSchema); +type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; +const ImageDocument = makeInterface(pageSchema, documentSchema); @observer export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) { @@ -64,9 +63,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD @observable private _isOpen: boolean = false; private dropDisposer?: DragManager.DragDropDisposer; + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } - + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } protected createDropTarget = (ele: HTMLDivElement) => { if (this.dropDisposer) { @@ -82,32 +81,18 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD console.log("IMPLEMENT ME PLEASE"); } - @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "Alternates"); } - @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - de.data.droppedDocuments.forEach(action((drop: Doc) => { - if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url); - e.stopPropagation(); - } else if (de.mods === "MetaKey") { - if (this.extensionDoc !== this.dataDoc) { - let layout = StrCast(drop.backgroundLayout); - if (layout.indexOf(ImageBox.name) !== -1) { - let imgData = this.extensionDoc.Alternates; - if (!imgData) { - Doc.GetProto(this.extensionDoc).Alternates = new List([]); - } - let imgList = Cast(this.extensionDoc.Alternates, listSpec(Doc), [] as any[]); - imgList && imgList.push(drop); - e.stopPropagation(); - } - } - } + if (de.mods === "AltKey" && de.data.draggedDocuments.length && de.data.draggedDocuments[0].data instanceof ImageField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.data.draggedDocuments[0].data.url); + e.stopPropagation(); + } + de.mods === "MetaKey" && de.data.droppedDocuments.forEach(action((drop: Doc) => { + Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop); + e.stopPropagation(); })); - // de.data.removeDocument() bcz: need to implement } } @@ -220,7 +205,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD let modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); - !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }) + !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" }); } @@ -244,9 +229,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD results.tags.map((tag: Tag) => { tagsList.push(tag.name); let sanitized = tag.name.replace(" ", "_"); - let script = `return (${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`; - let computed = CompileScript(script, { params: { this: "Doc" } }); - computed.compiled && (tagDoc[sanitized] = new ComputedField(computed)); + tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`); }); this.extensionDoc.generatedTags = tagsList; tagDoc.title = "Generated Tags Doc"; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index ee70942de..3a9318469 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -76,7 +76,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } else if (type === "script") { field = new ScriptField(script); } else { - let res = script.run({ this: target }); + let res = script.run({ this: target }, console.log); if (!res.success) return false; field = res.result; } @@ -124,7 +124,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { let i = 0; const self = this; for (let key of Object.keys(ids).slice().sort()) { - rows.push(<KeyValuePair doc={realDoc} ref={(function () { + rows.push(<KeyValuePair doc={realDoc} addDocTab={this.props.addDocTab} ref={(function () { let oldEl: KeyValuePair | undefined; return (el: KeyValuePair) => { if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 7e0f3735d..1fed4c8bb 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,7 +1,7 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { Doc, Field } from '../../../new_fields/Doc'; +import { Doc, Field, Opt } from '../../../new_fields/Doc'; import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { Transform } from '../../util/Transform'; @@ -22,6 +22,7 @@ export interface KeyValuePairProps { keyName: string; doc: Doc; keyWidth: number; + addDocTab: (doc: Doc, data: Opt<Doc>, where: string) => boolean; } @observer export class KeyValuePair extends React.Component<KeyValuePairProps> { @@ -45,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(value, { width: 300, height: 300 }); CollectionDockingView.Instance.AddRightSplit(kvp, undefined); }, icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } } @@ -55,6 +56,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { Document: this.props.doc, DataDoc: this.props.doc, ContainingCollectionView: undefined, + ContainingCollectionDoc: undefined, ruleProvider: undefined, fieldKey: this.props.keyName, fieldExt: "", @@ -67,7 +69,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { focus: emptyFunction, PanelWidth: returnZero, PanelHeight: returnZero, - addDocTab: returnZero, + addDocTab: returnFalse, pinToPres: returnZero, ContentScaling: returnOne }; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index df35b603c..764051d62 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -4,40 +4,28 @@ import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import 'react-image-lightbox/style.css'; -import { Doc, WidthSym, Opt } from "../../../new_fields/Doc"; +import { Doc, Opt, WidthSym } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast } from "../../../new_fields/Types"; +import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; +import { Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; import { KeyCodes } from '../../northstar/utils/KeyCodes'; -import { CompileScript } from '../../util/Scripting'; +import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; import { PDFViewer } from "../pdf/PDFViewer"; -import { positionSchema } from "./DocumentView"; +import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; -const PdfDocument = makeInterface(positionSchema, pageSchema); -export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === KeyCodes.BACKSPACE) e.stopPropagation(); }; +type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; +const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) { public static LayoutString() { return FieldView.LayoutString(PDFBox); } - - @observable private _flyout: boolean = false; - @observable private _alt = false; - @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>; - - @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; } - @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } - - - @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } - private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _keyValue: string = ""; @@ -47,16 +35,26 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen private _valueRef: React.RefObject<HTMLInputElement> = React.createRef(); private _scriptRef: React.RefObject<HTMLInputElement> = React.createRef(); + @observable private _flyout: boolean = false; + @observable private _alt = false; + @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>; + + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + componentDidMount() { this.props.setPdfBox && this.props.setPdfBox(this); + this.props.Document.curPage = ComputedField.MakeFunction("Math.floor(Number(this.panY) / Number(this.nativeHeight) + 1)"); + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); if (pdfUrl instanceof PdfField) { Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf)); } this._reactionDisposer = reaction( - () => this.props.Document.panY, - () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.panY), behavior: "auto" }) + () => this.Document.panY, + () => this._mainCont.current && this._mainCont.current.scrollTo({ top: this.Document.panY || 0, behavior: "auto" }) ); } @@ -65,24 +63,22 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } public GetPage() { - return Math.floor(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1; + return Math.floor((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1; } @action public BackPage() { - let cp = Math.ceil(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1; + let cp = Math.ceil((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1; cp = cp - 1; if (cp > 0) { - this.props.Document.curPage = cp; - this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); + this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0); } } @action - public GotoPage(p: number) { + public GotoPage = (p: number) => { if (p > 0 && p <= NumCast(this.dataDoc.numPages)) { - this.props.Document.curPage = p; - this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight); + this.Document.panY = (p - 1) * (this.Document.nativeHeight || 0); } } @@ -90,23 +86,20 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen public ForwardPage() { let cp = this.GetPage() + 1; if (cp <= NumCast(this.dataDoc.numPages)) { - this.props.Document.curPage = cp; - this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); + this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0); } } @action setPanY = (y: number) => { - this.containingCollectionDocument && (this.containingCollectionDocument.panY = y); + this.Document.panY = y; } @action private applyFilter = () => { - let scriptText = this._scriptValue.length > 0 ? this._scriptValue : - this._keyValue.length > 0 && this._valueValue.length > 0 ? - `return this.${this._keyValue} === ${this._valueValue}` : "return true"; - let script = CompileScript(scriptText, { params: { this: Doc.name } }); - script.compiled && (this.props.Document.filterScript = new ScriptField(script)); + let scriptText = this._scriptValue ? this._scriptValue : + this._keyValue && this._valueValue ? `this.${this._keyValue} === ${this._valueValue}` : "true"; + this.props.Document.filterScript = ScriptField.MakeFunction(scriptText); } scrollTo = (y: number) => { @@ -114,8 +107,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } private resetFilters = () => { - this._keyValue = this._valueValue = ""; - this._scriptValue = "return true"; + this._keyValue = this._valueValue = this._scriptValue = ""; this._keyRef.current && (this._keyRef.current.value = ""); this._valueRef.current && (this._valueRef.current.value = ""); this._scriptRef.current && (this._scriptRef.current.value = ""); @@ -129,7 +121,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen return !this.props.active() ? (null) : (<div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}> <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings" - style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}> + style={{ marginTop: `${this.Document.panY || 0}px` }}> <div className="pdfBox-settingsButton-arrow" style={{ borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`, @@ -146,13 +138,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen Annotation View Settings </div> <div className="pdfBox-settingsFlyout-kvpInput"> - <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange} + <input placeholder="Key" className="pdfBox-settingsFlyout-input" onChange={this.newKeyChange} style={{ gridColumn: 1 }} ref={this._keyRef} /> - <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange} + <input placeholder="Value" className="pdfBox-settingsFlyout-input" onChange={this.newValueChange} style={{ gridColumn: 3 }} ref={this._valueRef} /> </div> <div className="pdfBox-settingsFlyout-kvpInput"> - <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} /> + <input placeholder="Custom Script" onChange={this.newScriptChange} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} /> </div> <div className="pdfBox-settingsFlyout-kvpInput"> <button style={{ gridColumn: 1 }} onClick={this.resetFilters}> @@ -170,20 +162,20 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen loaded = (nw: number, nh: number, np: number) => { this.dataDoc.numPages = np; - if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight || !this.dataDoc.scrollHeight) { - let oldaspect = NumCast(this.dataDoc.nativeHeight) / NumCast(this.dataDoc.nativeWidth, 1); - this.dataDoc.nativeWidth = nw; - this.dataDoc.nativeHeight = this.dataDoc.nativeHeight ? nw * oldaspect : nh; - this.dataDoc.height = this.dataDoc[WidthSym]() * (nh / nw); - this.dataDoc.scrollHeight = np * this.dataDoc.nativeHeight; + if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) { + let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); + this.Document.nativeWidth = nw; + this.Document.nativeHeight = this.Document.nativeHeight ? nw * oldaspect : nh; + this.Document.height = this.Document[WidthSym]() * (nh / nw); + this.Document.scrollHeight = np * this.Document.nativeHeight; } } @action onScroll = (e: React.UIEvent<HTMLDivElement>) => { - if (e.currentTarget && this.containingCollectionDocument) { - this.containingCollectionDocument.panTransformType = "None"; - this.containingCollectionDocument.panY = e.currentTarget.scrollTop; + if (e.currentTarget && this.props.ContainingCollectionDoc) { + this.props.Document.panTransformType = "None"; + this.Document.panY = e.currentTarget.scrollTop; } } @@ -195,14 +187,14 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> : <div className={classname} onScroll={this.onScroll} - style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }} + style={{ marginTop: `${(this.Document.panY || 0)}px` }} ref={this._mainCont}> - <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + (NumCast(this.props.Document.nativeHeight) - NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.scale, 1)), width: "100%" }} /> - <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)} - Document={this.props.Document} DataDoc={this.props.DataDoc} - addDocTab={this.props.addDocTab} setPanY={this.setPanY} + <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + ((this.Document.nativeHeight || 0) - (this.Document.nativeHeight || 0) / (this.Document.scale || 1)) }} /> + <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={this.Document.panY || 0} + Document={this.props.Document} DataDoc={this.dataDoc} + addDocTab={this.props.addDocTab} setPanY={this.setPanY} GoToPage={this.GotoPage} pinToPres={this.props.pinToPres} addDocument={this.props.addDocument} - fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} /> + fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} /> {this.settingsPanel()} </div>); } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 96f011eff..b7d9a1eab 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -14,19 +14,18 @@ import { ContextMenuProps } from "../ContextMenuItem"; import { DocComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; -import { positionSchema } from "./DocumentView"; +import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./VideoBox.scss"; import { library } from "@fortawesome/fontawesome-svg-core"; import { faVideo } from "@fortawesome/free-solid-svg-icons"; -import { CompileScript } from "../../util/Scripting"; import { Doc } from "../../../new_fields/Doc"; import { ScriptField } from "../../../new_fields/ScriptField"; var path = require('path'); -type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; -const VideoDocument = makeInterface(positionSchema, pageSchema); +type VideoDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>; +const VideoDocument = makeInterface(documentSchema, pageSchema); library.add(faVideo); @@ -116,18 +115,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString() }); - const script = CompileScript(`(self as any).curPage = ${NumCast(this.props.Document.curPage)}`, { - params: { this: Doc.name }, - capturedVariables: { self: this.props.Document }, - typecheck: false, - editable: true, - }); - if (script.compiled) { - b.onClick = new ScriptField(script); - this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(b, false); - } else { - console.log(script.errors.map(error => error.messageText).join("\n")); - } + b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`); } else { //convert to desired file format var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' @@ -269,7 +257,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } - @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } @computed get youtubeContent() { this._youtubeIframeId = VideoBox._youtubeIframeCounter++; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index eeb2531a2..a9fa883c8 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,20 +1,18 @@ import React = require("react"); import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym, WidthSym, Opt } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { DocumentManager } from "../../util/DocumentManager"; import PDFMenu from "./PDFMenu"; import "./Annotation.scss"; -import { scale } from "./PDFViewer"; -import { PresBox } from "../nodes/PresBox"; interface IAnnotationProps { anno: Doc; fieldExtensionDoc: Doc; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean; pinToPres: (document: Doc) => void; } @@ -31,7 +29,7 @@ interface IRegionAnnotationProps { width: number; height: number; fieldExtensionDoc: Doc; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; document: Doc; } @@ -58,7 +56,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { runInAction(() => this._brushed = brushed); } } - ) + ); } componentWillUnmount() { diff --git a/src/client/views/pdf/PDFAnnotationLayer.scss b/src/client/views/pdf/PDFAnnotationLayer.scss deleted file mode 100644 index 733533007..000000000 --- a/src/client/views/pdf/PDFAnnotationLayer.scss +++ /dev/null @@ -1,6 +0,0 @@ -.pdfAnnotationLayer-cont { - width:100%; - height:100%; - position:relative; - top:-200%; -}
\ No newline at end of file diff --git a/src/client/views/pdf/PDFAnnotationLayer.tsx b/src/client/views/pdf/PDFAnnotationLayer.tsx deleted file mode 100644 index 4f267a5c0..000000000 --- a/src/client/views/pdf/PDFAnnotationLayer.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React = require("react"); -import { observer } from "mobx-react"; -import "./PDFAnnotationLayer.scss"; - -interface IAnnotationProps { - -} - -@observer -export class PDFAnnotationLayer extends React.Component { - onPointerDown = (e: React.PointerEvent) => { - if (e.ctrlKey) { - console.log("annotating"); - e.stopPropagation(); - } - } - - render() { - return <div className="pdfAnnotationLayer-cont" onPointerDown={this.onPointerDown} />; - } -}
\ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 7bc1d3507..c94b4e3a4 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; @@ -19,6 +19,7 @@ import Annotation from "./Annotation"; import Page from "./Page"; import "./PDFViewer.scss"; import React = require("react"); +import requestPromise = require("request-promise"); const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); export const scale = 2; @@ -35,7 +36,8 @@ interface IViewerProps { scrollTo: (y: number) => void; active: () => boolean; setPanY?: (n: number) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + GoToPage?: (n: number) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; } @@ -61,7 +63,7 @@ export class PDFViewer extends React.Component<IViewerProps> { private _filterReactionDisposer?: IReactionDisposer; private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - private _pdfViewer: any; + public _pdfViewer: any; private _pdfFindController: any; private _searchString: string = ""; private _selectionText: string = ""; @@ -77,18 +79,12 @@ export class PDFViewer extends React.Component<IViewerProps> { } @computed get allAnnotations() { - let annotations = DocListCast(this.props.fieldExtensionDoc.annotations); - return annotations.filter(anno => { - let run = this._script.run({ this: anno }); - return run.success ? run.result : true; - }) + return DocListCast(this.props.fieldExtensionDoc.annotations).filter( + anno => this._script.run({ this: anno }, console.log, true).result); } @computed get nonDocAnnotations() { - return this._annotations.filter(anno => { - let run = this._script.run({ this: anno }); - return run.success ? run.result : true; - }); + return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result); } componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages(); @@ -114,10 +110,7 @@ export class PDFViewer extends React.Component<IViewerProps> { if (this._script.originalScript !== oldScript) { this.Index = -1; } - annos.forEach(d => { - let run = this._script.run(d); - d.opacity = !run.success || run.result ? 1 : 0; - }); + annos.forEach(d => d.opacity = this._script.run({ this: d }, console.log, 1).result ? 1 : 0); }), { fireImmediately: true } ); @@ -372,7 +365,7 @@ export class PDFViewer extends React.Component<IViewerProps> { @action search = (searchString: string) => { if (this._pdfViewer._pageViewsReady) { - this._pdfFindController.executeCommand('find', { + this._pdfFindController.executeCommand('findagain', { caseSensitive: false, findPrevious: undefined, highlightAll: true, @@ -381,13 +374,15 @@ export class PDFViewer extends React.Component<IViewerProps> { }); } else if (this._mainCont.current) { - let executeFind = () => this._pdfFindController.executeCommand('find', { - caseSensitive: false, - findPrevious: undefined, - highlightAll: true, - phraseSearch: true, - query: searchString - }); + let executeFind = () => { + this._pdfFindController.executeCommand('find', { + caseSensitive: false, + findPrevious: undefined, + highlightAll: true, + phraseSearch: true, + query: searchString + }); + } this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); } @@ -400,14 +395,14 @@ export class PDFViewer extends React.Component<IViewerProps> { this._searching = !this._searching; if (this._searching) { - if (!this._pdfFindController && this._mainCont.current && this._viewer.current) { - let simpleLinkService = new SimpleLinkService(); + if (!this._pdfFindController && this._mainCont.current && this._viewer.current && !this._pdfViewer) { + let simpleLinkService = new SimpleLinkService(this); this._pdfViewer = new PDFJSViewer.PDFViewer({ container: this._mainCont.current, viewer: this._viewer.current, linkService: simpleLinkService - }); - simpleLinkService.setPdf(this.props.pdf); + }) + simpleLinkService.setPDFJSview(this._pdfViewer); this._mainCont.current.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1); this._mainCont.current.addEventListener("pagerendered", () => console.log("rendered")); this._pdfViewer.setDocument(this.props.pdf); @@ -415,21 +410,16 @@ export class PDFViewer extends React.Component<IViewerProps> { this._pdfViewer.findController = this._pdfFindController; } } - else { - this._pdfFindController = null; - if (this._viewer.current) { - let cns = this._viewer.current.childNodes; - for (let i = cns.length - 1; i >= 0; i--) { - cns.item(i).remove(); - } - } - } + } + @computed get visibleElementWrapper() { + trace(); + return this._visibleElements; } render() { return (<div className="pdfViewer-viewer" ref={this._mainCont} > <div className="pdfViewer-visibleElements" style={this._searching ? { position: "absolute", top: 0 } : {}}> - {this._visibleElements} + {this.visibleElementWrapper} </div> <div className="pdfViewer-text" ref={this._viewer} /> <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}> @@ -467,9 +457,13 @@ export class PDFViewer extends React.Component<IViewerProps> { export enum AnnotationTypes { Region } class SimpleLinkService { - externalLinkTarget: any = null; - externalLinkRel: any = null; - pdf: any = null; + _viewer: PDFViewer; + _pdfjsView: any; + + constructor(viewer: PDFViewer) { + this._viewer = viewer; + } + setPDFJSview(v: any) { this._pdfjsView = v; } navigateTo() { } @@ -479,15 +473,23 @@ class SimpleLinkService { setHash() { } + isPageVisible(page: number) { return true; } + executeNamedAction() { } cachePageRef() { } - get pagesCount() { return this.pdf ? this.pdf.numPages : 0; } + get pagesCount() { return this._viewer._pdfViewer.pagesCount; } - get page() { return 0; } + get page() { return NumCast(this._viewer.props.Document.curPage); } + set page(p: number) { + this._pdfjsView._ensurePdfPageLoaded(this._pdfjsView._pages[p - 1]).then(() => { + this._pdfjsView.renderingQueue.renderView(this._pdfjsView._pages[p - 1]); + if (this._viewer.props.GoToPage) + this._viewer.props.GoToPage(p); + }); + } - setPdf(pdf: any) { this.pdf = pdf; } get rotation() { return 0; } set rotation(value: any) { } diff --git a/src/client/views/pdf/Page.scss b/src/client/views/pdf/Page.scss index af1628a6f..d8034b4b4 100644 --- a/src/client/views/pdf/Page.scss +++ b/src/client/views/pdf/Page.scss @@ -1,4 +1,9 @@ +.pdfViewer-text { + .page { + position: relative; + } +} .pdfPage-cont { position: relative; diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 7be44faf6..126d62c52 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -366,6 +366,7 @@ export default class PresentationElement extends React.Component<PresentationEle zoomToScale={emptyFunction} getScale={returnOne} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} ContentScaling={scale} /> <div style={{ diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index c13d1d276..da733d64b 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -195,11 +195,9 @@ export class FilterBox extends React.Component { collections.push(element.props.Document); } } - //gets the selected doc's containing view - let containingView = element.props.ContainingCollectionView; //makes sure collections aren't added more than once - if (containingView && !collections.includes(containingView.props.Document)) { - collections.push(containingView.props.Document); + if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) { + collections.push(element.props.ContainingCollectionDoc); } }); diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 510672788..96eefacc2 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -76,7 +76,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { col.panX = newPanX; col.panY = newPanY; } - CollectionDockingView.Instance.AddRightSplit(col, undefined); + CollectionDockingView.AddRightSplit(col, undefined); }; } render() { @@ -110,7 +110,7 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> { unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc); - getOnClick = (col: Doc) => () => CollectionDockingView.Instance.AddRightSplit(col, undefined); + getOnClick = (col: Doc) => () => CollectionDockingView.AddRightSplit(col, undefined); render() { return ( @@ -180,6 +180,7 @@ export class SearchItem extends React.Component<SearchItemProps> { zoomToScale={emptyFunction} getScale={returnOne} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} ContentScaling={scale} /> </div>; diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx index 4f4db13d2..fd6b47ff0 100644 --- a/src/debug/Repl.tsx +++ b/src/debug/Repl.tsx @@ -28,12 +28,8 @@ class Repl extends React.Component { if (!script.compiled) { this.executedCommands.push({ command: this.text, result: "Compile Error" }); } else { - const result = script.run({ makeInterface }); - if (result.success) { - this.executedCommands.push({ command: this.text, result: result.result }); - } else { - this.executedCommands.push({ command: this.text, result: result.error.message || result.error }); - } + const result = script.run({ makeInterface }, e => this.executedCommands.push({ command: this.text, result: e.message || e })); + result.success && this.executedCommands.push({ command: this.text, result: result.result }); } this.text = ""; } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 614babd3c..b875e7896 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -2,7 +2,7 @@ import { observable, ObservableMap, runInAction, action } from "mobx"; import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; import { DocumentType } from "../client/documents/DocumentTypes"; -import { CompileScript, Scripting, scriptingGlobal } from "../client/util/Scripting"; +import { Scripting, scriptingGlobal } from "../client/util/Scripting"; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, Update } from "./FieldSymbols"; import { List } from "./List"; @@ -144,12 +144,12 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; public [WidthSym] = () => { - let iconAnimating = this[SelfProxy].isIconAnimating ? Array.from(Cast(this[SelfProxy].isIconAnimating, listSpec("number"))!) : undefined; - return iconAnimating ? iconAnimating[0] : NumCast(this[SelfProxy].width); + let animDims = this[SelfProxy].animateToDimensions ? Array.from(Cast(this[SelfProxy].animateToDimensions, listSpec("number"))!) : undefined; + return animDims ? animDims[0] : NumCast(this[SelfProxy].width); } public [HeightSym] = () => { - let iconAnimating = this[SelfProxy].isIconAnimating ? Array.from(Cast(this[SelfProxy].isIconAnimating, listSpec("number"))!) : undefined; - return iconAnimating ? iconAnimating[1] : NumCast(this[SelfProxy].height); + let animDims = this[SelfProxy].animateToDimensions ? Array.from(Cast(this[SelfProxy].animateToDimensions, listSpec("number"))!) : undefined; + return animDims ? animDims[1] : NumCast(this[SelfProxy].height); } [ToScriptString]() { @@ -334,7 +334,7 @@ export namespace Doc { } export function IndexOf(toFind: Doc, list: Doc[]) { - return list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)) + return list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); } export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { if (target[key] === undefined) { @@ -382,13 +382,13 @@ export namespace Doc { } // - // Resolves a reference to a field by returning 'doc' if field extension is specified, + // Resolves a reference to a field by returning 'doc' if no field extension is specified, // otherwise, it returns the extension document stored in doc.<fieldKey>_ext. // This mechanism allows any fields to be extended with an extension document that can // be used to capture field-specific metadata. For example, an image field can be extended // to store annotations, ink, and other data. // - export function resolvedFieldDataDoc(doc: Doc, fieldKey: string, fieldExt: string) { + export function fieldExtensionDoc(doc: Doc, fieldKey: string, fieldExt: string = "yes") { return fieldExt && doc[fieldKey + "_ext"] instanceof Doc ? doc[fieldKey + "_ext"] as Doc : doc; } @@ -405,29 +405,28 @@ export namespace Doc { return docExtensionForField; } - export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string) { + export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string, immediate: boolean = false) { let docExtensionForField = doc[fieldKey + "_ext"] as Doc; if (docExtensionForField === undefined) { - setTimeout(() => { - CreateDocumentExtensionForField(doc, fieldKey); - }, 0); + if (immediate) CreateDocumentExtensionForField(doc, fieldKey); + else setTimeout(() => CreateDocumentExtensionForField(doc, fieldKey), 0); } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0); docExtensionForField.type === undefined && setTimeout(() => docExtensionForField.type = DocumentType.EXTENSION, 0); } } + export function MakeTitled(title: string) { + let doc = new Doc(); + doc.title = title; + return doc; + } export function MakeAlias(doc: Doc) { let alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc) : Doc.MakeDelegate(doc); if (alias.layout instanceof Doc) { - alias.layout = Doc.MakeAlias(alias.layout as Doc); + alias.layout = Doc.MakeAlias(alias.layout); } let aliasNumber = Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1; - let script = `return renameAlias(self, ${aliasNumber})`; - //let script = "StrCast(self.title).replace(/\\([0-9]*\\)/, \"\") + `(${n})`"; - let compiled = CompileScript(script, { params: { this: "Doc" }, capturedVariables: { self: doc }, typecheck: false }); - if (compiled.compiled) { - alias.title = new ComputedField(compiled); - } + alias.title = ComputedField.MakeFunction(`renameAlias(this, ${aliasNumber})`); return alias; } @@ -478,7 +477,7 @@ export namespace Doc { let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc && dataDoc ? Doc.GetDataDoc(dataDoc) : undefined; if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) { Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey); - let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy"); + let fieldExtensionDoc = Doc.fieldExtensionDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy"); layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined); } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc); return { layout: layoutDoc, data: resolvedDataDoc }; @@ -545,22 +544,9 @@ export namespace Doc { let _applyCount: number = 0; export function ApplyTemplate(templateDoc: Doc) { - if (!templateDoc) return undefined; - let datadoc = new Doc(); - let otherdoc = Doc.MakeDelegate(datadoc); - otherdoc.width = templateDoc[WidthSym](); - otherdoc.height = templateDoc[HeightSym](); - otherdoc.title = templateDoc.title + "(..." + _applyCount++ + ")"; - otherdoc.layout = Doc.MakeDelegate(templateDoc); - otherdoc.miniLayout = StrCast(templateDoc.miniLayout); - otherdoc.detailedLayout = otherdoc.layout; - otherdoc.type = DocumentType.TEMPLATE; - !templateDoc.nativeWidth && (otherdoc.nativeWidth = 0); - !templateDoc.nativeHeight && (otherdoc.nativeHeight = 0); - !templateDoc.nativeWidth && (otherdoc.ignoreAspect = true); - return otherdoc; - } - export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc, useTemplateDoc?: boolean) { + return !templateDoc ? undefined : ApplyTemplateTo(templateDoc, Doc.MakeDelegate(new Doc()), templateDoc.title + "(..." + _applyCount++ + ")"); + } + export function ApplyTemplateTo(templateDoc: Doc, target: Doc, titleTarget: string | undefined = undefined) { if (!templateDoc) { target.layout = undefined; target.nativeWidth = undefined; @@ -569,25 +555,24 @@ export namespace Doc { target.type = undefined; return; } - let temp = useTemplateDoc ? templateDoc : Doc.MakeDelegate(templateDoc); - target.nativeWidth = Doc.GetProto(target).nativeWidth = undefined; - target.nativeHeight = Doc.GetProto(target).nativeHeight = undefined; - !templateDoc.nativeWidth && (target.nativeWidth = 0); - !templateDoc.nativeHeight && (target.nativeHeight = 0); - !templateDoc.nativeHeight && (target.ignoreAspect = true); + + let layoutCustom = Doc.MakeTitled("layoutCustom"); + let layoutCustomLayout = Doc.MakeDelegate(templateDoc); + + titleTarget && (target.title = titleTarget); + target.type = DocumentType.TEMPLATE; target.width = templateDoc.width; target.height = templateDoc.height; + target.nativeWidth = templateDoc.nativeWidth ? templateDoc.nativeWidth : 0; + target.nativeHeight = templateDoc.nativeHeight ? templateDoc.nativeHeight : 0; + target.ignoreAspect = templateDoc.nativeWidth ? true : false; target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy](); - target.type = DocumentType.TEMPLATE; - if (targetData && targetData.layout === target) { - targetData.layout = temp; - targetData.miniLayout = StrCast(templateDoc.miniLayout); - targetData.detailedLayout = targetData.layout; - } else { - target.layout = temp; - target.miniLayout = StrCast(templateDoc.miniLayout); - target.detailedLayout = target.layout; - } + target.layout = layoutCustomLayout; + target.backgroundLayout = layoutCustomLayout.backgroundLayout; + + target.layoutNative = Cast(templateDoc.layoutNative, Doc) as Doc; + target.layoutCustom = layoutCustom; + return target; } export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false) { @@ -629,30 +614,6 @@ export namespace Doc { }), 0); } - export function ToggleDetailLayout(d: Doc) { - runInAction(async () => { - let miniLayout = await PromiseValue(d.miniLayout); - let detailLayout = await PromiseValue(d.detailedLayout); - d.layout !== miniLayout ? miniLayout && (d.layout = d.miniLayout) : detailLayout && (d.layout = detailLayout); - if (d.layout === detailLayout) d.nativeWidth = d.nativeHeight = 0; - if (StrCast(d.layout) !== "") d.nativeWidth = d.nativeHeight = undefined; - }); - } - export function UseDetailLayout(d: Doc) { - runInAction(async () => { - let detailLayout = await d.detailedLayout; - if (detailLayout) { - d.layout = detailLayout; - d.nativeWidth = d.nativeHeight = undefined; - if (detailLayout instanceof Doc) { - let delegDetailLayout = Doc.MakeDelegate(detailLayout); - d.layout = delegDetailLayout; - delegDetailLayout.layout = await delegDetailLayout.detailedLayout; - } - } - }); - } - export function isBrushedHighlightedDegree(doc: Doc) { if (Doc.IsHighlighted(doc)) { return 3; @@ -722,7 +683,7 @@ export namespace Doc { manager.BrushedDoc.clear(); } } -Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`; }); +Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; }); Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); }); diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts index 83fb52d07..cdc9871a8 100644 --- a/src/new_fields/ScriptField.ts +++ b/src/new_fields/ScriptField.ts @@ -1,5 +1,5 @@ import { ObjectField } from "./ObjectField"; -import { CompiledScript, CompileScript, scriptingGlobal } from "../client/util/Scripting"; +import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions } from "../client/util/Scripting"; import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols"; import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr"; import { Deserializable, autoObject } from "../client/util/SerializationHelper"; @@ -101,19 +101,39 @@ export class ScriptField extends ObjectField { [ToScriptString]() { return "script field"; } + public static CompileScript(script: string, params: object = {}, addReturn = false) { + let compiled = CompileScript(script, { + params: { this: Doc.name, ...params }, + typecheck: false, + editable: true, + addReturn: addReturn + }); + return compiled; + } + public static MakeFunction(script: string, params: object = {}) { + let compiled = ScriptField.CompileScript(script, params, true); + return compiled.compiled ? new ScriptField(compiled) : undefined; + } + + public static MakeScript(script: string, params: object = {}) { + let compiled = ScriptField.CompileScript(script, params, false); + return compiled.compiled ? new ScriptField(compiled) : undefined; + } } @scriptingGlobal @Deserializable("computed", deserializeScript) export class ComputedField extends ScriptField { //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc - value = computedFn((doc: Doc) => { - const val = this.script.run({ this: doc }); - if (val.success) { - return val.result; - } - return undefined; - }); + value = computedFn((doc: Doc) => this.script.run({ this: doc }, console.log).result); + public static MakeScript(script: string, params: object = {}, ) { + let compiled = ScriptField.CompileScript(script, params, false); + return compiled.compiled ? new ComputedField(compiled) : undefined; + } + public static MakeFunction(script: string, params: object = {}) { + let compiled = ScriptField.CompileScript(script, params, true); + return compiled.compiled ? new ComputedField(compiled) : undefined; + } } export namespace ComputedField { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index af5774ebe..1af36fccd 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -36,46 +36,62 @@ export class CurrentUserUtils { doc.xMargin = 5; doc.yMargin = 5; doc.boxShadow = "0 0"; - doc.excludeFromLibrary = true; doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" }); return doc; } static updateUserDocument(doc: Doc) { + + // setup workspaces library item if (doc.workspaces === undefined) { const workspaces = Docs.Create.TreeDocument([], { title: "Workspaces", height: 100 }); - workspaces.excludeFromLibrary = true; - workspaces.workspaceLibrary = true; workspaces.boxShadow = "0 0"; doc.workspaces = workspaces; } - PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true)); + PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => { + if (workspaces) { + workspaces.preventTreeViewOpen = true; + workspaces.forceActive = true; + workspaces.lockedPosition = true; + } + }); + + // setup notes list if (doc.noteTypes === undefined) { let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }), Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }), Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }), Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })]; const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 }); - noteTypes.excludeFromLibrary = true; doc.noteTypes = noteTypes; } - PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(vals => DocListCast(vals))); + PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast)); + + // setup Recently Closed library item if (doc.recentlyClosed === undefined) { const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 }); - recentlyClosed.excludeFromLibrary = true; recentlyClosed.boxShadow = "0 0"; doc.recentlyClosed = recentlyClosed; } - PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => recent && (recent.preventTreeViewOpen = true)); + PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => { + if (recent) { + recent.preventTreeViewOpen = true; + recent.forceActive = true; + recent.lockedPosition = true; + } + }); + + if (doc.curPresentation === undefined) { const curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation" }); - curPresentation.excludeFromLibrary = true; curPresentation.boxShadow = "0 0"; doc.curPresentation = curPresentation; } + if (doc.sidebar === undefined) { const sidebar = Docs.Create.StackingDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { title: "Sidebar" }); - sidebar.excludeFromLibrary = true; + sidebar.forceActive = true; + sidebar.lockedPosition = true; sidebar.gridGap = 5; sidebar.xMargin = 5; sidebar.yMargin = 5; @@ -83,18 +99,22 @@ export class CurrentUserUtils { sidebar.boxShadow = "1 1 3"; doc.sidebar = sidebar; } + if (doc.overlays === undefined) { const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" }); - overlays.excludeFromLibrary = true; Doc.GetProto(overlays).backgroundColor = "#aca3a6"; doc.overlays = overlays; } + if (doc.linkFollowBox === undefined) { PromiseValue(Cast(doc.overlays, Doc)).then(overlays => overlays && Doc.AddDocToList(overlays, "data", doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" }))); } + StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library"); doc.width = 100; doc.preventTreeViewOpen = true; + doc.forceActive = true; + doc.lockedPosition = true; } public static loadCurrentUser() { |