From ffb4da00970f4146d7ce2c5022dabba193e763a3 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Mon, 19 Aug 2019 22:44:05 -0400 Subject: things won't highlight more than once --- src/new_fields/Doc.ts | 67 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 12 deletions(-) (limited to 'src/new_fields') diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index d634cf57f..425d532c0 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -329,12 +329,12 @@ export namespace Doc { export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean) { if (target[key] === undefined) { - console.log("target key undefined"); + // console.log("target key undefined"); Doc.GetProto(target)[key] = new List(); } let list = Cast(target[key], listSpec(Doc)); if (list) { - console.log("has list"); + // console.log("has list"); if (allowDuplicates !== true) { let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1); if (pind !== -1) { @@ -342,15 +342,15 @@ export namespace Doc { } } if (first) { - console.log("is first"); + // console.log("is first"); list.splice(0, 0, doc); } else { - console.log("not first"); + // console.log("not first"); let ind = relativeTo ? list.indexOf(relativeTo) : -1; if (ind === -1) list.push(doc); else list.splice(before ? ind : ind + 1, 0, doc); - console.log("index", ind); + // console.log("index", ind); } } return true; @@ -595,23 +595,66 @@ export namespace Doc { }); } + export function isBrushedHighlightedDegree(doc: Doc) { + if (Doc.IsHighlighted(doc)) { + return 3; + } + else { + return Doc.IsBrushedDegree(doc); + } + } + export class DocBrush { @observable BrushedDoc: ObservableMap = new ObservableMap(); } - const manager = new DocBrush(); + const brushManager = new DocBrush(); export function IsBrushed(doc: Doc) { - return manager.BrushedDoc.has(doc) || manager.BrushedDoc.has(Doc.GetDataDoc(doc)); + return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)); } export function IsBrushedDegree(doc: Doc) { - return manager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 2 : manager.BrushedDoc.has(doc) ? 1 : 0; + return brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 2 : brushManager.BrushedDoc.has(doc) ? 1 : 0; } export function BrushDoc(doc: Doc) { - manager.BrushedDoc.set(doc, true); - manager.BrushedDoc.set(Doc.GetDataDoc(doc), true); + brushManager.BrushedDoc.set(doc, true); + brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true); } export function UnBrushDoc(doc: Doc) { - manager.BrushedDoc.delete(doc); - manager.BrushedDoc.delete(Doc.GetDataDoc(doc)); + brushManager.BrushedDoc.delete(doc); + brushManager.BrushedDoc.delete(Doc.GetDataDoc(doc)); + } + + export class HighlightBrush { + @observable HighlightedDoc: ObservableMap = new ObservableMap(); + } + const highlightManager = new HighlightBrush(); + export function IsHighlighted(doc: Doc) { + // return highlightManager.HighlightedDoc.has(doc) || highlightManager.HighlightedDoc.has(Doc.GetDataDoc(doc)); + return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetDataDoc(doc)); + } + export function HighlightDoc(doc: Doc) { + console.log("is highlighting") + runInAction(() => { + highlightManager.HighlightedDoc.set(doc, true); + highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), true); + }); + } + export function UnHighlightDoc(doc: Doc) { + // highlightManager.HighlightedDoc.delete(doc); + // highlightManager.HighlightedDoc.delete(Doc.GetDataDoc(doc)); + runInAction(() => { + highlightManager.HighlightedDoc.set(doc, false); + highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), false); + }) + } + export function UnhighlightAll() { + // highlightManager.HighlightedDoc.clear(); + let docs = highlightManager.HighlightedDoc.keys(); + let doc = docs.next(); + while (docs.next !== null) { + Doc.UnHighlightDoc(doc.value); + doc = docs.next(); + } + } } Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`; }); -- cgit v1.2.3-70-g09d2 From bd484eac03d50b6ce517bf9d0f966d4c48d14570 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Tue, 20 Aug 2019 14:37:20 -0400 Subject: highlighting working --- src/client/views/nodes/LinkMenuItem.tsx | 16 +++------------- src/new_fields/Doc.ts | 22 +++++++++------------- 2 files changed, 12 insertions(+), 26 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx index c3d9d033f..12eb2c2f7 100644 --- a/src/client/views/nodes/LinkMenuItem.tsx +++ b/src/client/views/nodes/LinkMenuItem.tsx @@ -32,30 +32,20 @@ export class LinkMenuItem extends React.Component { private _drag = React.createRef(); @observable private _showMore: boolean = false; @action toggleShowMore() { this._showMore = !this._showMore; } - @observable shouldUnhighlight: boolean = false; - componentDidMount = () => { - // document.addEventListener("pointerdown", this.unhighlight); - } unhighlight = () => { - // if (this.shouldUnhighlight) - // Doc.UnhighlightAll(); - Doc.UnHighlightDoc(this.props.destinationDoc); + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", this.unhighlight); } @action highlightDoc = () => { - // this.shouldUnhighlight = false; document.removeEventListener("pointerdown", this.unhighlight); - Doc.HighlightDoc(this.props.destinationDoc); - window.setTimeout(() => { - // this.shouldUnhighlight = true; document.addEventListener("pointerdown", this.unhighlight); - - }, 3000); + }, 10000); } // NOT DONE? diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 425d532c0..b47811ac6 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -624,35 +624,31 @@ export namespace Doc { } export class HighlightBrush { - @observable HighlightedDoc: ObservableMap = new ObservableMap(); + @observable HighlightedDoc: Map = new Map(); } const highlightManager = new HighlightBrush(); export function IsHighlighted(doc: Doc) { - // return highlightManager.HighlightedDoc.has(doc) || highlightManager.HighlightedDoc.has(Doc.GetDataDoc(doc)); - return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetDataDoc(doc)); + let IsHighlighted = highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetDataDoc(doc)); + return IsHighlighted; } export function HighlightDoc(doc: Doc) { - console.log("is highlighting") runInAction(() => { highlightManager.HighlightedDoc.set(doc, true); highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), true); }); } export function UnHighlightDoc(doc: Doc) { - // highlightManager.HighlightedDoc.delete(doc); - // highlightManager.HighlightedDoc.delete(Doc.GetDataDoc(doc)); runInAction(() => { highlightManager.HighlightedDoc.set(doc, false); highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), false); - }) + }); } export function UnhighlightAll() { - // highlightManager.HighlightedDoc.clear(); - let docs = highlightManager.HighlightedDoc.keys(); - let doc = docs.next(); - while (docs.next !== null) { - Doc.UnHighlightDoc(doc.value); - doc = docs.next(); + let mapEntries = highlightManager.HighlightedDoc.keys(); + let docEntry: IteratorResult; + while (!(docEntry = mapEntries.next()).done) { + let targetDoc = docEntry.value; + targetDoc && Doc.UnHighlightDoc(targetDoc); } } -- cgit v1.2.3-70-g09d2 From dee6904f0fd4d54862bfd1c86611be294517951c Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 23 Aug 2019 18:03:47 -0400 Subject: fixed toggleDetail layout. --- src/new_fields/Doc.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/new_fields') diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 1d529ac84..6f1ef38d1 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -589,7 +589,8 @@ export namespace Doc { 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) Doc.GetProto(d).nativeWidth = Doc.GetProto(d).nativeHeight = undefined; + if (d.layout === detailLayout) d.nativeWidth = d.nativeHeight = 0; + if (StrCast(d.layout) !== "") d.nativeWidth = d.nativeHeight = undefined; }); } export function UseDetailLayout(d: Doc) { -- cgit v1.2.3-70-g09d2 From 187a411024668a46e7a80022d3d549118b81abbc Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 27 Aug 2019 12:49:04 -0400 Subject: can push links to google docs --- .../apis/google_docs/GoogleApiClientUtils.ts | 26 ++--- src/client/documents/Documents.ts | 7 +- src/client/views/MainView.tsx | 26 ----- src/client/views/nodes/FormattedTextBox.tsx | 51 ++++---- src/new_fields/RichTextField.ts | 49 -------- src/new_fields/RichTextUtils.ts | 129 +++++++++++++++++++++ 6 files changed, 173 insertions(+), 115 deletions(-) create mode 100644 src/new_fields/RichTextUtils.ts (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 61df69d5c..689009254 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -27,11 +27,14 @@ export namespace GoogleApiClientUtils { export type Identifier = string; export type Reference = Identifier | CreateOptions; - export type TextContent = string | string[]; + export interface Content { + text: string | string[]; + links: docs_v1.Schema$Request[]; + } export type IdHandler = (id: Identifier) => any; export type CreationResult = Opt; export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; - export type ReadResult = { title?: string, body?: string }; + export type ReadResult = { title: string, body: string }; export interface CreateOptions { service: Service; @@ -50,7 +53,7 @@ export namespace GoogleApiClientUtils { export interface WriteOptions { mode: WriteMode; - content: TextContent; + content: Content; reference: Reference; index?: number; // if excluded, will compute the last index of the document and append the content there } @@ -165,28 +168,24 @@ export namespace GoogleApiClientUtils { } }; - export const read = async (options: ReadOptions): Promise => { + export const read = async (options: ReadOptions): Promise> => { return retrieve({ ...options, service: Service.Documents }).then(document => { - let result: ReadResult = {}; if (document) { - let title = document.title; + let title = document.title!; let body = Utils.extractText(document, options.removeNewlines); - result = { title, body }; + return { title, body }; } - return result; }); }; - export const readLines = async (options: ReadOptions): Promise => { + export const readLines = async (options: ReadOptions): Promise> => { return retrieve({ ...options, service: Service.Documents }).then(document => { - let result: ReadLinesResult = {}; if (document) { let title = document.title; let bodyLines = Utils.extractText(document).split("\n"); options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length)); - result = { title, bodyLines }; + return { title, bodyLines }; } - return result; }); }; @@ -227,7 +226,7 @@ export namespace GoogleApiClientUtils { }); index = 1; } - const text = options.content; + const text = options.content.text; text.length && requests.push({ insertText: { text: isArray(text) ? text.join("\n") : text, @@ -237,6 +236,7 @@ export namespace GoogleApiClientUtils { if (!requests.length) { return undefined; } + requests.push(...options.content.links); let replies: any = await update({ documentId: identifier, requests }); if ("errors" in replies) { console.log("Write operation failed:"); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 47df17329..e40e095d6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -496,10 +496,13 @@ export namespace Docs { * @param title an optional title to give to the highest parent document in the hierarchy */ export function DocumentHierarchyFromJson(input: any, title?: string): Opt { - if (input === null || ![...primitives, "object"].includes(typeof input)) { + if (input === undefined || input === null || ![...primitives, "object"].includes(typeof input)) { return undefined; } - let parsed: any = typeof input === "string" ? JSONUtils.tryParse(input) : input; + let parsed = input; + if (typeof input === "string") { + parsed = JSONUtils.tryParse(input); + } let converted: Doc; if (typeof parsed === "object" && !(parsed instanceof Array)) { converted = convertObject(parsed, title); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b35b2d331..ab87f0c7b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -122,32 +122,6 @@ export class MainView extends React.Component { componentWillMount() { var tag = document.createElement('script'); - let requests: docs_v1.Schema$Request[] = - [{ - updateTextStyle: { - fields: "*", - range: { - startIndex: 1, - endIndex: 15 - }, - textStyle: { - bold: true, - link: { url: window.location.href }, - foregroundColor: { - color: { - rgbColor: { - red: 1.0, - green: 0.0, - blue: 0.0 - } - } - } - } - } - }]; - let documentId = "1xBwN4akVePW_Zp8wbiq0WNjlzGAE2PyNVvwzFbUyv3I"; - GoogleApiClientUtils.Docs.setStyle({ documentId, requests }); - tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d6ba1700a..02bee2f82 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,7 +12,7 @@ import { DateField } from '../../../new_fields/DateField'; import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; -import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; +import { RichTextField } from "../../../new_fields/RichTextField"; import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Utils } from '../../../Utils'; @@ -37,12 +37,11 @@ import { DocumentDecorations } from '../DocumentDecorations'; import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; +import { RichTextUtils } from '../../../new_fields/RichTextUtils'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); -export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; - export interface FormattedTextBoxProps { isOverlay?: boolean; hideOnLeave?: boolean; @@ -61,7 +60,7 @@ export const GoogleRef = "googleDocId"; type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); -type PullHandler = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => void; +type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { @@ -363,7 +362,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._reactionDisposer = reaction( () => { const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined; - return field ? field.Data : Blank; + return field ? field.Data : RichTextUtils.Initialize(); }, incomingValue => { if (this._editorView && !this._applyingChange) { @@ -431,7 +430,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } pushToGoogleDoc = async () => { - this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { + this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { let modes = GoogleApiClientUtils.WriteMode; let mode = modes.Replace; let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); @@ -440,9 +439,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe reference = { service: GoogleApiClientUtils.Service.Documents, title: StrCast(this.dataDoc.title) }; } let redo = async () => { - let data = Cast(this.dataDoc.data, RichTextField); - if (this._editorView && reference && data) { - let content = data[ToPlainText](); + if (this._editorView && reference) { + let content = RichTextUtils.GoogleDocs.Convert(this._editorView.state); let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); let pushSuccess = response !== undefined && !("errors" in response); @@ -451,7 +449,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } }; let undo = () => { - let content = exportState.body; + if (!exportState) { + return; + } + let content = { + text: exportState.body, + links: [] + }; if (reference && content) { GoogleApiClientUtils.Docs.write({ reference, content, mode }); } @@ -464,20 +468,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe pullFromGoogleDoc = async (handler: PullHandler) => { let dataDoc = this.dataDoc; let documentId = StrCast(dataDoc[GoogleRef]); - let exportState: GoogleApiClientUtils.ReadResult = {}; + let exportState: Opt; if (documentId) { exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId }); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } - updateState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { + updateState = (exportState: Opt, dataDoc: Doc) => { let pullSuccess = false; if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { const data = Cast(dataDoc.data, RichTextField); if (data instanceof RichTextField) { pullSuccess = true; - dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); + dataDoc.data = RichTextUtils.Synthesize(exportState.body, data); setTimeout(() => { if (this._editorView) { let state = this._editorView.state; @@ -495,18 +499,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe DocumentDecorations.Instance.startPullOutcome(pullSuccess); } - checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { - if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { - let data = Cast(dataDoc.data, RichTextField); - if (data) { - let storedPlainText = data[ToPlainText]() + "\n"; - let receivedPlainText = exportState.body; - let storedTitle = dataDoc.title; - let receivedTitle = exportState.title; - let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle; - dataDoc.unchanged = unchanged; - DocumentDecorations.Instance.setPullState(unchanged); - } + checkState = (exportState: Opt, dataDoc: Doc) => { + if (exportState && this._editorView) { + let storedPlainText = RichTextUtils.ToPlainText(this._editorView.state) + "\n"; + let receivedPlainText = exportState.body; + let storedTitle = dataDoc.title; + let receivedTitle = exportState.title; + let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle; + dataDoc.unchanged = unchanged; + DocumentDecorations.Instance.setPullState(unchanged); } } diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 1b52e6f82..d2f76c969 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -4,11 +4,6 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { Copy, ToScriptString } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; -export const ToPlainText = Symbol("PlainText"); -export const FromPlainText = Symbol("PlainText"); -const delimiter = "\n"; -const joiner = ""; - @scriptingGlobal @Deserializable("RichTextField") export class RichTextField extends ObjectField { @@ -28,48 +23,4 @@ export class RichTextField extends ObjectField { return `new RichTextField("${this.Data}")`; } - public static Initialize = (initial: string) => { - !initial.length && (initial = " "); - let pos = initial.length + 1; - return `{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"${initial}"}]}]},"selection":{"type":"text","anchor":${pos},"head":${pos}}}`; - } - - [ToPlainText]() { - // Because we're working with plain text, just concatenate all paragraphs - let content = JSON.parse(this.Data).doc.content; - let paragraphs = content.filter((item: any) => item.type === "paragraph"); - - // Functions to flatten ProseMirror paragraph objects (and their components) to plain text - // While this function already exists in state.doc.textBeteen(), it doesn't account for newlines - let blockText = (block: any) => block.text; - let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter; - - // Concatentate paragraphs and string the result together - let textParagraphs: string[] = paragraphs.map(concatenateParagraph); - let plainText = textParagraphs.join(joiner); - return plainText.substring(0, plainText.length - 1); - } - - [FromPlainText](plainText: string) { - // Remap the text, creating blocks split on newlines - let elements = plainText.split(delimiter); - - // Google Docs adds in an extra carriage return automatically, so this counteracts it - !elements[elements.length - 1].length && elements.pop(); - - // Preserve the current state, but re-write the content to be the blocks - let parsed = JSON.parse(this.Data); - parsed.doc.content = elements.map(text => { - let paragraph: any = { type: "paragraph" }; - text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break - return paragraph; - }); - - // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it - parsed.selection = { type: "text", anchor: 1, head: 1 }; - - // Export the ProseMirror-compatible state object we've jsut built - return JSON.stringify(parsed); - } - } \ No newline at end of file diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts new file mode 100644 index 000000000..b2b1dbaee --- /dev/null +++ b/src/new_fields/RichTextUtils.ts @@ -0,0 +1,129 @@ +import { EditorState } from "prosemirror-state"; +import { Node } from "prosemirror-model"; +import { RichTextField } from "./RichTextField"; +import { docs_v1 } from "googleapis"; +import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; + +export namespace RichTextUtils { + + const delimiter = "\n"; + const joiner = ""; + + + export const Initialize = (initial?: string) => { + let content: any[] = []; + let state = { + doc: { + type: "doc", + content, + }, + selection: { + type: "text", + anchor: 0, + head: 0 + } + }; + if (initial && initial.length) { + content.push({ + type: "paragraph", + content: { + type: "text", + text: initial + } + }); + state.selection.anchor = state.selection.head = initial.length + 1; + } + return JSON.stringify(state); + }; + + export const Synthesize = (plainText: string, oldState?: RichTextField) => { + return new RichTextField(ToProsemirrorState(plainText, oldState)); + }; + + export const ToPlainText = (state: EditorState) => { + // Because we're working with plain text, just concatenate all paragraphs + let content = state.doc.content; + let paragraphs: Node[] = []; + content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node)); + + // Functions to flatten ProseMirror paragraph objects (and their components) to plain text + // Concatentate paragraphs and string the result together + let textParagraphs: string[] = paragraphs.map(paragraph => { + let text: string[] = []; + paragraph.content.forEach(node => node.text && text.push(node.text)); + return text.join(joiner) + delimiter; + }); + let plainText = textParagraphs.join(joiner); + return plainText.substring(0, plainText.length - 1); + }; + + export const ToProsemirrorState = (plainText: string, oldState?: RichTextField) => { + // Remap the text, creating blocks split on newlines + let elements = plainText.split(delimiter); + + // Google Docs adds in an extra carriage return automatically, so this counteracts it + !elements[elements.length - 1].length && elements.pop(); + + // Preserve the current state, but re-write the content to be the blocks + let parsed = JSON.parse(oldState ? oldState.Data : Initialize()); + parsed.doc.content = elements.map(text => { + let paragraph: any = { type: "paragraph" }; + text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break + return paragraph; + }); + + // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it + parsed.selection = { type: "text", anchor: 1, head: 1 }; + + // Export the ProseMirror-compatible state object we've just built + return JSON.stringify(parsed); + }; + + export namespace GoogleDocs { + + export const Convert = (state: EditorState): GoogleApiClientUtils.Content => { + let textNodes: Node[] = []; + let text = ToPlainText(state); + let content = state.doc.content; + content.forEach(node => node.content.forEach(node => node.type.name === "text" && textNodes.push(node))); + let links: docs_v1.Schema$Request[] = []; + let position = 1; + for (let node of textNodes) { + let link, length = node.nodeSize; + let marks = node.marks; + if (marks.length && (link = marks.find(mark => mark.type.name === "link"))) { + links.push(encode({ + startIndex: position, + endIndex: position + length, + url: link.attrs.href, + })); + } + position += length; + } + return { text, links }; + }; + + interface LinkInformation { + startIndex: number; + endIndex: number; + url: string; + } + const encode = (information: LinkInformation) => { + return { + updateTextStyle: { + fields: "*", + range: { + startIndex: information.startIndex, + endIndex: information.endIndex + }, + textStyle: { + bold: true, + link: { url: information.url }, + foregroundColor: { color: { rgbColor: { red: 0.0, green: 0.0, blue: 1.0 } } } + } + } + }; + }; + } + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 916aa5377a9f105cd128264f46c83b987861c713 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 28 Aug 2019 17:45:44 -0400 Subject: separated docs and other apis, beginning less hacky import --- .../apis/google_docs/GoogleApiClientUtils.ts | 204 ++++++++------------- src/client/views/nodes/FormattedTextBox.tsx | 35 ++-- src/new_fields/RichTextUtils.ts | 56 +++++- 3 files changed, 146 insertions(+), 149 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 689009254..ae7c2f997 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -9,108 +9,104 @@ export const Pushes = "googleDocsPushCount"; export namespace GoogleApiClientUtils { - export enum Service { - Documents = "Documents", - Slides = "Slides" - } - export enum Actions { Create = "create", Retrieve = "retrieve", Update = "update" } - export enum WriteMode { - Insert, - Replace - } - - export type Identifier = string; - export type Reference = Identifier | CreateOptions; - export interface Content { - text: string | string[]; - links: docs_v1.Schema$Request[]; - } - export type IdHandler = (id: Identifier) => any; - export type CreationResult = Opt; - export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; - export type ReadResult = { title: string, body: string }; + export namespace Docs { - export interface CreateOptions { - service: Service; - title?: string; // if excluded, will use a default title annotated with the current date - } + export type RetrievalResult = Opt; + export type UpdateResult = Opt; - export interface RetrieveOptions { - service: Service; - identifier: Identifier; - } + export interface UpdateOptions { + documentId: DocumentId; + requests: docs_v1.Schema$Request[]; + } - export interface ReadOptions { - identifier: Identifier; - removeNewlines?: boolean; - } + export enum WriteMode { + Insert, + Replace + } - export interface WriteOptions { - mode: WriteMode; - content: Content; - reference: Reference; - index?: number; // if excluded, will compute the last index of the document and append the content there - } + export type DocumentId = string; + export type Reference = DocumentId | CreateOptions; + export interface Content { + text: string | string[]; + requests: docs_v1.Schema$Request[]; + } + export type IdHandler = (id: DocumentId) => any; + export type CreationResult = Opt; + export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; + export type ReadResult = { title: string, body: string }; - /** - * After following the authentication routine, which connects this API call to the current signed in account - * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which - * should appear in the user's Google Doc library instantaneously. - * - * @param options the title to assign to the new document, and the information necessary - * to store the new documentId returned from the creation process - * @returns the documentId of the newly generated document, or undefined if the creation process fails. - */ - export const create = async (options: CreateOptions): Promise => { - const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Create}`; - const parameters = { - requestBody: { - title: options.title || `Dash Export (${new Date().toDateString()})` - } - }; - try { - const schema: any = await PostToServer(path, parameters); - let key = ["document", "presentation"].find(prefix => `${prefix}Id` in schema) + "Id"; - return schema[key]; - } catch { - return undefined; + export interface CreateOptions { + title?: string; // if excluded, will use a default title annotated with the current date } - }; - export namespace Docs { + export interface RetrieveOptions { + documentId: DocumentId; + } - export type RetrievalResult = Opt; - export type UpdateResult = Opt; + export interface ReadOptions { + documentId: DocumentId; + removeNewlines?: boolean; + } - export interface UpdateOptions { - documentId: Identifier; - requests: docs_v1.Schema$Request[]; + export interface WriteOptions { + mode: WriteMode; + content: Content; + reference: Reference; + index?: number; // if excluded, will compute the last index of the document and append the content there } + /** + * After following the authentication routine, which connects this API call to the current signed in account + * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which + * should appear in the user's Google Doc library instantaneously. + * + * @param options the title to assign to the new document, and the information necessary + * to store the new documentId returned from the creation process + * @returns the documentId of the newly generated document, or undefined if the creation process fails. + */ + export const create = async (options: CreateOptions): Promise => { + const path = `${RouteStore.googleDocs}/Documents/${Actions.Create}`; + const parameters = { + requestBody: { + title: options.title || `Dash Export (${new Date().toDateString()})` + } + }; + try { + const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + return schema.documentId; + } catch { + return undefined; + } + }; + export namespace Utils { export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): string => { - const fragments: string[] = []; + let runs = extractTextRuns(document); + const text = runs.map(run => run.content).join(""); + return removeNewlines ? text.ReplaceAll("\n", "") : text; + }; + + export const extractTextRuns = (document: docs_v1.Schema$Document, filterEmpty = true) => { + const fragments: docs_v1.Schema$TextRun[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { if (element.paragraph && element.paragraph.elements) { for (const inner of element.paragraph.elements) { if (inner && inner.textRun) { - const fragment = inner.textRun.content; - fragment && fragments.push(fragment); + fragments.push(inner.textRun); } } } } } - const text = fragments.join(""); - return removeNewlines ? text.ReplaceAll("\n", "") : text; + return filterEmpty ? fragments.filter(run => run.content) : fragments; }; export const endOf = (schema: docs_v1.Schema$Document): number | undefined => { @@ -133,27 +129,19 @@ export namespace GoogleApiClientUtils { } - const KeyMapping = new Map([ - [Service.Documents, "documentId"], - [Service.Slides, "presentationId"] - ]); - export const retrieve = async (options: RetrieveOptions): Promise => { - const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Retrieve}`; + const path = `${RouteStore.googleDocs}/Documents/${Actions.Retrieve}`; try { - let parameters: any = {}, key: string | undefined; - if ((key = KeyMapping.get(options.service))) { - parameters[key] = options.identifier; - const schema: RetrievalResult = await PostToServer(path, parameters); - return schema; - } + const parameters = { documentId: options.documentId }; + const schema: RetrievalResult = await PostToServer(path, parameters); + return schema; } catch { return undefined; } }; export const update = async (options: UpdateOptions): Promise => { - const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Update}`; + const path = `${RouteStore.googleDocs}/Documents/${Actions.Update}`; const parameters = { documentId: options.documentId, requestBody: { @@ -169,7 +157,7 @@ export namespace GoogleApiClientUtils { }; export const read = async (options: ReadOptions): Promise> => { - return retrieve({ ...options, service: Service.Documents }).then(document => { + return retrieve({ documentId: options.documentId }).then(document => { if (document) { let title = document.title!; let body = Utils.extractText(document, options.removeNewlines); @@ -179,7 +167,7 @@ export namespace GoogleApiClientUtils { }; export const readLines = async (options: ReadOptions): Promise> => { - return retrieve({ ...options, service: Service.Documents }).then(document => { + return retrieve({ documentId: options.documentId }).then(document => { if (document) { let title = document.title; let bodyLines = Utils.extractText(document).split("\n"); @@ -203,14 +191,14 @@ export namespace GoogleApiClientUtils { export const write = async (options: WriteOptions): Promise => { const requests: docs_v1.Schema$Request[] = []; - const identifier = await Utils.initialize(options.reference); - if (!identifier) { + const documentId = await Utils.initialize(options.reference); + if (!documentId) { return undefined; } let index = options.index; const mode = options.mode; if (!(index && mode === WriteMode.Insert)) { - let schema = await retrieve({ identifier, service: Service.Documents }); + let schema = await retrieve({ documentId }); if (!schema || !(index = Utils.endOf(schema))) { return undefined; } @@ -236,8 +224,8 @@ export namespace GoogleApiClientUtils { if (!requests.length) { return undefined; } - requests.push(...options.content.links); - let replies: any = await update({ documentId: identifier, requests }); + requests.push(...options.content.requests); + let replies: any = await update({ documentId: documentId, requests }); if ("errors" in replies) { console.log("Write operation failed:"); console.log(replies.errors.map((error: any) => error.message)); @@ -247,36 +235,4 @@ export namespace GoogleApiClientUtils { } - export namespace Slides { - - export namespace Utils { - - export const extractTextBoxes = (slides: slides_v1.Schema$Page[]) => { - slides.map(slide => { - let elements = slide.pageElements; - if (elements) { - let textboxes: slides_v1.Schema$TextContent[] = []; - for (let element of elements) { - if (element && element.shape && element.shape.shapeType === "TEXT_BOX" && element.shape.text) { - textboxes.push(element.shape.text); - } - } - textboxes.map(text => { - if (text.textElements) { - text.textElements.map(element => { - - }); - } - if (text.lists) { - - } - }); - } - }); - }; - - } - - } - } \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 02bee2f82..eefac2285 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -60,14 +60,18 @@ export const GoogleRef = "googleDocId"; type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); -type PullHandler = (exportState: Opt, dataDoc: Doc) => void; +type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } + public static blankState = () => { + return EditorState.create(FormattedTextBox.Instance._configuration); + } public static Instance: FormattedTextBox; + private _configuration: any; private _ref: React.RefObject; private _proseRef?: HTMLDivElement; private _editorView: Opt; @@ -325,7 +329,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentDidMount() { - const config = { + this._configuration = { schema, inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ @@ -367,7 +371,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe incomingValue => { if (this._editorView && !this._applyingChange) { let updatedState = JSON.parse(incomingValue); - this._editorView.updateState(EditorState.fromJSON(config, updatedState)); + this._editorView.updateState(EditorState.fromJSON(this._configuration, updatedState)); this.tryUpdateHeight(); } } @@ -409,7 +413,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc.lastModified = undefined; } }, { fireImmediately: true }); - this.setupEditor(config, this.dataDoc, this.props.fieldKey); + this.setupEditor(this._configuration, this.dataDoc, this.props.fieldKey); this._searchReactionDisposer = reaction(() => { return StrCast(this.props.Document.search_string); @@ -430,17 +434,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } pushToGoogleDoc = async () => { - this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { - let modes = GoogleApiClientUtils.WriteMode; + this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { + let modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); + let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); if (!reference) { mode = modes.Insert; - reference = { service: GoogleApiClientUtils.Service.Documents, title: StrCast(this.dataDoc.title) }; + reference = { title: StrCast(this.dataDoc.title) }; } let redo = async () => { if (this._editorView && reference) { - let content = RichTextUtils.GoogleDocs.Convert(this._editorView.state); + let content = RichTextUtils.GoogleDocs.Export(this._editorView.state); let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); let pushSuccess = response !== undefined && !("errors" in response); @@ -452,9 +456,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!exportState) { return; } - let content = { + let content: GoogleApiClientUtils.Docs.Content = { text: exportState.body, - links: [] + requests: [] }; if (reference && content) { GoogleApiClientUtils.Docs.write({ reference, content, mode }); @@ -468,14 +472,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe pullFromGoogleDoc = async (handler: PullHandler) => { let dataDoc = this.dataDoc; let documentId = StrCast(dataDoc[GoogleRef]); - let exportState: Opt; + let test = await RichTextUtils.GoogleDocs.Import(documentId); + let exportState: Opt; if (documentId) { - exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId }); + exportState = await GoogleApiClientUtils.Docs.read({ documentId }); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } - updateState = (exportState: Opt, dataDoc: Doc) => { + updateState = (exportState: Opt, dataDoc: Doc) => { let pullSuccess = false; if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { const data = Cast(dataDoc.data, RichTextField); @@ -499,7 +504,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe DocumentDecorations.Instance.startPullOutcome(pullSuccess); } - checkState = (exportState: Opt, dataDoc: Doc) => { + checkState = (exportState: Opt, dataDoc: Doc) => { if (exportState && this._editorView) { let storedPlainText = RichTextUtils.ToPlainText(this._editorView.state) + "\n"; let receivedPlainText = exportState.body; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index b2b1dbaee..189819591 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -3,6 +3,8 @@ import { Node } from "prosemirror-model"; import { RichTextField } from "./RichTextField"; import { docs_v1 } from "googleapis"; import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; +import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; +import { Opt } from "./Doc"; export namespace RichTextUtils { @@ -81,34 +83,68 @@ export namespace RichTextUtils { export namespace GoogleDocs { - export const Convert = (state: EditorState): GoogleApiClientUtils.Content => { + export const Export = (state: EditorState): GoogleApiClientUtils.Docs.Content => { let textNodes: Node[] = []; let text = ToPlainText(state); let content = state.doc.content; content.forEach(node => node.content.forEach(node => node.type.name === "text" && textNodes.push(node))); + let linkRequests = ExtractLinks(textNodes); + return { + text, + requests: [...linkRequests] + }; + }; + + export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId) => { + let document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); + if (!document) { + return; + } + // let title = document.title!; + let runs = GoogleApiClientUtils.Docs.Utils.extractTextRuns(document); + let state = FormattedTextBox.blankState(); + let from = 0; + runs.map(run => { + let text = run.content!; + state = state.apply(state.tr.insertText(text, from)); + let to = from + text.length + 1; + let href: Opt; + if (run.textStyle && run.textStyle.link && (href = run.textStyle.link.url)) { + let mark = state.schema.mark(state.schema.marks.link, { href }); + state = state.apply(state.tr.addMark(from, to, mark)); + } + from = to; + }); + // return { title, body }; + }; + + interface LinkInformation { + startIndex: number; + endIndex: number; + bold: boolean; + url: string; + } + + const ExtractLinks = (nodes: Node[]) => { let links: docs_v1.Schema$Request[] = []; let position = 1; - for (let node of textNodes) { + for (let node of nodes) { let link, length = node.nodeSize; let marks = node.marks; if (marks.length && (link = marks.find(mark => mark.type.name === "link"))) { - links.push(encode({ + links.push(Encode({ startIndex: position, endIndex: position + length, url: link.attrs.href, + bold: false })); } position += length; } - return { text, links }; + return links; }; - interface LinkInformation { - startIndex: number; - endIndex: number; - url: string; - } - const encode = (information: LinkInformation) => { + const Encode = (information: LinkInformation) => { return { updateTextStyle: { fields: "*", -- cgit v1.2.3-70-g09d2 From c53d721dd95bdb4ccddc7a89dbdda72a88d29058 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 30 Aug 2019 13:43:04 -0400 Subject: model sharing --- .../apis/google_docs/GoogleApiClientUtils.ts | 22 ++++-- src/client/northstar/utils/Extensions.ts | 2 + src/client/views/DocumentDecorations.scss | 31 +++++++- src/client/views/DocumentDecorations.tsx | 7 +- src/client/views/Main.tsx | 20 ++++++ src/client/views/nodes/FormattedTextBox.tsx | 50 ++++++------- src/new_fields/RichTextUtils.ts | 82 +++++++++++++++++----- 7 files changed, 159 insertions(+), 55 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index ae7c2f997..9bf3cae38 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -3,6 +3,8 @@ import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { Opt } from "../../../new_fields/Doc"; import { isArray } from "util"; +import { EditorState } from "prosemirror-state"; +import { RichTextField } from "../../../new_fields/RichTextField"; export const Pulls = "googleDocsPullCount"; export const Pushes = "googleDocsPushCount"; @@ -40,6 +42,11 @@ export namespace GoogleApiClientUtils { export type CreationResult = Opt; export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; export type ReadResult = { title: string, body: string }; + export interface ImportResult { + title: string; + text: string; + data: RichTextField; + } export interface CreateOptions { title?: string; // if excluded, will use a default title annotated with the current date @@ -87,13 +94,16 @@ export namespace GoogleApiClientUtils { export namespace Utils { - export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): string => { + export type ExtractResult = { text: string, runs: docs_v1.Schema$TextRun[] }; + export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => { let runs = extractTextRuns(document); - const text = runs.map(run => run.content).join(""); - return removeNewlines ? text.ReplaceAll("\n", "") : text; + let text = runs.map(run => run.content).join(""); + text = text.substring(0, text.length - 1); + removeNewlines && text.ReplaceAll("\n", ""); + return { text, runs }; }; - export const extractTextRuns = (document: docs_v1.Schema$Document, filterEmpty = true) => { + const extractTextRuns = (document: docs_v1.Schema$Document, filterEmpty = true) => { const fragments: docs_v1.Schema$TextRun[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { @@ -160,7 +170,7 @@ export namespace GoogleApiClientUtils { return retrieve({ documentId: options.documentId }).then(document => { if (document) { let title = document.title!; - let body = Utils.extractText(document, options.removeNewlines); + let body = Utils.extractText(document, options.removeNewlines).text; return { title, body }; } }); @@ -170,7 +180,7 @@ export namespace GoogleApiClientUtils { return retrieve({ documentId: options.documentId }).then(document => { if (document) { let title = document.title; - let bodyLines = Utils.extractText(document).split("\n"); + let bodyLines = Utils.extractText(document).text.split("\n"); options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length)); return { title, bodyLines }; } diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts index df14d4da0..ab9384f1f 100644 --- a/src/client/northstar/utils/Extensions.ts +++ b/src/client/northstar/utils/Extensions.ts @@ -1,6 +1,8 @@ interface String { ReplaceAll(toReplace: string, replacement: string): string; Truncate(length: number, replacement: string): String; + removeTrailingNewlines(): string; + hasNewline(): boolean; } String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string { diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index ac8497bd0..470365627 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -266,6 +266,31 @@ $linkGap : 3px; } } -@-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 +@-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); + } +} + +@keyframes shadow-pulse { + 0% { + box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2); + } + + 100% { + box-shadow: 0 0 0 35px rgba(0, 0, 0, 0); + } +} \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 579806a89..109228b54 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -82,6 +82,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @observable public pullIcon: IconProp = "arrow-alt-circle-down"; @observable public pullColor: string = "white"; @observable public isAnimatingFetch = false; + @observable public isAnimatingPulse = false; @observable public openHover = false; public pullColorAnimating = false; @@ -102,6 +103,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> public startPushOutcome = action((success: boolean) => { if (!this.pushAnimating) { this.pushAnimating = true; + this.isAnimatingPulse = false; this.pushIcon = success ? "check-circle" : "stop-circle"; setTimeout(() => runInAction(() => { this.pushIcon = "arrow-alt-circle-up"; @@ -698,9 +700,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return (
{ + if (!published) { + runInAction(() => this.isAnimatingPulse = true); + } DocumentDecorations.hasPushedHack = false; this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; - }}> + }} style={{ animation: this.isAnimatingPulse ? "shadow-pulse 1s infinite" : "none" }}>
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 0e687737d..271326f70 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -8,6 +8,26 @@ import { Doc, DocListCastAsync } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { DocServer } from "../DocServer"; +String.prototype.removeTrailingNewlines = function () { + let sliced = this; + while (sliced.endsWith("\n")) { + sliced = sliced.substring(0, this.length - 1); + } + return sliced as string; +}; + +String.prototype.hasNewline = function () { + return this.endsWith("\n"); +}; + +(Array.prototype as any).lastElement = function (this: any[]) { + if (!this.length) { + return undefined; + } + return this[this.length - 1]; +}; + + let swapDocs = async () => { let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc); // Docs.Prototypes.MainLinkDocument().allLinks = new List(); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index eefac2285..83011e590 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -60,16 +60,14 @@ export const GoogleRef = "googleDocId"; type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); -type PullHandler = (exportState: Opt, dataDoc: Doc) => void; +type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } - public static blankState = () => { - return EditorState.create(FormattedTextBox.Instance._configuration); - } + public static blankState = () => EditorState.create(FormattedTextBox.Instance._configuration); public static Instance: FormattedTextBox; private _configuration: any; private _ref: React.RefObject; @@ -434,7 +432,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } pushToGoogleDoc = async () => { - this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { + this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { let modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); @@ -457,7 +455,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return; } let content: GoogleApiClientUtils.Docs.Content = { - text: exportState.body, + text: exportState.text, requests: [] }; if (reference && content) { @@ -472,42 +470,38 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe pullFromGoogleDoc = async (handler: PullHandler) => { let dataDoc = this.dataDoc; let documentId = StrCast(dataDoc[GoogleRef]); - let test = await RichTextUtils.GoogleDocs.Import(documentId); - let exportState: Opt; + let exportState: Opt; if (documentId) { - exportState = await GoogleApiClientUtils.Docs.read({ documentId }); + exportState = await RichTextUtils.GoogleDocs.Import(documentId); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } - updateState = (exportState: Opt, dataDoc: Doc) => { + updateState = (exportState: Opt, dataDoc: Doc) => { let pullSuccess = false; - if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { - const data = Cast(dataDoc.data, RichTextField); - if (data instanceof RichTextField) { - pullSuccess = true; - dataDoc.data = RichTextUtils.Synthesize(exportState.body, data); - setTimeout(() => { - if (this._editorView) { - let state = this._editorView.state; - let end = state.doc.content.size - 1; - this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end))); - } - }, 0); - dataDoc.title = exportState.title; - this.Document.customTitle = true; - dataDoc.unchanged = true; - } + if (exportState !== undefined) { + pullSuccess = true; + dataDoc.data = exportState.data; + setTimeout(() => { + if (this._editorView) { + let state = this._editorView.state; + let end = state.doc.content.size - 1; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end))); + } + }, 0); + dataDoc.title = exportState.title; + this.Document.customTitle = true; + dataDoc.unchanged = true; } else { delete dataDoc[GoogleRef]; } DocumentDecorations.Instance.startPullOutcome(pullSuccess); } - checkState = (exportState: Opt, dataDoc: Doc) => { + checkState = (exportState: Opt, dataDoc: Doc) => { if (exportState && this._editorView) { let storedPlainText = RichTextUtils.ToPlainText(this._editorView.state) + "\n"; - let receivedPlainText = exportState.body; + let receivedPlainText = exportState.text; let storedTitle = dataDoc.title; let receivedTitle = exportState.title; let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 189819591..4ca51d311 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -1,5 +1,5 @@ import { EditorState } from "prosemirror-state"; -import { Node } from "prosemirror-model"; +import { Node, Fragment, Mark } from "prosemirror-model"; import { RichTextField } from "./RichTextField"; import { docs_v1 } from "googleapis"; import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; @@ -95,27 +95,75 @@ export namespace RichTextUtils { }; }; - export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId) => { - let document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); + export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId): Promise> => { + let Docs = GoogleApiClientUtils.Docs; + let document = await Docs.retrieve({ documentId }); + if (!document) { - return; + return undefined; } - // let title = document.title!; - let runs = GoogleApiClientUtils.Docs.Utils.extractTextRuns(document); + + let title = document.title!; + + let { text, runs } = Docs.Utils.extractText(document); + let segments = runs[Symbol.iterator](); + let state = FormattedTextBox.blankState(); + let breaks: number[] = []; let from = 0; - runs.map(run => { - let text = run.content!; - state = state.apply(state.tr.insertText(text, from)); - let to = from + text.length + 1; - let href: Opt; - if (run.textStyle && run.textStyle.link && (href = run.textStyle.link.url)) { - let mark = state.schema.mark(state.schema.marks.link, { href }); - state = state.apply(state.tr.addMark(from, to, mark)); + let result = segments.next(); + while (!result.done) { + let run = result.value; + let fragment = run.content!; + if (fragment.hasNewline()) { + let trimmed = fragment.removeTrailingNewlines(); + if (fragment.length === 1) { + breaks.push(from); + } else { + let content = Fragment.from(state.schema.text(trimmed, styleToMarks(state.schema, run.textStyle))); + let node = state.schema.node("paragraph", null, content); + state = state.apply(state.tr.insert(from, node)); + from += node.nodeSize; + } + result = segments.next(); + } else { + let nodes: Node[] = []; + nodes.push(state.schema.text(fragment, styleToMarks(state.schema, run.textStyle))); + result = segments.next(); + while (!result.done) { + run = result.value; + fragment = run.content!; + let trimmed = fragment.removeTrailingNewlines(); + nodes.push(state.schema.text(trimmed, styleToMarks(state.schema, run.textStyle))); + if (fragment.hasNewline()) { + let node = state.schema.node("paragraph", null, Fragment.fromArray(nodes)); + state = state.apply(state.tr.insert(from, node)); + from += node.nodeSize; + result = segments.next(); + break; + } + result = segments.next(); + } + if (result.done) { + break; + } } - from = to; - }); - // return { title, body }; + } + breaks.forEach(position => state = state.apply(state.tr.insert(position, state.schema.node("paragraph")))); + let data = new RichTextField(JSON.stringify(state.toJSON())); + return { title, text, data }; + }; + + const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { + if (!textStyle) { + return undefined; + } + let marks: Mark[] = []; + if (textStyle.link) { + let href = textStyle.link.url; + marks.push(schema.mark(schema.marks.link, { href })); + } + return marks; }; interface LinkInformation { -- cgit v1.2.3-70-g09d2 From c9a7d747916c31730f71479d8e516ba0ed2c658f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 30 Aug 2019 15:51:13 -0400 Subject: complete transfer except for list items --- package.json | 2 + .../apis/google_docs/GoogleApiClientUtils.ts | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 11 ++- src/new_fields/RichTextUtils.ts | 89 +++++++++++++--------- 4 files changed, 59 insertions(+), 45 deletions(-) (limited to 'src/new_fields') diff --git a/package.json b/package.json index cd60b7b55..f98224469 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@types/bluebird": "^3.5.25", "@types/body-parser": "^1.17.0", "@types/classnames": "^2.2.8", + "@types/color": "^3.0.0", "@types/connect-flash": "0.0.34", "@types/cookie-parser": "^1.4.1", "@types/cookie-session": "^2.0.36", @@ -121,6 +122,7 @@ "canvas": "^2.5.0", "child_process": "^1.0.2", "class-transformer": "^0.2.0", + "color": "^3.1.2", "connect-flash": "^0.1.1", "connect-mongo": "^2.0.3", "cookie-parser": "^1.4.4", diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 9bf3cae38..fdd708e31 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -45,7 +45,7 @@ export namespace GoogleApiClientUtils { export interface ImportResult { title: string; text: string; - data: RichTextField; + state: EditorState; } export interface CreateOptions { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 83011e590..6af5c8f0b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -38,6 +38,7 @@ import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; +import * as _ from "lodash"; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -481,7 +482,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let pullSuccess = false; if (exportState !== undefined) { pullSuccess = true; - dataDoc.data = exportState.data; + dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON())); setTimeout(() => { if (this._editorView) { let state = this._editorView.state; @@ -500,11 +501,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe checkState = (exportState: Opt, dataDoc: Doc) => { if (exportState && this._editorView) { - let storedPlainText = RichTextUtils.ToPlainText(this._editorView.state) + "\n"; - let receivedPlainText = exportState.text; - let storedTitle = dataDoc.title; - let receivedTitle = exportState.title; - let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle; + let equalContent = _.isEqual(this._editorView.state.doc, exportState.state.doc); + let equalTitles = dataDoc.title === exportState.title; + let unchanged = equalContent && equalTitles; dataDoc.unchanged = unchanged; DocumentDecorations.Instance.setPullState(unchanged); } diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 4ca51d311..bc338e45b 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -5,6 +5,7 @@ import { docs_v1 } from "googleapis"; import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; import { Opt } from "./Doc"; +import * as Color from "color"; export namespace RichTextUtils { @@ -96,73 +97,85 @@ export namespace RichTextUtils { }; export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId): Promise> => { - let Docs = GoogleApiClientUtils.Docs; - let document = await Docs.retrieve({ documentId }); - + const Docs = GoogleApiClientUtils.Docs; + const document = await Docs.retrieve({ documentId }); if (!document) { return undefined; } - let title = document.title!; - - let { text, runs } = Docs.Utils.extractText(document); - let segments = runs[Symbol.iterator](); + const title = document.title!; + const { text, runs } = Docs.Utils.extractText(document); + const segments = runs[Symbol.iterator](); let state = FormattedTextBox.blankState(); - let breaks: number[] = []; - let from = 0; + const schema = state.schema; + const nodes: Node[] = []; + let result = segments.next(); while (!result.done) { let run = result.value; - let fragment = run.content!; - if (fragment.hasNewline()) { - let trimmed = fragment.removeTrailingNewlines(); - if (fragment.length === 1) { - breaks.push(from); - } else { - let content = Fragment.from(state.schema.text(trimmed, styleToMarks(state.schema, run.textStyle))); - let node = state.schema.node("paragraph", null, content); - state = state.apply(state.tr.insert(from, node)); - from += node.nodeSize; - } + if (run.content!.hasNewline()) { + addParagraph(nodes, schema, textNode(schema, run)); result = segments.next(); } else { - let nodes: Node[] = []; - nodes.push(state.schema.text(fragment, styleToMarks(state.schema, run.textStyle))); + const inner: Node[] = []; + inner.push(textNode(schema, run)); result = segments.next(); while (!result.done) { run = result.value; - fragment = run.content!; - let trimmed = fragment.removeTrailingNewlines(); - nodes.push(state.schema.text(trimmed, styleToMarks(state.schema, run.textStyle))); - if (fragment.hasNewline()) { - let node = state.schema.node("paragraph", null, Fragment.fromArray(nodes)); - state = state.apply(state.tr.insert(from, node)); - from += node.nodeSize; - result = segments.next(); + inner.push(textNode(schema, run)); + result = segments.next(); + if (run.content!.hasNewline()) { + addParagraph(nodes, schema, inner); break; } - result = segments.next(); } if (result.done) { break; } } } - breaks.forEach(position => state = state.apply(state.tr.insert(position, state.schema.node("paragraph")))); - let data = new RichTextField(JSON.stringify(state.toJSON())); - return { title, text, data }; + state = state.apply(state.tr.replaceWith(0, 2, nodes)); + return { title, text, state }; + }; + + const addParagraph = (list: Node[], schema: any, content?: Node[] | Node) => { + list.push(schema.node("paragraph", null, content ? Fragment.from(content) : null)); }; + const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { + let text = run.content!.removeTrailingNewlines(); + return text.length ? schema.text(text, styleToMarks(schema, run.textStyle)) : undefined; + }; + + const MarkMapping = new Map([ + ["bold", "strong"], + ["italic", "em"], + ["foregroundColor", "pFontColor"] + ]); + const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { if (!textStyle) { return undefined; } let marks: Mark[] = []; - if (textStyle.link) { - let href = textStyle.link.url; - marks.push(schema.mark(schema.marks.link, { href })); - } + Object.keys(textStyle).forEach(key => { + let value: any; + let targeted = key as keyof docs_v1.Schema$TextStyle; + if (value = textStyle[targeted]) { + let attributes: any = {}; + let converted = MarkMapping.get(targeted) || targeted; + + value.url && (attributes.href = value.url); + if (value.color) { + let object: { [key: string]: number } = value.color.rgbColor; + attributes.color = Color.rgb(Object.values(object).map(value => value * 255)).hex(); + } + + let mark = schema.mark(schema.marks[converted], attributes); + mark && marks.push(mark); + } + }); return marks; }; -- cgit v1.2.3-70-g09d2 From e139441a1f3bbec9a51ef8594a9c785733d28415 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 30 Aug 2019 17:28:34 -0400 Subject: restructured paragraphs --- .../apis/google_docs/GoogleApiClientUtils.ts | 32 ++++++++++------ src/new_fields/RichTextUtils.ts | 44 +++++----------------- 2 files changed, 31 insertions(+), 45 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index fdd708e31..3026f6e17 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -94,29 +94,39 @@ export namespace GoogleApiClientUtils { export namespace Utils { - export type ExtractResult = { text: string, runs: docs_v1.Schema$TextRun[] }; + export type ExtractResult = { text: string, paragraphs: DeconstructedParagraph[] }; export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => { - let runs = extractTextRuns(document); - let text = runs.map(run => run.content).join(""); + let paragraphs = extractParagraphs(document); + let text = paragraphs.map(paragraph => paragraph.runs.map(run => run.content).join("")).join(""); text = text.substring(0, text.length - 1); removeNewlines && text.ReplaceAll("\n", ""); - return { text, runs }; + return { text, paragraphs }; }; - const extractTextRuns = (document: docs_v1.Schema$Document, filterEmpty = true) => { - const fragments: docs_v1.Schema$TextRun[] = []; + export type DeconstructedParagraph = { runs: docs_v1.Schema$TextRun[], bullet: Opt }; + const extractParagraphs = (document: docs_v1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => { + const fragments: DeconstructedParagraph[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { - if (element.paragraph && element.paragraph.elements) { - for (const inner of element.paragraph.elements) { - if (inner && inner.textRun) { - fragments.push(inner.textRun); + let runs: docs_v1.Schema$TextRun[] = []; + let bullet: Opt; + if (element.paragraph) { + if (element.paragraph.elements) { + for (const inner of element.paragraph.elements) { + if (inner && inner.textRun) { + let run = inner.textRun; + (run.content || !filterEmpty) && runs.push(inner.textRun); + } } } + if (element.paragraph.bullet) { + bullet = element.paragraph.bullet.nestingLevel || 0; + } } + runs.length && fragments.push({ runs, bullet }); } } - return filterEmpty ? fragments.filter(run => run.content) : fragments; + return fragments; }; export const endOf = (schema: docs_v1.Schema$Document): number | undefined => { diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index bc338e45b..4d40040ac 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -97,50 +97,26 @@ export namespace RichTextUtils { }; export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId): Promise> => { - const Docs = GoogleApiClientUtils.Docs; - const document = await Docs.retrieve({ documentId }); + const document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); if (!document) { return undefined; } const title = document.title!; - const { text, runs } = Docs.Utils.extractText(document); - const segments = runs[Symbol.iterator](); - + const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document); let state = FormattedTextBox.blankState(); - const schema = state.schema; - const nodes: Node[] = []; - - let result = segments.next(); - while (!result.done) { - let run = result.value; - if (run.content!.hasNewline()) { - addParagraph(nodes, schema, textNode(schema, run)); - result = segments.next(); - } else { - const inner: Node[] = []; - inner.push(textNode(schema, run)); - result = segments.next(); - while (!result.done) { - run = result.value; - inner.push(textNode(schema, run)); - result = segments.next(); - if (run.content!.hasNewline()) { - addParagraph(nodes, schema, inner); - break; - } - } - if (result.done) { - break; - } - } - } + + const nodes = paragraphs.map(paragraph => paragraphNode(state.schema, paragraph)); state = state.apply(state.tr.replaceWith(0, 2, nodes)); + return { title, text, state }; }; - const addParagraph = (list: Node[], schema: any, content?: Node[] | Node) => { - list.push(schema.node("paragraph", null, content ? Fragment.from(content) : null)); + const paragraphNode = (schema: any, content: GoogleApiClientUtils.Docs.Utils.DeconstructedParagraph) => { + let children = content.runs.map(run => textNode(schema, run)); + let complete = children.every(child => child !== undefined); + let fragment = complete ? Fragment.from(children) : undefined; + return schema.node("paragraph", null, fragment); }; const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { -- cgit v1.2.3-70-g09d2 From 420b17379afe3e3ba2c17628fd00ff524ec1a743 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 31 Aug 2019 15:12:22 -0400 Subject: mostly functional bullet structure import --- .../apis/google_docs/GoogleApiClientUtils.ts | 2 +- src/new_fields/RichTextUtils.ts | 84 ++++++++++++++++++++-- 2 files changed, 79 insertions(+), 7 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 3026f6e17..828d4451a 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -123,7 +123,7 @@ export namespace GoogleApiClientUtils { bullet = element.paragraph.bullet.nestingLevel || 0; } } - runs.length && fragments.push({ runs, bullet }); + (runs.length || !filterEmpty) && fragments.push({ runs, bullet }); } } return fragments; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 4d40040ac..5a16227ef 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -1,4 +1,4 @@ -import { EditorState } from "prosemirror-state"; +import { EditorState, Transaction, TextSelection } from "prosemirror-state"; import { Node, Fragment, Mark } from "prosemirror-model"; import { RichTextField } from "./RichTextField"; import { docs_v1 } from "googleapis"; @@ -6,6 +6,8 @@ import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClient import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; import { Opt } from "./Doc"; import * as Color from "color"; +import { sinkListItem } from "prosemirror-schema-list"; +import { number } from "prop-types"; export namespace RichTextUtils { @@ -96,6 +98,8 @@ export namespace RichTextUtils { }; }; + type BulletPosition = { value: number, sinks: number }; + export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId): Promise> => { const document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); if (!document) { @@ -105,17 +109,85 @@ export namespace RichTextUtils { const title = document.title!; const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document); let state = FormattedTextBox.blankState(); + let structured = parseLists(paragraphs); - const nodes = paragraphs.map(paragraph => paragraphNode(state.schema, paragraph)); + let position = 3; + let lists: ListGroup[] = []; + const indentMap = new Map(); + let globalOffset = 0; + const nodes = structured.map(element => { + if (Array.isArray(element)) { + lists.push(element); + let positions: BulletPosition[] = []; + let items = element.map(paragraph => { + let item = listItem(state.schema, paragraph.runs); + let sinks = paragraph.bullet!; + positions.push({ + value: position + globalOffset, + sinks + }); + position += item.nodeSize; + globalOffset += 2 * sinks; + return item; + }); + indentMap.set(element, positions); + return list(state.schema, items); + } else { + let paragraph = paragraphNode(state.schema, element.runs); + position += paragraph.nodeSize; + return paragraph; + } + }); state = state.apply(state.tr.replaceWith(0, 2, nodes)); + let sink = sinkListItem(state.schema.nodes.list_item); + let dispatcher = (tr: Transaction) => state = state.apply(tr); + for (let list of lists) { + for (let pos of indentMap.get(list)!) { + let resolved = state.doc.resolve(pos.value); + state = state.apply(state.tr.setSelection(new TextSelection(resolved))); + for (let i = 0; i < pos.sinks; i++) { + sink(state, dispatcher); + } + } + } + return { title, text, state }; }; - const paragraphNode = (schema: any, content: GoogleApiClientUtils.Docs.Utils.DeconstructedParagraph) => { - let children = content.runs.map(run => textNode(schema, run)); - let complete = children.every(child => child !== undefined); - let fragment = complete ? Fragment.from(children) : undefined; + type Paragraph = GoogleApiClientUtils.Docs.Utils.DeconstructedParagraph; + type ListGroup = Paragraph[]; + type PreparedParagraphs = (ListGroup | Paragraph)[]; + + const parseLists = (paragraphs: ListGroup) => { + let groups: PreparedParagraphs = []; + let group: ListGroup = []; + for (let paragraph of paragraphs) { + if (paragraph.bullet !== undefined) { + group.push(paragraph); + } else { + if (group.length) { + groups.push(group); + group = []; + } + groups.push(paragraph); + } + } + group.length && groups.push(group); + return groups; + }; + + const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { + return schema.node("list_item", null, paragraphNode(schema, runs)); + }; + + const list = (schema: any, items: Node[]): Node => { + return schema.node("bullet_list", null, items); + }; + + const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { + let children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined); + let fragment = children.length ? Fragment.from(children) : undefined; return schema.node("paragraph", null, fragment); }; -- cgit v1.2.3-70-g09d2 From 179b2c7c68e3d240f3f39083bdb686ad31761c04 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 5 Sep 2019 12:00:18 -0400 Subject: fixed some template issues. added attribution for pasted text --- src/client/util/RichTextSchema.tsx | 11 ++++-- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 4 ++ src/client/views/nodes/FormattedTextBox.scss | 3 ++ src/client/views/nodes/FormattedTextBox.tsx | 44 ++++------------------ src/client/views/nodes/PDFBox.tsx | 15 +++++--- src/new_fields/Doc.ts | 4 +- 7 files changed, 35 insertions(+), 48 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 2b689b7ef..9c38ee458 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -205,7 +205,7 @@ export const nodes: { [index: string]: NodeSpec } = { const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : // ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"]; } @@ -262,7 +262,8 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { href: {}, location: { default: null }, - title: { default: null } + title: { default: null }, + docref: { default: false } }, inclusive: false, parseDOM: [{ @@ -270,7 +271,11 @@ export const marks: { [index: string]: MarkSpec } = { return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") }; } }], - toDOM(node: any) { return ["a", node.attrs, 0]; } + toDOM(node: any) { + return node.attrs.docref && node.attrs.title ? + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : + ["a", { ...node.attrs }, 0]; + } }, // :: MarkSpec An emphasis mark. Rendered as an `` element. diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 95f94875c..fb8b0c41b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -607,7 +607,7 @@ export class DockedFrameRenderer extends React.Component { } 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()) / 2 : 0; } addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { if (doc.dockingConfig) { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 97b31bf2a..654ff2279 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -310,6 +310,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) { let cols = Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + if (isNaN(cols)) { + console.log("naN"); + cols = 1; + } return
{!heading ? (null) :
{ - if (e.clipboardData && this._editorView) { - // let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`; - // for (let i = 0; i < e.clipboardData.items.length; i++) { - // let item = e.clipboardData.items.item(i); - // console.log(item) - // if (item.type === "text/plain") { - // console.log("plain") - // item.getAsString((text) => { - // let pdfPasteIndex = text.indexOf(pdfPasteText); - // if (pdfPasteIndex > -1) { - // let insertText = text.substr(0, pdfPasteIndex); - // const tx = this._editorView!.state.tr.insertText(insertText); - // // tx.setSelection(new Selection(tx.)) - // const state = this._editorView!.state; - // this._editorView!.dispatch(tx); - // if (FormattedTextBox._toolTipTextMenu) { - // // this._toolTipTextMenu.makeLinkWithState(state) - // } - // e.stopPropagation(); - // e.preventDefault(); - // } - // }); - // } - // } - } - } - // this should be internal to prosemirror, but is needed // here to make sure that footnote view nodes in the overlay editor // get removed when they're not selected. + syncNodeSelection(view: any, sel: any) { if (sel instanceof NodeSelection) { var desc = view.docView.descAt(sel.from); @@ -363,8 +336,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentDidMount() { - document.addEventListener("paste", this.paste); - if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { @@ -584,7 +555,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (link) { cbe.clipboardData!.setData("dash/linkDoc", link[Id]); linkId = link[Id]; - let frag = addMarkToFrag(slice.content); + let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title))); slice = new Slice(frag, slice.openStart, slice.openEnd); var tr = view.state.tr.replaceSelection(slice); view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); @@ -594,19 +565,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return true; - function addMarkToFrag(frag: Fragment) { + function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { const nodes: Node[] = []; - frag.forEach(node => nodes.push(addLinkMark(node))); + frag.forEach(node => nodes.push(marker(node))); return Fragment.fromArray(nodes); } - function addLinkMark(node: Node) { + function addLinkMark(node: Node, title: string) { if (!node.isText) { - const content = addMarkToFrag(node.content); + const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title)); return node.copy(content); } const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type.name === "link"); - const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight" }); + const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true }); if (linkIndex !== -1) { marks.splice(linkIndex, 1, link); } else { @@ -668,7 +639,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._pullReactionDisposer && this._pullReactionDisposer(); this._heightReactionDisposer && this._heightReactionDisposer(); this._searchReactionDisposer && this._searchReactionDisposer(); - document.removeEventListener("paste", this.paste); this._editorView && this._editorView.destroy(); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 18f82ff47..df35b603c 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -33,7 +33,9 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _pdf: Opt; @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; } - @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.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 = React.createRef(); @@ -48,7 +50,7 @@ export class PDFBox extends DocComponent(PdfDocumen componentDidMount() { this.props.setPdfBox && this.props.setPdfBox(this); - const pdfUrl = Cast(this.props.Document.data, PdfField); + 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)); } @@ -78,7 +80,7 @@ export class PDFBox extends DocComponent(PdfDocumen @action public GotoPage(p: number) { - if (p > 0 && p <= NumCast(this.props.Document.numPages)) { + if (p > 0 && p <= NumCast(this.dataDoc.numPages)) { this.props.Document.curPage = p; this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight); } @@ -87,7 +89,7 @@ export class PDFBox extends DocComponent(PdfDocumen @action public ForwardPage() { let cp = this.GetPage() + 1; - if (cp <= NumCast(this.props.Document.numPages)) { + if (cp <= NumCast(this.dataDoc.numPages)) { this.props.Document.curPage = cp; this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); } @@ -185,11 +187,12 @@ export class PDFBox extends DocComponent(PdfDocumen } } + render() { - const pdfUrl = Cast(this.props.Document.data, PdfField); + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (!(pdfUrl instanceof PdfField) || !this._pdf ? -
{`pdf, ${this.props.Document.data}, not found`}
: +
{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}
:
Date: Fri, 6 Sep 2019 01:41:06 -0400 Subject: fixed summarizing in text notes. --- src/Utils.ts | 14 +++ src/client/util/RichTextSchema.tsx | 157 ++++++++++++--------------- src/client/util/TooltipTextMenu.tsx | 18 ++- src/client/views/nodes/FormattedTextBox.scss | 20 ++++ src/client/views/nodes/FormattedTextBox.tsx | 41 ++----- src/new_fields/Doc.ts | 2 +- 6 files changed, 120 insertions(+), 132 deletions(-) (limited to 'src/new_fields') diff --git a/src/Utils.ts b/src/Utils.ts index 959b89fe5..f805ae872 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -133,6 +133,20 @@ export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => vo return dup; } +export function timenow() { + var now = new Date(); + let ampm = 'am'; + let h = now.getHours(); + let m: any = now.getMinutes(); + let s: any = now.getSeconds(); + if (h >= 12) { + if (h > 12) h -= 12; + ampm = 'pm'; + } + if (m < 10) m = '0' + m; + return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm; +} + export function numberRange(num: number) { return Array.from(Array(num)).map((v, i) => i); } export function returnTrue() { return true; } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 2b689b7ef..a8ba0a4be 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,4 +1,4 @@ -import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice, Fragment } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; import { TextSelection, EditorState } from "prosemirror-state"; import { Doc } from "../../new_fields/Doc"; @@ -7,6 +7,8 @@ import { EditorView } from "prosemirror-view"; import { keymap } from "prosemirror-keymap"; import { undo, redo } from "prosemirror-history"; import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands"; +import { Domain } from "domain"; +import { DOM } from "@fortawesome/fontawesome-svg-core"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -42,14 +44,6 @@ export const nodes: { [index: string]: NodeSpec } = { toDOM() { return pDOM; } }, - // starmine: { - // inline: true, - // attrs: { oldtext: { default: "" } }, - // group: "inline", - // toDOM() { return ["star", "㊉"]; }, - // parseDOM: [{ tag: "star" }] - // }, - // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. blockquote: { content: "block+", @@ -107,10 +101,9 @@ export const nodes: { [index: string]: NodeSpec } = { visibility: { default: false }, text: { default: undefined }, textslice: { default: undefined }, - textlen: { default: 0 } - }, group: "inline", + inclusive: false, toDOM(node) { const attrs = { style: `width: 40px` }; return ["span", { ...node.attrs, ...attrs }]; @@ -205,12 +198,10 @@ export const nodes: { [index: string]: NodeSpec } = { const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; - //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : - // ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"]; + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; } }, - //this doesn't currently work for some reason + bullet_list: { ...bulletList, content: 'list_item+', @@ -221,14 +212,6 @@ export const nodes: { [index: string]: NodeSpec } = { } }, - //bullet_list: { - // content: 'list_item+', - // group: 'block', - //active: blockActive(schema.nodes.bullet_list), - //enable: wrapInList(schema.nodes.bullet_list), - //run: wrapInList(schema.nodes.bullet_list), - //select: state => true, - // }, list_item: { attrs: { bulletStyle: { default: 0 }, @@ -251,7 +234,6 @@ export const nodes: { [index: string]: NodeSpec } = { const emDOM: DOMOutputSpecArray = ["em", 0]; const strongDOM: DOMOutputSpecArray = ["strong", 0]; const codeDOM: DOMOutputSpecArray = ["code", 0]; -const underlineDOM: DOMOutputSpecArray = ["underline", 0]; // :: Object [Specs](#model.MarkSpec) for the marks in the schema. export const marks: { [index: string]: MarkSpec } = { @@ -289,16 +271,6 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return strongDOM; } }, - underline: { - parseDOM: [ - { tag: 'u' }, - { style: 'text-decoration=underline' } - ], - toDOM: () => ['span', { - style: 'text-decoration:underline' - }] - }, - strikethrough: { parseDOM: [ { tag: 'strike' }, @@ -340,14 +312,52 @@ export const marks: { [index: string]: MarkSpec } = { }, highlight: { - parseDOM: [{ style: 'text-decoration: underline' }], + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + 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) + return null; + } + return false; + } + }, + ], + inclusive: false, + priority: 100, toDOM() { return ['span', { - style: 'text-decoration: underline; text-decoration-color: rgba(204, 206, 210, 0.92)' + style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' }]; } }, + underline: { + parseDOM: [ + { + tag: "span", + 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) + return null; + } + return false; + } + } + // { style: "text-decoration=underline" } + ], + toDOM: () => ['span', { + style: 'text-decoration:underline;text-decoration-style:line' + }] + }, + search_highlight: { parseDOM: [{ style: 'background: yellow' }], toDOM() { @@ -530,9 +540,6 @@ export const marks: { [index: string]: MarkSpec } = { }] }, }; -function getFontSize(element: any) { - return parseFloat((getComputedStyle(element) as any).fontSize); -} export class ImageResizeView { _handle: HTMLElement; @@ -733,73 +740,52 @@ export class FootnoteView { } export class SummarizedView { - // TODO: highlight text that is summarized. to find end of region, walk along mark _collapsed: HTMLElement; _view: any; constructor(node: any, view: any, getPos: any) { this._collapsed = document.createElement("span"); - this._collapsed.textContent = node.attrs.visibility ? "㊀" : "㊉"; - this._collapsed.style.opacity = "0.5"; - this._collapsed.style.position = "relative"; - this._collapsed.style.width = "40px"; - this._collapsed.style.height = "20px"; - let self = this; + this._collapsed.className = this.className(node.attrs.visibility); this._view = view; const js = node.toJSON; node.toJSON = function () { - return js.apply(this, arguments); }; - this._collapsed.onpointerdown = function (e: any) { - if (node.attrs.visibility) { - // node.attrs.visibility = !node.attrs.visibility; - let y = getPos(); - const attrs = { ...node.attrs }; - attrs.visibility = !attrs.visibility; - let { from, to } = self.updateSummarizedText(y + 1, view.state.schema.marks.highlight); - let length = to - from; - let newSelection = TextSelection.create(view.state.doc, y + 1, y + 1 + length); - // update attrs of node - attrs.text = newSelection.content(); - attrs.textslice = newSelection.content().toJSON(); - view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs)); - view.dispatch(view.state.tr.setSelection(newSelection).deleteSelection(view.state, () => { })); - let marks = view.state.storedMarks.filter((m: any) => m.type !== view.state.schema.marks.highlight); - view.state.storedMarks = marks; - self._collapsed.textContent = "㊉"; - } else { - // node.attrs.visibility = !node.attrs.visibility; - let y = getPos(); - const attrs = { ...node.attrs }; - attrs.visibility = !attrs.visibility; - view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs)); - let mark = view.state.schema.mark(view.state.schema.marks.highlight); - view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1))); - const from = view.state.selection.from; - let size = node.attrs.text.size; - view.dispatch(view.state.tr.replaceSelection(node.attrs.text).addMark(from, from + size, mark).removeStoredMark(mark)); - self._collapsed.textContent = "㊀"; + + this._collapsed.onpointerdown = (e: any) => { + const visible = !node.attrs.visibility; + const attrs = { ...node.attrs, visibility: visible }; + let textSelection = TextSelection.create(view.state.doc, getPos() + 1, getPos() + 1); + if (!visible) { // update summarized text and save in attrs + textSelection = this.updateSummarizedText(getPos() + 1); + attrs.text = textSelection.content(); + attrs.textslice = attrs.text.toJSON(); } + view.dispatch(view.state.tr. + setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) + replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it + setNodeMarkup(getPos(), undefined, attrs)); // update the attrs e.preventDefault(); e.stopPropagation(); + this._collapsed.className = this.className(visible); }; (this as any).dom = this._collapsed; - - } - selectNode() { } + selectNode() { } + + deselectNode() { } - updateSummarizedText(start?: any, mark?: any) { - let $start = this._view.state.doc.resolve(start); + className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); + + updateSummarizedText(start?: any) { + let mark = this._view.state.schema.marks.highlight.create(); let endPos = start; - let _mark = this._view.state.schema.mark(this._view.state.schema.marks.highlight); let visited = new Set(); for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) { let skip = false; this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { if (node.isLeaf && !visited.has(node) && !skip) { - if (node.marks.find((m: any) => m.type === _mark.type)) { + if (node.marks.find((m: any) => m.type === mark.type)) { visited.add(node); endPos = i + node.nodeSize - 1; } @@ -807,10 +793,7 @@ export class SummarizedView { } }); } - return { from: start, to: endPos }; - } - - deselectNode() { + return TextSelection.create(this._view.state.doc, start, endPos); } } // :: Schema diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 6d375fc1d..020c51c36 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -307,10 +307,9 @@ export class TooltipTextMenu { { handlers: { dragComplete: action(() => { - // let m = dragData.droppedDocuments; let linkDoc = dragData.linkDocument; let proto = Doc.GetProto(linkDoc); - if (docView && docView.props.ContainingCollectionView) { + if (proto && docView && docView.props.ContainingCollectionView) { proto.sourceContext = docView.props.ContainingCollectionView.props.Document; } linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab"); @@ -322,8 +321,6 @@ export class TooltipTextMenu { e.preventDefault(); }; this.linkEditor.appendChild(this.linkDrag); - // this.linkEditor.appendChild(this.linkText); - // this.linkEditor.appendChild(linkBtn); this.tooltip.appendChild(this.linkEditor); } @@ -432,11 +429,13 @@ export class TooltipTextMenu { } public static insertStar(state: EditorState, dispatch: any) { - let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from }); - if (dispatch) { - //console.log(newNode.attrs.text.toString()); - dispatch(state.tr.replaceSelectionWith(newNode)); - } + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); return true; } @@ -637,7 +636,6 @@ export class TooltipTextMenu { Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; this.changeToMarkInGroup(markType, this.view, []); - }); } } diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 8f47402c4..0de050e79 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -131,6 +131,26 @@ footnote::after { width: 0; } +.formattedTextBox-summarizer { + opacity :0.5; + position: relative; + width:40px; + height:20px; +} +.formattedTextBox-summarizer::after{ + content: "←" ; +} + +.formattedTextBox-summarizer-collapsed { + opacity :0.5; + position: relative; + width:40px; + height:20px; +} +.formattedTextBox-summarizer-collapsed::after { + content: "..."; +} + ol { counter-reset: deci1 0;} .decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 } .decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d811273e8..006a33011 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -15,7 +15,7 @@ import { List } from '../../../new_fields/List'; import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Utils, numberRange } from '../../../Utils'; +import { Utils, numberRange, timenow } from '../../../Utils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; @@ -117,6 +117,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch public setFontColor(color: string) { + this._editorView!.state.storedMarks if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false; if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) { this.props.Document.color = color; @@ -654,10 +655,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.props.select(false); } else if (this.props.isOverlay) this._editorView!.focus(); - var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks()); - let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })]; - this._editorView!.state.storedMarks = newMarks; - + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark). + addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }))); } componentWillUnmount() { @@ -681,10 +680,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); - if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) { - //this._toolTipTextMenu.tooltip.style.opacity = "0"; - } } + this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark). + addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); let ctrlKey = e.ctrlKey; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; @@ -744,13 +742,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { - let view = this._editorView!; - let mid = view.state.doc.resolve(Math.round((start + end) / 2)); - let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); - view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark).setSelection(new TextSelection(mid, mid))); - } - @action onFocused = (e: React.FocusEvent): void => { document.removeEventListener("keypress", this.recordKeyHandler); @@ -774,7 +765,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onClick = (e: React.MouseEvent): void => { if (this.props.isSelected() && e.nativeEvent.offsetX < 40) { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - if (pos) { + if (pos && pos.pos > 0) { let node = this._editorView!.state.doc.nodeAt(pos.pos); let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { @@ -833,24 +824,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); // }, 0); } - function timenow() { - var now = new Date(); - let ampm = 'am'; - let h = now.getHours(); - let m: any = now.getMinutes(); - let s: any = now.getSeconds(); - if (h >= 12) { - if (h > 12) h -= 12; - ampm = 'pm'; - } - - if (m < 10) m = '0' + m; - return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm; - } - var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks()); - let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - this._editorView!.state.storedMarks = newMarks; - // stop propagation doesn't seem to stop propagation of native keyboard events. // so we set a flag on the native event that marks that the event's been handled. (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index a703f1cef..1462cdfad 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -308,7 +308,7 @@ export namespace Doc { // gets the document's prototype or returns the document if it is a prototype export function GetProto(doc: Doc) { - return Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc); + return doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc)); } export function GetDataDoc(doc: Doc): Doc { let proto = Doc.GetProto(doc); -- cgit v1.2.3-70-g09d2 From 4d4bebb5d2e7555fac4224a497b815c40365fe91 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Sep 2019 21:10:31 -0400 Subject: fixed clustering to be persistent --- src/client/views/ContextMenu.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 41 +++++++++++++++++++--- src/new_fields/Doc.ts | 9 +++-- 3 files changed, 44 insertions(+), 8 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index e27318429..890bfdfb7 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -248,7 +248,7 @@ export class ContextMenu extends React.Component { e.preventDefault(); } else if (e.key === "Enter" || e.key === "Tab") { const item = this.flatItems[this.selectedIndex]; - item.event(); + item && item.event(); this.closeMenu(); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index fac4d4970..4f6055260 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -256,7 +256,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); this.bringToFront(newBox); - this.updateClusters(); + this.updateCluster(newBox); return true; } private selectDocuments = (docs: Doc[]) => { @@ -324,7 +324,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.bringToFront(d); }); - this.updateClusters(); + de.data.droppedDocuments.length == 1 && this.updateCluster(de.data.droppedDocuments[0]); } } else if (de.data instanceof DragManager.AnnotationDragData) { @@ -384,6 +384,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return false; } @observable sets: (Doc[])[] = []; + + @undoBatch @action updateClusters() { this.sets.length = 0; @@ -412,12 +414,42 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.sets.map((set, i) => set.map(member => member.cluster = i)); } + @undoBatch + @action + updateCluster(doc: Doc) { + if (this.props.Document.useClusters) { + this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); + let preferredInd = NumCast(doc.cluster); + doc.cluster = -1; + this.sets.map((set, i) => set.map(member => { + if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) { + doc.cluster = i; + } + })); + if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) { + doc.cluster = preferredInd; + } + this.sets.map((set, i) => { + if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) { + doc.cluster = i; + } + }); + if (doc.cluster === -1) { + doc.cluster = this.sets.length; + this.sets.push([doc]); + } else { + for (let i = this.sets.length; i <= doc.cluster; i++) !this.sets[i] && this.sets.push([]); + this.sets[doc.cluster].push(doc); + } + } + } + getClusterColor = (doc: Doc) => { if (this.props.Document.useClusters) { let cluster = NumCast(doc.cluster); if (this.sets.length <= cluster) { - setTimeout(() => this.updateClusters(), 0); - return; + setTimeout(() => this.updateCluster(doc), 0);// this.updateClusters(), 0); + return ""; } let set = this.sets.length > cluster ? this.sets[cluster] : undefined; let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; @@ -869,6 +901,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; this.props.Document.useClusters = !this.props.Document.useClusters; + this.updateClusters(); }, icon: !this.props.Document.useClusters ? "braille" : "braille" }); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index be4bf232b..e3d7cc9ed 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -300,9 +300,9 @@ export namespace Doc { export function AreProtosEqual(doc?: Doc, other?: Doc) { if (!doc || !other) return false; let r = (doc === other); - let r2 = (doc.proto === other); - let r3 = (other.proto === doc); - let r4 = (doc.proto === other.proto && other.proto !== undefined); + let r2 = (Doc.GetProto(doc) === other); + let r3 = (Doc.GetProto(other) === doc); + let r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined); return r || r2 || r3 || r4; } @@ -327,6 +327,9 @@ export namespace Doc { return Array.from(results); } + export function IndexOf(toFind: Doc, list: Doc[]) { + 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) { Doc.GetProto(target)[key] = new List(); -- cgit v1.2.3-70-g09d2 From b24c475d8cd36af860fc374b0c5621b0d096be1d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 9 Sep 2019 20:47:34 -0400 Subject: nearly finished transferring images between text notes and google docs --- package.json | 2 +- .../apis/google_docs/GooglePhotosClientUtils.ts | 23 ++-- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/views/MainView.tsx | 6 +- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/new_fields/RichTextUtils.ts | 67 ++++++++++- src/server/RouteStore.ts | 3 +- src/server/apis/google/GoogleApiServerUtils.ts | 2 +- src/server/apis/google/GooglePhotosUploadUtils.ts | 5 + src/server/credentials/google_docs_token.json | 2 +- src/server/index.ts | 20 +++- src/server/updateSearch.ts | 123 --------------------- 13 files changed, 112 insertions(+), 147 deletions(-) delete mode 100644 src/server/updateSearch.ts (limited to 'src/new_fields') diff --git a/package.json b/package.json index f56e34ce0..f0f2b467e 100644 --- a/package.json +++ b/package.json @@ -224,4 +224,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 5f5b39b14..fddcf3aa5 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -51,17 +51,26 @@ export namespace GooglePhotosClientUtils { description: string; } - export const UploadImageDocuments = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption") => { + export const UploadImages = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption") => { if (album && "title" in album) { album = await (await endpoint()).albums.create(album.title); } const media: MediaInput[] = []; - sources.forEach(document => { - const data = Cast(Doc.GetProto(document).data, ImageField); - data && media.push({ - url: data.url.href, - description: parseDescription(document, descriptionKey), - }); + sources.forEach(source => { + let url: string; + let description: string; + if (source instanceof Doc) { + const data = Cast(Doc.GetProto(source).data, ImageField); + if (!data) { + return; + } + url = data.url.href; + description = parseDescription(source, descriptionKey); + } else { + url = source; + description = Utils.GenerateGuid(); + } + media.push({ url, description }); }); if (media.length) { return PostToServer(RouteStore.googlePhotosMediaUpload, { media, album }); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 7693a388f..a19fd39b7 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -117,7 +117,7 @@ export default class DirectoryImportBox extends React.Component console.log(`(${this.quota - this.remaining}/${this.quota}) ${upload.name}`); })); - await GooglePhotosClientUtils.UploadImageDocuments(docs, { title: directory }); + await GooglePhotosClientUtils.UploadImages(docs, { title: directory }); console.log("Finished upload!"); for (let i = 0; i < docs.length; i++) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 326c13424..0c0ed9072 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -155,7 +155,7 @@ export class MainView extends React.Component { doc.caption = "Well isn't this a nice cat image!"; let photos = await GooglePhotosClientUtils.endpoint(); let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id; - console.log(await GooglePhotosClientUtils.UploadImageDocuments([doc], { id: albumId })); + console.log(await GooglePhotosClientUtils.UploadImages([doc], { id: albumId })); } componentWillUnMount() { @@ -470,7 +470,7 @@ export class MainView extends React.Component { // let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; // let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" })); - let googlePhotosSearch = () => GooglePhotosClientUtils.CollectionFromSearch(Docs.Create.MasonryDocument, { included: [GooglePhotosClientUtils.ContentCategories.LANDSCAPES] }); + // let googlePhotosSearch = () => GooglePhotosClientUtils.CollectionFromSearch(Docs.Create.MasonryDocument, { included: [GooglePhotosClientUtils.ContentCategories.LANDSCAPES] }); let btns: [React.RefObject, IconName, string, () => Doc | Promise][] = [ [React.createRef(), "object-group", "Add Collection", addColNode], @@ -478,7 +478,7 @@ export class MainView extends React.Component { [React.createRef(), "globe-asia", "Add Website", addWebNode], [React.createRef(), "bolt", "Add Button", addButtonDocument], [React.createRef(), "file", "Add Document Dragger", addDragboxNode], - [React.createRef(), "object-group", "Test Google Photos Search", googlePhotosSearch], + // [React.createRef(), "object-group", "Test Google Photos Search", googlePhotosSearch], [React.createRef(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode //[React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], ]; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 99e5ab7b3..5fc4f36a7 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -253,7 +253,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { }).then(async (res: Response) => { (await res.json()).map(action((file: any) => { let full = { ...options, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300, width: 300, title: dropFileName }; - let path = Utils.prepend(file); + let path = Utils.prepend(file.path); Docs.Get.DocumentFromType(type, path, full).then(doc => doc && this.props.addDocument(doc)); })); }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4033ffd9c..cb9346a8b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -591,7 +591,7 @@ export class DocumentView extends DocComponent(Docu subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); if (Cast(this.props.Document.data, ImageField)) { - cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotosClientUtils.UploadImageDocuments([this.props.Document]), icon: "caret-square-right" }); + cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotosClientUtils.UploadImages([this.props.Document]), icon: "caret-square-right" }); } let existingMake = ContextMenu.Instance.findByDescription("Make..."); let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 0aba50c0d..500b93676 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -7,6 +7,11 @@ import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; import { Opt } from "./Doc"; import * as Color from "color"; import { sinkListItem } from "prosemirror-schema-list"; +import { Utils, PostToServer } from "../Utils"; +import { RouteStore } from "../server/RouteStore"; +import { Docs } from "../client/documents/Documents"; +import { schema } from "../client/util/RichTextSchema"; +import { GooglePhotosClientUtils } from "../client/apis/google_docs/GooglePhotosClientUtils"; export namespace RichTextUtils { @@ -86,19 +91,36 @@ export namespace RichTextUtils { export namespace GoogleDocs { export const Export = (state: EditorState): GoogleApiClientUtils.Docs.Content => { - let textNodes: Node[] = []; + let nodes: { [type: string]: Node[] } = { + text: [], + image: [] + }; let text = ToPlainText(state); let content = state.doc.content; - content.forEach(node => node.content.forEach(node => node.type.name === "text" && textNodes.push(node))); - let linkRequests = ExtractLinks(textNodes); + content.forEach(node => node.content.forEach(node => { + const type = node.type.name; + let existing = nodes[type]; + if (existing) { + existing.push(node); + } else { + nodes[type] = [node]; + } + })); + let linkRequests = ExtractLinks(nodes.text); + let imageRequests = ExtractImages(nodes.image); return { text, - requests: [...linkRequests] + requests: [...linkRequests, ...imageRequests] }; }; type BulletPosition = { value: number, sinks: number }; + interface MediaItem { + baseUrl: string; + filename: string; + width: number; + } export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId): Promise> => { const document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); if (!document) { @@ -109,6 +131,17 @@ export namespace RichTextUtils { const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document); let state = FormattedTextBox.blankState(); let structured = parseLists(paragraphs); + const inline = document.inlineObjects; + let inlineUrls: MediaItem[] = []; + if (inline) { + inlineUrls = Object.keys(inline).map(key => { + const embedded = inline[key].inlineObjectProperties!.embeddedObject!; + const baseUrl = embedded.imageProperties!.contentUri!; + const filename = `upload_${Utils.GenerateGuid()}.png`; + const width = embedded.size!.width!.magnitude!; + return { baseUrl, filename, width }; + }); + } let position = 3; let lists: ListGroup[] = []; @@ -151,6 +184,12 @@ export namespace RichTextUtils { } } + const uploads = await PostToServer(RouteStore.googlePhotosMediaDownload, { mediaItems: inlineUrls }); + for (let i = 0; i < uploads.length; i++) { + const src = Utils.prepend(`/files/${uploads[i].fileNames.clean}`); + state = state.apply(state.tr.insert(0, schema.nodes.image.create({ src, width: inlineUrls[i].width }))); + } + return { title, text, state }; }; @@ -252,6 +291,26 @@ export namespace RichTextUtils { return links; }; + const ExtractImages = async (nodes: Node[]) => { + const images: docs_v1.Schema$Request[] = []; + let position = 1; + for (let node of nodes) { + const length = node.nodeSize; + const attrs = node.attrs; + const uri = attrs.src; + const result = (await GooglePhotosClientUtils.UploadImages([uri])).newMediaItemResults; + images.push({ + insertInlineImage: { + uri: result[0].mediaItem.productUrl, + objectSize: { width: { magnitude: parseFloat(attrs.width.replace("px", "")), unit: "PT" } }, + location: { index: position + length } + } + }); + position += length; + } + return images; + }; + const Encode = (information: LinkInformation) => { return { updateTextStyle: { diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts index f65e6134c..ee9cd8a0e 100644 --- a/src/server/RouteStore.ts +++ b/src/server/RouteStore.ts @@ -34,6 +34,7 @@ export enum RouteStore { googleDocs = "/googleDocs", googlePhotosAccessToken = "/googlePhotosAccessToken", googlePhotosMediaUpload = "/googlePhotosMediaUpload", - googlePhotosMediaDownload = "/googlePhotosMediaDownload" + googlePhotosMediaDownload = "/googlePhotosMediaDownload", + googleDocsGet = "/googleDocsGet" } \ No newline at end of file diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index ac8023ce1..e0bd8a800 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -42,7 +42,7 @@ export namespace GoogleApiServerUtils { export type ApiResponse = Promise; export type ApiRouter = (endpoint: Endpoint, parameters: any) => ApiResponse; - export type ApiHandler = (parameters: any) => ApiResponse; + export type ApiHandler = (parameters: any, methodOptions?: any) => ApiResponse; export type Action = "create" | "retrieve" | "update"; export type Endpoint = { get: ApiHandler, create: ApiHandler, batchUpdate: ApiHandler }; diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index 35f986250..d1f1f81bd 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -151,6 +151,11 @@ export namespace DownloadUtils { .on('close', resolve) .on('error', reject); }); + if (!isLocal) { + await new Promise(resolve => { + stream(url).pipe(fs.createWriteStream(uploadDirectory + resolved)).on('close', resolve); + }); + } } resolve(information); }); diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index fabc18cfd..5c142fba1 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.Glx-BwgWcpQUukTNyuUqvSAYrDyxDNUhCLtrFDJAViROvicm0DrcRvCn4OaQdn2m2IZQYcG-19cvQYoOC3UJCtWXLRvKZzQCbZZSykpxYu_lflUyEnIGZOIHMbbEjA","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568008211814} \ No newline at end of file +{"access_token":"ya29.Glx_B6G7Q_FYs1LK5VcyV6P6Zg9JkoHO2aC_TsnN7AVxPYWHEpsBSC0WyWX7Ztr8HWhOUYA5JXqnZDkLrK1V3Hb-0GgtyApLRNtEPOWf1dJ7lOm_iKVw2tRvPe7XDQ","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568078116605} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index baef94a59..8469770d5 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -38,6 +38,7 @@ import flash = require('connect-flash'); import { Search } from './Search'; import _ = require('lodash'); import * as Archiver from 'archiver'; +import * as request_promise from 'request-promise'; var AdmZip = require('adm-zip'); import * as YoutubeApi from "./apis/youtube/youtubeApiSample"; import { Response } from 'express-serve-static-core'; @@ -576,9 +577,9 @@ app.post( form.parse(req, async (_err, _fields, files) => { let results: FileResponse[] = []; for (const key in files) { - const { name, type, path: location } = files[key]; + const { type, path: location, name } = files[key]; const filename = path.basename(location); - await UploadUtils.UploadImage(uploadDirectory + filename, path.basename(name)); + await UploadUtils.UploadImage(uploadDirectory + filename, filename); results.push({ name, type, path: `/files/${filename}` }); console.log(path.basename(name)); } @@ -790,10 +791,23 @@ const tokenPath = path.join(__dirname, "./credentials/google_docs_token.json"); const EndpointHandlerMap = new Map([ ["create", (api, params) => api.create(params)], - ["retrieve", (api, params) => api.get(params)], + ["retrieve", (api, params) => api.get(params, { params: "fields=inlineObjects" })], ["update", (api, params) => api.batchUpdate(params)], ]); +// app.post(RouteStore.googleDocsGet, async (req, res) => { +// const token = await GoogleApiServerUtils.RetrieveAccessToken({ credentialsPath, tokenPath }); +// request_promise.get({ +// uri: `https://docs.googleapis.com/v1/documents/${req.body.documentId}?fields=inlineObjects`, +// headers: { +// 'Authorization': `Bearer ${token}` +// } +// }).then(response => { +// console.log(response); +// res.send(response); +// }); +// }); + app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => { let sector: GoogleApiServerUtils.Service = req.params.sector; let action: GoogleApiServerUtils.Action = req.params.action; diff --git a/src/server/updateSearch.ts b/src/server/updateSearch.ts deleted file mode 100644 index 906b795f1..000000000 --- a/src/server/updateSearch.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Database } from "./database"; -import { Cursor } from "mongodb"; -import { Search } from "./Search"; -import pLimit from 'p-limit'; - -const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { - "number": "_n", - "string": "_t", - "boolean": "_b", - // "image": ["_t", "url"], - "video": ["_t", "url"], - "pdf": ["_t", "url"], - "audio": ["_t", "url"], - "web": ["_t", "url"], - "date": ["_d", value => new Date(value.date).toISOString()], - "proxy": ["_i", "fieldId"], - "list": ["_l", list => { - const results = []; - for (const value of list.fields) { - const term = ToSearchTerm(value); - if (term) { - results.push(term.value); - } - } - return results.length ? results : null; - }] -}; - -function ToSearchTerm(val: any): { suffix: string, value: any } | undefined { - if (val === null || val === undefined) { - return; - } - const type = val.__type || typeof val; - let suffix = suffixMap[type]; - if (!suffix) { - return; - } - - if (Array.isArray(suffix)) { - const accessor = suffix[1]; - if (typeof accessor === "function") { - val = accessor(val); - } else { - val = val[accessor]; - } - suffix = suffix[0]; - } - - return { suffix, value: val }; -} - -function getSuffix(value: string | [string, any]): string { - return typeof value === "string" ? value : value[0]; -} - -const limit = pLimit(5); -async function update() { - // await new Promise(res => setTimeout(res, 5)); - console.log("update"); - await Search.Instance.clear(); - const cursor = await Database.Instance.query({}); - console.log("Cleared"); - const updates: any[] = []; - let numDocs = 0; - function updateDoc(doc: any) { - numDocs++; - if ((numDocs % 50) === 0) { - console.log("updateDoc " + numDocs); - } - // console.log("doc " + numDocs); - if (doc.__type !== "Doc") { - return; - } - const fields = doc.fields; - if (!fields) { - return; - } - const update: any = { id: doc._id }; - let dynfield = false; - for (const key in fields) { - const value = fields[key]; - const term = ToSearchTerm(value); - if (term !== undefined) { - let { suffix, value } = term; - update[key + suffix] = value; - dynfield = true; - } - } - if (dynfield) { - updates.push(update); - // console.log(updates.length); - } - } - await cursor.forEach(updateDoc); - console.log(`Updating ${updates.length} documents`); - const result = await Search.Instance.updateDocuments(updates); - try { - console.log(JSON.parse(result).responseHeader.status); - } catch { - console.log("Error:"); - // console.log(updates[i]); - console.log(result); - console.log("\n"); - } - // for (let i = 0; i < updates.length; i++) { - // console.log(i); - // const result = await Search.Instance.updateDocument(updates[i]); - // try { - // console.log(JSON.parse(result).responseHeader.status); - // } catch { - // console.log("Error:"); - // console.log(updates[i]); - // console.log(result); - // console.log("\n"); - // } - // } - // await Promise.all(updates.map(update => { - // return limit(() => Search.Instance.updateDocument(update)); - // })); - cursor.close(); -} - -update(); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 9608245db4ba8cca6054a0641f2eb2bd2032eba6 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 10 Sep 2019 15:55:51 -0400 Subject: fixed up some stuff with portals and added a ButtonBox menu item for running scripts over a collection --- src/client/views/ContextMenuItem.tsx | 2 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/ScriptBox.tsx | 15 +++++++++++++-- src/client/views/collections/CollectionBaseView.tsx | 4 ++++ src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 11 +++++++---- src/client/views/nodes/DocumentView.tsx | 12 +++++++++--- src/new_fields/Doc.ts | 3 ++- 7 files changed, 37 insertions(+), 12 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 1a0839060..0366a6a30 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -89,7 +89,7 @@ export class ContextMenuItem extends React.Component +
{this._items.map(prop => )}
; return ( diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 519792308..aa573f16b 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -64,7 +64,7 @@ export class InkingControl extends React.Component { closest = i; } } - cp[closest] = "rgb(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + ")"; + cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")"; view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp); targetDoc.backgroundColor = cp[closest]; } else diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 2b862a81e..7afba5e01 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -66,13 +66,24 @@ export class ScriptBox extends React.Component {
); } - public static EditClickScript(doc: Doc, fieldKey: string) { + public static EditClickScript(doc: Doc, fieldKey: string, prewrapper?: string, postwrapper?: string) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); let originalText: string | undefined = undefined; - if (script) originalText = script.script.originalScript; + if (script) { + originalText = script.script.originalScript; + if (prewrapper && originalText.startsWith(prewrapper)) { + originalText = originalText.substr(prewrapper.length); + } + if (postwrapper && originalText.endsWith(postwrapper)) { + originalText = originalText.substr(0, originalText.length - postwrapper.length); + } + } // tslint:disable-next-line: no-unnecessary-callback-wrapper let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + if (prewrapper) { + text = prewrapper + text + (postwrapper ? postwrapper : ""); + } const script = CompileScript(text, { params: { this: Doc.name }, typecheck: false, diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index b6ed6aaa0..bd8d56851 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -12,6 +12,7 @@ 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, @@ -103,6 +104,9 @@ export class CollectionBaseView extends React.Component { if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } + if (doc.type === DocumentType.BUTTON) { + doc.collectionContext = this.props.Document; // used by docList() function in Doc.ts so that buttons can iterate over the documents in their collection + } 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; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c059ff50d..9692dd8a9 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -99,10 +99,13 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); + makes.push({ description: "OnClick foreach doc", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick", "docList(this.collectionContext.data).map(d => {", "});\n") }); makes.push({ description: "Into Portal", event: () => { - let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); - this.makeBtnClicked(); + if (!DocListCast(this.props.Document.links).find(doc => { + if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true; + return false; + })) { + let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); + DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); + Doc.GetProto(this.props.Document).isButton = true; + } }, icon: "window-restore" }); makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" }); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e3d7cc9ed..ccb8f4aa2 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -685,4 +685,5 @@ export namespace Doc { Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(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(field.map((d: any) => Doc.MakeAlias(d))); }); \ No newline at end of file +Scripting.addGlobal(function aliasDocs(field: any) { return new List(field.map((d: any) => Doc.MakeAlias(d))); }); +Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 628eef55533118b1f2312b86b2ac5f7b64f7fc4a Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 10 Sep 2019 20:01:55 -0400 Subject: lots of refactoring, beginning autotagging --- src/Utils.ts | 5 + .../apis/google_docs/GooglePhotosClientUtils.ts | 339 ++++++++++++++------- .../util/Import & Export/DirectoryImportBox.tsx | 5 +- src/client/views/MainView.tsx | 18 +- src/client/views/nodes/DocumentView.tsx | 7 +- src/client/views/nodes/FormattedTextBox.tsx | 6 +- src/new_fields/RichTextUtils.ts | 13 +- src/server/apis/google/GooglePhotosUploadUtils.ts | 7 +- src/server/apis/google/SharedTypes.ts | 21 ++ src/server/credentials/google_docs_token.json | 2 +- src/server/index.ts | 21 +- 11 files changed, 284 insertions(+), 160 deletions(-) create mode 100644 src/server/apis/google/SharedTypes.ts (limited to 'src/new_fields') diff --git a/src/Utils.ts b/src/Utils.ts index 959b89fe5..71d88683a 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -37,6 +37,11 @@ export class Utils { public static prepend(extension: string): string { return window.location.origin + extension; } + + public static fileUrl(filename: string): string { + return this.prepend(`/file/${filename}`); + } + public static CorsProxy(url: string): string { return this.prepend(RouteStore.corsProxy + "/") + encodeURIComponent(url); } diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index fddcf3aa5..b1de24d1a 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -1,8 +1,8 @@ import { PostToServer, Utils } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { ImageField } from "../../../new_fields/URLField"; -import { Cast } from "../../../new_fields/Types"; -import { Doc, Opt } from "../../../new_fields/Doc"; +import { Cast, StrCast } from "../../../new_fields/Types"; +import { Doc, Opt, DocListCastAsync } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import Photos = require('googlephotos'); import { RichTextField } from "../../../new_fields/RichTextField"; @@ -10,32 +10,15 @@ import { RichTextUtils } from "../../../new_fields/RichTextUtils"; import { EditorState } from "prosemirror-state"; import { FormattedTextBox } from "../../views/nodes/FormattedTextBox"; import { Docs, DocumentOptions } from "../../documents/Documents"; -import { type } from "os"; - -export namespace GooglePhotosClientUtils { - - export enum ContentCategories { - NONE = 'NONE', - LANDSCAPES = 'LANDSCAPES', - RECEIPTS = 'RECEIPTS', - CITYSCAPES = 'CITYSCAPES', - LANDMARKS = 'LANDMARKS', - SELFIES = 'SELFIES', - PEOPLE = 'PEOPLE', - PETS = 'PETS', - WEDDINGS = 'WEDDINGS', - BIRTHDAYS = 'BIRTHDAYS', - DOCUMENTS = 'DOCUMENTS', - TRAVEL = 'TRAVEL', - ANIMALS = 'ANIMALS', - FOOD = 'FOOD', - SPORT = 'SPORT', - NIGHT = 'NIGHT', - PERFORMANCES = 'PERFORMANCES', - WHITEBOARDS = 'WHITEBOARDS', - SCREENSHOTS = 'SCREENSHOTS', - UTILITY = 'UTILITY' - } +import { MediaItemCreationResult, NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes"; + +export namespace GooglePhotos { + + const endpoint = async () => { + const getToken = Utils.prepend(RouteStore.googlePhotosAccessToken); + const token = await (await fetch(getToken)).text(); + return new Photos(token); + }; export enum MediaType { ALL_MEDIA = 'ALL_MEDIA', @@ -44,118 +27,238 @@ export namespace GooglePhotosClientUtils { } export type AlbumReference = { id: string } | { title: string }; - export const endpoint = () => fetch(Utils.prepend(RouteStore.googlePhotosAccessToken)).then(async response => new Photos(await response.text())); export interface MediaInput { url: string; description: string; } - export const UploadImages = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption") => { - if (album && "title" in album) { - album = await (await endpoint()).albums.create(album.title); + export const ContentCategories = { + NONE: 'NONE', + LANDSCAPES: 'LANDSCAPES', + RECEIPTS: 'RECEIPTS', + CITYSCAPES: 'CITYSCAPES', + LANDMARKS: 'LANDMARKS', + SELFIES: 'SELFIES', + PEOPLE: 'PEOPLE', + PETS: 'PETS', + WEDDINGS: 'WEDDINGS', + BIRTHDAYS: 'BIRTHDAYS', + DOCUMENTS: 'DOCUMENTS', + TRAVEL: 'TRAVEL', + ANIMALS: 'ANIMALS', + FOOD: 'FOOD', + SPORT: 'SPORT', + NIGHT: 'NIGHT', + PERFORMANCES: 'PERFORMANCES', + WHITEBOARDS: 'WHITEBOARDS', + SCREENSHOTS: 'SCREENSHOTS', + UTILITY: 'UTILITY' + }; + + export namespace Export { + + export interface AlbumCreationResult { + albumId: string; + mediaItems: MediaItem[]; } - const media: MediaInput[] = []; - sources.forEach(source => { - let url: string; - let description: string; - if (source instanceof Doc) { - const data = Cast(Doc.GetProto(source).data, ImageField); - if (!data) { - return; + + export const CollectionToAlbum = async (collection: Doc, title?: string, descriptionKey?: string): Promise> => { + const dataDocument = Doc.GetProto(collection); + const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField)); + if (!images || !images.length) { + return undefined; + } + const resolved = title ? title : (StrCast(collection.title) || `Dash Collection (${collection[Id]}`); + const { id } = await Create.Album(resolved); + const result = await Transactions.UploadImages(images, { id }, descriptionKey); + if (result) { + const mediaItems = result.newMediaItemResults.map(item => item.mediaItem); + return { albumId: id, mediaItems }; + } + }; + + } + + export namespace Import { + + export type CollectionConstructor = (data: Array, options: DocumentOptions, ...args: any) => Doc; + + export const CollectionFromSearch = async (constructor: CollectionConstructor, requested: Opt>): Promise => { + let response = await Query.Search(requested); + let uploads = await Transactions.WriteMediaItemsToServer(response); + const children = uploads.map((upload: Transactions.UploadInformation) => { + let document = Docs.Create.ImageDocument(Utils.fileUrl(upload.fileNames.clean)); + document.fillColumn = true; + document.contentSize = upload.contentSize; + return document; + }); + const options = { width: 500, height: 500 }; + return constructor(children, options); + }; + + } + + export namespace Query { + + export const AppendImageMetadata = (sources: (Doc | string)[]) => { + let keys = Object.keys(ContentCategories); + let included: string[] = []; + let excluded: string[] = []; + for (let i = 0; i < keys.length; i++) { + for (let j = 0; j < keys.length; j++) { + let value = ContentCategories[keys[i] as keyof typeof ContentCategories]; + if (j === i) { + included.push(value); + } else { + excluded.push(value); + } } - url = data.url.href; - description = parseDescription(source, descriptionKey); - } else { - url = source; - description = Utils.GenerateGuid(); + //... + included = excluded = []; } - media.push({ url, description }); - }); - if (media.length) { - return PostToServer(RouteStore.googlePhotosMediaUpload, { media, album }); + }; + + interface DateRange { + after: Date; + before: Date; } - }; - const parseDescription = (document: Doc, descriptionKey: string) => { - let description: string = Utils.prepend("/doc/" + document[Id]); - const target = document[descriptionKey]; - if (typeof target === "string") { - description = target; - } else if (target instanceof RichTextField) { - description = RichTextUtils.ToPlainText(EditorState.fromJSON(FormattedTextBox.Instance.config, JSON.parse(target.Data))); + const DefaultSearchOptions: SearchOptions = { + pageSize: 20, + included: [], + excluded: [], + date: undefined, + includeArchivedMedia: true, + type: MediaType.ALL_MEDIA, + }; + + export interface SearchOptions { + pageSize: number; + included: ContentCategories[]; + excluded: ContentCategories[]; + date: Opt; + includeArchivedMedia: boolean; + type: MediaType; } - return description; - }; - export interface DateRange { - after: Date; - before: Date; - } - export interface SearchOptions { - pageSize: number; - included: ContentCategories[]; - excluded: ContentCategories[]; - date: Opt; - includeArchivedMedia: boolean; - type: MediaType; + export interface SearchResponse { + mediaItems: any[]; + nextPageToken: string; + } + + export const Search = async (requested: Opt>): Promise => { + const options = requested || DefaultSearchOptions; + const photos = await endpoint(); + const filters = new photos.Filters(options.includeArchivedMedia === undefined ? true : options.includeArchivedMedia); + + const included = options.included || []; + const excluded = options.excluded || []; + const contentFilter = new photos.ContentFilter(); + included.length && included.forEach(category => contentFilter.addIncludedContentCategories(category)); + excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category)); + filters.setContentFilter(contentFilter); + + const date = options.date; + if (date) { + const dateFilter = new photos.DateFilter(); + if (date instanceof Date) { + dateFilter.addDate(date); + } else { + dateFilter.addRange(date.after, date.before); + } + filters.setDateFilter(dateFilter); + } + + filters.setMediaTypeFilter(new photos.MediaTypeFilter(options.type || MediaType.ALL_MEDIA)); + + return new Promise(resolve => { + photos.mediaItems.search(filters, options.pageSize || 20).then(resolve); + }); + }; + + export const GetImage = async (mediaItemId: string): Promise => { + return (await endpoint()).mediaItems.get(mediaItemId); + }; + } - const DefaultSearchOptions: SearchOptions = { - pageSize: 20, - included: [], - excluded: [], - date: undefined, - includeArchivedMedia: true, - type: MediaType.ALL_MEDIA, - }; + export namespace Create { + + export const Album = async (title: string) => { + return (await endpoint()).albums.create(title); + }; - export interface SearchResponse { - mediaItems: any[]; - nextPageToken: string; } - export type CollectionConstructor = (data: Array, options: DocumentOptions, ...args: any) => Doc; - export const CollectionFromSearch = async (provider: CollectionConstructor, requested: Opt>): Promise => { - let downloads = await Search(requested); - return provider(downloads.map((download: any) => { - let document = Docs.Create.ImageDocument(Utils.prepend(`/files/${download.fileNames.clean}`)); - document.fillColumn = true; - document.contentSize = download.contentSize; - return document; - }), { width: 500, height: 500 }); - }; + export namespace Transactions { - export const Search = async (requested: Opt>): Promise => { - const options = requested || DefaultSearchOptions; - const photos = await endpoint(); - const filters = new photos.Filters(options.includeArchivedMedia === undefined ? true : options.includeArchivedMedia); - - const included = options.included || []; - const excluded = options.excluded || []; - const contentFilter = new photos.ContentFilter(); - included.length && included.forEach(category => contentFilter.addIncludedContentCategories(category)); - excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category)); - filters.setContentFilter(contentFilter); - - const date = options.date; - if (date) { - const dateFilter = new photos.DateFilter(); - if (date instanceof Date) { - dateFilter.addDate(date); - } else { - dateFilter.addRange(date.after, date.before); - } - filters.setDateFilter(dateFilter); + export interface UploadInformation { + mediaPaths: string[]; + fileNames: { [key: string]: string }; + contentSize?: number; + contentType?: string; + } + + export interface MediaItem { + id: string; + filename: string; + baseUrl: string; } - filters.setMediaTypeFilter(new photos.MediaTypeFilter(options.type || MediaType.ALL_MEDIA)); + export const WriteMediaItemsToServer = async (body: { mediaItems: any[] }): Promise => { + const uploads = await PostToServer(RouteStore.googlePhotosMediaDownload, body); + return uploads; + }; - return new Promise(resolve => { - photos.mediaItems.search(filters, options.pageSize || 20).then(async (response: SearchResponse) => { - response && resolve(await PostToServer(RouteStore.googlePhotosMediaDownload, response)); + export const UploadThenFetch = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption") => { + const result = await UploadImages(sources, album, descriptionKey); + if (!result) { + return undefined; + } + const baseUrls: string[] = await Promise.all(result.newMediaItemResults.map((result: any) => { + return new Promise(resolve => Query.GetImage(result.mediaItem.id).then(item => resolve(item.baseUrl))); + })); + return baseUrls; + }; + + export const UploadImages = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption"): Promise> => { + if (album && "title" in album) { + album = await Create.Album(album.title); + } + const media: MediaInput[] = []; + sources.forEach(source => { + let url: string; + let description: string; + if (source instanceof Doc) { + const data = Cast(Doc.GetProto(source).data, ImageField); + if (!data) { + return; + } + url = data.url.href; + description = parseDescription(source, descriptionKey); + } else { + url = source; + description = Utils.GenerateGuid(); + } + media.push({ url, description }); }); - }); - }; + if (media.length) { + return PostToServer(RouteStore.googlePhotosMediaUpload, { media, album }); + } + }; + + const parseDescription = (document: Doc, descriptionKey: string) => { + let description: string = Utils.prepend("/doc/" + document[Id]); + const target = document[descriptionKey]; + if (typeof target === "string") { + description = target; + } else if (target instanceof RichTextField) { + description = RichTextUtils.ToPlainText(EditorState.fromJSON(FormattedTextBox.Instance.config, JSON.parse(target.Data))); + } + return description; + }; + + } } \ No newline at end of file diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index a19fd39b7..d58c02ce5 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -18,7 +18,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { Cast, BoolCast, NumCast } from "../../../new_fields/Types"; import { listSpec } from "../../../new_fields/Schema"; -import { GooglePhotosClientUtils } from "../../apis/google_docs/GooglePhotosClientUtils"; +import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; const unsupported = ["text/html", "text/plain"]; interface FileResponse { @@ -117,8 +117,7 @@ export default class DirectoryImportBox extends React.Component console.log(`(${this.quota - this.remaining}/${this.quota}) ${upload.name}`); })); - await GooglePhotosClientUtils.UploadImages(docs, { title: directory }); - console.log("Finished upload!"); + await GooglePhotos.Transactions.UploadImages(docs, { title: directory }); for (let i = 0; i < docs.length; i++) { let doc = docs[i]; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0c0ed9072..8d10a91ce 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,7 +40,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; -import { GooglePhotosClientUtils } from '../apis/google_docs/GooglePhotosClientUtils'; +import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils'; import { ImageField } from '../../new_fields/URLField'; import { LinkFollowBox } from './linking/LinkFollowBox'; import { DocumentManager } from '../util/DocumentManager'; @@ -149,14 +149,14 @@ export class MainView extends React.Component { }, { fireImmediately: true }); } - executeGooglePhotosRoutine = async () => { - let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; - let doc = Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }); - doc.caption = "Well isn't this a nice cat image!"; - let photos = await GooglePhotosClientUtils.endpoint(); - let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id; - console.log(await GooglePhotosClientUtils.UploadImages([doc], { id: albumId })); - } + // executeGooglePhotosRoutine = async () => { + // let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; + // let doc = Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }); + // doc.caption = "Well isn't this a nice cat image!"; + // let photos = await GooglePhotos.endpoint(); + // let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id; + // console.log(await GooglePhotos.UploadImages([doc], { id: albumId })); + // } componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index cb9346a8b..a51f783ad 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,7 +41,7 @@ import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); import { DocumentType } from '../../documents/DocumentTypes'; -import { GooglePhotosClientUtils } from '../../apis/google_docs/GooglePhotosClientUtils'; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { ImageField } from '../../../new_fields/URLField'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -591,7 +591,10 @@ export class DocumentView extends DocComponent(Docu subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); if (Cast(this.props.Document.data, ImageField)) { - cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotosClientUtils.UploadImages([this.props.Document]), icon: "caret-square-right" }); + cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); + } + if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) { + cm.addItem({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum(this.props.Document).then(console.log), icon: "caret-square-right" }); } let existingMake = ContextMenu.Instance.findByDescription("Make..."); let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index fda9ea33f..9d83fbd04 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -177,7 +177,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe syncNodeSelection(view: any, sel: any) { if (sel instanceof NodeSelection) { var desc = view.docView.descAt(sel.from); - if (desc != view.lastSelectedViewDesc) { + if (desc !== view.lastSelectedViewDesc) { if (view.lastSelectedViewDesc) { view.lastSelectedViewDesc.deselectNode(); view.lastSelectedViewDesc = null; @@ -463,7 +463,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } let redo = async () => { if (this._editorView && reference) { - let content = RichTextUtils.GoogleDocs.Export(this._editorView.state); + let content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); let pushSuccess = response !== undefined && !("errors" in response); @@ -636,7 +636,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }, star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(node, view, getPos); }, - footnote(node, view, getPos) { return new FootnoteView(node, view, getPos) } + footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 500b93676..27737782b 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -11,7 +11,7 @@ import { Utils, PostToServer } from "../Utils"; import { RouteStore } from "../server/RouteStore"; import { Docs } from "../client/documents/Documents"; import { schema } from "../client/util/RichTextSchema"; -import { GooglePhotosClientUtils } from "../client/apis/google_docs/GooglePhotosClientUtils"; +import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; export namespace RichTextUtils { @@ -90,7 +90,7 @@ export namespace RichTextUtils { export namespace GoogleDocs { - export const Export = (state: EditorState): GoogleApiClientUtils.Docs.Content => { + export const Export = async (state: EditorState): Promise => { let nodes: { [type: string]: Node[] } = { text: [], image: [] @@ -107,7 +107,7 @@ export namespace RichTextUtils { } })); let linkRequests = ExtractLinks(nodes.text); - let imageRequests = ExtractImages(nodes.image); + let imageRequests = await ExtractImages(nodes.image); return { text, requests: [...linkRequests, ...imageRequests] @@ -298,10 +298,13 @@ export namespace RichTextUtils { const length = node.nodeSize; const attrs = node.attrs; const uri = attrs.src; - const result = (await GooglePhotosClientUtils.UploadImages([uri])).newMediaItemResults; + const baseUrls = await GooglePhotos.Transactions.UploadThenFetch([uri]); + if (!baseUrls) { + continue; + } images.push({ insertInlineImage: { - uri: result[0].mediaItem.productUrl, + uri: baseUrls[0], objectSize: { width: { magnitude: parseFloat(attrs.width.replace("px", "")), unit: "PT" } }, location: { index: position + length } } diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index d1f1f81bd..447ed23ac 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -5,6 +5,7 @@ import { Utils } from '../../../Utils'; import * as path from 'path'; import { Opt } from '../../../new_fields/Doc'; import * as sharp from 'sharp'; +import { MediaItemCreationResult } from './SharedTypes'; const uploadDirectory = path.join(__dirname, "../../public/files/"); @@ -52,8 +53,10 @@ export namespace GooglePhotosUploadUtils { return new Promise(resolve => request(parameters, (error, _response, body) => resolve(error ? undefined : body))); }; - export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }) => { - return new Promise((resolve, reject) => { + + + export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }): Promise => { + return new Promise((resolve, reject) => { const parameters = { method: 'POST', headers: headers('json'), diff --git a/src/server/apis/google/SharedTypes.ts b/src/server/apis/google/SharedTypes.ts new file mode 100644 index 000000000..9ad6130b6 --- /dev/null +++ b/src/server/apis/google/SharedTypes.ts @@ -0,0 +1,21 @@ +export interface NewMediaItemResult { + uploadToken: string; + status: { code: number, message: string }; + mediaItem: MediaItem; +} + +export interface MediaItem { + id: string; + description: string; + productUrl: string; + baseUrl: string; + mimeType: string; + mediaMetadata: { + creationTime: string; + width: string; + height: string; + }; + filename: string; +} + +export type MediaItemCreationResult = { newMediaItemResults: NewMediaItemResult[] }; \ No newline at end of file diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 5c142fba1..22d57d744 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.Glx_B6G7Q_FYs1LK5VcyV6P6Zg9JkoHO2aC_TsnN7AVxPYWHEpsBSC0WyWX7Ztr8HWhOUYA5JXqnZDkLrK1V3Hb-0GgtyApLRNtEPOWf1dJ7lOm_iKVw2tRvPe7XDQ","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568078116605} \ No newline at end of file +{"access_token":"ya29.GlyAB5T3dgJqWuYBcLaT94wQo7MZkmzJQZxDB2sSU95mdhW24E3diuFdLeNsUDVI57D3S765RweMnL98d-fdgu1dRxpzkV_J_3rLih99pZ8A4d6jVdm1354UT4py_w","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568161931458} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 8469770d5..2c3e76c55 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -795,19 +795,6 @@ const EndpointHandlerMap = new Map api.batchUpdate(params)], ]); -// app.post(RouteStore.googleDocsGet, async (req, res) => { -// const token = await GoogleApiServerUtils.RetrieveAccessToken({ credentialsPath, tokenPath }); -// request_promise.get({ -// uri: `https://docs.googleapis.com/v1/documents/${req.body.documentId}?fields=inlineObjects`, -// headers: { -// 'Authorization': `Bearer ${token}` -// } -// }).then(response => { -// console.log(response); -// res.send(response); -// }); -// }); - app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => { let sector: GoogleApiServerUtils.Service = req.params.sector; let action: GoogleApiServerUtils.Action = req.params.action; @@ -841,11 +828,11 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => { }; })); if (!newMediaItems.every(item => item)) { - return res.status(STATUS.EXECUTION_ERROR).send(tokenError); + return _error(res, tokenError); } GooglePhotosUploadUtils.CreateMediaItems(newMediaItems, req.body.album).then( - success => res.status(STATUS.OK).send(success), - () => res.status(STATUS.EXECUTION_ERROR).send(mediaError) + mediaItems => _success(res, mediaItems), + error => _error(res, mediaError, error) ); }); @@ -871,7 +858,7 @@ app.post(RouteStore.googlePhotosMediaDownload, async (req, res) => { _invalid(res, requestError); }); -const _error = (res: Response, message: string, error: any) => { +const _error = (res: Response, message: string, error?: any) => { res.statusMessage = message; res.status(STATUS.EXECUTION_ERROR).send(error); }; -- cgit v1.2.3-70-g09d2 From 71594c0e64d2559f2a72bcbc5faee1db78eecfb8 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 11:19:21 -0400 Subject: added makeCustomView to promote documents to a new layout. --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/ScriptBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 57 +++++++++++++++++++++----------- src/new_fields/Doc.ts | 12 ++++--- 4 files changed, 47 insertions(+), 26 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 773ab8b9f..fe409d9a6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -283,7 +283,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onCloseUp = async (e: PointerEvent) => { e.stopPropagation(); if (e.button === 0) { - const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc); + const recent = Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc; SelectionManager.SelectedDocuments().map(dv => { recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true); dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 375a5cc93..1f3673390 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -68,7 +68,7 @@ export class ScriptBox extends React.Component {
); } - //let l = docList(this.source.data).length; if (l) { let ind = this.target.index !== undefined ? (this.target.index+1) % l : 0; this.target.index = ind; this.target.proto = getProto(docList(this.source.data)[ind]);} + //let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);} public static EditButtonScript(doc: Doc, fieldKey: string, content: any, clientX: number, clientY: number) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 84169cc93..ca8fb573f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -444,6 +444,19 @@ export class DocumentView extends DocComponent(Docu this.props.addDocTab(kvp, this.dataDoc, "onRight"); } + @undoBatch + makeCustomViewClicked = (): void => { + let options = { title: "data", width: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) }; + let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + + let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: NumCast(this.props.Document.height) + 20 }); + let metaKey = "data"; + let proto = Doc.GetProto(docTemplate); + Doc.MakeTemplate(fieldTemplate, metaKey, proto, true); + + Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, true); + } + @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); @@ -552,17 +565,30 @@ export class DocumentView extends DocComponent(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.props.Document.title + ".portal") return true; + return false; + })) { + let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); + DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); + Doc.GetProto(this.props.Document).isButton = true; + } + } + @undoBatch @action makeBackground = (): void => { - this.props.Document.isBackground = !this.props.Document.isBackground; - this.props.Document.isBackground && this.props.bringToFront(this.props.Document, true); + this.layoutDoc.isBackground = !this.layoutDoc.isBackground; + this.layoutDoc.isBackground && this.props.bringToFront(this.layoutDoc, true); } @undoBatch @action toggleLockPosition = (): void => { - this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; + this.layoutDoc.lockedPosition = BoolCast(this.layoutDoc.lockedPosition) ? undefined : true; } listen = async () => { @@ -601,30 +627,21 @@ export class DocumentView extends DocComponent(Docu let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); + makes.push({ description: "Custom View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); + makes.push({ description: "Custom Field", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document, true), icon: "concierge-bell" }) makes.push({ description: "OnClick Button script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript(this.props.Document, "onClick", this._mainCont, obj.x, obj.y) }); makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); makes.push({ description: "OnClick foreach doc", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick", "docList(this.collectionContext.data).map(d => {", "});\n") }); - makes.push({ - description: "Into Portal", event: () => { - if (!DocListCast(this.props.Document.links).find(doc => { - if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true; - return false; - })) { - let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); - Doc.GetProto(this.props.Document).isButton = true; - } - }, icon: "window-restore" - }); - makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" }); + makes.push({ description: "Into Portal", event: this.makeIntoPortal, icon: "window-restore" }); + makes.push({ description: this.layoutDoc.ignoreClick ? "Selectable" : "Unselectable", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; - layoutItems.push({ description: `${this.props.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.props.Document.chromeStatus = (this.props.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.props.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.props.Document.autoHeight = !this.props.Document.autoHeight, icon: "plus" }); - layoutItems.push({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); - layoutItems.push({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); + 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.props.Document.ignoreAspect || !this.props.Document.nativeWidth || !this.props.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: "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.props.Document.isTemplate) { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index ccb8f4aa2..d4b784cac 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -459,7 +459,7 @@ export namespace Doc { } if (expandedTemplateLayout === undefined) { setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined && - (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0); + (dataDoc[expandedLayoutFieldKey] = !BoolCast(templateLayoutDoc.suppressTemplateInstance) ? Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]") : templateLayoutDoc), 0); } return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending. } @@ -528,7 +528,7 @@ export namespace Doc { !templateDoc.nativeWidth && (otherdoc.ignoreAspect = true); return otherdoc; } - export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc) { + export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc, useTemplateDoc?: boolean) { if (!templateDoc) { target.layout = undefined; target.nativeWidth = undefined; @@ -537,7 +537,7 @@ export namespace Doc { target.type = undefined; return; } - let temp = Doc.MakeDelegate(templateDoc); + 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); @@ -558,7 +558,7 @@ export namespace Doc { } } - export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) { + export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc, suppressTemplateFlag?: boolean) { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; @@ -576,6 +576,7 @@ export namespace Doc { fieldTemplate.templateField = metaKey; fieldTemplate.title = metaKey; fieldTemplate.isTemplate = true; + fieldTemplate.suppressTemplateInstance = suppressTemplateFlag; fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; fieldTemplate.backgroundLayout = backgroundLayout; /* move certain layout properties from the original data doc to the template layout to avoid @@ -585,6 +586,9 @@ export namespace Doc { fieldTemplate.singleColumn = BoolCast(fieldTemplate.singleColumn); fieldTemplate.nativeWidth = Cast(fieldTemplate.nativeWidth, "number"); fieldTemplate.nativeHeight = Cast(fieldTemplate.nativeHeight, "number"); + fieldTemplate.panX = 0; + fieldTemplate.panY = 0; + fieldTemplate.scale = 1; fieldTemplate.showTitle = "title"; setTimeout(() => fieldTemplate.proto = templateDataDoc); } -- cgit v1.2.3-70-g09d2 From 5a0794a74ce62612435133907395482f494747f4 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 11 Sep 2019 12:19:30 -0400 Subject: now support auto tagging --- .../apis/google_docs/GooglePhotosClientUtils.ts | 126 ++++++++++++++------- .../util/Import & Export/DirectoryImportBox.tsx | 7 +- src/client/views/MainView.tsx | 19 ++-- src/client/views/nodes/DocumentView.tsx | 3 +- src/new_fields/RichTextUtils.ts | 2 +- .../apis/google/CustomizedWrapper/filters.js | 46 ++++++++ src/server/apis/google/GooglePhotosUploadUtils.ts | 34 ++++-- src/server/credentials/google_docs_token.json | 2 +- src/server/index.ts | 2 +- 9 files changed, 168 insertions(+), 73 deletions(-) create mode 100644 src/server/apis/google/CustomizedWrapper/filters.js (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index b1de24d1a..a28b183d1 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -10,7 +10,10 @@ import { RichTextUtils } from "../../../new_fields/RichTextUtils"; import { EditorState } from "prosemirror-state"; import { FormattedTextBox } from "../../views/nodes/FormattedTextBox"; import { Docs, DocumentOptions } from "../../documents/Documents"; -import { MediaItemCreationResult, NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes"; +import { NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes"; +import { AssertionError } from "assert"; +import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; export namespace GooglePhotos { @@ -53,7 +56,14 @@ export namespace GooglePhotos { PERFORMANCES: 'PERFORMANCES', WHITEBOARDS: 'WHITEBOARDS', SCREENSHOTS: 'SCREENSHOTS', - UTILITY: 'UTILITY' + UTILITY: 'UTILITY', + ARTS: 'ARTS', + CRAFTS: 'CRAFTS', + FASHION: 'FASHION', + HOUSES: 'HOUSES', + GARDENS: 'GARDENS', + FLOWERS: 'FLOWERS', + HOLIDAYS: 'HOLIDAYS' }; export namespace Export { @@ -63,7 +73,15 @@ export namespace GooglePhotos { mediaItems: MediaItem[]; } - export const CollectionToAlbum = async (collection: Doc, title?: string, descriptionKey?: string): Promise> => { + export interface AlbumCreationOptions { + collection: Doc; + title?: string; + descriptionKey?: string; + tag?: boolean; + } + + export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise> => { + const { collection, title, descriptionKey, tag } = options; const dataDocument = Doc.GetProto(collection); const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField)); if (!images || !images.length) { @@ -71,9 +89,24 @@ export namespace GooglePhotos { } const resolved = title ? title : (StrCast(collection.title) || `Dash Collection (${collection[Id]}`); const { id } = await Create.Album(resolved); - const result = await Transactions.UploadImages(images, { id }, descriptionKey); - if (result) { - const mediaItems = result.newMediaItemResults.map(item => item.mediaItem); + const newMediaItemResults = await Transactions.UploadImages(images, { id }, descriptionKey); + if (newMediaItemResults) { + const mediaItems = newMediaItemResults.map(item => item.mediaItem); + if (mediaItems.length !== images.length) { + throw new AssertionError({ actual: mediaItems.length, expected: images.length }); + } + const idMapping = new Doc; + for (let i = 0; i < images.length; i++) { + const image = images[i]; + const mediaItem = mediaItems[i]; + image.googlePhotosId = mediaItem.id; + image.googlePhotosUrl = mediaItem.baseUrl || mediaItem.productUrl; + idMapping[mediaItem.id] = image; + } + collection.googlePhotosIdMapping = idMapping; + if (tag) { + await Query.AppendImageMetadata(collection); + } return { albumId: id, mediaItems }; } }; @@ -101,21 +134,32 @@ export namespace GooglePhotos { export namespace Query { - export const AppendImageMetadata = (sources: (Doc | string)[]) => { - let keys = Object.keys(ContentCategories); - let included: string[] = []; - let excluded: string[] = []; - for (let i = 0; i < keys.length; i++) { - for (let j = 0; j < keys.length; j++) { - let value = ContentCategories[keys[i] as keyof typeof ContentCategories]; - if (j === i) { - included.push(value); - } else { - excluded.push(value); + export const AppendImageMetadata = async (collection: Doc) => { + const idMapping = await Cast(collection.googlePhotosIdMapping, Doc); + if (!idMapping) { + throw new Error("Appending image metadata requires that the targeted collection have already been mapped to an album!"); + } + const images = await DocListCastAsync(collection.data); + images && images.forEach(image => image.googlePhotosTags = new List()); + const values = Object.values(ContentCategories); + for (let value of values) { + console.log("Searching for ", value); + const results = await Search({ included: [value] }); + if (results.mediaItems) { + console.log(`${results.mediaItems.length} found!`); + const ids = results.mediaItems.map(item => item.id); + for (let id of ids) { + const image = await Cast(idMapping[id], Doc); + if (image) { + const tags = Cast(image.googlePhotosTags, listSpec("string"))!; + if (!tags.includes(value)) { + tags.push(value); + console.log(`${value}: ${id}`); + } + } } } - //... - included = excluded = []; + console.log(); } }; @@ -125,20 +169,22 @@ export namespace GooglePhotos { } const DefaultSearchOptions: SearchOptions = { - pageSize: 20, + pageSize: 50, included: [], excluded: [], date: undefined, includeArchivedMedia: true, + excludeNonAppCreatedData: false, type: MediaType.ALL_MEDIA, }; export interface SearchOptions { pageSize: number; - included: ContentCategories[]; - excluded: ContentCategories[]; + included: string[]; + excluded: string[]; date: Opt; includeArchivedMedia: boolean; + excludeNonAppCreatedData: boolean; type: MediaType; } @@ -173,7 +219,7 @@ export namespace GooglePhotos { filters.setMediaTypeFilter(new photos.MediaTypeFilter(options.type || MediaType.ALL_MEDIA)); return new Promise(resolve => { - photos.mediaItems.search(filters, options.pageSize || 20).then(resolve); + photos.mediaItems.search(filters, options.pageSize || 100).then(resolve); }); }; @@ -183,7 +229,7 @@ export namespace GooglePhotos { } - export namespace Create { + namespace Create { export const Album = async (title: string) => { return (await endpoint()).albums.create(title); @@ -211,40 +257,34 @@ export namespace GooglePhotos { return uploads; }; - export const UploadThenFetch = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption") => { - const result = await UploadImages(sources, album, descriptionKey); - if (!result) { + export const UploadThenFetch = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption") => { + const newMediaItems = await UploadImages(sources, album, descriptionKey); + if (!newMediaItems) { return undefined; } - const baseUrls: string[] = await Promise.all(result.newMediaItemResults.map((result: any) => { - return new Promise(resolve => Query.GetImage(result.mediaItem.id).then(item => resolve(item.baseUrl))); + const baseUrls: string[] = await Promise.all(newMediaItems.map(item => { + return new Promise(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl))); })); return baseUrls; }; - export const UploadImages = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption"): Promise> => { + export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption"): Promise> => { if (album && "title" in album) { album = await Create.Album(album.title); } const media: MediaInput[] = []; sources.forEach(source => { - let url: string; - let description: string; - if (source instanceof Doc) { - const data = Cast(Doc.GetProto(source).data, ImageField); - if (!data) { - return; - } - url = data.url.href; - description = parseDescription(source, descriptionKey); - } else { - url = source; - description = Utils.GenerateGuid(); + const data = Cast(Doc.GetProto(source).data, ImageField); + if (!data) { + return; } + const url = data.url.href; + const description = parseDescription(source, descriptionKey); media.push({ url, description }); }); if (media.length) { - return PostToServer(RouteStore.googlePhotosMediaUpload, { media, album }); + const uploads: NewMediaItemResult[] = await PostToServer(RouteStore.googlePhotosMediaUpload, { media, album }); + return uploads; } }; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index d58c02ce5..348f216a5 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -74,13 +74,12 @@ export default class DirectoryImportBox extends React.Component handleSelection = async (e: React.ChangeEvent) => { runInAction(() => this.uploading = true); - let promises: Promise[] = []; let docs: Doc[] = []; let files = e.target.files; if (!files || files.length === 0) return; - let directory = (files.item(0) as any).webkitRelativePath.split("/", 1); + let directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0]; let validated: File[] = []; for (let i = 0; i < files.length; i++) { @@ -117,8 +116,6 @@ export default class DirectoryImportBox extends React.Component console.log(`(${this.quota - this.remaining}/${this.quota}) ${upload.name}`); })); - await GooglePhotos.Transactions.UploadImages(docs, { title: directory }); - for (let i = 0; i < docs.length; i++) { let doc = docs[i]; doc.size = sizes[i]; @@ -142,11 +139,11 @@ export default class DirectoryImportBox extends React.Component let parent = this.props.ContainingCollectionView; if (parent) { let importContainer = Docs.Create.StackingDocument(docs, options); + await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer }); importContainer.singleColumn = false; Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); DocumentManager.Instance.jumpToDocument(importContainer, true); - } runInAction(() => { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8d10a91ce..28edf181b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -130,7 +130,7 @@ export class MainView extends React.Component { window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); - // this.executeGooglePhotosRoutine(); + this.executeGooglePhotosRoutine(); reaction(() => { let workspaces = CurrentUserUtils.UserDocument.workspaces; @@ -149,14 +149,15 @@ export class MainView extends React.Component { }, { fireImmediately: true }); } - // executeGooglePhotosRoutine = async () => { - // let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; - // let doc = Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }); - // doc.caption = "Well isn't this a nice cat image!"; - // let photos = await GooglePhotos.endpoint(); - // let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id; - // console.log(await GooglePhotos.UploadImages([doc], { id: albumId })); - // } + executeGooglePhotosRoutine = async () => { + // let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; + // let doc = Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }); + // doc.caption = "Well isn't this a nice cat image!"; + // let photos = await GooglePhotos.endpoint(); + // let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id; + // console.log(await GooglePhotos.UploadImages([doc], { id: albumId })); + GooglePhotos.Query.Search({ included: [GooglePhotos.ContentCategories.ANIMALS] }).then(console.log); + } componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a51f783ad..a38f42751 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -594,7 +594,8 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); } if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) { - cm.addItem({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum(this.props.Document).then(console.log), icon: "caret-square-right" }); + cm.addItem({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" }); + cm.addItem({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.AppendImageMetadata(this.props.Document), icon: "caret-square-right" }); } let existingMake = ContextMenu.Instance.findByDescription("Make..."); let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 27737782b..6afe4ddfd 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -298,7 +298,7 @@ export namespace RichTextUtils { const length = node.nodeSize; const attrs = node.attrs; const uri = attrs.src; - const baseUrls = await GooglePhotos.Transactions.UploadThenFetch([uri]); + const baseUrls = await GooglePhotos.Transactions.UploadThenFetch([Docs.Create.ImageDocument(uri)]); if (!baseUrls) { continue; } diff --git a/src/server/apis/google/CustomizedWrapper/filters.js b/src/server/apis/google/CustomizedWrapper/filters.js new file mode 100644 index 000000000..576a90b75 --- /dev/null +++ b/src/server/apis/google/CustomizedWrapper/filters.js @@ -0,0 +1,46 @@ +'use strict'; + +const DateFilter = require('../common/date_filter'); +const MediaTypeFilter = require('./media_type_filter'); +const ContentFilter = require('./content_filter'); + +class Filters { + constructor(includeArchivedMedia = false) { + this.includeArchivedMedia = includeArchivedMedia; + } + + setDateFilter(dateFilter) { + this.dateFilter = dateFilter; + return this; + } + + setContentFilter(contentFilter) { + this.contentFilter = contentFilter; + return this; + } + + setMediaTypeFilter(mediaTypeFilter) { + this.mediaTypeFilter = mediaTypeFilter; + return this; + } + + setIncludeArchivedMedia(includeArchivedMedia) { + this.includeArchivedMedia = includeArchivedMedia; + return this; + } + + toJSON() { + return { + dateFilter: this.dateFilter instanceof DateFilter ? this.dateFilter.toJSON() : this.dateFilter, + mediaTypeFilter: this.mediaTypeFilter instanceof MediaTypeFilter ? + this.mediaTypeFilter.toJSON() : + this.mediaTypeFilter, + contentFilter: this.contentFilter instanceof ContentFilter ? + this.contentFilter.toJSON() : + this.contentFilter, + includeArchivedMedia: this.includeArchivedMedia + }; + } +} + +module.exports = Filters; \ No newline at end of file diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index 447ed23ac..1a8adc836 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -5,7 +5,7 @@ import { Utils } from '../../../Utils'; import * as path from 'path'; import { Opt } from '../../../new_fields/Doc'; import * as sharp from 'sharp'; -import { MediaItemCreationResult } from './SharedTypes'; +import { MediaItemCreationResult, NewMediaItemResult } from './SharedTypes'; const uploadDirectory = path.join(__dirname, "../../public/files/"); @@ -55,24 +55,34 @@ export namespace GooglePhotosUploadUtils { - export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }): Promise => { - return new Promise((resolve, reject) => { + export const CreateMediaItems = async (newMediaItems: any[], album?: { id: string }): Promise => { + const quota = newMediaItems.length; + let handled = 0; + const newMediaItemResults: NewMediaItemResult[] = []; + while (handled < quota) { + const cap = Math.min(newMediaItems.length, handled + 50); + const batch = newMediaItems.slice(handled, cap); + console.log(batch.length); const parameters = { method: 'POST', headers: headers('json'), uri: prepend('mediaItems:batchCreate'), - body: { newMediaItems } as any, + body: { newMediaItems: batch } as any, json: true }; album && (parameters.body.albumId = album.id); - request(parameters, (error, _response, body) => { - if (error) { - reject(error); - } else { - resolve(body); - } - }); - }); + newMediaItemResults.push(...(await new Promise((resolve, reject) => { + request(parameters, (error, _response, body) => { + if (error) { + reject(error); + } else { + resolve(body); + } + }); + })).newMediaItemResults); + handled = cap; + } + return { newMediaItemResults }; }; } diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 22d57d744..0c06f68b7 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.GlyAB5T3dgJqWuYBcLaT94wQo7MZkmzJQZxDB2sSU95mdhW24E3diuFdLeNsUDVI57D3S765RweMnL98d-fdgu1dRxpzkV_J_3rLih99pZ8A4d6jVdm1354UT4py_w","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568161931458} \ No newline at end of file +{"access_token":"ya29.GlyAB7VxfbK7fwV9-lqu9NZ1-p73aC8KaEXAYGHFOIIgAhx40CCUgS07vy485y7O0x9RwK-7FL6P547SscD5bVlTlJkclP-9uupKxDaeez7Tc7o2pJwt6bgJlbbw7w","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568220636395} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 2c3e76c55..507463841 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -831,7 +831,7 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => { return _error(res, tokenError); } GooglePhotosUploadUtils.CreateMediaItems(newMediaItems, req.body.album).then( - mediaItems => _success(res, mediaItems), + result => _success(res, result.newMediaItemResults), error => _error(res, mediaError, error) ); }); -- cgit v1.2.3-70-g09d2 From 186d7aed7b99b1373e99b51cfe0c88c8167c8290 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Sep 2019 22:52:07 -0400 Subject: fixed some template issues specifically for self-templates. --- src/client/views/DocumentDecorations.tsx | 22 +++++++++++++------- .../views/collections/CollectionBaseView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++++++++++-------- src/client/views/nodes/DocumentView.tsx | 6 +++--- src/client/views/nodes/FormattedTextBox.tsx | 3 +-- src/new_fields/Doc.ts | 5 ++--- 6 files changed, 37 insertions(+), 25 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index fe409d9a6..814d718be 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -30,6 +30,7 @@ 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'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -145,13 +146,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let fieldTemplateView = SelectionManager.SelectedDocuments()[0]; SelectionManager.DeselectAll(); let fieldTemplate = fieldTemplateView.props.Document; - let docTemplate = fieldTemplateView.props.ContainingCollectionView!.props.Document; - let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length); - let proto = Doc.GetProto(docTemplate); - Doc.MakeTemplate(fieldTemplate, metaKey, proto); - if (text.startsWith(">>")) { - proto.detailedLayout = proto.layout; - proto.miniLayout = ImageBox.LayoutString(metaKey); + let containerView = fieldTemplateView.props.ContainingCollectionView; + if (containerView) { + let docTemplate = containerView.props.Document; + 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)); + } + Doc.MakeTemplate(fieldTemplate, metaKey, proto); + if (text.startsWith(">>")) { + proto.detailedLayout = proto.layout; + proto.miniLayout = ImageBox.LayoutString(metaKey); + } } } else { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 5829f0626..b7036b3ff 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -127,7 +127,7 @@ export class CollectionBaseView extends React.Component { 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 && v[Id] === doc[Id]) ? i : p, -1); + let 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/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index eb7ab64f8..07dd1cae7 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -74,10 +74,10 @@ export class CollectionFreeFormDocumentView extends DocComponent { - let br = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.borderRounding : this.props.Document.borderRounding); + let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding); if (br.endsWith("%")) { let percent = Number(br.substr(0, br.length - 1)) / 100; - let nativeDim = Math.min(NumCast(this.props.Document.nativeWidth), NumCast(this.props.Document.nativeHeight)); + let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight)); let minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight())); return minDim; } @@ -89,6 +89,12 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; + get layoutDoc() { + // 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 this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; + } + render() { const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return ( @@ -98,15 +104,15 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) }); let metaKey = "data"; let proto = Doc.GetProto(docTemplate); - Doc.MakeTemplate(fieldTemplate, metaKey, proto, true); + Doc.MakeTemplate(fieldTemplate, metaKey, proto); - Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, true); + Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); } @undoBatch @@ -634,7 +634,7 @@ export class DocumentView extends DocComponent(Docu let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: "Custom Document View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); - makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document, true), icon: "concierge-bell" }) + makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document), icon: "concierge-bell" }) makes.push({ description: "Into Portal", event: this.makeIntoPortal, icon: "window-restore" }); makes.push({ description: this.layoutDoc.ignoreClick ? "Selectable" : "Unselectable", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 194026a08..0ea36cdc2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -289,8 +289,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") { - if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc; - else this.props.Document.layout = draggedDoc; + this.props.Document.layout = draggedDoc; draggedDoc.isTemplate = true; e.stopPropagation(); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index d4b784cac..e94b9f1eb 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -459,7 +459,7 @@ export namespace Doc { } if (expandedTemplateLayout === undefined) { setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined && - (dataDoc[expandedLayoutFieldKey] = !BoolCast(templateLayoutDoc.suppressTemplateInstance) ? Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]") : templateLayoutDoc), 0); + (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0); } return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending. } @@ -558,7 +558,7 @@ export namespace Doc { } } - export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc, suppressTemplateFlag?: boolean) { + export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; @@ -576,7 +576,6 @@ export namespace Doc { fieldTemplate.templateField = metaKey; fieldTemplate.title = metaKey; fieldTemplate.isTemplate = true; - fieldTemplate.suppressTemplateInstance = suppressTemplateFlag; fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; fieldTemplate.backgroundLayout = backgroundLayout; /* move certain layout properties from the original data doc to the template layout to avoid -- cgit v1.2.3-70-g09d2 From 6cff2800f0aa2069787ff43831d18898c851bf3b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Sep 2019 03:11:48 -0400 Subject: semicolon --- src/new_fields/Doc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/new_fields') diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e94b9f1eb..74d7b3ae7 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -328,7 +328,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) { -- cgit v1.2.3-70-g09d2 From e241e61d6521ff5d63de1292f2b4269493f5d7cc Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 13 Sep 2019 00:55:39 -0400 Subject: added "publish" option to convert a document to a recognizable id --- src/client/documents/Documents.ts | 28 +++++++++++++++++++++++++++- src/client/views/DocumentDecorations.tsx | 9 +++++++-- src/client/views/nodes/DocumentView.tsx | 3 ++- src/client/views/nodes/FormattedTextBox.tsx | 4 ++-- src/new_fields/Doc.ts | 8 ++++---- 5 files changed, 42 insertions(+), 10 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 602a7f9ad..28e5e5f40 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -20,7 +20,7 @@ import { AttributeTransformationModel } from "../northstar/core/attribute/Attrib import { AggregateFunction } from "../northstar/model/idea/idea"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { IconBox } from "../views/nodes/IconBox"; -import { Field, Doc, Opt } from "../../new_fields/Doc"; +import { Field, Doc, Opt, DocListCastAsync } from "../../new_fields/Doc"; import { OmitKeys, JSONUtils } from "../../Utils"; import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; @@ -607,6 +607,32 @@ export namespace Docs { export namespace DocUtils { + export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) { + if (targetID.startsWith("-")) { + targetID = targetID.substr(1, targetID.length - 1); + Doc.GetProto(promoteDoc).title = targetID; + } + DocServer.GetRefField(targetID).then(doc => { + let copy = doc instanceof Doc ? doc : Doc.MakeCopy(promoteDoc, true, targetID); + !doc && (Doc.GetProto(copy).title = targetID); + addDoc && addDoc(copy); + !doc && remDoc && remDoc(promoteDoc); + if (!doc) { + DocListCastAsync(promoteDoc.links).then(links => { + links && links.map(async link => { + if (link) { + let a1 = await Cast(link.anchor1, Doc); + if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy; + let a2 = await Cast(link.anchor2, Doc); + if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy; + 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) { if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; let sv = DocumentManager.Instance.getDocumentView(source); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4ab2ade8e..589d69264 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -3,12 +3,12 @@ import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faChe import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, DocListCastAsync } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; import { URLField } from '../../new_fields/URLField'; import { emptyFunction, Utils } from "../../Utils"; -import { Docs } from "../documents/Documents"; +import { Docs, DocUtils } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; @@ -31,6 +31,7 @@ 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'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -142,6 +143,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (text[0] === '#') { this._fieldKey = text.slice(1, text.length); this._title = this.selectionTitle; + } else if (text.startsWith("::")) { + let targetID = text.slice(2, text.length); + let promoteDoc = SelectionManager.SelectedDocuments()[0]; + DocUtils.Publish(promoteDoc.props.Document, targetID, promoteDoc.props.addDocument, promoteDoc.props.removeDocument); } else if (text.startsWith(">")) { let fieldTemplateView = SelectionManager.SelectedDocuments()[0]; SelectionManager.DeselectAll(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7b9ed12a7..44e9b3180 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -438,7 +438,6 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeNativeViewClicked = (): void => { - (this.dataDoc || Doc.GetProto(this.props.Document)).customLayout = this.props.Document.layout; this.props.Document.layout = this.props.Document.nativeLayout; this.props.Document.type = this.props.Document.nativeType; this.props.Document.nativeWidth = this.props.Document.nativeNativeWidth; @@ -473,6 +472,7 @@ export class DocumentView extends DocComponent(Docu Doc.MakeTemplate(fieldTemplate, metaKey, proto); Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); + Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.props.Document.layout; } }); } @@ -690,6 +690,7 @@ export class DocumentView extends DocComponent(Docu } }); + cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, StrCast(this.props.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[] = []; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c07461e13..04d24fe8c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -293,7 +293,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } recordKeyHandler = (e: KeyboardEvent) => { - if (this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { + if (SelectionManager.SelectedDocuments().length && this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { if (e.key === "R" && e.altKey) { e.stopPropagation(); e.preventDefault(); @@ -473,7 +473,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._fontFamily = rules.font; this._fontSize = rules.size; setTimeout(() => { - if (this._editorView!.state.doc.childCount) { + if (this._editorView!.state.doc.childCount && this._proseRef) { let tr = this._editorView!.state.tr; let n = new NodeSelection(this._editorView!.state.doc.resolve(0)); if (this._editorView!.state.doc.textContent === "") { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e94b9f1eb..29925feb8 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -475,13 +475,13 @@ export namespace Doc { return { layout: layoutDoc, data: resolvedDataDoc }; } - export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc { - const copy = new Doc; + export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc { + const copy = new Doc(copyProtoId, true); Object.keys(doc).forEach(key => { const field = ProxyField.WithoutProxy(() => doc[key]); if (key === "proto" && copyProto) { - if (field instanceof Doc) { - copy[key] = Doc.MakeCopy(field); + if (doc[key] instanceof Doc) { + copy[key] = Doc.MakeCopy(doc[key]!, false); } } else { if (field instanceof RefField) { -- cgit v1.2.3-70-g09d2 From 106d7ca39e36fc114f79fd5fef27998a68fd3d5b Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Sep 2019 11:15:01 -0400 Subject: fixed video w/ templates. changed headings with text boxes, tweaked MakeTemplate titling --- src/client/documents/Documents.ts | 11 ++++------- src/client/util/RichTextRules.ts | 3 ++- src/client/views/nodes/DocumentView.tsx | 17 +++++++++++------ src/client/views/nodes/ImageBox.tsx | 7 +------ src/client/views/nodes/VideoBox.tsx | 11 +++++++---- src/new_fields/Doc.ts | 3 ++- 6 files changed, 27 insertions(+), 25 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 28e5e5f40..9db2ac558 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -121,7 +121,7 @@ export namespace Docs { }], [DocumentType.IMG, { layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, - options: { nativeWidth: 600, curPage: 0 } + options: { curPage: 0 } }], [DocumentType.WEB, { layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, @@ -137,7 +137,7 @@ export namespace Docs { }], [DocumentType.VID, { layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType }, - options: { nativeWidth: 600, curPage: 0 }, + options: { curPage: 0 }, }], [DocumentType.AUDIO, { layout: { view: AudioBox }, @@ -608,13 +608,10 @@ export namespace Docs { export namespace DocUtils { export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) { - if (targetID.startsWith("-")) { - targetID = targetID.substr(1, targetID.length - 1); - Doc.GetProto(promoteDoc).title = targetID; - } + targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(targetID).then(doc => { let copy = doc instanceof Doc ? doc : Doc.MakeCopy(promoteDoc, true, targetID); - !doc && (Doc.GetProto(copy).title = targetID); + !doc && (copy.title = undefined) && (Doc.GetProto(copy).title = targetID); addDoc && addDoc(copy); !doc && remDoc && remDoc(promoteDoc); if (!doc) { diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 8ceb56f2f..c0c62463a 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -64,7 +64,8 @@ export const inpRules = { let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; + (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]) })) }), diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 44e9b3180..31f1c7583 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -464,7 +464,9 @@ export class DocumentView extends DocComponent(Docu !custom.nativeWidth && (this.props.Document.ignoreAspect = true); } else { let options = { title: "data", width: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) + 25, x: -NumCast(this.props.Document.width) / 2, y: -NumCast(this.props.Document.height) / 2, }; - let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : + this.props.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: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) }); let metaKey = "data"; @@ -576,9 +578,12 @@ export class DocumentView extends DocComponent(Docu if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true; return false; })) { - let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); - Doc.GetProto(this.props.Document).isButton = true; + let portalID = (this.props.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); + DocServer.GetRefField(portalID).then(existingPortal => { + let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: portalID }); + DocUtils.MakeLink(this.props.Document, portal, undefined, portalID); + Doc.GetProto(this.props.Document).isButton = true; + }) } } @@ -646,8 +651,8 @@ export class DocumentView extends DocComponent(Docu let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: this.props.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); - if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.layout instanceof Doc) { - layoutItems.push({ description: "Make View of Metadata Field", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document), icon: "concierge-bell" }) + if (this.props.DataDoc) { + layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), 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" }); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 19788c21a..95f304641 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -262,13 +262,8 @@ export class ImageBox extends DocComponent(ImageD onDotDown(index: number) { this.Document.curPage = index; } - - @computed get fieldExtensionDoc() { - return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); - } - @computed private get url() { - let data = Cast(Doc.GetProto(this.props.Document).data, ImageField); + let data = Cast(Doc.GetProto(this.props.Document)[this.props.fieldKey], ImageField); return data ? data.url.href : undefined; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 3f4ee8960..96f011eff 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { InkTool } from "../../../new_fields/InkField"; import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; import { Utils } from "../../../Utils"; @@ -204,7 +204,7 @@ export class VideoBox extends DocComponent(VideoD } } specificContextMenu = (e: React.MouseEvent): void => { - let field = Cast(this.Document[this.props.fieldKey], VideoField); + let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); if (field) { let url = field.url.href; let subitems: ContextMenuProps[] = []; @@ -216,7 +216,7 @@ export class VideoBox extends DocComponent(VideoD } @computed get content() { - let field = Cast(this.Document[this.props.fieldKey], VideoField); + let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ?
Loading
: @@ -228,7 +228,7 @@ export class VideoBox extends DocComponent(VideoD } @computed get youtubeVideoId() { - let field = Cast(this.Document[this.props.fieldKey], VideoField); + let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : ""; } @@ -269,6 +269,8 @@ export class VideoBox extends DocComponent(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 youtubeContent() { this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; @@ -281,6 +283,7 @@ export class VideoBox extends DocComponent(VideoD } render() { + Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); return
{this.youtubeVideoId ? this.youtubeContent : this.content}
; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 29925feb8..6f7453bbe 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -558,7 +558,8 @@ export namespace Doc { } } - export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) { + export function MakeTemplate(fieldTemplate: Doc, metaKeyRaw: string, templateDataDoc: Doc) { + let metaKey = metaKeyRaw.replace(/^-/, "").replace(/\([0-9]*\)$/, ""); // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; -- cgit v1.2.3-70-g09d2 From 3665945fd4ef1b1dfc300f9188fd358df76e38b3 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Sep 2019 11:43:03 -0400 Subject: preserved data from field being converted to metadata template field --- src/client/views/DocumentDecorations.tsx | 3 ++- src/client/views/nodes/DocumentView.tsx | 5 ++--- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/new_fields/Doc.ts | 14 ++++++++------ 4 files changed, 13 insertions(+), 11 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 589d69264..6d63e8f73 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -160,7 +160,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const fd = fieldTemplate.data; fd instanceof ObjectField && (Doc.GetProto(containerView.props.DataDoc)[metaKey] = ObjectField.MakeCopy(fd)); } - Doc.MakeTemplate(fieldTemplate, metaKey, proto); + fieldTemplate.title = metaKey; + Doc.MakeMetadataFieldTemplate(fieldTemplate, proto); if (text.startsWith(">>")) { proto.detailedLayout = proto.layout; proto.miniLayout = ImageBox.LayoutString(metaKey); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 31f1c7583..0a1367b56 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -469,9 +469,8 @@ export class DocumentView extends DocComponent(Docu Docs.Create.ImageDocument("http://www.cs.brown.edu", options); let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) }); - let metaKey = "data"; let proto = Doc.GetProto(docTemplate); - Doc.MakeTemplate(fieldTemplate, metaKey, proto); + Doc.MakeMetadataFieldTemplate(fieldTemplate, proto); Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.props.Document.layout; @@ -652,7 +651,7 @@ export class DocumentView extends DocComponent(Docu let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: this.props.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); if (this.props.DataDoc) { - layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), 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" }); diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index f80f414b1..ee70942de 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -198,7 +198,7 @@ export class KeyValueBox extends React.Component { return; } let previousViewType = fieldTemplate.viewType; - Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(parentStackingDoc)); + Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc)); previousViewType && (fieldTemplate.viewType = previousViewType); Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6f7453bbe..1a3d689bb 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -558,24 +558,24 @@ export namespace Doc { } } - export function MakeTemplate(fieldTemplate: Doc, metaKeyRaw: string, templateDataDoc: Doc) { - let metaKey = metaKeyRaw.replace(/^-/, "").replace(/\([0-9]*\)$/, ""); + export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc) { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) + let metadataFieldName = StrCast(fieldTemplate.title); let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; if (fieldTemplate.layout instanceof Doc) { fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout); } - let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`); + let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`); if (backgroundLayout) { - backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`); + backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`); } let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate; layoutDelegate.layout = layout; - fieldTemplate.templateField = metaKey; - fieldTemplate.title = metaKey; + fieldTemplate.templateField = metadataFieldName; + fieldTemplate.title = metadataFieldName; fieldTemplate.isTemplate = true; fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; fieldTemplate.backgroundLayout = backgroundLayout; @@ -590,6 +590,8 @@ export namespace Doc { fieldTemplate.panY = 0; fieldTemplate.scale = 1; fieldTemplate.showTitle = "title"; + let data = fieldTemplate.data; + !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (templateDataDoc[metadataFieldName] = ObjectField.MakeCopy(data)); setTimeout(() => fieldTemplate.proto = templateDataDoc); } -- cgit v1.2.3-70-g09d2 From 233893698083cbcfcf39ddad8b57049aeb1ba842 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Sep 2019 14:18:55 -0400 Subject: refactored how ruleProvider's work. overloaded custom template for creating metadata fields --- src/client/util/RichTextRules.ts | 8 +++---- src/client/util/TooltipTextMenu.tsx | 4 ++-- src/client/views/DocumentDecorations.tsx | 11 ++------- src/client/views/InkingControl.tsx | 5 ++-- src/client/views/MainOverlayTextBox.tsx | 2 +- src/client/views/MainView.tsx | 2 ++ src/client/views/TemplateMenu.tsx | 16 ++++++------- .../views/collections/CollectionDockingView.tsx | 1 + .../views/collections/CollectionSchemaView.tsx | 1 + src/client/views/collections/CollectionSubView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 27 +++++++++++----------- .../collections/collectionFreeForm/MarqueeView.tsx | 2 -- .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 13 +++++++---- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/FormattedTextBox.tsx | 2 +- .../views/presentationview/PresentationElement.tsx | 1 + src/client/views/search/SearchItem.tsx | 1 + src/new_fields/Doc.ts | 2 +- 19 files changed, 51 insertions(+), 51 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index c0c62463a..c727eec73 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -61,7 +61,7 @@ export const inpRules = { new RegExp(/^#([0-9]+)\s$/), (state, match, start, end) => { let size = Number(match[1]); - let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = Number(match[1]); @@ -74,7 +74,7 @@ export const inpRules = { (state, match, start, end) => { let node = (state.doc.resolve(start) as any).nodeAfter; let sm = state.storedMarks || undefined; - let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "center"; @@ -88,7 +88,7 @@ export const inpRules = { (state, match, start, end) => { let node = (state.doc.resolve(start) as any).nodeAfter; let sm = state.storedMarks || undefined; - let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "left"; @@ -100,7 +100,7 @@ export const inpRules = { (state, match, start, end) => { let node = (state.doc.resolve(start) as any).nodeAfter; let sm = state.storedMarks || undefined; - let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "right"; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index c376b6f86..84d045e6f 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -496,7 +496,7 @@ export class TooltipTextMenu { if (markType.name[0] === 'p') { let size = this.fontSizeToNum.get(markType); if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc; + let ruleProvider = this.editorProps.ruleProvider; let heading = NumCast(this.editorProps.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleSize_" + heading] = size; @@ -505,7 +505,7 @@ export class TooltipTextMenu { else { let fontName = this.fontStylesToName.get(markType); if (fontName) { this.updateFontStyleDropdown(fontName); } - let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc; + let ruleProvider = this.editorProps.ruleProvider; let heading = NumCast(this.editorProps.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleFont_" + heading] = fontName; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 6d63e8f73..ebdf2a749 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -365,14 +365,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) { let docViews = SelectionManager.ViewsSortedVertically(); let topDocView = docViews[0]; - let ind = topDocView.templates.indexOf(Templates.Bullet.Layout); - if (ind !== -1) { - topDocView.templates.splice(ind, 1); - topDocView.props.Document.subBulletDocs = undefined; - } else { - topDocView.addTemplate(Templates.Bullet); - topDocView.props.Document.subBulletDocs = new List(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!)); - } + topDocView.props.Document.subBulletDocs = new List(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!)); } } this._removeIcon = false; @@ -439,7 +432,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let usingRule = false; SelectionManager.SelectedDocuments().map(dv => { let cv = dv.props.ContainingCollectionView; - let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); + let ruleProvider = cv && cv.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); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 867735c0b..86d0fc0be 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -71,9 +71,8 @@ export class InkingControl extends React.Component { targetDoc.backgroundColor = this._selectedColor; if (view.props.Document.heading) { let cv = view.props.ContainingCollectionView; - let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); - let parback = cv && StrCast(cv.props.Document.backgroundColor); - cv && parback && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)); + let ruleProvider = cv && (Cast(cv.props.ruleProvider, Doc) as Doc); + cv && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)); // if (parback && cv && parback.indexOf("rgb") !== -1) { // let parcol = Utils.fromRGBAstr(parback); // let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b); diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index c3a2cb214..71fb2707d 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -72,7 +72,6 @@ export class MainOverlayTextBox extends React.Component if (this._textTargetDiv) { this._textTargetDiv.style.color = this._textColor; } - this._textAutoHeight = autoHeight; this.TextFieldKey = textFieldKey!; let txf = tx ? tx : () => Transform.Identity(); this._textXf = txf; @@ -143,6 +142,7 @@ export class MainOverlayTextBox extends React.Component Document={FormattedTextBox.InputBoxOverlay.props.Document} 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} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b64986084..2cec1c052 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -322,6 +322,7 @@ export class MainView extends React.Component { addDocTab={emptyFunction} pinToPres={emptyFunction} onClick={undefined} + ruleProvider={undefined} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} @@ -385,6 +386,7 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} removeDocument={undefined} + ruleProvider={undefined} onClick={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 0ef1a137d..060191e29 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -51,16 +51,16 @@ export class TemplateMenu extends React.Component { @observable private _hidden: boolean = true; dragRef = React.createRef(); - constructor(props: TemplateMenuProps) { - super(props); - } - toggleCustom = (e: React.MouseEvent): void => { this.props.docs.map(dv => { - if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) { - dv.makeCustomViewClicked(); - } else if (dv.Document.nativeLayout) { - dv.makeNativeViewClicked(); + if (dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.DataDoc) { + Doc.MakeMetadataFieldTemplate(dv.props.Document, dv.props.ContainingCollectionView.props.DataDoc) + } else { + if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) { + dv.makeCustomViewClicked(); + } else if (dv.Document.nativeLayout) { + dv.makeNativeViewClicked(); + } } }); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index fb8b0c41b..166fa0811 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -631,6 +631,7 @@ export class DockedFrameRenderer extends React.Component { bringToFront={emptyFunction} addDocument={undefined} removeDocument={undefined} + ruleProvider={undefined} ContentScaling={this.contentScaling} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 9d83aa6c1..dca1d7c1d 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -995,6 +995,7 @@ export class CollectionSchemaPreview extends React.Component(schemaCtor: (doc: Doc) => T) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9a8ae3535..4a3e5039a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -257,18 +257,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed - let heading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); - heading = heading === 0 || this.childDocs.length === 0 ? 1 : heading === 1 ? 2 : 0; + let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0; if (heading === 0) { let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 : DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0); - heading = !sorted.length ? 1 : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); - } - newBox.heading = heading; - - if (Cast(this.props.Document.ruleProvider, Doc) as Doc) { - newBox.ruleProvider = Doc.GetProto(Cast(this.props.Document.ruleProvider, Doc) as Doc); + heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } + !this.props.Document.isRuleProvider && (newBox.heading = heading); this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { @@ -698,6 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, + ruleProvider: this.props.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, onClick: this.props.onClick, ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, @@ -723,6 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, + ruleProvider: this.props.ruleProvider, onClick: this.props.onClick, ScreenToLocalTransform: this.getTransform, renderDepth: this.props.renderDepth, @@ -817,6 +815,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (pair.layout && !(pair.data instanceof Promise)) { prev.push({ ele: , @@ -873,6 +872,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, "arrange contents"); } + autoFormat = () => { + this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider; + this.childDocs.map(child => child.heading = undefined); + } + analyzeStrokes = async () => { let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); if (!data) { @@ -900,11 +904,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, icon: !this.props.Document.useClusters ? "braille" : "braille" }); - layoutItems.push({ - description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, - event: () => this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider, - icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard" - }); + layoutItems.push({ description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: !this.props.Document.isRuleProvider ? "chalkboard" : "chalkboard" }); layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); @@ -1034,7 +1034,6 @@ class CollectionFreeFormOverlayView extends React.Component boolean }> { @computed get backgroundView() { - let props = this.props; return (); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 4308497a1..e46e8cb88 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -329,7 +329,6 @@ export class MarqueeView extends React.Component selected = [newCollection]; newCollection.x = bounds.left + bounds.width; summary.proto!.subBulletDocs = new List(selected); - summary.templates = new List([Templates.Bullet.Layout]); 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; @@ -356,7 +355,6 @@ export class MarqueeView extends React.Component this.props.addLiveTextDocument(summary); } else { - newCollection.ruleProvider = this.props.container.props.Document.isRuleProvider ? this.props.container.props.Document : this.props.container.props.Document.ruleProvider; this.props.addDocument(newCollection, false); this.props.selectDocuments([newCollection]); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 082e5c5e3..4872a7aa1 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -74,7 +74,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { - let ruleProvider = this.props.Document.ruleProvider as Doc; + let ruleProvider = this.props.ruleProvider; let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined; let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding); br = !br && ruleRounding ? ruleRounding : br; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0816cb813..cc04c5a9f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction, runInAction, trace, observable } from "mobx"; +import { action, computed, IReactionDisposer, reaction, 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"; @@ -9,12 +9,13 @@ import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { RouteStore } from '../../../server/RouteStore'; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from "../../util/DocumentManager"; @@ -35,12 +36,10 @@ import { MainView } from '../MainView'; import { OverlayView } from '../OverlayView'; import { ScriptBox } from '../ScriptBox'; import { ScriptingRepl } from '../ScriptingRepl'; -import { Template } from "./../Templates"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { DocumentType } from '../../documents/DocumentTypes'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -89,6 +88,7 @@ export interface DocumentViewProps { renderDepth: number; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; ContentScaling: () => number; + ruleProvider: Doc | undefined; PanelWidth: () => number; PanelHeight: () => number; focus: (doc: Doc, willZoom: boolean, scale?: number) => void; @@ -470,6 +470,9 @@ export class DocumentView extends DocComponent(Docu this.props.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + fieldTemplate.backgroundColor = StrCast(this.props.Document.backgroundColor); + fieldTemplate.heading = 1; + let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) }); let proto = Doc.GetProto(docTemplate); Doc.MakeMetadataFieldTemplate(fieldTemplate, proto); @@ -800,7 +803,7 @@ export class DocumentView extends DocComponent(Docu render() { - let ruleProvider = this.props.Document.ruleProvider as Doc; + let ruleProvider = this.props.ruleProvider; let ruleColor = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(this.props.Document.heading)]) : undefined; let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined; let colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor; diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index d9774303b..943d181d6 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; + ruleProvider: Doc | undefined; Document: Doc; DataDoc?: Doc; onClick?: ScriptField; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d39291743..a0dc054cf 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -458,7 +458,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._rulesReactionDisposer = reaction(() => { - let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); + let ruleProvider = this.props.ruleProvider; let heading = NumCast(this.props.Document.heading); if (ruleProvider instanceof Doc) { return { diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 80aa25f48..7be44faf6 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -351,6 +351,7 @@ export default class PresentationElement extends React.Component { Document={this.props.doc} addDocument={returnFalse} removeDocument={returnFalse} + ruleProvider={undefined} ScreenToLocalTransform={Transform.Identity} addDocTab={returnFalse} pinToPres={returnFalse} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 1a3d689bb..5b22a62a1 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -560,7 +560,7 @@ export namespace Doc { export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc) { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) - let metadataFieldName = StrCast(fieldTemplate.title); + let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, ""); let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; if (fieldTemplate.layout instanceof Doc) { -- cgit v1.2.3-70-g09d2 From 8a4163eedcfd37a5e245c710ffc674c1d16088f8 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Sep 2019 18:51:13 -0400 Subject: fixed applying template to template --- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 10 +++++++--- src/new_fields/Doc.ts | 17 +++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 591a507eb..3dd384c80 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -285,7 +285,7 @@ export class DocumentView extends DocComponent(Docu } onClick = async (e: React.MouseEvent) => { - if (e.nativeEvent.cancelBubble) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing. + if (e.nativeEvent.cancelBubble || SelectionManager.IsSelected(this)) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing. if (this.onClickHandler && this.onClickHandler.script) { e.stopPropagation(); this.onClickHandler.script.run({ this: this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ffb829825..3e8b01dfd 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -285,10 +285,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; if (draggedDoc && draggedDoc.type === DocumentType.TEXT) { - // let m = Doc.MakeDelegate(draggedDoc); // under construction - // m.layout = m.layout.replace(/fieldKey={}) - this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; draggedDoc.isTemplate = true; + if (typeof (draggedDoc.layout) === "string") { + let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); + layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); + this.props.Document.layout = layoutDelegateToOverrideFieldKey; + } else { + this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + } e.stopPropagation(); } } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 5b22a62a1..474644dba 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -1,4 +1,4 @@ -import { observable, ObservableMap, runInAction } from "mobx"; +import { observable, ObservableMap, runInAction, action } from "mobx"; import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; import { DocumentType } from "../client/documents/DocumentTypes"; @@ -566,18 +566,13 @@ export namespace Doc { if (fieldTemplate.layout instanceof Doc) { fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout); } - let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`); if (backgroundLayout) { backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`); } - let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate; - layoutDelegate.layout = layout; - fieldTemplate.templateField = metadataFieldName; fieldTemplate.title = metadataFieldName; fieldTemplate.isTemplate = true; - fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; fieldTemplate.backgroundLayout = backgroundLayout; /* move certain layout properties from the original data doc to the template layout to avoid inheriting them from the template's data doc which may also define these fields for its own use. @@ -591,8 +586,14 @@ export namespace Doc { fieldTemplate.scale = 1; fieldTemplate.showTitle = "title"; let data = fieldTemplate.data; - !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (templateDataDoc[metadataFieldName] = ObjectField.MakeCopy(data)); - setTimeout(() => fieldTemplate.proto = templateDataDoc); + setTimeout(action(() => { + !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (Doc.GetProto(templateDataDoc)[metadataFieldName] = ObjectField.MakeCopy(data)); + let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`); + let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate; + layoutDelegate.layout = layout; + fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; + fieldTemplate.proto = templateDataDoc; + }), 0); } export function ToggleDetailLayout(d: Doc) { -- cgit v1.2.3-70-g09d2 From d9fa64c229b13f9c8121a40b76d180775be5f6c6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 14 Sep 2019 00:33:18 -0400 Subject: fixed color assignments for rule providers. no titles are shown when promoting to a custom layout --- src/client/views/InkingControl.tsx | 32 ++++++++++------------ .../collectionFreeForm/CollectionFreeFormView.tsx | 16 +++++++++++ src/client/views/nodes/DocumentView.tsx | 4 +-- src/client/views/nodes/FormattedTextBox.tsx | 16 ++++++----- src/new_fields/Doc.ts | 5 ++-- 5 files changed, 44 insertions(+), 29 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 94cc1f06c..57dad5e6b 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -51,14 +51,17 @@ export class InkingControl extends React.Component { let oldColors = selected.map(view => { 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); - if (view.props.ContainingCollectionView) { - if (!view.props.ContainingCollectionView.props.Document.colorPalette) { + 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)", "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; - let colorPalette = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")); - if (!colorPalette) view.props.ContainingCollectionView.props.Document.colorPalette = new List(defaultPalette); + let colorPalette = Cast(cv.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) cv.props.Document.colorPalette = new List(defaultPalette); } - let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[]; + let cp = Cast(cv.props.Document.colorPalette, listSpec("string")) as string[]; let closest = 0; let dist = 10000000; let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor)); @@ -71,20 +74,13 @@ export class InkingControl extends React.Component { } } cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")"; - view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp); - targetDoc.backgroundColor = cp[closest]; - } else - targetDoc.backgroundColor = this._selectedColor; - if (view.props.Document.heading) { - let cv = view.props.ContainingCollectionView; - let ruleProvider = cv && (Cast(cv.props.ruleProvider, Doc) as Doc); - cv && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)); - // if (parback && cv && parback.indexOf("rgb") !== -1) { - // let parcol = Utils.fromRGBAstr(parback); - // let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b); - // cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = color.hsl.s - hsl.s); - // } + cv.props.Document.colorPalette = new List(cp); + matchedColor = cp[closest]; + ruleProvider = (view.props.Document.heading && cv && cv.props.ruleProvider) ? cv.props.ruleProvider : undefined; + ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } + !ruleProvider && (targetDoc.backgroundColor = matchedColor); + return { target: targetDoc, previous: oldColor diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4a3e5039a..ad91eb007 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -875,6 +875,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { autoFormat = () => { this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider; this.childDocs.map(child => child.heading = undefined); + this.childDocs.map(child => { + DocListCast(child.layout instanceof Doc ? child.layout.data : child.data).map(heading => { + let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); + let disp = (child.data_ext instanceof Doc) && pair.layout && (child.data_ext[`Layout[${pair.layout[Id]}]`] as Doc); + if (disp && NumCast(disp.heading) > 0) { + if (disp.backgroundColor !== disp.defaultBackgroundColor) { + Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(disp.heading)] = disp.backgroundColor; + } + } + if (pair.layout && NumCast(pair.layout.heading) > 0) { + if (pair.layout.backgroundColor !== pair.layout.defaultBackgroundColor) { + Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(pair.layout.heading)] = pair.layout.backgroundColor; + } + } + }) + }) } analyzeStrokes = async () => { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3dd384c80..d37a0ee59 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -475,7 +475,7 @@ export class DocumentView extends DocComponent(Docu let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) }); let proto = Doc.GetProto(docTemplate); - Doc.MakeMetadataFieldTemplate(fieldTemplate, proto); + Doc.MakeMetadataFieldTemplate(fieldTemplate, proto, true); Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.props.Document.layout; @@ -811,7 +811,7 @@ export class DocumentView extends DocComponent(Docu let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ? this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : - ruleColor ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); + ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); let foregroundColor = StrCast(this.layoutDoc.color); var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%"; var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 3e8b01dfd..2e05268a6 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -285,13 +285,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; if (draggedDoc && draggedDoc.type === DocumentType.TEXT) { - draggedDoc.isTemplate = true; - if (typeof (draggedDoc.layout) === "string") { - let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); - layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); - this.props.Document.layout = layoutDelegateToOverrideFieldKey; - } else { - this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + if (!Doc.AreProtosEqual(draggedDoc, this.props.Document)) { + draggedDoc.isTemplate = true; + if (typeof (draggedDoc.layout) === "string") { + let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); + layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); + this.props.Document.layout = layoutDelegateToOverrideFieldKey; + } else { + this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + } } e.stopPropagation(); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 474644dba..b6b3bf73e 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -558,7 +558,7 @@ export namespace Doc { } } - export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc) { + export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false) { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, ""); let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); @@ -584,7 +584,7 @@ export namespace Doc { fieldTemplate.panX = 0; fieldTemplate.panY = 0; fieldTemplate.scale = 1; - fieldTemplate.showTitle = "title"; + fieldTemplate.showTitle = suppressTitle ? undefined : "title"; let data = fieldTemplate.data; setTimeout(action(() => { !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (Doc.GetProto(templateDataDoc)[metadataFieldName] = ObjectField.MakeCopy(data)); @@ -592,6 +592,7 @@ export namespace Doc { let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate; layoutDelegate.layout = layout; fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; + if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor; fieldTemplate.proto = templateDataDoc; }), 0); } -- cgit v1.2.3-70-g09d2 From 4ec15a9576a27b8290fb37b6959cb13ae76feeaa Mon Sep 17 00:00:00 2001 From: bob Date: Sat, 14 Sep 2019 11:18:51 -0400 Subject: various fixes for templating and publishing document ids --- src/client/documents/Documents.ts | 39 ++++++++++------- .../CollectionStackingViewFieldColumn.tsx | 3 ++ .../views/collections/CollectionTreeView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 49 +++++++++++++++++----- src/client/views/nodes/FormattedTextBox.tsx | 28 +++++++++---- src/new_fields/Doc.ts | 23 ++++++++++ 6 files changed, 110 insertions(+), 33 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2eff73b87..e7ac1e321 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -610,23 +610,30 @@ export namespace DocUtils { export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) { targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(targetID).then(doc => { - let copy = doc instanceof Doc ? doc : Doc.MakeCopy(promoteDoc, true, targetID); - !doc && (copy.title = undefined) && (Doc.GetProto(copy).title = targetID); - addDoc && addDoc(copy); - !doc && remDoc && remDoc(promoteDoc); - if (!doc) { - DocListCastAsync(promoteDoc.links).then(links => { - links && links.map(async link => { - if (link) { - let a1 = await Cast(link.anchor1, Doc); - if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy; - let a2 = await Cast(link.anchor2, Doc); - if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy; - LinkManager.Instance.deleteLink(link); - LinkManager.Instance.addLink(link); - } + if (promoteDoc !== doc) { + let copy = doc as Doc; + if (copy) { + Doc.Overwrite(promoteDoc, copy, true); + } else { + copy = Doc.MakeCopy(promoteDoc, true, targetID); + } + !doc && (copy.title = undefined) && (Doc.GetProto(copy).title = targetID); + addDoc && addDoc(copy); + remDoc && remDoc(promoteDoc); + if (!doc) { + DocListCastAsync(promoteDoc.links).then(links => { + links && links.map(async link => { + if (link) { + let a1 = await Cast(link.anchor1, Doc); + if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy; + let a2 = await Cast(link.anchor2, Doc); + if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy; + LinkManager.Instance.deleteLink(link); + LinkManager.Instance.addLink(link); + } + }) }) - }) + } } }); } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index bc4fe7dd7..185bec7a2 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -155,6 +155,9 @@ export class CollectionStackingViewFieldColumn extends React.Component NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; + newDoc.heading = heading; return this.props.parent.props.addDocument(newDoc); } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b1e063997..6217ef859 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -201,6 +201,7 @@ class TreeView extends React.Component { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" }); } ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" }); ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); e.stopPropagation(); e.preventDefault(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d37a0ee59..d90224eae 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -441,22 +441,39 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeNativeViewClicked = (): void => { this.props.Document.layout = this.props.Document.nativeLayout; - this.props.Document.type = this.props.Document.nativeType; - this.props.Document.nativeWidth = this.props.Document.nativeNativeWidth; - this.props.Document.nativeHeight = this.props.Document.nativeNativeHeight; - this.props.Document.ignoreAspect = this.props.Document.nativeIgnoreAspect; this.props.Document.nativeLayout = undefined; - this.props.Document.nativeNativeWidth = undefined; - this.props.Document.nativeNativeHeight = undefined; - this.props.Document.nativeIgnoreAspect = undefined; + this.props.Document.type = this.props.Document.nativeType; + + this.props.Document.customAutoHeight = this.props.Document.autoHeight; + this.props.Document.customWidth = this.props.Document.nativeWidth; + this.props.Document.customHeight = this.props.Document.nativeHeight; + this.props.Document.customNativeWidth = this.props.Document.nativeWidth; + this.props.Document.customNativeHeight = this.props.Document.nativeHeight; + this.props.Document.customIgnoreAspect = this.props.Document.ignoreAspect; + + this.props.Document.autoHeight = this.props.Document.nonCustomAutoHeight; + this.props.Document.width = this.props.Document.nonCustomWidth; + this.props.Document.height = this.props.Document.nonCustomHeight; + this.props.Document.nativeWidth = this.props.Document.nonCustomNativeWidth; + this.props.Document.nativeHeight = this.props.Document.nonCustomNativeHeight; + this.props.Document.ignoreAspect = this.props.Document.nonCustomIgnoreAspect; + this.props.Document.nonCustomAutoHeight = undefined; + this.props.Document.nonCustomWidth = undefined; + this.props.Document.nonCustomHeight = undefined; + this.props.Document.nonCustomNativeWidth = undefined; + this.props.Document.nonCustomNativeHeight = undefined; + this.props.Document.nonCustomIgnoreAspect = undefined; } @undoBatch makeCustomViewClicked = (): void => { this.props.Document.nativeLayout = this.props.Document.layout; this.props.Document.nativeType = this.props.Document.type; - this.props.Document.nativeNativeWidth = this.props.Document.nativeWidth; - this.props.Document.nativeNativeHeight = this.props.Document.nativeHeight; - this.props.Document.nativeIgnoreAspect = this.props.Document.ignoreAspect; + this.props.Document.nonCustomAutoHeight = this.props.Document.autoHeight; + this.props.Document.nonCustomWidth = this.props.Document.nativeWidth; + this.props.Document.nonCustomHeight = this.props.Document.nativeHeight; + this.props.Document.nonCustomNativeWidth = this.props.Document.nativeWidth; + this.props.Document.nonCustomNativeHeight = this.props.Document.nativeHeight; + this.props.Document.nonCustomIgnoreAspect = this.props.Document.ignoreAspect; PromiseValue(Cast(this.props.Document.customLayout, Doc)).then(custom => { if (custom) { this.props.Document.type = DocumentType.TEMPLATE; @@ -464,6 +481,18 @@ export class DocumentView extends DocComponent(Docu !custom.nativeWidth && (this.props.Document.nativeWidth = 0); !custom.nativeHeight && (this.props.Document.nativeHeight = 0); !custom.nativeWidth && (this.props.Document.ignoreAspect = true); + this.props.Document.autoHeight = this.props.Document.autoHeight; + this.props.Document.width = this.props.Document.customWidth; + this.props.Document.height = this.props.Document.customHeight; + this.props.Document.nativeWidth = this.props.Document.customNativeWidth; + this.props.Document.nativeHeight = this.props.Document.customNativeHeight; + this.props.Document.ignoreAspect = this.props.Document.ignoreAspect; + 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: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) + 25, x: -NumCast(this.props.Document.width) / 2, y: -NumCast(this.props.Document.height) / 2, }; let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 2e05268a6..77e29632e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -169,6 +169,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + linkOnDeselect: Map = new Map(); + + doLinkOnDeselect() { + Array.from(this.linkOnDeselect.entries()).map(entry => { + let key = entry[0]; + let value = entry[1]; + let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + DocServer.GetRefField(value).then(doc => { + DocServer.GetRefField(id).then(linkDoc => { + this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); + DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); + if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } + else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true); + }) + }); + }) + this.linkOnDeselect.clear(); + } + dispatchTransaction = (tx: Transaction) => { if (this._editorView) { let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); @@ -183,15 +202,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (split.length > 1 && split[1]) { let key = split[0]; let value = split[split.length - 1]; + this.linkOnDeselect.set(key, value); let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - DocServer.GetRefField(value).then(doc => { - DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); - 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); - }) - }); const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); const mval = this._editorView!.state.schema.marks.metadataVal.create(); let offset = (tx.selection.to === range!.end - 1 ? -1 : 0); @@ -875,6 +888,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._undoTyping.end(); this._undoTyping = undefined; } + this.doLinkOnDeselect(); } onKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Escape") { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index b6b3bf73e..eef14ad25 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -475,6 +475,29 @@ export namespace Doc { return { layout: layoutDoc, data: resolvedDataDoc }; } + export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { + Object.keys(doc).forEach(key => { + const field = ProxyField.WithoutProxy(() => doc[key]); + if (key === "proto" && copyProto) { + if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) { + overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto); + } + } else { + if (field instanceof RefField) { + overwrite[key] = field; + } else if (field instanceof ObjectField) { + overwrite[key] = ObjectField.MakeCopy(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happend... + } else { + overwrite[key] = field; + } + } + }); + + return overwrite; + } + export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc { const copy = new Doc(copyProtoId, true); Object.keys(doc).forEach(key => { -- cgit v1.2.3-70-g09d2 From 42500ed737e17c2f973a653d59491eb33d6ba66b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 14 Sep 2019 13:49:34 -0400 Subject: beginnings of formatting conversion on push --- src/client/views/nodes/FormattedTextBox.tsx | 3 +- src/new_fields/RichTextUtils.ts | 153 ++++++++++++++------------ src/server/credentials/google_docs_token.json | 2 +- 3 files changed, 83 insertions(+), 75 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8a623e648..23f615f32 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -284,10 +284,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let target = de.data.embeddableSourceDoc; // We're dealing with an internal document drop let url = de.data.urlField.url.href; - let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image; + let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] }))); DocUtils.MakeLink(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]); + this.tryUpdateHeight(); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 6afe4ddfd..178cce839 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -5,7 +5,7 @@ import { docs_v1 } from "googleapis"; import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; import { Opt } from "./Doc"; -import * as Color from "color"; +import Color from "color"; import { sinkListItem } from "prosemirror-schema-list"; import { Utils, PostToServer } from "../Utils"; import { RouteStore } from "../server/RouteStore"; @@ -91,27 +91,11 @@ export namespace RichTextUtils { export namespace GoogleDocs { export const Export = async (state: EditorState): Promise => { - let nodes: { [type: string]: Node[] } = { - text: [], - image: [] - }; + const nodes: Node[] = []; let text = ToPlainText(state); - let content = state.doc.content; - content.forEach(node => node.content.forEach(node => { - const type = node.type.name; - let existing = nodes[type]; - if (existing) { - existing.push(node); - } else { - nodes[type] = [node]; - } - })); - let linkRequests = ExtractLinks(nodes.text); - let imageRequests = await ExtractImages(nodes.image); - return { - text, - requests: [...linkRequests, ...imageRequests] - }; + state.doc.content.forEach(node => node.content.forEach(child => nodes.push(child))); + const requests = await marksToStyle(nodes); + return { text, requests }; }; type BulletPosition = { value: number, sinks: number }; @@ -186,8 +170,10 @@ export namespace RichTextUtils { const uploads = await PostToServer(RouteStore.googlePhotosMediaDownload, { mediaItems: inlineUrls }); for (let i = 0; i < uploads.length; i++) { - const src = Utils.prepend(`/files/${uploads[i].fileNames.clean}`); - state = state.apply(state.tr.insert(0, schema.nodes.image.create({ src, width: inlineUrls[i].width }))); + const src = Utils.fileUrl(uploads[i].fileNames.clean); + const width = inlineUrls[i].width; + const imageNode = schema.nodes.image.create({ src, width }); + state = state.apply(state.tr.insert(0, imageNode)); } return { title, text, state }; @@ -265,71 +251,92 @@ export namespace RichTextUtils { return marks; }; - interface LinkInformation { - startIndex: number; - endIndex: number; - bold: boolean; - url: string; - } - - const ExtractLinks = (nodes: Node[]) => { - let links: docs_v1.Schema$Request[] = []; + const marksToStyle = async (nodes: Node[]) => { + let requests: docs_v1.Schema$Request[] = []; let position = 1; for (let node of nodes) { - let link, length = node.nodeSize; - let marks = node.marks; - if (marks.length && (link = marks.find(mark => mark.type.name === "link"))) { - links.push(Encode({ - startIndex: position, - endIndex: position + length, - url: link.attrs.href, - bold: false + const length = node.nodeSize; + const marks = node.marks; + const attrs = node.attrs; + const textStyle: docs_v1.Schema$TextStyle = {}; + const information: LinkInformation = { + startIndex: position, + endIndex: position + length, + textStyle + }; + if (marks.length) { + + const link = marks.find(mark => mark.type.name === "link"); + if (link) { + textStyle.link = { url: link.attrs.href }; + textStyle.foregroundColor = fromRgb(0, 0, 1); + textStyle.bold = true; + } + const bold = marks.find(mark => mark.type.name === "strong"); + bold && (textStyle.bold = true); + const foregroundColor = marks.find(mark => mark.type.name === "pFontColor"); + foregroundColor && (textStyle.foregroundColor = fromHex(foregroundColor.attrs.color)); + } + requests.push(EncodeStyleUpdate(information)); + if (node.type.name === "image") { + requests.push(await EncodeImage({ + startIndex: position + length, + uri: attrs.src, + width: attrs.width })); } position += length; } - return links; + return requests; }; - const ExtractImages = async (nodes: Node[]) => { - const images: docs_v1.Schema$Request[] = []; - let position = 1; - for (let node of nodes) { - const length = node.nodeSize; - const attrs = node.attrs; - const uri = attrs.src; - const baseUrls = await GooglePhotos.Transactions.UploadThenFetch([Docs.Create.ImageDocument(uri)]); - if (!baseUrls) { - continue; - } - images.push({ - insertInlineImage: { - uri: baseUrls[0], - objectSize: { width: { magnitude: parseFloat(attrs.width.replace("px", "")), unit: "PT" } }, - location: { index: position + length } - } - }); - position += length; - } - return images; + interface LinkInformation { + startIndex: number; + endIndex: number; + textStyle: docs_v1.Schema$TextStyle; + } + + interface ImageInformation { + startIndex: number; + width: number; + uri: string; + } + + const fromRgb = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { + return { color: { rgbColor: { red, green, blue } } }; + }; + + const fromHex = (color: string): docs_v1.Schema$OptionalColor => { + const converted = new Color().hex(color).rgb(); + const { red, blue, green } = converted; + return fromRgb(red(), blue(), green()); }; - const Encode = (information: LinkInformation) => { + const EncodeStyleUpdate = (information: LinkInformation): docs_v1.Schema$Request => { + const { startIndex, endIndex, textStyle } = information; return { updateTextStyle: { fields: "*", - range: { - startIndex: information.startIndex, - endIndex: information.endIndex - }, - textStyle: { - bold: true, - link: { url: information.url }, - foregroundColor: { color: { rgbColor: { red: 0.0, green: 0.0, blue: 1.0 } } } - } - } + range: { startIndex, endIndex }, + textStyle + } as docs_v1.Schema$UpdateTextStyleRequest }; }; + + const EncodeImage = async (information: ImageInformation) => { + const source = [Docs.Create.ImageDocument(information.uri)]; + const baseUrls = await GooglePhotos.Transactions.UploadThenFetch(source); + if (baseUrls) { + return { + insertInlineImage: { + uri: baseUrls[0], + objectSize: { width: { magnitude: information.width, unit: "PT" } }, + location: { index: information.startIndex } + } + }; + } + return {}; + }; } } \ No newline at end of file diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 293404853..c8fd3bbf5 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.ImCDB68jWP3W09KYIKQwzO_9eizRTpSx9UhP4tIPbSeMvjDsDyNOOl2hmffWHBrRFqz8kgaDxl_6lp-rlfbArI0x1Wm13J2h7wm5kBvOlCToqR-5qoINHetlWisCb11ig9A","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568480323588} \ No newline at end of file +{"access_token":"ya29.GlyDBwCmpO9R1fAOMIzpdZiCWhEeaDHiJOPy7sNRAo-vAIqzIk7zy1DLdOhSFWaBQrbmewSOJZPvbBUAxqdDELc_aW_BsjwXFbxiTd4Us_N8IWkPDCtUeBmLAZjodA","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568484185591} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 037098aca0993bee6f986b592c17aa54a7225905 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 14 Sep 2019 17:53:17 -0400 Subject: rich text google photos transfer improvments --- src/Utils.ts | 2 +- .../util/Import & Export/DirectoryImportBox.tsx | 6 +- .../CollectionFreeFormLinkView.scss | 2 +- .../CollectionFreeFormLinkView.tsx | 2 +- src/new_fields/RichTextUtils.ts | 121 +++++++++++++++------ src/server/apis/google/GooglePhotosUploadUtils.ts | 2 +- src/server/credentials/google_docs_token.json | 2 +- src/server/index.ts | 2 +- 8 files changed, 101 insertions(+), 38 deletions(-) (limited to 'src/new_fields') diff --git a/src/Utils.ts b/src/Utils.ts index ec2bee9bf..60f18eac2 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -39,7 +39,7 @@ export class Utils { } public static fileUrl(filename: string): string { - return this.prepend(`/file/${filename}`); + return this.prepend(`/files/${filename}`); } public static CorsProxy(url: string): string { diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 87c187162..44075ecdd 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -156,7 +156,11 @@ export default class DirectoryImportBox extends React.Component if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); } else { - const headers = ["title", "size"].map(key => new SchemaHeaderField(key)); + const headers = [ + new SchemaHeaderField("title", "yellow"), + new SchemaHeaderField("size", "blue"), + new SchemaHeaderField("googlePhotosTags", "green") + ]; importContainer = Docs.Create.SchemaDocument(headers, docs, options); } runInAction(() => this.phase = 'External: uploading files to Google Photos...'); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index fc5212edd..2a64a7afb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,7 +1,7 @@ .collectionfreeformlinkview-linkLine { stroke: black; transform: translate(10000px,10000px); - opacity: 0.5; + // opacity: 0.5; pointer-events: all; } .collectionfreeformlinkview-linkCircle { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 790c6694b..f19243bd6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -50,7 +50,7 @@ export class CollectionFreeFormLinkView extends React.Component {/* ([ + const StyleToMark = new Map([ ["bold", "strong"], ["italic", "em"], ["foregroundColor", "pFontColor"] ]); - const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { + const MarkToStyle = new Map([ + ["strong", "bold"], + ["em", "italic"], + ["pFontColor", "foregroundColor"], + ["timesNewRoman", "weightedFontFamily"], + ["georgia", "weightedFontFamily"], + ["comicSans", "weightedFontFamily"], + ["tahoma", "weightedFontFamily"], + ["impact", "weightedFontFamily"] + ]); + + const FontFamilyMapping = new Map([ + ["timesNewRoman", "Times New Roman"], + ["arial", "Arial"], + ["georgia", "Georgia"], + ["comicSans", "Comic Sans MS"], + ["tahoma", "Tahoma"], + ["impact", "Impact"] + ]); + + const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle): Opt => { if (!textStyle) { return undefined; } @@ -236,12 +256,12 @@ export namespace RichTextUtils { let targeted = key as keyof docs_v1.Schema$TextStyle; if (value = textStyle[targeted]) { let attributes: any = {}; - let converted = MarkMapping.get(targeted) || targeted; + let converted = StyleToMark.get(targeted) || targeted; value.url && (attributes.href = value.url); if (value.color) { - let object: { [key: string]: number } = value.color.rgbColor; - attributes.color = Color.rgb(Object.values(object).map(value => value * 255)).hex(); + let object = value.color.rgbColor; + attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex(); } let mark = schema.mark(schema.marks[converted], attributes); @@ -251,45 +271,69 @@ export namespace RichTextUtils { return marks; }; - const marksToStyle = async (nodes: Node[]) => { + const ignored = ["user_mark"]; + + const marksToStyle = async (nodes: Node[]): Promise => { let requests: docs_v1.Schema$Request[] = []; let position = 1; for (let node of nodes) { - const length = node.nodeSize; - const marks = node.marks; - const attrs = node.attrs; + const { marks, attrs, nodeSize } = node; const textStyle: docs_v1.Schema$TextStyle = {}; const information: LinkInformation = { startIndex: position, - endIndex: position + length, + endIndex: position + nodeSize, textStyle }; if (marks.length) { - - const link = marks.find(mark => mark.type.name === "link"); - if (link) { - textStyle.link = { url: link.attrs.href }; - textStyle.foregroundColor = fromRgb(0, 0, 1); - textStyle.bold = true; + let mark: Mark; + const markMap = BuildMarkMap(marks); + Object.keys(schema.marks).map(markName => { + if (!ignored.includes(markName) && (mark = markMap[markName])) { + const converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle; + let value: any = true; + if (converted) { + const { attrs } = mark; + switch (converted) { + case "link": + value = { url: attrs.href }; + textStyle.foregroundColor = fromRgb.blue; + textStyle.bold = true; + break; + case "fontSize": + value = attrs.fontSize; + break; + case "foregroundColor": + value = fromHex(attrs.color); + break; + case "weightedFontFamily": + value = { fontFamily: FontFamilyMapping.get(markName) }; + } + textStyle[converted] = value; + } + } + }); + if (Object.keys(textStyle).length) { + requests.push(EncodeStyleUpdate(information)); } - const bold = marks.find(mark => mark.type.name === "strong"); - bold && (textStyle.bold = true); - const foregroundColor = marks.find(mark => mark.type.name === "pFontColor"); - foregroundColor && (textStyle.foregroundColor = fromHex(foregroundColor.attrs.color)); } - requests.push(EncodeStyleUpdate(information)); if (node.type.name === "image") { requests.push(await EncodeImage({ - startIndex: position + length, + startIndex: position + nodeSize, uri: attrs.src, width: attrs.width })); } - position += length; + position += nodeSize; } return requests; }; + const BuildMarkMap = (marks: Mark[]) => { + const markMap: { [type: string]: Mark } = {}; + marks.forEach(mark => markMap[mark.type.name] = mark); + return markMap; + }; + interface LinkInformation { startIndex: number; endIndex: number; @@ -302,14 +346,29 @@ export namespace RichTextUtils { uri: string; } - const fromRgb = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { - return { color: { rgbColor: { red, green, blue } } }; - }; + namespace fromRgb { + + export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { + return { + color: { + rgbColor: { + red: red / 255, + green: green / 255, + blue: blue / 255 + } + } + }; + }; + + export const red = convert(255, 0, 0); + export const green = convert(0, 255, 0); + export const blue = convert(0, 0, 255); + + } const fromHex = (color: string): docs_v1.Schema$OptionalColor => { - const converted = new Color().hex(color).rgb(); - const { red, blue, green } = converted; - return fromRgb(red(), blue(), green()); + const c = Color(color); + return fromRgb.convert(c.red(), c.green(), c.blue()); }; const EncodeStyleUpdate = (information: LinkInformation): docs_v1.Schema$Request => { diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index e1478a097..f582cebd2 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -80,7 +80,7 @@ export namespace GooglePhotosUploadUtils { }); })).newMediaItemResults; }; - const newMediaItemResults = await newMediaItems.convertInBatchesAtInterval(50, createFromUploadTokens, 1); + const newMediaItemResults = await newMediaItems.convertInBatchesAtInterval(50, createFromUploadTokens, 0.1); return { newMediaItemResults }; }; diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index c8fd3bbf5..330d27141 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.GlyDBwCmpO9R1fAOMIzpdZiCWhEeaDHiJOPy7sNRAo-vAIqzIk7zy1DLdOhSFWaBQrbmewSOJZPvbBUAxqdDELc_aW_BsjwXFbxiTd4Us_N8IWkPDCtUeBmLAZjodA","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568484185591} \ No newline at end of file +{"access_token":"ya29.GlyEBxgqsCRjX9SAJGGss3EtfrPgwSjeMsfsuwJqTk7o4GRrBpwU0eQXXBNgPdAPRSrJzuVgAqWxap9kKrtkpf2tuHxk7Ml9Jblj48tU0BN2X0lMh66S2EIRhLnQnw","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568501067486} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index b18059053..2e60d9be7 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -863,7 +863,7 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => { return newMediaItems; }; - const newMediaItems = await mediaInput.convertInBatchesAtInterval(25, dispatchUpload, 1); + const newMediaItems = await mediaInput.convertInBatchesAtInterval(25, dispatchUpload, 0.1); if (failed) { return _error(res, tokenError); -- cgit v1.2.3-70-g09d2 From c2f749238e9db63b81c3bec08a14dd6006ab876f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 14 Sep 2019 21:00:37 -0400 Subject: sharing routine for docs and final formatting fixes --- src/Utils.ts | 4 + .../CollectionFreeFormLinkView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 9 +- src/new_fields/RichTextUtils.ts | 153 +++++++++++++-------- src/server/credentials/google_docs_token.json | 2 +- 5 files changed, 108 insertions(+), 62 deletions(-) (limited to 'src/new_fields') diff --git a/src/Utils.ts b/src/Utils.ts index 60f18eac2..a842f5a20 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -42,6 +42,10 @@ export class Utils { return this.prepend(`/files/${filename}`); } + public static shareUrl(documentId: string): string { + return this.prepend(`/doc/${documentId}?sharing=true`); + } + public static CorsProxy(url: string): string { return this.prepend(RouteStore.corsProxy + "/") + encodeURIComponent(url); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f19243bd6..12771d11e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -50,7 +50,7 @@ export class CollectionFreeFormLinkView extends React.Component {/* this.props.addDocTab(document, undefined, location ? location : "onRight"), NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + return; + // } } if (targetContext) { DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 84744db2f..555c41b67 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -4,7 +4,7 @@ import { RichTextField } from "./RichTextField"; import { docs_v1 } from "googleapis"; import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; -import { Opt } from "./Doc"; +import { Opt, Doc } from "./Doc"; import Color = require('color'); import { sinkListItem } from "prosemirror-schema-list"; import { Utils, PostToServer } from "../Utils"; @@ -12,6 +12,11 @@ import { RouteStore } from "../server/RouteStore"; import { Docs } from "../client/documents/Documents"; import { schema } from "../client/util/RichTextSchema"; import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; +import { SchemaHeaderField } from "./SchemaHeaderField"; +import { DocServer } from "../client/DocServer"; +import { Cast } from "./Types"; +import { Id } from "./FieldSymbols"; +import { DocumentView } from "../client/views/nodes/DocumentView"; export namespace RichTextUtils { @@ -91,9 +96,15 @@ export namespace RichTextUtils { export namespace GoogleDocs { export const Export = async (state: EditorState): Promise => { - const nodes: Node[] = []; + const nodes: (Node | null)[] = []; let text = ToPlainText(state); - state.doc.content.forEach(node => node.content.forEach(child => nodes.push(child))); + state.doc.content.forEach(node => { + if (!node.childCount) { + nodes.push(null); + } else { + node.content.forEach(child => nodes.push(child)); + } + }); const requests = await marksToStyle(nodes); return { text, requests }; }; @@ -223,30 +234,11 @@ export namespace RichTextUtils { const StyleToMark = new Map([ ["bold", "strong"], ["italic", "em"], - ["foregroundColor", "pFontColor"] - ]); - - const MarkToStyle = new Map([ - ["strong", "bold"], - ["em", "italic"], - ["pFontColor", "foregroundColor"], - ["timesNewRoman", "weightedFontFamily"], - ["georgia", "weightedFontFamily"], - ["comicSans", "weightedFontFamily"], - ["tahoma", "weightedFontFamily"], - ["impact", "weightedFontFamily"] + ["foregroundColor", "pFontColor"], + ["fontSize", "pFontSize"] ]); - const FontFamilyMapping = new Map([ - ["timesNewRoman", "Times New Roman"], - ["arial", "Arial"], - ["georgia", "Georgia"], - ["comicSans", "Comic Sans MS"], - ["tahoma", "Tahoma"], - ["impact", "Impact"] - ]); - - const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle): Opt => { + const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { if (!textStyle) { return undefined; } @@ -263,20 +255,53 @@ export namespace RichTextUtils { let object = value.color.rgbColor; attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex(); } + if (value.magnitude) { + attributes.fontSize = value.magnitude; + } - let mark = schema.mark(schema.marks[converted], attributes); + let mapped = schema.marks[converted]; + if (!mapped) { + alert(`No mapping found for ${converted}!`); + return; + } + + let mark = schema.mark(mapped, attributes); mark && marks.push(mark); } }); return marks; }; + const MarkToStyle = new Map([ + ["strong", "bold"], + ["em", "italic"], + ["pFontColor", "foregroundColor"], + ["timesNewRoman", "weightedFontFamily"], + ["georgia", "weightedFontFamily"], + ["comicSans", "weightedFontFamily"], + ["tahoma", "weightedFontFamily"], + ["impact", "weightedFontFamily"] + ]); + + const FontFamilyMapping = new Map([ + ["timesNewRoman", "Times New Roman"], + ["arial", "Arial"], + ["georgia", "Georgia"], + ["comicSans", "Comic Sans MS"], + ["tahoma", "Tahoma"], + ["impact", "Impact"] + ]); + const ignored = ["user_mark"]; - const marksToStyle = async (nodes: Node[]): Promise => { + const marksToStyle = async (nodes: (Node | null)[]): Promise => { let requests: docs_v1.Schema$Request[] = []; let position = 1; for (let node of nodes) { + if (node === null) { + position += 2; + continue; + } const { marks, attrs, nodeSize } = node; const textStyle: docs_v1.Schema$TextStyle = {}; const information: LinkInformation = { @@ -284,37 +309,55 @@ export namespace RichTextUtils { endIndex: position + nodeSize, textStyle }; - if (marks.length) { - let mark: Mark; - const markMap = BuildMarkMap(marks); - Object.keys(schema.marks).map(markName => { - if (!ignored.includes(markName) && (mark = markMap[markName])) { - const converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle; - let value: any = true; - if (converted) { - const { attrs } = mark; - switch (converted) { - case "link": - value = { url: attrs.href }; - textStyle.foregroundColor = fromRgb.blue; - textStyle.bold = true; - break; - case "fontSize": - value = attrs.fontSize; - break; - case "foregroundColor": - value = fromHex(attrs.color); - break; - case "weightedFontFamily": - value = { fontFamily: FontFamilyMapping.get(markName) }; + let mark: Mark; + const markMap = BuildMarkMap(marks); + for (let markName of Object.keys(schema.marks)) { + if (ignored.includes(markName) || !(mark = markMap[markName])) { + continue; + } + let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle; + let value: any = true; + if (!converted) { + continue; + } + const { attrs } = mark; + switch (converted) { + case "link": + let url = attrs.href; + const delimiter = "/doc/"; + const alreadyShared = "?sharing=true"; + if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { + const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); + if (linkDoc instanceof Doc) { + const target = (await Cast(linkDoc.anchor2, Doc))!; + const exported = Doc.MakeAlias(target); + DocumentView.makeCustomViewClicked(exported); + target && (url = Utils.shareUrl(exported[Id])); + linkDoc.anchor2 = exported; } - textStyle[converted] = value; } - } - }); - if (Object.keys(textStyle).length) { - requests.push(EncodeStyleUpdate(information)); + value = { url }; + textStyle.foregroundColor = fromRgb.blue; + textStyle.bold = true; + break; + case "fontSize": + value = attrs.fontSize; + break; + case "foregroundColor": + value = fromHex(attrs.color); + break; + case "weightedFontFamily": + value = { fontFamily: FontFamilyMapping.get(markName) }; + } + let matches: RegExpExecArray | null; + if ((matches = /p(\d+)/g.exec(markName)) !== null) { + converted = "fontSize"; + value = { magnitude: parseInt(matches[1]), unit: "PT" }; } + textStyle[converted] = value; + } + if (Object.keys(textStyle).length) { + requests.push(EncodeStyleUpdate(information)); } if (node.type.name === "image") { requests.push(await EncodeImage({ diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 330d27141..265c07c69 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.GlyEBxgqsCRjX9SAJGGss3EtfrPgwSjeMsfsuwJqTk7o4GRrBpwU0eQXXBNgPdAPRSrJzuVgAqWxap9kKrtkpf2tuHxk7Ml9Jblj48tU0BN2X0lMh66S2EIRhLnQnw","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568501067486} \ No newline at end of file +{"access_token":"ya29.ImCEByJgpv8e3CNTi-EwwqOtXUB1sNsOyOxM4WEyTybrQzCKc80SkjQZgb9gFCChbA7fFsdvewVAS_SiohfFziPOV4-YffeO417NS2CQf1cksmCQQBWxmL3i7qvQgz4VkdI","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568509688650} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From d7012323e87c21a25c29d89d66a1c54b99c8b458 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 15 Sep 2019 19:28:10 -0400 Subject: syncing of images in imported google doc --- .../apis/google_docs/GoogleApiClientUtils.ts | 19 ++-- src/client/views/nodes/FormattedTextBox.tsx | 6 +- src/new_fields/RichTextUtils.ts | 115 +++++++++++++++------ src/server/apis/google/GooglePhotosUploadUtils.ts | 56 ++++++---- src/server/apis/google/existing_uploads.json | 1 + src/server/credentials/google_docs_token.json | 2 +- src/server/index.ts | 30 ++++-- 7 files changed, 162 insertions(+), 67 deletions(-) create mode 100644 src/server/apis/google/existing_uploads.json (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 828d4451a..cbc5da15b 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -97,25 +97,30 @@ export namespace GoogleApiClientUtils { export type ExtractResult = { text: string, paragraphs: DeconstructedParagraph[] }; export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => { let paragraphs = extractParagraphs(document); - let text = paragraphs.map(paragraph => paragraph.runs.map(run => run.content).join("")).join(""); + let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => run as docs_v1.Schema$TextRun).join("")).join(""); text = text.substring(0, text.length - 1); removeNewlines && text.ReplaceAll("\n", ""); return { text, paragraphs }; }; - export type DeconstructedParagraph = { runs: docs_v1.Schema$TextRun[], bullet: Opt }; + export type ContentArray = (docs_v1.Schema$TextRun | docs_v1.Schema$InlineObjectElement)[]; + export type DeconstructedParagraph = { contents: ContentArray, bullet: Opt }; const extractParagraphs = (document: docs_v1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => { const fragments: DeconstructedParagraph[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { - let runs: docs_v1.Schema$TextRun[] = []; + let runs: ContentArray = []; let bullet: Opt; if (element.paragraph) { if (element.paragraph.elements) { for (const inner of element.paragraph.elements) { - if (inner && inner.textRun) { - let run = inner.textRun; - (run.content || !filterEmpty) && runs.push(inner.textRun); + if (inner) { + if (inner.textRun) { + let run = inner.textRun; + (run.content || !filterEmpty) && runs.push(inner.textRun); + } else if (inner.inlineObjectElement) { + runs.push(inner.inlineObjectElement); + } } } } @@ -123,7 +128,7 @@ export namespace GoogleApiClientUtils { bullet = element.paragraph.bullet.nestingLevel || 0; } } - (runs.length || !filterEmpty) && fragments.push({ runs, bullet }); + (runs.length || !filterEmpty) && fragments.push({ contents: runs, bullet }); } } return fragments; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index fc5b27220..8f0f142c4 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -193,8 +193,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id); }); }); - const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); - const mval = this._editorView!.state.schema.marks.metadataVal.create(); + const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); + const mval = this._editorView.state.schema.marks.metadataVal.create(); let offset = (tx.selection.to === range!.end - 1 ? -1 : 0); tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); this.dataDoc[key] = value; @@ -506,7 +506,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let documentId = StrCast(dataDoc[GoogleRef]); let exportState: Opt; if (documentId) { - exportState = await RichTextUtils.GoogleDocs.Import(documentId); + exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 555c41b67..ab5e677c8 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -14,9 +14,10 @@ import { schema } from "../client/util/RichTextSchema"; import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; import { SchemaHeaderField } from "./SchemaHeaderField"; import { DocServer } from "../client/DocServer"; -import { Cast } from "./Types"; +import { Cast, StrCast } from "./Types"; import { Id } from "./FieldSymbols"; import { DocumentView } from "../client/views/nodes/DocumentView"; +import { AssertionError } from "assert"; export namespace RichTextUtils { @@ -109,45 +110,78 @@ export namespace RichTextUtils { return { text, requests }; }; + interface ImageTemplate { + width: number; + title: string; + url: string; + } + + const parseInlineObjects = async (document: docs_v1.Schema$Document): Promise> => { + const inlineObjectMap = new Map(); + const inlineObjects = document.inlineObjects; + + if (inlineObjects) { + const objects = Object.keys(inlineObjects).map(objectId => inlineObjects[objectId]); + const mediaItems: MediaItem[] = objects.map(object => { + const embeddedObject = object.inlineObjectProperties!.embeddedObject!; + const baseUrl = embeddedObject.imageProperties!.contentUri!; + const filename = `upload_${Utils.GenerateGuid()}.png`; + return { baseUrl, filename }; + }); + + const uploads = await PostToServer(RouteStore.googlePhotosMediaDownload, { mediaItems }); + + if (uploads.length !== mediaItems.length) { + throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" }); + } + + for (let i = 0; i < objects.length; i++) { + const object = objects[i]; + const { fileNames } = uploads[i]; + const embeddedObject = object.inlineObjectProperties!.embeddedObject!; + const size = embeddedObject.size!; + const width = size.width!.magnitude!; + const url = Utils.fileUrl(fileNames.clean); + + inlineObjectMap.set(object.objectId!, { + title: embeddedObject.title || `Imported Image from ${document.title}`, + width, + url + }); + } + } + return inlineObjectMap; + }; + type BulletPosition = { value: number, sinks: number }; interface MediaItem { baseUrl: string; filename: string; - width: number; } - export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId): Promise> => { + + export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId, textNote: Doc): Promise> => { const document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); if (!document) { return undefined; } - + const inlineObjectMap = await parseInlineObjects(document); const title = document.title!; const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document); let state = FormattedTextBox.blankState(); let structured = parseLists(paragraphs); - const inline = document.inlineObjects; - let inlineUrls: MediaItem[] = []; - if (inline) { - inlineUrls = Object.keys(inline).map(key => { - const embedded = inline[key].inlineObjectProperties!.embeddedObject!; - const baseUrl = embedded.imageProperties!.contentUri!; - const filename = `upload_${Utils.GenerateGuid()}.png`; - const width = embedded.size!.width!.magnitude!; - return { baseUrl, filename, width }; - }); - } let position = 3; let lists: ListGroup[] = []; const indentMap = new Map(); let globalOffset = 0; - const nodes = structured.map(element => { + const nodes: Node[] = []; + for (let element of structured) { if (Array.isArray(element)) { lists.push(element); let positions: BulletPosition[] = []; let items = element.map(paragraph => { - let item = listItem(state.schema, paragraph.runs); + let item = listItem(state.schema, paragraph.contents); let sinks = paragraph.bullet!; positions.push({ value: position + globalOffset, @@ -158,13 +192,26 @@ export namespace RichTextUtils { return item; }); indentMap.set(element, positions); - return list(state.schema, items); + nodes.push(list(state.schema, items)); } else { - let paragraph = paragraphNode(state.schema, element.runs); - position += paragraph.nodeSize; - return paragraph; + if (element.contents.some(child => "inlineObjectId" in child)) { + let node: Node; + for (const child of element.contents) { + if ("inlineObjectId" in child) { + node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); + } else { + node = paragraphNode(state.schema, [child]); + } + nodes.push(node); + position += node.nodeSize; + } + } else { + let paragraph = paragraphNode(state.schema, element.contents); + nodes.push(paragraph); + position += paragraph.nodeSize; + } } - }); + } state = state.apply(state.tr.replaceWith(0, 2, nodes)); let sink = sinkListItem(state.schema.nodes.list_item); @@ -179,14 +226,6 @@ export namespace RichTextUtils { } } - const uploads = await PostToServer(RouteStore.googlePhotosMediaDownload, { mediaItems: inlineUrls }); - for (let i = 0; i < uploads.length; i++) { - const src = Utils.fileUrl(uploads[i].fileNames.clean); - const width = inlineUrls[i].width; - const imageNode = schema.nodes.image.create({ src, width }); - state = state.apply(state.tr.insert(0, imageNode)); - } - return { title, text, state }; }; @@ -226,6 +265,22 @@ export namespace RichTextUtils { return schema.node("paragraph", null, fragment); }; + const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => { + const { url: src, width } = image; + let docid: string; + const guid = Utils.GenerateDeterministicGuid(src); + const backingDocId = StrCast(textNote[guid]); + if (!backingDocId) { + const backingDoc = Docs.Create.ImageDocument(src, { width: 300, height: 300 }); + DocumentView.makeCustomViewClicked(backingDoc); + docid = backingDoc[Id]; + textNote[guid] = docid; + } else { + docid = backingDocId; + } + return schema.node("image", { src, width, docid }); + }; + const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { let text = run.content!.removeTrailingNewlines(); return text.length ? schema.text(text, styleToMarks(schema, run.textStyle)) : undefined; diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index f582cebd2..3ab9ba90f 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -118,28 +118,44 @@ export namespace DownloadUtils { const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`; const sanitize = (filename: string) => filename.replace(/\s+/g, "_"); - export const UploadImage = async (url: string, filename?: string, prefix = ""): Promise> => { - const resolved = filename ? sanitize(filename) : generate(prefix, url); - let extension = path.extname(url) || path.extname(resolved); + export interface InspectionResults { + isLocal: boolean; + stream: any; + normalizedUrl: string; + contentSize: number; + contentType: string; + } + + export const InspectImage = async (url: string) => { + const { isLocal, stream, normalized: normalizedUrl } = classify(url); + const metadata = (await new Promise((resolve, reject) => { + request.head(url, async (error, res) => { + if (error) { + return reject(error); + } + resolve(res); + }); + })).headers; + return { + contentSize: parseInt(metadata[size]), + contentType: metadata[type], + isLocal, + stream, + normalizedUrl + }; + }; + + export const UploadImage = async (metadata: InspectionResults, filename?: string, prefix = ""): Promise> => { + const { isLocal, stream, normalizedUrl, contentSize, contentType } = metadata; + const resolved = filename ? sanitize(filename) : generate(prefix, normalizedUrl); + let extension = path.extname(normalizedUrl) || path.extname(resolved); extension && (extension = extension.toLowerCase()); let information: UploadInformation = { mediaPaths: [], - fileNames: { clean: resolved } + fileNames: { clean: resolved }, + contentSize, + contentType, }; - const { isLocal, stream, normalized } = classify(url); - url = normalized; - if (!isLocal) { - const metadata = (await new Promise((resolve, reject) => { - request.head(url, async (error, res) => { - if (error) { - return reject(error); - } - resolve(res); - }); - })).headers; - information.contentSize = parseInt(metadata[size]); - information.contentType = metadata[type]; - } return new Promise(async (resolve, reject) => { const resizers = [ { resizer: sharp().rotate(), suffix: "_o" }, @@ -164,7 +180,7 @@ export namespace DownloadUtils { const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension; information.mediaPaths.push(mediaPath = uploadDirectory + filename); information.fileNames[suffix] = filename; - stream(url).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) + stream(normalizedUrl).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) .on('close', resolve) .on('error', reject); }); @@ -172,7 +188,7 @@ export namespace DownloadUtils { } if (!isLocal || nonVisual) { await new Promise(resolve => { - stream(url).pipe(fs.createWriteStream(uploadDirectory + resolved)).on('close', resolve); + stream(normalizedUrl).pipe(fs.createWriteStream(uploadDirectory + resolved)).on('close', resolve); }); } resolve(information); diff --git a/src/server/apis/google/existing_uploads.json b/src/server/apis/google/existing_uploads.json new file mode 100644 index 000000000..05c20c33b --- /dev/null +++ b/src/server/apis/google/existing_uploads.json @@ -0,0 +1 @@ +{"23625":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_l.png"],"fileNames":{"clean":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180.png","_o":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_o.png","_s":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_s.png","_m":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_m.png","_l":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_l.png"},"contentSize":23625,"contentType":"image/jpeg"}} \ No newline at end of file diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 4f2fb0f9d..c10b0797f 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.GlyEB-6kaRm7dCD9x3j1b5AyujXvfpS5NWuJQwy6UKLO06KYXcF2e5XaCxvR7QJgH3Pn2iu3btjYrrJxNNaLffgEszcJHNsN_5IIWJBA4sdG6KLW63MmFwfV4U1hyQ","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568573667294} \ No newline at end of file +{"access_token":"ya29.ImCFB_ghOybVB6A4HvIIwIlyGyZw6wOymdwJyWJJECIpCmFTHNEzOAfP98KFzm5OUV2zZNS5Wx1iUT1xYWW35PY7NoZc7PWwjzmOaGkMzDm7_fxpsgjT0StdvEwTJprFIv0","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568590984976} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 2e60d9be7..07ce4b6f0 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -46,6 +46,7 @@ const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); import * as qs from 'query-string'; +import { Opt } from '../new_fields/Doc'; const extensions = require("../client/util/UtilExtensions"); const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest)); @@ -580,7 +581,8 @@ app.post( for (const key in files) { const { type, path: location, name } = files[key]; const filename = path.basename(location); - await UploadUtils.UploadImage(uploadDirectory + filename, filename).catch(() => console.log(`Unable to process ${filename}`)); + const metadata = await UploadUtils.InspectImage(uploadDirectory + filename); + await UploadUtils.UploadImage(metadata, filename).catch(() => console.log(`Unable to process ${filename}`)); results.push({ name, type, path: `/files/${filename}` }); } _success(res, results); @@ -884,14 +886,30 @@ const prefix = "google_photos_"; const downloadError = "Encountered an error while executing downloads."; const requestError = "Unable to execute download: the body's media items were malformed."; +app.get("/gapiCleanup", (req, res) => { + write_text_file(file, ""); + res.redirect(RouteStore.delete); +}); + +const file = "./apis/google/existing_uploads.json"; app.post(RouteStore.googlePhotosMediaDownload, async (req, res) => { const contents: { mediaItems: MediaItem[] } = req.body; if (contents) { - const pending = contents.mediaItems.map(item => - UploadUtils.UploadImage(item.baseUrl, item.filename, prefix) - ); - const completed = await Promise.all(pending).catch(error => _error(res, downloadError, error)); - Array.isArray(completed) && _success(res, completed); + const completed: Opt[] = []; + const content = await read_text_file(file); + let existing = content.length ? JSON.parse(content) : {}; + for (let item of contents.mediaItems) { + const { contentSize, ...attributes } = await UploadUtils.InspectImage(item.baseUrl); + const found: UploadUtils.UploadInformation = existing[contentSize]; + if (!found) { + const upload = await UploadUtils.UploadImage({ contentSize, ...attributes }, item.filename, prefix).catch(error => _error(res, downloadError, error)); + upload && completed.push(existing[contentSize] = upload); + } else { + completed.push(found); + } + } + await write_text_file(file, JSON.stringify(existing)); + _success(res, completed); return; } _invalid(res, requestError); -- cgit v1.2.3-70-g09d2 From fe2b302288d120a0b68a3fa9e078d14445de1251 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 16 Sep 2019 10:27:55 -0400 Subject: updates --- src/client/northstar/utils/Extensions.ts | 24 ++++---- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 10 +++- .../collectionFreeForm/CollectionFreeFormView.tsx | 64 +++++++++++----------- src/new_fields/RichTextUtils.ts | 10 +++- src/server/apis/google/GooglePhotosUploadUtils.ts | 20 ++++--- src/server/apis/google/existing_uploads.json | 2 +- src/server/credentials/google_docs_token.json | 2 +- src/server/index.ts | 18 +++--- 9 files changed, 83 insertions(+), 69 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts index f1fddf6c8..04af36731 100644 --- a/src/client/northstar/utils/Extensions.ts +++ b/src/client/northstar/utils/Extensions.ts @@ -29,22 +29,22 @@ type BatchHandler = BatchHandlerSync | BatchHandlerAsync; interface Array { batch(batchSize: number): T[][]; - executeInBatches(batchSize: number, handler: BatchHandlerSync): void; - convertInBatches(batchSize: number, handler: BatchConverterSync): O[]; - executeInBatchesAsync(batchSize: number, handler: BatchHandler): Promise; - convertInBatchesAsync(batchSize: number, handler: BatchConverter): Promise; - executeInBatchesAtInterval(batchSize: number, handler: BatchHandler, interval: number): Promise; - convertInBatchesAtInterval(batchSize: number, handler: BatchConverter, interval: number): Promise; + batchedForEach(batchSize: number, handler: BatchHandlerSync): void; + batchedMap(batchSize: number, handler: BatchConverterSync): O[]; + batchedForEachAsync(batchSize: number, handler: BatchHandler): Promise; + batchedMapAsync(batchSize: number, handler: BatchConverter): Promise; + batchedForEachInterval(batchSize: number, handler: BatchHandler, interval: number): Promise; + batchedMapInterval(batchSize: number, handler: BatchConverter, interval: number): Promise; lastElement(): T; } Array.prototype.batch = extensions.Batch; -Array.prototype.executeInBatches = extensions.ExecuteInBatches; -Array.prototype.convertInBatches = extensions.ConvertInBatches; -Array.prototype.executeInBatchesAsync = extensions.ExecuteInBatchesAsync; -Array.prototype.convertInBatchesAsync = extensions.ConvertInBatchesAsync; -Array.prototype.executeInBatchesAtInterval = extensions.ExecuteInBatchesAtInterval; -Array.prototype.convertInBatchesAtInterval = extensions.ConvertInBatchesAtInterval; +Array.prototype.batchedForEach = extensions.ExecuteInBatches; +Array.prototype.batchedMap = extensions.ConvertInBatches; +Array.prototype.batchedForEachAsync = extensions.ExecuteInBatchesAsync; +Array.prototype.batchedMapAsync = extensions.ConvertInBatchesAsync; +Array.prototype.batchedForEachInterval = extensions.ExecuteInBatchesAtInterval; +Array.prototype.batchedMapInterval = extensions.ConvertInBatchesAtInterval; Array.prototype.lastElement = function () { if (!this.length) { diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 44075ecdd..d371766dd 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -103,7 +103,7 @@ export default class DirectoryImportBox extends React.Component runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); - const uploads = await validated.convertInBatchesAsync(15, async (batch: File[]) => { + const uploads = await validated.batchedMapAsync(15, async (batch: File[]) => { const formData = new FormData(); const parameters = { method: 'POST', body: formData }; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 4d4f69b92..59fc11359 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,7 +1,7 @@ import { action, computed } from "mobx"; import * as rp from 'request-promise'; import CursorField from "../../../new_fields/CursorField"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; @@ -23,6 +23,7 @@ import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; import React = require("react"); import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; +import { CollectionDockingView } from "./CollectionDockingView"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; @@ -212,11 +213,14 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) { let newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." }); let proto = newBox.proto!; - proto.autoHeight = true; - proto[GoogleRef] = matches[2]; + const documentId = matches[2]; + proto[GoogleRef] = documentId; proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs..."; proto.backgroundColor = "#eeeeff"; this.props.addDocument(newBox); + // const parent = Docs.Create.StackingDocument([newBox], { title: `Google Doc Import (${documentId})` }); + // CollectionDockingView.Instance.AddRightSplit(parent, undefined); + // proto.height = parent[HeightSym](); return; } if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b4ca6d797..9c7e8d22f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -523,38 +523,38 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let y = this.Document.panY || 0; let docs = this.childDocs || []; let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - if (!this.isAnnotationOverlay) { - PDFMenu.Instance.fadeOut(true); - let minx = docs.length ? NumCast(docs[0].x) : 0; - let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; - let miny = docs.length ? NumCast(docs[0].y) : 0; - let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; - let ranges = docs.filter(doc => doc).reduce((range, doc) => { - let x = NumCast(doc.x); - let xe = x + NumCast(doc.width); - let y = NumCast(doc.y); - let ye = y + NumCast(doc.height); - return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], - [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; - }, [[minx, maxx], [miny, maxy]]); - let ink = Cast(this.fieldExtensionDoc.ink, InkField); - if (ink && ink.inkData) { - ink.inkData.forEach((value: StrokeData, key: string) => { - let bounds = InkingCanvas.StrokeRect(value); - ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; - ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; - }); - } - - 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; - } + // if (!this.isAnnotationOverlay) { + // PDFMenu.Instance.fadeOut(true); + // let minx = docs.length ? NumCast(docs[0].x) : 0; + // let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; + // let miny = docs.length ? NumCast(docs[0].y) : 0; + // let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; + // let ranges = docs.filter(doc => doc).reduce((range, doc) => { + // let x = NumCast(doc.x); + // let xe = x + NumCast(doc.width); + // let y = NumCast(doc.y); + // let ye = y + NumCast(doc.height); + // return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], + // [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; + // }, [[minx, maxx], [miny, maxy]]); + // let ink = Cast(this.fieldExtensionDoc.ink, InkField); + // if (ink && ink.inkData) { + // ink.inkData.forEach((value: StrokeData, key: string) => { + // let bounds = InkingCanvas.StrokeRect(value); + // ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; + // ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; + // }); + // } + + // 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; + // } this.setPan(x - dx, y - dy); this._lastX = e.pageX; this._lastY = e.pageY; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index ab5e677c8..5b1da2669 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -278,7 +278,7 @@ export namespace RichTextUtils { } else { docid = backingDocId; } - return schema.node("image", { src, width, docid }); + return schema.node("image", { src, width, docid, float: null }); }; const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { @@ -331,6 +331,7 @@ export namespace RichTextUtils { ["strong", "bold"], ["em", "italic"], ["pFontColor", "foregroundColor"], + ["pFontSize", "fontSize"], ["timesNewRoman", "weightedFontFamily"], ["georgia", "weightedFontFamily"], ["comicSans", "weightedFontFamily"], @@ -382,21 +383,24 @@ export namespace RichTextUtils { const delimiter = "/doc/"; const alreadyShared = "?sharing=true"; if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { + alert("Reassigning alias!"); const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); if (linkDoc instanceof Doc) { const target = (await Cast(linkDoc.anchor2, Doc))!; const exported = Doc.MakeAlias(target); DocumentView.makeCustomViewClicked(exported); - target && (url = Utils.shareUrl(exported[Id])); + const documentId = exported[Id]; + target && (url = Utils.shareUrl(documentId)); linkDoc.anchor2 = exported; } } + alert(`url: ${url}`); value = { url }; textStyle.foregroundColor = fromRgb.blue; textStyle.bold = true; break; case "fontSize": - value = attrs.fontSize; + value = { magnitude: attrs.fontSize, unit: "PT" }; break; case "foregroundColor": value = fromHex(attrs.color); diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index 3ab9ba90f..734d77fd7 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -80,7 +80,7 @@ export namespace GooglePhotosUploadUtils { }); })).newMediaItemResults; }; - const newMediaItemResults = await newMediaItems.convertInBatchesAtInterval(50, createFromUploadTokens, 0.1); + const newMediaItemResults = await newMediaItems.batchedMapInterval(50, createFromUploadTokens, 0.1); return { newMediaItemResults }; }; @@ -122,12 +122,20 @@ export namespace DownloadUtils { isLocal: boolean; stream: any; normalizedUrl: string; - contentSize: number; - contentType: string; + contentSize?: number; + contentType?: string; } - export const InspectImage = async (url: string) => { + export const InspectImage = async (url: string): Promise => { const { isLocal, stream, normalized: normalizedUrl } = classify(url); + const results = { + isLocal, + stream, + normalizedUrl + }; + if (isLocal) { + return results; + } const metadata = (await new Promise((resolve, reject) => { request.head(url, async (error, res) => { if (error) { @@ -139,9 +147,7 @@ export namespace DownloadUtils { return { contentSize: parseInt(metadata[size]), contentType: metadata[type], - isLocal, - stream, - normalizedUrl + ...results }; }; diff --git a/src/server/apis/google/existing_uploads.json b/src/server/apis/google/existing_uploads.json index 05c20c33b..4d723868e 100644 --- a/src/server/apis/google/existing_uploads.json +++ b/src/server/apis/google/existing_uploads.json @@ -1 +1 @@ -{"23625":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_l.png"],"fileNames":{"clean":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180.png","_o":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_o.png","_s":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_s.png","_m":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_m.png","_l":"upload_7e2d5fef-860a-49a8-b9ec-b91f28073180_l.png"},"contentSize":23625,"contentType":"image/jpeg"}} \ No newline at end of file +{"23394":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_l.png"],"fileNames":{"clean":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2.png","_o":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_o.png","_s":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_s.png","_m":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_m.png","_l":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_l.png"},"contentSize":23394,"contentType":"image/jpeg"},"23406":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_l.png"],"fileNames":{"clean":"upload_2b310488-a12b-4ecf-8adf-38943282381a.png","_o":"upload_2b310488-a12b-4ecf-8adf-38943282381a_o.png","_s":"upload_2b310488-a12b-4ecf-8adf-38943282381a_s.png","_m":"upload_2b310488-a12b-4ecf-8adf-38943282381a_m.png","_l":"upload_2b310488-a12b-4ecf-8adf-38943282381a_l.png"},"contentSize":23406,"contentType":"image/jpeg"},"23408":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_l.png"],"fileNames":{"clean":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78.png","_o":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_o.png","_s":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_s.png","_m":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_m.png","_l":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_l.png"},"contentSize":23408,"contentType":"image/jpeg"},"23413":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_l.png"],"fileNames":{"clean":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c.png","_o":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_o.png","_s":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_s.png","_m":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_m.png","_l":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_l.png"},"contentSize":23413,"contentType":"image/jpeg"},"23415":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_l.png"],"fileNames":{"clean":"upload_2e51d02c-a113-4eec-8030-badb54d0f719.png","_o":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_o.png","_s":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_s.png","_m":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_m.png","_l":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_l.png"},"contentSize":23415,"contentType":"image/jpeg"},"23466":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_l.png"],"fileNames":{"clean":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f.png","_o":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_o.png","_s":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_s.png","_m":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_m.png","_l":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_l.png"},"contentSize":23466,"contentType":"image/jpeg"},"23625":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_l.png"],"fileNames":{"clean":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1.png","_o":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_o.png","_s":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_s.png","_m":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_m.png","_l":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_l.png"},"contentSize":23625,"contentType":"image/jpeg"},"45184":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_l.png"],"fileNames":{"clean":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402.png","_o":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_o.png","_s":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_s.png","_m":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_m.png","_l":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_l.png"},"contentSize":45184,"contentType":"image/jpeg"},"45211":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_l.png"],"fileNames":{"clean":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8.png","_o":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_o.png","_s":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_s.png","_m":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_m.png","_l":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_l.png"},"contentSize":45211,"contentType":"image/jpeg"},"45228":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_l.png"],"fileNames":{"clean":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb.png","_o":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_o.png","_s":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_s.png","_m":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_m.png","_l":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_l.png"},"contentSize":45228,"contentType":"image/jpeg"},"45247":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_l.png"],"fileNames":{"clean":"upload_1de378be-f389-41d7-a6bf-d680b2eee551.png","_o":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_o.png","_s":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_s.png","_m":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_m.png","_l":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_l.png"},"contentSize":45247,"contentType":"image/jpeg"},"45263":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_l.png"],"fileNames":{"clean":"upload_984f220d-1409-418d-998b-44667879fde2.png","_o":"upload_984f220d-1409-418d-998b-44667879fde2_o.png","_s":"upload_984f220d-1409-418d-998b-44667879fde2_s.png","_m":"upload_984f220d-1409-418d-998b-44667879fde2_m.png","_l":"upload_984f220d-1409-418d-998b-44667879fde2_l.png"},"contentSize":45263,"contentType":"image/jpeg"},"45273":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_l.png"],"fileNames":{"clean":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384.png","_o":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_o.png","_s":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_s.png","_m":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_m.png","_l":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_l.png"},"contentSize":45273,"contentType":"image/jpeg"},"45492":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_l.png"],"fileNames":{"clean":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930.png","_o":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_o.png","_s":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_s.png","_m":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_m.png","_l":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_l.png"},"contentSize":45492,"contentType":"image/jpeg"},"45510":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_l.png"],"fileNames":{"clean":"upload_892f666b-ac98-4feb-9017-39448434b732.png","_o":"upload_892f666b-ac98-4feb-9017-39448434b732_o.png","_s":"upload_892f666b-ac98-4feb-9017-39448434b732_s.png","_m":"upload_892f666b-ac98-4feb-9017-39448434b732_m.png","_l":"upload_892f666b-ac98-4feb-9017-39448434b732_l.png"},"contentSize":45510,"contentType":"image/jpeg"},"45585":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_l.png"],"fileNames":{"clean":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15.png","_o":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_o.png","_s":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_s.png","_m":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_m.png","_l":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_l.png"},"contentSize":45585,"contentType":"image/jpeg"},"74829":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_l.png"],"fileNames":{"clean":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5.png","_o":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_o.png","_s":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_s.png","_m":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_m.png","_l":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_l.png"},"contentSize":74829,"contentType":"image/jpeg"}} \ No newline at end of file diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index c10b0797f..5998ccfd8 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.ImCFB_ghOybVB6A4HvIIwIlyGyZw6wOymdwJyWJJECIpCmFTHNEzOAfP98KFzm5OUV2zZNS5Wx1iUT1xYWW35PY7NoZc7PWwjzmOaGkMzDm7_fxpsgjT0StdvEwTJprFIv0","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568590984976} \ No newline at end of file +{"access_token":"ya29.GlyFByFfWe7VNNjHImJwA58yoh2cAKDJUPhBKn5IDVUY9oPlXbpyhdJMjfGSRhDZpgEWM0QoSqONu9gBVWDV9aXsf7p8r9TDXK7jBfWs1Qnox4zPf54kSfCHsmV6iw","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568646012183} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 07ce4b6f0..9da6a8b38 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -836,12 +836,12 @@ export interface NewMediaItem { } Array.prototype.batch = extensions.Batch; -Array.prototype.executeInBatches = extensions.ExecuteInBatches; -Array.prototype.convertInBatches = extensions.ConvertInBatches; -Array.prototype.executeInBatchesAsync = extensions.ExecuteInBatchesAsync; -Array.prototype.convertInBatchesAsync = extensions.ConvertInBatchesAsync; -Array.prototype.executeInBatchesAtInterval = extensions.ExecuteInBatchesAtInterval; -Array.prototype.convertInBatchesAtInterval = extensions.ConvertInBatchesAtInterval; +Array.prototype.batchedForEach = extensions.ExecuteInBatches; +Array.prototype.batchedMap = extensions.ConvertInBatches; +Array.prototype.batchedForEachAsync = extensions.ExecuteInBatchesAsync; +Array.prototype.batchedMapAsync = extensions.ConvertInBatchesAsync; +Array.prototype.batchedForEachInterval = extensions.ExecuteInBatchesAtInterval; +Array.prototype.batchedMapInterval = extensions.ConvertInBatchesAtInterval; app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => { const mediaInput: GooglePhotosUploadUtils.MediaInput[] = req.body.media; @@ -865,7 +865,7 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => { return newMediaItems; }; - const newMediaItems = await mediaInput.convertInBatchesAtInterval(25, dispatchUpload, 0.1); + const newMediaItems = await mediaInput.batchedMapInterval(25, dispatchUpload, 0.1); if (failed) { return _error(res, tokenError); @@ -900,10 +900,10 @@ app.post(RouteStore.googlePhotosMediaDownload, async (req, res) => { let existing = content.length ? JSON.parse(content) : {}; for (let item of contents.mediaItems) { const { contentSize, ...attributes } = await UploadUtils.InspectImage(item.baseUrl); - const found: UploadUtils.UploadInformation = existing[contentSize]; + const found: UploadUtils.UploadInformation = existing[contentSize!]; if (!found) { const upload = await UploadUtils.UploadImage({ contentSize, ...attributes }, item.filename, prefix).catch(error => _error(res, downloadError, error)); - upload && completed.push(existing[contentSize] = upload); + upload && completed.push(existing[contentSize!] = upload); } else { completed.push(found); } -- cgit v1.2.3-70-g09d2 From c23d29de4bacc71de729f4ef0f6836efe80eb96f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 16 Sep 2019 10:34:12 -0400 Subject: removed alerts --- src/new_fields/RichTextUtils.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/new_fields') diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 5b1da2669..44fdae638 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -383,7 +383,6 @@ export namespace RichTextUtils { const delimiter = "/doc/"; const alreadyShared = "?sharing=true"; if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { - alert("Reassigning alias!"); const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); if (linkDoc instanceof Doc) { const target = (await Cast(linkDoc.anchor2, Doc))!; @@ -394,7 +393,6 @@ export namespace RichTextUtils { linkDoc.anchor2 = exported; } } - alert(`url: ${url}`); value = { url }; textStyle.foregroundColor = fromRgb.blue; textStyle.bold = true; -- cgit v1.2.3-70-g09d2 From 5e3c12128f4d15d621b323e0f1d78bfff2452b92 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 16 Sep 2019 10:36:16 -0400 Subject: fixed templates to allow aliasing, clustering and other things. --- src/client/views/collections/CollectionSubView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 44 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 4 +- src/new_fields/Doc.ts | 3 ++ 4 files changed, 29 insertions(+), 26 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index bc994cffd..d13c69ecf 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -53,8 +53,10 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @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); } + 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() { - let self = this; //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]); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5d5d9de9e..49640c040 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -280,10 +280,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } public getActiveDocuments = () => { const curPage = FieldValue(this.Document.curPage, -1); - return this.childDocs.filter(doc => { - var page = NumCast(doc.page, -1); + return this.childLayoutPairs.filter(pair => { + var page = NumCast(pair.layout!.page, -1); return page === curPage || page === -1; - }).map(doc => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc).layout); + }).map(pair => pair.layout); } @computed get fieldExtensionDoc() { @@ -361,7 +361,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { tryDragCluster(e: PointerEvent) { let probe = this.getTransform().transformPoint(e.clientX, e.clientY); - let cluster = this.childDocs.reduce((cluster, cd) => { + let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { let cx = NumCast(cd.x) - this._clusterDistance; let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(cd.width) + 2 * this._clusterDistance; @@ -372,7 +372,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return cluster; }, -1); if (cluster !== -1) { - let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view let prevSelected = SelectionManager.SelectedDocuments(); @@ -403,7 +403,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action updateClusters() { this.sets.length = 0; - this.childDocs.map(c => { + this.childLayoutPairs.map(pair => pair.layout).map(c => { let included = []; for (let i = 0; i < this.sets.length; i++) { for (let member of this.sets[i]) { @@ -431,20 +431,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action updateCluster(doc: Doc) { + let childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.props.Document.useClusters) { this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; this.sets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && this.boundsOverlap(doc, member)) { doc.cluster = i; } })); - if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) { + if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } this.sets.map((set, i) => { - if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) { + if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { doc.cluster = i; } }); @@ -506,7 +507,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childDocs || []; + let docs = this.childLayoutPairs.map(pair => pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { PDFMenu.Instance.fadeOut(true); @@ -556,7 +557,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return; } - let childSelected = this.childDocs.some(doc => { + let childSelected = this.childLayoutPairs.map(pair => pair.layout).some(doc => { var dv = DocumentManager.Instance.getDocumentView(doc); return dv && SelectionManager.IsSelected(dv) ? true : false; }); @@ -619,7 +620,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { doc.zIndex = 0; return; } - const docs = this.childDocs; + const docs = this.childLayoutPairs.map(pair => pair.layout); docs.slice().sort((doc1, doc2) => { if (doc1 === doc) return 1; if (doc2 === doc) return -1; @@ -790,12 +791,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const initScript = this.Document.arrangeInit; const script = this.Document.arrangeScript; let state: any = undefined; - let docs = this.childDocs; - let overlayDocs = DocListCast(this.props.Document.localOverlays); - overlayDocs && docs.push(...overlayDocs); + let pairs = this.childLayoutPairs; let elements: ViewDefResult[] = []; if (initScript) { - const initResult = initScript.script.run({ docs, collection: this.Document }); + const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }); if (initResult.success) { const result = initResult.result; const { state: scriptState, views } = result; @@ -803,18 +802,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { elements = this.viewDefsToJSX(views); } } - let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { - var page = NumCast(doc.page, -1); + 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(doc.isMinimized); + let minim = BoolCast(pair.layout.isMinimized); if (minim === undefined || !minim) { - const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : - { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; + 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; - let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc); if (pair.layout && !(pair.data instanceof Promise)) { prev.push({ - ele: (Docu this.props.Document.nativeLayout = this.props.Document.layout; this.props.Document.nativeType = this.props.Document.type; this.props.Document.nonCustomAutoHeight = this.props.Document.autoHeight; - this.props.Document.nonCustomWidth = this.props.Document.nativeWidth; - this.props.Document.nonCustomHeight = this.props.Document.nativeHeight; + this.props.Document.nonCustomWidth = this.props.Document.width; + this.props.Document.nonCustomHeight = this.props.Document.height; this.props.Document.nonCustomNativeWidth = this.props.Document.nativeWidth; this.props.Document.nonCustomNativeHeight = this.props.Document.nativeHeight; this.props.Document.nonCustomIgnoreAspect = this.props.Document.ignoreAspect; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index eef14ad25..2434a439a 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -412,6 +412,9 @@ export namespace 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); + } 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})`"; -- cgit v1.2.3-70-g09d2 From 36a4caa28a7a83823027e6f0af47ffb1878ae86b Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 16 Sep 2019 12:19:17 -0400 Subject: fixes for converting between google and our doc formats --- src/client/util/RichTextSchema.tsx | 2 ++ src/client/views/nodes/DocumentView.tsx | 1 + src/new_fields/RichTextUtils.ts | 32 +++++++++++++++------------ src/server/apis/google/existing_uploads.json | 2 +- src/server/credentials/google_docs_token.json | 2 +- 5 files changed, 23 insertions(+), 16 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index f027a4bf7..2750203f2 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -135,6 +135,7 @@ export const nodes: { [index: string]: NodeSpec } = { alt: { default: null }, title: { default: null }, float: { default: "left" }, + location: { default: "onRight" }, docid: { default: "" } }, group: "inline", @@ -616,6 +617,7 @@ export class ImageResizeView { e.preventDefault(); e.stopPropagation(); DocServer.GetRefField(node.attrs.docid).then(async linkDoc => { + const location = node.attrs.location; if (linkDoc instanceof Doc) { let proto = Doc.GetProto(linkDoc); let targetContext = await Cast(proto.targetContext, Doc); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 81805af64..bf7e7df97 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -606,6 +606,7 @@ export class DocumentView extends DocComponent(Docu Doc.MakeTemplate(fieldTemplate, metaKey, proto); Doc.ApplyTemplateTo(docTemplate, document, undefined, false); + document.customLayout = document.layout; } }); }); diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 44fdae638..ff28e6861 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -195,16 +195,19 @@ export namespace RichTextUtils { nodes.push(list(state.schema, items)); } else { if (element.contents.some(child => "inlineObjectId" in child)) { - let node: Node; - for (const child of element.contents) { + const group = element.contents; + group.forEach((child, i) => { + let node: Opt>; if ("inlineObjectId" in child) { node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); - } else { + } else if ("content" in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { node = paragraphNode(state.schema, [child]); } - nodes.push(node); - position += node.nodeSize; - } + if (node) { + position += node.nodeSize; + nodes.push(node); + } + }); } else { let paragraph = paragraphNode(state.schema, element.contents); nodes.push(paragraph); @@ -278,7 +281,7 @@ export namespace RichTextUtils { } else { docid = backingDocId; } - return schema.node("image", { src, width, docid, float: null }); + return schema.node("image", { src, width, docid, float: null, location: "onRight" }); }; const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { @@ -385,12 +388,13 @@ export namespace RichTextUtils { if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); if (linkDoc instanceof Doc) { - const target = (await Cast(linkDoc.anchor2, Doc))!; - const exported = Doc.MakeAlias(target); - DocumentView.makeCustomViewClicked(exported); - const documentId = exported[Id]; - target && (url = Utils.shareUrl(documentId)); - linkDoc.anchor2 = exported; + let exported = (await Cast(linkDoc.anchor2, Doc))!; + if (!exported.customLayout) { + exported = Doc.MakeAlias(exported); + DocumentView.makeCustomViewClicked(exported); + linkDoc.anchor2 = exported; + } + url = Utils.shareUrl(exported[Id]); } } value = { url }; @@ -418,7 +422,7 @@ export namespace RichTextUtils { } if (node.type.name === "image") { requests.push(await EncodeImage({ - startIndex: position + nodeSize, + startIndex: position + nodeSize - 1, uri: attrs.src, width: attrs.width })); diff --git a/src/server/apis/google/existing_uploads.json b/src/server/apis/google/existing_uploads.json index 4d723868e..399c27672 100644 --- a/src/server/apis/google/existing_uploads.json +++ b/src/server/apis/google/existing_uploads.json @@ -1 +1 @@ -{"23394":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_l.png"],"fileNames":{"clean":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2.png","_o":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_o.png","_s":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_s.png","_m":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_m.png","_l":"upload_e6e6239c-a436-4f08-bea5-de29ad4e72f2_l.png"},"contentSize":23394,"contentType":"image/jpeg"},"23406":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2b310488-a12b-4ecf-8adf-38943282381a_l.png"],"fileNames":{"clean":"upload_2b310488-a12b-4ecf-8adf-38943282381a.png","_o":"upload_2b310488-a12b-4ecf-8adf-38943282381a_o.png","_s":"upload_2b310488-a12b-4ecf-8adf-38943282381a_s.png","_m":"upload_2b310488-a12b-4ecf-8adf-38943282381a_m.png","_l":"upload_2b310488-a12b-4ecf-8adf-38943282381a_l.png"},"contentSize":23406,"contentType":"image/jpeg"},"23408":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_l.png"],"fileNames":{"clean":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78.png","_o":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_o.png","_s":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_s.png","_m":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_m.png","_l":"upload_a35aef02-3dac-434f-a2d5-932ee3fc6b78_l.png"},"contentSize":23408,"contentType":"image/jpeg"},"23413":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_l.png"],"fileNames":{"clean":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c.png","_o":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_o.png","_s":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_s.png","_m":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_m.png","_l":"upload_8babdd04-c4af-4ebf-9e48-bcf5cb51ab6c_l.png"},"contentSize":23413,"contentType":"image/jpeg"},"23415":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2e51d02c-a113-4eec-8030-badb54d0f719_l.png"],"fileNames":{"clean":"upload_2e51d02c-a113-4eec-8030-badb54d0f719.png","_o":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_o.png","_s":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_s.png","_m":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_m.png","_l":"upload_2e51d02c-a113-4eec-8030-badb54d0f719_l.png"},"contentSize":23415,"contentType":"image/jpeg"},"23466":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_l.png"],"fileNames":{"clean":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f.png","_o":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_o.png","_s":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_s.png","_m":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_m.png","_l":"upload_ec4c15ea-9858-4886-bc15-03a5073b8f4f_l.png"},"contentSize":23466,"contentType":"image/jpeg"},"23625":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_l.png"],"fileNames":{"clean":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1.png","_o":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_o.png","_s":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_s.png","_m":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_m.png","_l":"upload_e1488792-46cb-4ff5-bb26-f62ea2ef91d1_l.png"},"contentSize":23625,"contentType":"image/jpeg"},"45184":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_l.png"],"fileNames":{"clean":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402.png","_o":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_o.png","_s":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_s.png","_m":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_m.png","_l":"upload_c0de990f-e3b5-4830-ab5d-17cfb4ddc402_l.png"},"contentSize":45184,"contentType":"image/jpeg"},"45211":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_l.png"],"fileNames":{"clean":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8.png","_o":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_o.png","_s":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_s.png","_m":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_m.png","_l":"upload_43ae4f14-b987-4a1a-9430-cc90bb24a9c8_l.png"},"contentSize":45211,"contentType":"image/jpeg"},"45228":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_608688a6-53bc-4d1f-adfc-3aac153066fb_l.png"],"fileNames":{"clean":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb.png","_o":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_o.png","_s":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_s.png","_m":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_m.png","_l":"upload_608688a6-53bc-4d1f-adfc-3aac153066fb_l.png"},"contentSize":45228,"contentType":"image/jpeg"},"45247":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_1de378be-f389-41d7-a6bf-d680b2eee551_l.png"],"fileNames":{"clean":"upload_1de378be-f389-41d7-a6bf-d680b2eee551.png","_o":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_o.png","_s":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_s.png","_m":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_m.png","_l":"upload_1de378be-f389-41d7-a6bf-d680b2eee551_l.png"},"contentSize":45247,"contentType":"image/jpeg"},"45263":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_984f220d-1409-418d-998b-44667879fde2_l.png"],"fileNames":{"clean":"upload_984f220d-1409-418d-998b-44667879fde2.png","_o":"upload_984f220d-1409-418d-998b-44667879fde2_o.png","_s":"upload_984f220d-1409-418d-998b-44667879fde2_s.png","_m":"upload_984f220d-1409-418d-998b-44667879fde2_m.png","_l":"upload_984f220d-1409-418d-998b-44667879fde2_l.png"},"contentSize":45263,"contentType":"image/jpeg"},"45273":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_l.png"],"fileNames":{"clean":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384.png","_o":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_o.png","_s":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_s.png","_m":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_m.png","_l":"upload_86280e72-0c1e-40cf-a6c3-79291a7ec384_l.png"},"contentSize":45273,"contentType":"image/jpeg"},"45492":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_a3070781-0b4d-43f7-80b8-5be8138d0930_l.png"],"fileNames":{"clean":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930.png","_o":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_o.png","_s":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_s.png","_m":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_m.png","_l":"upload_a3070781-0b4d-43f7-80b8-5be8138d0930_l.png"},"contentSize":45492,"contentType":"image/jpeg"},"45510":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_892f666b-ac98-4feb-9017-39448434b732_l.png"],"fileNames":{"clean":"upload_892f666b-ac98-4feb-9017-39448434b732.png","_o":"upload_892f666b-ac98-4feb-9017-39448434b732_o.png","_s":"upload_892f666b-ac98-4feb-9017-39448434b732_s.png","_m":"upload_892f666b-ac98-4feb-9017-39448434b732_m.png","_l":"upload_892f666b-ac98-4feb-9017-39448434b732_l.png"},"contentSize":45510,"contentType":"image/jpeg"},"45585":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_l.png"],"fileNames":{"clean":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15.png","_o":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_o.png","_s":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_s.png","_m":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_m.png","_l":"upload_f684c94c-7d37-47ed-91cb-bb458f9cff15_l.png"},"contentSize":45585,"contentType":"image/jpeg"},"74829":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_l.png"],"fileNames":{"clean":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5.png","_o":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_o.png","_s":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_s.png","_m":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_m.png","_l":"upload_cc43cdb6-b877-4b30-a702-b7f591a8bca5_l.png"},"contentSize":74829,"contentType":"image/jpeg"}} \ No newline at end of file +{"23394":{"mediaPaths":["C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_o.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_s.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_m.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_l.png"],"fileNames":{"clean":"upload_33298c77-531a-4559-822c-46a4c43ec062.png","_o":"upload_33298c77-531a-4559-822c-46a4c43ec062_o.png","_s":"upload_33298c77-531a-4559-822c-46a4c43ec062_s.png","_m":"upload_33298c77-531a-4559-822c-46a4c43ec062_m.png","_l":"upload_33298c77-531a-4559-822c-46a4c43ec062_l.png"},"contentSize":23394,"contentType":"image/jpeg"},"23406":{"mediaPaths":["C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_o.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_s.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_m.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_l.png"],"fileNames":{"clean":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb.png","_o":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_o.png","_s":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_s.png","_m":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_m.png","_l":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_l.png"},"contentSize":23406,"contentType":"image/jpeg"}} \ No newline at end of file diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 5998ccfd8..8900f3301 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.GlyFByFfWe7VNNjHImJwA58yoh2cAKDJUPhBKn5IDVUY9oPlXbpyhdJMjfGSRhDZpgEWM0QoSqONu9gBVWDV9aXsf7p8r9TDXK7jBfWs1Qnox4zPf54kSfCHsmV6iw","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568646012183} \ No newline at end of file +{"access_token":"ya29.GlyFB38lolkvmzKJYKWGKXTSSAkRH3HA1SrCMEp3YH8Gy_I06P7w5MN1C9zckoYXQPb-7OuO4vSCchAmiwfds19hhyXDFZhegUZ30y5WhBChJ0vS5X-086QWLgKbsg","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568653300717} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 44175322d76505ac732435876464fe1f17005ad2 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 16 Sep 2019 13:11:29 -0400 Subject: show title and parse font size changes --- src/client/views/nodes/DocumentView.tsx | 3 ++- src/new_fields/RichTextUtils.ts | 5 +++-- src/server/credentials/google_docs_token.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bf7e7df97..9b97705a0 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -583,7 +583,7 @@ export class DocumentView extends DocComponent(Docu document.nativeIgnoreAspect = undefined; }); - public static makeCustomViewClicked = undoBatch((document: Doc): void => { + public static makeCustomViewClicked = undoBatch((document: Doc, showTitle = undefined): void => { document.nativeLayout = document.layout; document.nativeType = document.type; document.nativeNativeWidth = document.nativeWidth; @@ -604,6 +604,7 @@ export class DocumentView extends DocComponent(Docu let metaKey = "data"; let proto = Doc.GetProto(docTemplate); Doc.MakeTemplate(fieldTemplate, metaKey, proto); + fieldTemplate.showTitle = showTitle; Doc.ApplyTemplateTo(docTemplate, document, undefined, false); document.customLayout = document.layout; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index ff28e6861..9b6e55948 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -413,7 +413,7 @@ export namespace RichTextUtils { let matches: RegExpExecArray | null; if ((matches = /p(\d+)/g.exec(markName)) !== null) { converted = "fontSize"; - value = { magnitude: parseInt(matches[1]), unit: "PT" }; + value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" }; } textStyle[converted] = value; } @@ -421,10 +421,11 @@ export namespace RichTextUtils { requests.push(EncodeStyleUpdate(information)); } if (node.type.name === "image") { + const width = attrs.width; requests.push(await EncodeImage({ startIndex: position + nodeSize - 1, uri: attrs.src, - width: attrs.width + width: Number(typeof width === "string" ? width.replace("px", "") : width) })); } position += nodeSize; diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 8900f3301..64bd7a58d 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.GlyFB38lolkvmzKJYKWGKXTSSAkRH3HA1SrCMEp3YH8Gy_I06P7w5MN1C9zckoYXQPb-7OuO4vSCchAmiwfds19hhyXDFZhegUZ30y5WhBChJ0vS5X-086QWLgKbsg","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568653300717} \ No newline at end of file +{"access_token":"ya29.GlyFBwglzOsVNH90uoePSgwPkCqGNDMfx_us2wVe8YyS-MOA54Zdo7F_iiGOTDm9kGsINkQVgLu4rBZE7OTMa5Qxm8BuZIbTG66PPdVI0vbH96nfSlHQL8fnX1WOMQ","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568657110414} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 97f1835d9ee5fba6aa6ebc5d792f6d8a4d979cfa Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 16 Sep 2019 19:34:49 -0400 Subject: directory import and document decorations tweaks --- src/client/apis/google_docs/GoogleApiClientUtils.ts | 1 - src/client/util/Import & Export/DirectoryImportBox.tsx | 6 +----- src/client/views/DocumentDecorations.scss | 4 ++-- src/client/views/DocumentDecorations.tsx | 4 +--- src/new_fields/RichTextUtils.ts | 17 +++++++++++++++-- src/server/apis/google/existing_uploads.json | 2 +- src/server/credentials/google_docs_token.json | 2 +- 7 files changed, 21 insertions(+), 15 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index cbc5da15b..2c84741db 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -4,7 +4,6 @@ import { RouteStore } from "../../../server/RouteStore"; import { Opt } from "../../../new_fields/Doc"; import { isArray } from "util"; import { EditorState } from "prosemirror-state"; -import { RichTextField } from "../../../new_fields/RichTextField"; export const Pulls = "googleDocsPullCount"; export const Pushes = "googleDocsPushCount"; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index d371766dd..c9d34b594 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -156,11 +156,7 @@ export default class DirectoryImportBox extends React.Component if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); } else { - const headers = [ - new SchemaHeaderField("title", "yellow"), - new SchemaHeaderField("size", "blue"), - new SchemaHeaderField("googlePhotosTags", "green") - ]; + const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")]; importContainer = Docs.Create.SchemaDocument(headers, docs, options); } runInAction(() => this.phase = 'External: uploading files to Google Photos...'); diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 39fc7031a..117e63a37 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -287,10 +287,10 @@ $linkGap : 3px; @keyframes shadow-pulse { 0% { - box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8); } 100% { - box-shadow: 0 0 0 35px rgba(0, 0, 0, 0); + box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); } } \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 0a971ed22..e8a1d08e4 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -727,9 +727,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return (
{ - if (!published) { - runInAction(() => this.isAnimatingPulse = true); - } + runInAction(() => this.isAnimatingPulse = true); DocumentDecorations.hasPushedHack = false; this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; }} style={{ animation: this.isAnimatingPulse ? "shadow-pulse 1s infinite" : "none" }}> diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 9b6e55948..8f4ab58eb 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -317,6 +317,10 @@ export namespace RichTextUtils { attributes.fontSize = value.magnitude; } + if (converted === "weightedFontFamily") { + converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman"; + } + let mapped = schema.marks[converted]; if (!mapped) { alert(`No mapping found for ${converted}!`); @@ -342,7 +346,7 @@ export namespace RichTextUtils { ["impact", "weightedFontFamily"] ]); - const FontFamilyMapping = new Map([ + const ExportFontFamilyMapping = new Map([ ["timesNewRoman", "Times New Roman"], ["arial", "Arial"], ["georgia", "Georgia"], @@ -351,6 +355,15 @@ export namespace RichTextUtils { ["impact", "Impact"] ]); + const ImportFontFamilyMapping = new Map([ + ["Times New Roman", "timesNewRoman"], + ["Arial", "arial"], + ["Georgia", "georgia"], + ["Comic Sans MS", "comicSans"], + ["Tahoma", "tahoma"], + ["Impact", "impact"] + ]); + const ignored = ["user_mark"]; const marksToStyle = async (nodes: (Node | null)[]): Promise => { @@ -408,7 +421,7 @@ export namespace RichTextUtils { value = fromHex(attrs.color); break; case "weightedFontFamily": - value = { fontFamily: FontFamilyMapping.get(markName) }; + value = { fontFamily: ExportFontFamilyMapping.get(markName) }; } let matches: RegExpExecArray | null; if ((matches = /p(\d+)/g.exec(markName)) !== null) { diff --git a/src/server/apis/google/existing_uploads.json b/src/server/apis/google/existing_uploads.json index 399c27672..e3cca7a97 100644 --- a/src/server/apis/google/existing_uploads.json +++ b/src/server/apis/google/existing_uploads.json @@ -1 +1 @@ -{"23394":{"mediaPaths":["C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_o.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_s.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_m.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_33298c77-531a-4559-822c-46a4c43ec062_l.png"],"fileNames":{"clean":"upload_33298c77-531a-4559-822c-46a4c43ec062.png","_o":"upload_33298c77-531a-4559-822c-46a4c43ec062_o.png","_s":"upload_33298c77-531a-4559-822c-46a4c43ec062_s.png","_m":"upload_33298c77-531a-4559-822c-46a4c43ec062_m.png","_l":"upload_33298c77-531a-4559-822c-46a4c43ec062_l.png"},"contentSize":23394,"contentType":"image/jpeg"},"23406":{"mediaPaths":["C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_o.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_s.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_m.png","C:\\gitstuff\\GitCode\\Dash-Web\\src\\server\\public\\files\\upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_l.png"],"fileNames":{"clean":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb.png","_o":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_o.png","_s":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_s.png","_m":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_m.png","_l":"upload_bd462cce-cf8d-4aa8-bd39-c6ad69bf1fbb_l.png"},"contentSize":23406,"contentType":"image/jpeg"}} \ No newline at end of file +{"23394":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_l.png"],"fileNames":{"clean":"upload_9d5ae803-8c10-4e85-8751-53d2fe71277f.png","_o":"upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_o.png","_s":"upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_s.png","_m":"upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_m.png","_l":"upload_9d5ae803-8c10-4e85-8751-53d2fe71277f_l.png"},"contentSize":23394,"contentType":"image/jpeg"},"23406":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_l.png"],"fileNames":{"clean":"upload_fd27edc4-99a0-4405-b1f6-4c70924667c8.png","_o":"upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_o.png","_s":"upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_s.png","_m":"upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_m.png","_l":"upload_fd27edc4-99a0-4405-b1f6-4c70924667c8_l.png"},"contentSize":23406,"contentType":"image/jpeg"},"45210":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e46cf381-3841-48bc-833b-019a5c6157e3_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e46cf381-3841-48bc-833b-019a5c6157e3_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e46cf381-3841-48bc-833b-019a5c6157e3_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_e46cf381-3841-48bc-833b-019a5c6157e3_l.png"],"fileNames":{"clean":"upload_e46cf381-3841-48bc-833b-019a5c6157e3.png","_o":"upload_e46cf381-3841-48bc-833b-019a5c6157e3_o.png","_s":"upload_e46cf381-3841-48bc-833b-019a5c6157e3_s.png","_m":"upload_e46cf381-3841-48bc-833b-019a5c6157e3_m.png","_l":"upload_e46cf381-3841-48bc-833b-019a5c6157e3_l.png"},"contentSize":45210,"contentType":"image/jpeg"},"45229":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_l.png"],"fileNames":{"clean":"upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b.png","_o":"upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_o.png","_s":"upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_s.png","_m":"upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_m.png","_l":"upload_ba4f46ea-f2ab-4dcd-8da0-4f89916e665b_l.png"},"contentSize":45229,"contentType":"image/jpeg"},"45230":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_l.png"],"fileNames":{"clean":"upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5.png","_o":"upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_o.png","_s":"upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_s.png","_m":"upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_m.png","_l":"upload_2d39b115-4fcb-4bef-abbe-cdaa029c11f5_l.png"},"contentSize":45230,"contentType":"image/jpeg"},"45286":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_l.png"],"fileNames":{"clean":"upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0.png","_o":"upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_o.png","_s":"upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_s.png","_m":"upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_m.png","_l":"upload_3c726016-3c72-4a4a-8dfd-ce7a6977c8e0_l.png"},"contentSize":45286,"contentType":"image/jpeg"},"45585":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_l.png"],"fileNames":{"clean":"upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64.png","_o":"upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_o.png","_s":"upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_s.png","_m":"upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_m.png","_l":"upload_7e097c1d-1c8b-433b-ae3b-25beafe95a64_l.png"},"contentSize":45585,"contentType":"image/jpeg"},"74829":{"mediaPaths":["C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_o.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_s.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_m.png","C:\\Users\\avd\\Desktop\\Sam\\Dash-Web\\src\\server\\public\\files\\upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_l.png"],"fileNames":{"clean":"upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a.png","_o":"upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_o.png","_s":"upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_s.png","_m":"upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_m.png","_l":"upload_8eacbcc4-b350-4767-9808-01d5b46e9b6a_l.png"},"contentSize":74829,"contentType":"image/jpeg"}} \ No newline at end of file diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index 64bd7a58d..a536d6c3d 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.GlyFBwglzOsVNH90uoePSgwPkCqGNDMfx_us2wVe8YyS-MOA54Zdo7F_iiGOTDm9kGsINkQVgLu4rBZE7OTMa5Qxm8BuZIbTG66PPdVI0vbH96nfSlHQL8fnX1WOMQ","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568657110414} \ No newline at end of file +{"access_token":"ya29.GlyGB1dc7fKtWD1FtSvh_6aL3eaDJMFAfiV2EGTDK20fCjinY2FNpzJKhDn8p_IN2NupjQ_fXwqM6orx-E6MUCyGN3YZdTmPOaSd-pQlqIl6TFN49pxuzoxguBL4Sw","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568679048543} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 1310633790e3db50a31a1cc6d357306d7884a053 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 17 Sep 2019 15:30:02 -0400 Subject: cleaning up icon animations --- .../views/collections/CollectionStackingView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 60 +++++++++------- src/client/views/nodes/DocumentView.tsx | 82 +++++++++------------- src/new_fields/Doc.ts | 10 ++- 4 files changed, 80 insertions(+), 74 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 0a96676e3..14a9dc9d9 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -163,7 +163,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { ; } getDocHeight(d?: Doc) { - if (!d || d.willMaximize) return 0; + if (!d) return 0; let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); if (!d.ignoreAspect && nw && nh) { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 9f0bd736d..19d4a6784 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,6 +1,6 @@ -import { computed } from "mobx"; +import { computed, action, observable, reaction, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; +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"; @@ -8,7 +8,7 @@ import { percent2frac } from "../../../Utils" import { DocumentView, DocumentViewProps, documentSchema } from "./DocumentView"; import "./CollectionFreeFormDocumentView.scss"; import React = require("react"); -import { Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -30,11 +30,12 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(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.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } - @computed get width() { return this.Document.willMaximize ? 0 : this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } - @computed get height() { return this.Document.willMaximize ? 0 : this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.Document.height || 0; } + @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 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); } @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); } @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); } @@ -54,26 +55,35 @@ export class CollectionFreeFormDocumentView extends DocComponent this.props.Document.iconTarget, + () => { + 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; + } + }, { fireImmediately: true }); + } + contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect ? this.width / this.nativeWidth : 1; panelWidth = () => this.props.PanelWidth(); panelHeight = () => this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth) - - animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { - this.props.bringToFront(this.props.Document); - let targetPos = [this.Document.x || 0, this.Document.y || 0]; - let iconPos = this.props.ScreenToLocalTransform().transformPoint(icon[0], icon[1]); - DocumentView.animateBetweenIconFunc(this.props.Document, - this.Document.width || 0, this.Document.height || 0, stime, maximizing, (progress: number) => { - let pval = maximizing ? - [iconPos[0] + (targetPos[0] - iconPos[0]) * progress, iconPos[1] + (targetPos[1] - iconPos[1]) * progress] : - [targetPos[0] + (iconPos[0] - targetPos[0]) * progress, targetPos[1] + (iconPos[1] - targetPos[1]) * progress]; - this.Document.x = progress === 1 ? targetPos[0] : pval[0]; - this.Document.y = progress === 1 ? targetPos[1] : pval[1]; - }); - } + .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth); borderRounding = () => { let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; @@ -97,6 +107,9 @@ export class CollectionFreeFormDocumentView extends DocComponent
); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f58587066..37c38cc04 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -102,7 +102,7 @@ export interface DocumentViewProps { zoomToScale: (scale: number) => void; backgroundColor: (doc: Doc) => string | undefined; getScale: () => number; - animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void; + animateBetweenIcon?: (maximize: boolean, target: number[]) => void; ChromeHeight?: () => number; } @@ -115,7 +115,6 @@ export const documentSchema = createSchema({ opacity: "number", hidden: "boolean", onClick: ScriptField, - willMaximize: "boolean", ignoreAspect: "boolean", autoHeight: "boolean", isTemplate: "boolean", @@ -148,7 +147,6 @@ export class DocumentView extends DocComponent(Docu private _hitTemplateDrag = false; private _mainCont = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; - private _animateToIconDisposer?: IReactionDisposer; public get ContentDiv() { return this._mainCont.current; } @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @@ -161,35 +159,9 @@ export class DocumentView extends DocComponent(Docu handlers: { drop: this.drop.bind(this) } }); } - this._animateToIconDisposer = reaction(() => this.Document.isIconAnimating, (values) => - (values instanceof List) && this.animateBetweenIcon(values, values[2], values[3] ? true : false) - , { fireImmediately: true }); DocumentManager.Instance.DocumentViews.push(this); } - animateBetweenIcon = (iconPos: number[], startTime: number, maximizing: boolean) => { - this.props.animateBetweenIcon ? this.props.animateBetweenIcon(iconPos, startTime, maximizing) : - DocumentView.animateBetweenIconFunc(this.props.Document, this.Document.width || 0, this.Document.height || 0, startTime, maximizing); - } - - public static animateBetweenIconFunc = (doc: Doc, width: number, height: number, stime: number, maximizing: boolean, cb?: (progress: number) => void) => { - setTimeout(() => { - let now = Date.now(); - let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1; - doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress; - doc.height = progress === 1 ? height : maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress; - cb && cb(progress); - if (now < stime + 200) { - DocumentView.animateBetweenIconFunc(doc, width, height, stime, maximizing, cb); - } - else { - doc.isMinimized = !maximizing; - doc.isIconAnimating = undefined; - } - doc.willMaximize = false; - }, - 2); - } @action componentDidUpdate() { this._dropDisposer && this._dropDisposer(); @@ -201,7 +173,6 @@ export class DocumentView extends DocComponent(Docu } @action componentWillUnmount() { - this._animateToIconDisposer && this._animateToIconDisposer(); this._dropDisposer && this._dropDisposer(); DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } @@ -235,30 +206,45 @@ export class DocumentView extends DocComponent(Docu } } - static _undoBatch?: UndoManager.Batch = undefined; @action public collapseTargetsToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { SelectionManager.DeselectAll(); if (expandedDocs) { - if (!DocumentView._undoBatch) { - DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); - } let isMinimized: boolean | undefined; expandedDocs.map(maximizedDoc => { - let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); - if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { - if (isMinimized === undefined) { - isMinimized = BoolCast(maximizedDoc.isMinimized); + 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([0, 0]); + maximizedDoc.isMinimized = false; } - maximizedDoc.willMaximize = isMinimized; - maximizedDoc.isMinimized = false; - maximizedDoc.isIconAnimating = new List([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); + maximizedDoc.iconTarget = new List([...scrpt, 1]); + setTimeout(() => { + maximizedDoc.isIconAnimating = new List([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([...scrpt, 0]); + // MINIMIZE DOC + maximizedDoc.isIconAnimating = new List([0, 0]); + setTimeout(() => { + if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] === 0) { + maximizedDoc.isMinimized = true; + maximizedDoc.isIconAnimating = undefined; + } + }, 750); } }); - setTimeout(() => { - DocumentView._undoBatch && DocumentView._undoBatch.end(); - DocumentView._undoBatch = undefined; - }, 500); } } @@ -791,8 +777,8 @@ export class DocumentView extends DocComponent(Docu this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); - const nativeWidth = this.Document.willMaximize ? 0 : this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%"; - const nativeHeight = this.Document.willMaximize ? 0 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; + 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; @@ -800,6 +786,7 @@ export class DocumentView extends DocComponent(Docu const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); const borderRounding = this.Document.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) :
{this.Document.searchFields} @@ -830,6 +817,7 @@ export class DocumentView extends DocComponent(Docu
NumCast(this[SelfProxy].width); // bcz: is this the right way to access width/height? it didn't work with : this.width - public [HeightSym] = () => NumCast(this[SelfProxy].height); + public [WidthSym] = () => { + let iconAnimating = this[SelfProxy].isIconAnimating ? Array.from(Cast(this[SelfProxy].isIconAnimating, listSpec("number"))!) : undefined; + return iconAnimating ? iconAnimating[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); + } [ToScriptString]() { return "invalid"; -- cgit v1.2.3-70-g09d2 From 3e6f24e4ac4a4b64620d8c9f614f214f8f1c7b94 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 17 Sep 2019 19:47:05 -0400 Subject: cleanup for animating document icon collapse/expand - still need to fix for docs without native dimensions. lots of lint fixes. --- src/Utils.ts | 17 ++--- src/client/documents/Documents.ts | 4 +- src/client/util/ProsemirrorExampleTransfer.ts | 4 +- src/client/util/RichTextRules.ts | 6 +- src/client/util/RichTextSchema.tsx | 14 ++-- src/client/util/TooltipTextMenu.tsx | 4 +- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/OverlayView.tsx | 1 + src/client/views/ScriptBox.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 14 ++-- src/client/views/nodes/ButtonBox.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 35 ++++------ src/client/views/nodes/DocumentView.tsx | 75 +++++++++------------- src/client/views/nodes/FormattedTextBox.tsx | 6 +- src/client/views/nodes/ImageBox.tsx | 8 +-- src/client/views/nodes/PDFBox.tsx | 6 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 2 +- src/new_fields/Doc.ts | 12 ++-- .../authentication/models/current_user_utils.ts | 2 +- 23 files changed, 99 insertions(+), 129 deletions(-) (limited to 'src/new_fields') 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 673acfbaf..206e2c4f1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -631,8 +631,8 @@ export namespace DocUtils { LinkManager.Instance.deleteLink(link); LinkManager.Instance.addLink(link); } - }) - }) + }); + }); } } }); 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>(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>(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, dispatch: (tx: Transaction) => 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 f027a4bf7..ba4b92a25 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -349,8 +349,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; } @@ -371,10 +372,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; } @@ -633,11 +633,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(); @@ -776,7 +776,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/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 84d045e6f..5764af282 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -523,12 +523,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/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7829bd7f1..9a2105467 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -439,7 +439,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> 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(); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index ba125d6e5..59229418d 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -166,7 +166,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/OverlayView.tsx b/src/client/views/OverlayView.tsx index da4b71e5c..15faea3cd 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} 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 { // tslint:disable-next-line: no-unnecessary-callback-wrapper let params: string[] = []; let setParams = (p: string[]) => params.splice(0, params.length, ...p); - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + let scriptingBox = { if (prewrapper) { text = prewrapper + text + (postwrapper ? postwrapper : ""); } 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 { 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/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 03ac012b4..8d392d764 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"; @@ -176,8 +176,8 @@ 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) { @@ -341,7 +341,7 @@ 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) { @@ -473,7 +473,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)); @@ -848,7 +848,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor; } }) - ) + ); } analyzeStrokes = async () => { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 68d3b8ae1..eebf6c167 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -49,7 +49,7 @@ export class ButtonBox extends DocComponent(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" }); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 19d4a6784..bade3f8c1 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"); @@ -18,7 +18,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { height?: number; jitterRotation: number; } -const positionSchema = createSchema({ +export const positionSchema = createSchema({ zIndex: "number", x: "number", y: "number", @@ -32,8 +32,8 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema); export class CollectionFreeFormDocumentView extends DocComponent(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 +59,10 @@ export class CollectionFreeFormDocumentView extends DocComponent 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 +71,7 @@ export class CollectionFreeFormDocumentView extends DocComponent 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,8 +95,7 @@ export class CollectionFreeFormDocumentView extends DocComponent(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); + 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([...scrpt, 0]); + expDoc.animateToDimensions = new List([0, 0]); } - 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([0, 0]); - maximizedDoc.isMinimized = false; + setTimeout(() => { + expDoc.isAnimating = "max"; + expDoc.animateToPos = new List([0, 0, 1]); + expDoc.animateToDimensions = new List([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([...scrpt, 0]); + expDoc.animateToDimensions = new List([0, 0]); + setTimeout(() => { + if (expDoc.isAnimating === "min") { + expDoc.isMinimized = true; + expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined; } - maximizedDoc.iconTarget = new List([...scrpt, 1]); - setTimeout(() => { - maximizedDoc.isIconAnimating = new List([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([...scrpt, 0]); - // MINIMIZE DOC - maximizedDoc.isIconAnimating = new List([0, 0]); - setTimeout(() => { - if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] === 0) { - maximizedDoc.isMinimized = true; - maximizedDoc.isIconAnimating = undefined; - } - }, 750); - } - }); - } + }, 600); + } + }); } onClick = async (e: React.MouseEvent) => { @@ -530,14 +518,14 @@ export class DocumentView extends DocComponent(Docu 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; - }) + }); } } @undoBatch @action toggleCustomView = (): void => { if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { - Doc.MakeMetadataFieldTemplate(this.props.Document, 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(); @@ -623,7 +611,7 @@ export class DocumentView extends DocComponent(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" }); @@ -786,7 +774,6 @@ export class DocumentView extends DocComponent(Docu const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); const borderRounding = this.Document.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) :
{this.Document.searchFields} @@ -817,7 +804,7 @@ export class DocumentView extends DocComponent(Docu
{ doc.nonCustomNativeWidth = undefined; doc.nonCustomNativeHeight = undefined; doc.nonCustomIgnoreAspect = undefined; -} +}; let makeCustomView = (doc: any): void => { doc.nativeLayout = doc.layout; doc.nativeType = doc.type; @@ -911,7 +898,7 @@ let makeCustomView = (doc: any): void => { doc.customNativeHeight = undefined; doc.customIgnoreAspect = undefined; } -} +}; Scripting.addGlobal(function toggleDetail(doc: any) { if (doc.type !== DocumentType.COL && doc.type !== DocumentType.TEMPLATE) { makeCustomView(doc); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 77e29632e..1c946aa78 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -182,9 +182,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(); } @@ -195,7 +195,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("::"); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index beccce9dd..42307ee02 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -23,7 +23,7 @@ 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 +50,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(ImageDocument) { @@ -220,7 +220,7 @@ export class ImageBox extends DocComponent(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" }); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index df35b603c..31e8f122b 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -14,14 +14,14 @@ import { CompileScript } from '../../util/Scripting'; 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); +type PdfDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>; +const PdfDocument = makeInterface(documentSchema, pageSchema); export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === KeyCodes.BACKSPACE) e.stopPropagation(); }; @observer diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 96f011eff..a696f255d 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -14,7 +14,7 @@ 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"; @@ -25,8 +25,8 @@ 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); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index eeb2531a2..34e3b0931 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -58,7 +58,7 @@ class RegionAnnotation extends React.Component { runInAction(() => this._brushed = brushed); } } - ) + ); } componentWillUnmount() { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 7bc1d3507..c508935f2 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -81,7 +81,7 @@ export class PDFViewer extends React.Component { return annotations.filter(anno => { let run = this._script.run({ this: anno }); return run.success ? run.result : true; - }) + }); } @computed get nonDocAnnotations() { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 614babd3c..0cf1208d7 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -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) { @@ -419,7 +419,7 @@ export namespace 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})`; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index af5774ebe..8cac8550c 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -59,7 +59,7 @@ export class CurrentUserUtils { 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)); if (doc.recentlyClosed === undefined) { const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 }); recentlyClosed.excludeFromLibrary = true; -- cgit v1.2.3-70-g09d2 From b93a9b48a00571e07859f5b6c6ade1cdf764e8ea Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 18 Sep 2019 00:20:58 -0400 Subject: cleaned up custom template layouts --- src/client/util/DragManager.ts | 2 +- src/client/views/DocumentDecorations.tsx | 27 ++-- src/client/views/TemplateMenu.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/nodes/ButtonBox.tsx | 4 +- src/client/views/nodes/DocumentContentsView.tsx | 9 +- src/client/views/nodes/DocumentView.tsx | 166 +++++++-------------- src/new_fields/Doc.ts | 16 +- 8 files changed, 80 insertions(+), 150 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 252accefa..cb55e1bc6 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -250,7 +250,7 @@ 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 : diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9a2105467..2826f4422 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,40 +1,37 @@ -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 { RichTextField } from '../../new_fields/RichTextField'; 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 { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs, DocUtils } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; +import { LinkManager } from '../util/LinkManager'; 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 './DocumentDecorations.scss'; +import { LinkMenu } from "./linking/LinkMenu"; +import { MetadataEntryMenu } from './MetadataEntryMenu'; +import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; -import { LinkMenu } from "./linking/LinkMenu"; +import { ImageBox } from './nodes/ImageBox'; 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; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index d57a72dad..397bb14a4 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -123,7 +123,7 @@ export class TemplateMenu extends React.Component {
this.toggleTemplateActivity()}>+
    {templateMenu} - + {/* */}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8d392d764..0c559d83d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -903,9 +903,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" }); diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index eebf6c167..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'; @@ -68,7 +68,7 @@ export class ButtonBox extends DocComponent(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 (
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 { - let field = this.props.Document.templates; - if (field && field instanceof List) { - return field; - } - return new List(); - } @computed get finalLayout() { return this.props.layoutKey === "overlayLayout" ? "
" : this.layout; } @@ -107,7 +100,7 @@ export class DocumentContentsView extends React.Component 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 (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.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.dataDoc || this.props.Document).layoutCustom = Doc.MakeTitled("layoutCustom"); + } else { + swapViews(this.props.Document, "layoutCustom", "layoutNative"); + } } @undoBatch @@ -487,9 +462,9 @@ export class DocumentView extends DocComponent(Docu onDrop = (e: React.DragEvent) => { let text = e.dataTransfer.getData("text/plain"); if (!e.isDefaultPrevented() && text && text.startsWith("(Docu 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) { + if (typeof this.props.Document.layout === "string") { this.makeCustomViewClicked(); - } else if (this.Document.nativeLayout) { + } else { this.makeNativeViewClicked(); } } @@ -624,7 +599,7 @@ export class DocumentView extends DocComponent(Docu } 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" }); @@ -741,12 +716,6 @@ export class DocumentView extends DocComponent(Docu 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?? } @@ -843,66 +812,33 @@ export class DocumentView extends DocComponent(Docu } } +let swapViews = async (doc: any, newLayoutField: any, oldLayoutField: any) => { + let oldLayoutExt = 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.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.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/new_fields/Doc.ts b/src/new_fields/Doc.ts index 0cf1208d7..ffa9a4e98 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -405,17 +405,21 @@ 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) { @@ -560,7 +564,7 @@ export namespace Doc { !templateDoc.nativeWidth && (otherdoc.ignoreAspect = true); return otherdoc; } - export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc, useTemplateDoc?: boolean) { + export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc) { if (!templateDoc) { target.layout = undefined; target.nativeWidth = undefined; @@ -569,7 +573,7 @@ export namespace Doc { target.type = undefined; return; } - let temp = useTemplateDoc ? templateDoc : Doc.MakeDelegate(templateDoc); + let temp = Doc.MakeDelegate(templateDoc); target.nativeWidth = Doc.GetProto(target).nativeWidth = undefined; target.nativeHeight = Doc.GetProto(target).nativeHeight = undefined; !templateDoc.nativeWidth && (target.nativeWidth = 0); -- cgit v1.2.3-70-g09d2 From 088a16c9589e640e814d9120fecc0003ab9d594e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 18 Sep 2019 09:48:15 -0400 Subject: cleaned up some CompileScript code --- src/client/documents/Documents.ts | 6 ++-- src/client/util/DragManager.ts | 10 +------ .../views/collections/CollectionSchemaView.tsx | 7 ++--- .../CollectionStackingViewFieldColumn.tsx | 10 ++----- .../views/collections/CollectionViewChromes.tsx | 17 ++++------- .../collections/collectionFreeForm/MarqueeView.tsx | 21 +++++--------- src/client/views/nodes/DocumentView.tsx | 15 ++-------- src/client/views/nodes/IconBox.tsx | 5 ++-- src/client/views/nodes/ImageBox.tsx | 5 +--- src/client/views/nodes/PDFBox.tsx | 6 ++-- src/client/views/nodes/VideoBox.tsx | 14 +-------- src/new_fields/Doc.ts | 9 ++---- src/new_fields/ScriptField.ts | 33 ++++++++++++++++++---- 13 files changed, 58 insertions(+), 100 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 206e2c4f1..99707db19 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -663,10 +663,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/DragManager.ts b/src/client/util/DragManager.ts index cb55e1bc6..bd7051993 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -256,15 +256,7 @@ export namespace DragManager { 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(params); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 25d3bd128..5d09ade32 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -946,13 +946,10 @@ export class CollectionSchemaPreview extends React.Component { 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)); + target.layout = target.detailedLayout = Doc.MakeDelegate(de.data.draggedDocuments[0]); + target.miniLayout = ComputedField.MakeFunction("this.image_data[0]"); }); e.stopPropagation(); } 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 { - 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/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 8decebe0d..479e98840 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; @@ -327,11 +321,10 @@ export class MarqueeView extends React.Component }); 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([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; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 152dc3ddd..16f8275c7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -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); @@ -559,18 +559,7 @@ export class DocumentView extends DocComponent(Docu 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: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript("toggleDetail(this)"), 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: 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) }); 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 { } 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 42307ee02..bbcc296e6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -17,7 +17,6 @@ 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'; @@ -244,9 +243,7 @@ export class ImageBox extends DocComponent(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/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 31e8f122b..64b84ba55 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -10,7 +10,6 @@ import { ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; import { KeyCodes } from '../../northstar/utils/KeyCodes'; -import { CompileScript } from '../../util/Scripting'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; import { PDFViewer } from "../pdf/PDFViewer"; @@ -104,9 +103,8 @@ export class PDFBox extends DocComponent(PdfDocumen 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)); + `this.${this._keyValue} === ${this._valueValue}` : "true"; + this.props.Document.filterScript = ScriptField.MakeFunction(scriptText); } scrollTo = (y: number) => { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index a696f255d..aa6be56bb 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -20,7 +20,6 @@ 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'); @@ -116,18 +115,7 @@ export class VideoBox extends DocComponent(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' diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index ffa9a4e98..98b3d824e 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"; @@ -426,12 +426,7 @@ export namespace 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; } diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts index 83fb52d07..8f10766df 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,6 +101,24 @@ 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 @@ -109,11 +127,16 @@ 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; + return val.success ? val.result : undefined; }); + 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 { -- cgit v1.2.3-70-g09d2 From 90e36ff4532bfc23831ad0b481c88c00cfac6ffe Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 18 Sep 2019 15:00:59 -0400 Subject: cleaned up a lot more stuff in DocumentView and related --- src/client/util/DictationManager.ts | 2 +- src/client/util/DragManager.ts | 11 +- src/client/views/DocumentDecorations.tsx | 16 +- src/client/views/MainOverlayTextBox.tsx | 3 +- src/client/views/TemplateMenu.tsx | 8 +- .../views/collections/CollectionBaseView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 10 +- .../views/collections/CollectionTreeView.tsx | 5 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 12 +- src/client/views/nodes/DocumentView.tsx | 181 ++++++++------------- src/client/views/nodes/DragBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 3 +- src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 40 +++-- 14 files changed, 129 insertions(+), 168 deletions(-) (limited to 'src/new_fields') 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/DragManager.ts b/src/client/util/DragManager.ts index bd7051993..e3cdb3f39 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -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; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2826f4422..12bd84684 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -226,7 +226,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; } @@ -251,11 +251,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; @@ -846,11 +844,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } let templates: Map = 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); - }); + Array.from(Object.values(Templates.TemplateList)).map(template => + templates.set(template, SelectionManager.SelectedDocuments().reduce((checked, doc) => checked || (doc. + Document["show" + template.Name] ? true : false), false))); 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; diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index e926fb1ad..8e72d236c 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -104,8 +104,7 @@ export class MainOverlayTextBox extends React.Component 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), diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 397bb14a4..34876cc79 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -90,16 +90,16 @@ export class TemplateMenu extends React.Component { @action toggleTemplate = (event: React.ChangeEvent, 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.map(template => this.props.docs.map(d => d.Document["show" + template.Name] = undefined)); } @action @@ -110,7 +110,7 @@ export class TemplateMenu extends React.Component { @undoBatch @action toggleChrome = (): void => { - this.props.docs.map(dv => dv.layoutDoc.chromeStatus = (dv.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled")); + this.props.docs.map(dv => dv.Document.chromeStatus = (dv.Document.chromeStatus !== "disabled" ? "disabled" : "enabled")); } render() { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index a54718e9e..e4e798608 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -85,7 +85,7 @@ export class CollectionBaseView extends React.Component { 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? diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a350cfcc5..55ba3a314 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -44,7 +44,7 @@ export class CollectionDockingView extends React.Component { @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); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 40bea2f9d..fed833261 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -405,7 +405,7 @@ class TreeView extends React.Component {
; } public static GetChildElements( - docList: Doc[], + docs: Doc[], treeViewId: string, containingCollection: Doc, dataDoc: Doc | undefined, @@ -425,7 +425,6 @@ class TreeView extends React.Component { preventTreeViewOpen: boolean, renderedIds: string[] ) { - let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0); let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); if (viewSpecScript) { let script = viewSpecScript.script; @@ -548,7 +547,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(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0c559d83d..359731bda 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -323,8 +323,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 => { @@ -347,8 +347,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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 +387,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 diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 16f8275c7..11924f86a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -65,17 +65,6 @@ 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; Document: Doc; @@ -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); @@ -154,47 +141,28 @@ export class DocumentView extends DocComponent(Docu @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, { @@ -251,7 +219,7 @@ export class DocumentView extends DocComponent(Docu let fullScreenAlias = Doc.MakeAlias(this.props.Document); Doc.UseDetailLayout(fullScreenAlias); fullScreenAlias.showCaption = "caption"; - this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab"); + this.props.addDocTab(fullScreenAlias, this.props.DataDoc, "inTab"); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } @@ -306,7 +274,6 @@ export class DocumentView extends DocComponent(Docu // @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)) { @@ -355,7 +322,7 @@ export class DocumentView extends DocComponent(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); @@ -382,7 +349,7 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeCustomViewClicked = async () => { if (this.props.Document.layoutCustom === undefined) { - Doc.GetProto(this.dataDoc || this.props.Document).layoutNative = Doc.MakeTitled("layoutNative"); + Doc.GetProto(this.props.DataDoc || this.props.Document).layoutNative = Doc.MakeTitled("layoutNative"); await swapViews(this.props.Document, "", "layoutNative"); let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; @@ -398,7 +365,7 @@ export class DocumentView extends DocComponent(Docu Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true); Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined); - Doc.GetProto(this.dataDoc || this.props.Document).layoutCustom = Doc.MakeTitled("layoutCustom"); + Doc.GetProto(this.props.DataDoc || this.props.Document).layoutCustom = Doc.MakeTitled("layoutCustom"); } else { swapViews(this.props.Document, "layoutCustom", "layoutNative"); } @@ -406,12 +373,12 @@ export class DocumentView extends DocComponent(Docu @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; } } @@ -492,7 +459,7 @@ export class DocumentView extends DocComponent(Docu 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; }); } } @@ -513,14 +480,14 @@ export class DocumentView extends DocComponent(Docu @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 () => { @@ -549,10 +516,10 @@ export class DocumentView extends DocComponent(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" }); @@ -560,7 +527,7 @@ export class DocumentView extends DocComponent(Docu 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: () => this.Document.onClick = ScriptField.MakeScript("toggleDetail(this)"), 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: 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({ @@ -577,10 +544,10 @@ export class DocumentView extends DocComponent(Docu 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: `${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) { @@ -683,9 +650,6 @@ export class DocumentView extends DocComponent(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; } @@ -698,37 +662,30 @@ export class DocumentView extends DocComponent(Docu select={this.select} onClick={this.onClickHandler} layoutKey={"layout"} - fitToBox={this.Document.fitToBox ? true : this.props.fitToBox} - DataDoc={this.dataDoc} />); + DataDoc={this.props.DataDoc} />); } 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 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;// bcz: why 8?? } - 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); - } - 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 colorSet = this.Document.backgroundColor !== this.Document.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 backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ? + this.props.backgroundColor(this.Document) || StrCast(this.Document.backgroundColor) : + ruleColor && !colorSet ? ruleColor : StrCast(this.Document.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.Document.showTitle; + const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.Document.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 localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; @@ -739,7 +696,7 @@ export class DocumentView extends DocComponent(Docu const captionView = (!showCaption ? (null) :
@@ -752,19 +709,19 @@ export class DocumentView extends DocComponent(Docu transform: `scale(${1 / this.props.ContentScaling()})` }}> 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} />
); return (
(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 ? diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx index 067d47de4..2d1a98df2 100644 --- a/src/client/views/nodes/DragBox.tsx +++ b/src/client/views/nodes/DragBox.tsx @@ -45,7 +45,7 @@ export class DragBox extends DocComponent(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; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 157309cf5..3883886e8 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -921,7 +921,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 ( @@ -947,7 +946,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} > -
+
); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 98b3d824e..af7775d94 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -721,7 +721,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(field.map((d: any) => Doc.MakeAlias(d))); }); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 8cac8550c..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(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(), { 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() { -- cgit v1.2.3-70-g09d2 From 25ea85f7cf2c7f6109eb740ffc111325a39fb745 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 18 Sep 2019 22:20:12 -0400 Subject: a bunch more template cleanup. no more miniLayout and detailedLayout. just layoutNative and layoutCustom --- src/client/views/DocumentDecorations.tsx | 15 +++-- src/client/views/GlobalKeyHandler.ts | 3 - .../views/collections/CollectionBaseView.tsx | 1 - .../views/collections/CollectionSchemaView.tsx | 6 +- .../views/collections/CollectionStackingView.tsx | 6 -- src/client/views/collections/CollectionSubView.tsx | 16 ++++- .../views/collections/CollectionTreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 26 -------- src/client/views/nodes/DocumentView.tsx | 19 +++--- src/new_fields/Doc.ts | 74 ++++++---------------- 10 files changed, 56 insertions(+), 114 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 12bd84684..b730f88a4 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,7 +24,7 @@ import './DocumentDecorations.scss'; import { LinkMenu } from "./linking/LinkMenu"; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; -import { DocumentView } from "./nodes/DocumentView"; +import { DocumentView, swapViews } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; @@ -155,16 +155,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (containerView) { let docTemplate = containerView.props.Document; 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}"}`); } } } @@ -845,8 +847,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let templates: Map = new Map(); Array.from(Object.values(Templates.TemplateList)).map(template => - templates.set(template, SelectionManager.SelectedDocuments().reduce((checked, doc) => checked || (doc. - Document["show" + template.Name] ? true : false), false))); + templates.set(template, SelectionManager.SelectedDocuments().reduce((checked, doc) => checked || (doc.props.Document["show" + template.Name] ? true : false), false))); 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; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 59229418d..ef5d76ce8 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 { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index e4e798608..fdb2f0dc9 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, diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 5d09ade32..4d2b8e1c1 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -948,8 +948,10 @@ export class CollectionSchemaPreview extends React.Component { let target = Doc.GetProto(otherdoc); - target.layout = target.detailedLayout = Doc.MakeDelegate(de.data.draggedDocuments[0]); - target.miniLayout = ComputedField.MakeFunction("this.image_data[0]"); + 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(); } 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(); _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/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d13c69ecf..001560167 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"; @@ -50,6 +50,18 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this.createDropTarget(ele); } + _childLayoutDisposer?: IReactionDisposer; + + 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(); + } + @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); } @@ -119,7 +131,7 @@ export function CollectionSubView(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; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index fed833261..b4026a810 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -178,7 +178,7 @@ class TreeView extends React.Component { 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([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; return this.props.addDocument(doc); @@ -613,7 +613,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([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/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index baf907634..c9e78cee6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -186,32 +186,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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() { return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 90dbe4c86..d4e396752 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -220,9 +220,11 @@ export class DocumentView extends DocComponent(Docu 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.props.DataDoc, "inTab"); + 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); } @@ -392,7 +394,7 @@ export class DocumentView extends DocComponent(Docu DocUtils.MakeLink(sourceDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `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) { @@ -528,9 +530,6 @@ export class DocumentView extends DocComponent(Docu 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.layoutNative) { @@ -728,8 +727,8 @@ export class DocumentView extends DocComponent(Docu } } -let swapViews = async (doc: any, newLayoutField: any, oldLayoutField: any) => { - let oldLayoutExt = await Cast(doc[oldLayoutField], Doc); +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; @@ -737,6 +736,7 @@ let swapViews = async (doc: any, newLayoutField: any, oldLayoutField: any) => { oldLayoutExt.nativeWidth = doc.nativeWidth; oldLayoutExt.nativeHeight = doc.nativeHeight; oldLayoutExt.ignoreAspect = doc.ignoreAspect; + oldLayoutExt.backgroundLayout = doc.backgroundLayout; oldLayoutExt.type = doc.type; oldLayoutExt.layout = doc.layout; } @@ -749,6 +749,7 @@ let swapViews = async (doc: any, newLayoutField: any, oldLayoutField: any) => { 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; } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index af7775d94..0859cf41a 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -544,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) { + 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; @@ -568,25 +555,24 @@ export namespace Doc { target.type = undefined; return; } - let temp = 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) { @@ -628,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; -- cgit v1.2.3-70-g09d2 From 6f7936d5c71bf3c802d73f47b19abe96c6d61848 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 20 Sep 2019 15:11:30 -0400 Subject: simplified script execution api a little. fixed dataDoc() related stuff in various Box's. fixed some template stuff. --- src/client/util/Scripting.ts | 8 ++-- src/client/util/SelectionManager.ts | 4 ++ src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/ScriptingRepl.tsx | 20 ++++---- src/client/views/TemplateMenu.tsx | 11 ++++- .../views/collections/CollectionBaseView.tsx | 4 +- .../views/collections/CollectionSchemaCells.tsx | 10 ++-- src/client/views/collections/CollectionSubView.tsx | 40 ++++++---------- .../views/collections/CollectionTreeView.tsx | 13 +---- .../collectionFreeForm/CollectionFreeFormView.tsx | 56 +++++++++++----------- .../collections/collectionFreeForm/MarqueeView.tsx | 14 ++---- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 29 +++++++---- src/client/views/nodes/DragBox.tsx | 8 ++-- src/client/views/nodes/FormattedTextBox.tsx | 7 ++- src/client/views/nodes/ImageBox.tsx | 6 +-- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 22 ++++----- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/pdf/PDFAnnotationLayer.scss | 6 --- src/client/views/pdf/PDFAnnotationLayer.tsx | 21 -------- src/client/views/pdf/PDFViewer.tsx | 17 ++----- src/debug/Repl.tsx | 8 +--- src/new_fields/Doc.ts | 6 +-- src/new_fields/ScriptField.ts | 5 +- 25 files changed, 136 insertions(+), 189 deletions(-) delete mode 100644 src/client/views/pdf/PDFAnnotationLayer.scss delete mode 100644 src/client/views/pdf/PDFAnnotationLayer.tsx (limited to 'src/new_fields') 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; - 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/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 6c29a2dc7..b482e3298 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -339,7 +339,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let templates: Map = new Map(); Array.from(Object.values(Templates.TemplateList)).map(template => - templates.set(template, this.props.views.reduce((checked, doc) => checked || (doc.props.Document["show" + template.Name] ? true : false), false as boolean))); + templates.set(template, this.props.views.reduce((checked, doc) => checked || doc.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean))); return (
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 bfb8168e4..e4ef8313d 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -98,7 +98,14 @@ export class TemplateMenu extends React.Component { @undoBatch @action clearTemplates = (event: React.MouseEvent) => { - Templates.TemplateList.map(template => this.props.docs.map(d => d.Document["show" + template.Name] = undefined)); + 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 @@ -128,7 +135,7 @@ export class TemplateMenu extends React.Component {
this.toggleTemplateActivity()}>+
    {templateMenu} - {/* */} + {}
); diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 0399371ff..56d12bd84 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -79,7 +79,7 @@ export class CollectionBaseView extends React.Component { } } - @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 => { @@ -94,7 +94,7 @@ export class CollectionBaseView extends React.Component { 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 { diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 0306d415c..4dac27e60 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -238,13 +238,11 @@ export class CollectionSchemaCell extends React.Component { 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)); }} />
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 804bfa2b2..774e6b1b9 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -40,6 +40,8 @@ export interface SubCollectionViewProps extends CollectionViewProps { export function CollectionSubView(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; + private _childLayoutDisposer?: IReactionDisposer; + protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer && this.dropDisposer(); if (ele) { @@ -50,8 +52,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this.createDropTarget(ele); } - _childLayoutDisposer?: IReactionDisposer; - componentDidMount() { this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], async (args) => args[1] instanceof Doc && @@ -62,35 +62,25 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this._childLayoutDisposer && this._childLayoutDisposer(); } - @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); } + // 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 diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 08d87c7b2..e5313f68c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -425,18 +425,9 @@ class TreeView extends React.Component { preventTreeViewOpen: boolean, renderedIds: string[] ) { - 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); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7383c5551..36e62842c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -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)} />, @@ -183,11 +184,9 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch 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(); } - - get parentScaling() { + private get parentScaling() { return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1; } @@ -264,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 }, @@ -700,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[]) => { @@ -745,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; @@ -760,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: , - 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: , + bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 } + }); } return prev; }, elements); @@ -838,7 +833,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } 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); } @@ -932,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 (
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 18d0fea8c..bbea4a555 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -228,18 +228,14 @@ export class MarqueeView extends React.Component 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 diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index d05e39e2b..9685f9bca 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -17,6 +17,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { width?: number; height?: number; jitterRotation: number; + transition?: string; } export const positionSchema = createSchema({ zIndex: "number", @@ -98,7 +99,6 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu 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 }); + 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); @@ -569,32 +569,45 @@ export class DocumentView extends DocComponent(Docu }); } + + // 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); + } + + // 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]); + 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;// bcz: why 8?? + 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.Document.backgroundColor !== this.Document.defaultBackgroundColor; + 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.Document.backgroundColor) : - ruleColor && !colorSet ? ruleColor : StrCast(this.Document.backgroundColor) || this.props.backgroundColor(this.Document); + 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.Document) : undefined; - const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.Document.showTitle; - const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.Document.showCaption; + 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 searchHighlight = (!this.Document.searchFields ? (null) :
diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx index 2d1a98df2..6c3db18c4 100644 --- a/src/client/views/nodes/DragBox.tsx +++ b/src/client/views/nodes/DragBox.tsx @@ -51,11 +51,9 @@ export class DragBox extends DocComponent(DragDocu 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/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 031929bbd..eb4718581 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -142,10 +142,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 @@ -641,7 +640,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); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 3cb64aa40..624593245 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -63,9 +63,9 @@ export class ImageBox extends DocComponent(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) { @@ -81,8 +81,6 @@ export class ImageBox extends DocComponent(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) => { diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 8fcd44f46..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 { } 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; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index c8534ed0d..37bf6dbb7 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -31,11 +31,9 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _alt = false; @observable private _pdf: Opt; - @computed get containingCollectionDocument() { return this.props.ContainingCollectionDoc; } - @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 fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } private _mainCont: React.RefObject = React.createRef(); private _reactionDisposer?: IReactionDisposer; @@ -96,7 +94,7 @@ export class PDFBox extends DocComponent(PdfDocumen @action setPanY = (y: number) => { - this.containingCollectionDocument && (this.containingCollectionDocument.panY = y); + this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.panY = y); } @action @@ -113,7 +111,7 @@ export class PDFBox extends DocComponent(PdfDocumen private resetFilters = () => { this._keyValue = this._valueValue = ""; - this._scriptValue = "return true"; + this._scriptValue = ""; this._keyRef.current && (this._keyRef.current.value = ""); this._valueRef.current && (this._valueRef.current.value = ""); this._scriptRef.current && (this._scriptRef.current.value = ""); @@ -127,7 +125,7 @@ export class PDFBox extends DocComponent(PdfDocumen return !this.props.active() ? (null) : (
e.stopPropagation()}>
; 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 { // tslint:disable-next-line: no-unnecessary-callback-wrapper let params: string[] = []; let setParams = (p: string[]) => params.splice(0, params.length, ...p); - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + let scriptingBox = { 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 4e371ffd1..9e5e62e03 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"; @@ -9,6 +8,7 @@ import './DocumentDecorations.scss'; import { DocumentView } from "./nodes/DocumentView"; import { Template, Templates } from "./Templates"; import React = require("react"); +import { Doc } from "../../new_fields/Doc"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -29,12 +29,12 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool } } @observer -class ChromeToggle extends React.Component<{ checked: boolean, toggle: (event: React.ChangeEvent) => void }> { +class OtherToggle extends React.Component<{ checked: boolean, name: string, toggle: (event: React.ChangeEvent) => void }> { render() { return (
  • this.props.toggle(event)} /> - Chrome + {this.props.name}
  • ); } @@ -50,33 +50,23 @@ export class TemplateMenu extends React.Component { @observable private _hidden: boolean = true; dragRef = React.createRef(); - constructor(props: TemplateMenuProps) { - super(props); + toggleCustom = (e: React.ChangeEvent): void => { + this.props.docs.map(dv => dv.setCustomView(e.target.checked)); } - toggleCustom = (e: React.MouseEvent): void => { - this.props.docs.map(dv => { - if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) { - DocumentView.makeCustomViewClicked(dv.props.Document); - } else if (dv.Document.nativeLayout) { - DocumentView.makeNativeViewClicked(dv.props.Document); - } - }); - } - - toggleFloat = (e: React.MouseEvent): void => { + toggleFloat = (e: React.ChangeEvent): 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) { - let de = new DragManager.DocumentDragData([topDoc], [undefined]); + let de = new DragManager.DocumentDragData([topDoc]); de.moveDocument = topDocView.props.moveDocument; let xf = newDocView.ContentDiv!.getBoundingClientRect(); DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, { @@ -99,16 +89,23 @@ export class TemplateMenu extends React.Component { @action toggleTemplate = (event: React.ChangeEvent, template: Template): void => { if (event.target.checked) { - this.props.docs.map(d => d.props.Document["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 => d.props.Document["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.props.Document["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 @@ -119,22 +116,26 @@ export class TemplateMenu extends React.Component { @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 : 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 : this.props.docs[0].Document; let templateMenu: Array = []; this.props.templates.forEach((checked, template) => templateMenu.push()); - templateMenu.push(); + templateMenu.push(); + templateMenu.push(); + templateMenu.push(); return (
    this.toggleTemplateActivity()}>+
      {templateMenu} - - - {/* */} + {}
    ); diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 93eaab453..818a41009 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'; import { ImageField } from '../../../new_fields/URLField'; export enum CollectionViewType { @@ -81,12 +80,12 @@ export class CollectionBaseView extends React.Component { } } - @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? @@ -96,7 +95,7 @@ export class CollectionBaseView extends React.Component { 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 { @@ -105,7 +104,6 @@ export class CollectionBaseView extends React.Component { 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)); @@ -128,7 +126,8 @@ export class CollectionBaseView extends React.Component { 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)); @@ -142,17 +141,16 @@ export class CollectionBaseView extends React.Component { return false; } + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. @action.bound moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - let self = this; - let targetDataDoc = this.props.Document; - if (Doc.AreProtosEqual(targetDataDoc, targetCollection)) { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } - if (this.removeDocument(doc)) { - return addDocument(doc); - } - return false; + return this.removeDocument(doc) ? addDocument(doc) : false; } showIsTagged = () => { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f184a3944..ef2681410 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, runInAction } from "mobx"; +import { action, Lambda, observable, reaction, computed, runInAction } 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 CollectionDockingView.Instance = this); - } + !CollectionDockingView.Instance && (CollectionDockingView.Instance = this); //Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; } hack: boolean = false; undohack: any = null; - public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs: (Doc | undefined)[] = []) { + public StartOtherDrag(e: any, dragDocs: Doc[]) { let config: any; if (dragDocs.length === 1) { - config = CollectionDockingView.makeDocumentConfig(dragDocs[0], dragDataDocs[0]); + config = CollectionDockingView.makeDocumentConfig(dragDocs[0], undefined); } else { config = { type: 'row', content: dragDocs.map((doc, i) => { - CollectionDockingView.makeDocumentConfig(doc, dragDataDocs[i]); + CollectionDockingView.makeDocumentConfig(doc, undefined); }) }; } @@ -90,18 +87,13 @@ export class CollectionDockingView extends React.Component - // this.AddRightSplit(dragDoc, dragDataDocs[i], true).contentItems[0].tab._dragListener. - // onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); } + @undoBatch @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)] @@ -113,6 +105,7 @@ export class CollectionDockingView extends React.Component { @@ -131,21 +124,25 @@ export class CollectionDockingView extends React.Component { + 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; } @@ -156,7 +153,7 @@ export class CollectionDockingView extends React.Component { - 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); } @@ -192,15 +192,15 @@ export class CollectionDockingView extends React.Component { Doc.GetProto(document).lastOpened = new DateField; @@ -245,6 +246,7 @@ export class CollectionDockingView extends React.Component { e.preventDefault(); e.stopPropagation(); - DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc], [dataDoc]), e.clientX, e.clientY, { + DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { handlers: { dragComplete: emptyFunction }, hideSource: false }); }}>, dragSpan); - ReactDOM.render( CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv); - tab.reactComponents = [dragSpan, upDiv]; + ReactDOM.render(, gearSpan); + // ReactDOM.render( { + // 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 }; @@ -538,11 +549,7 @@ export class DockedFrameRenderer extends React.Component { @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); @@ -616,17 +623,18 @@ export class DockedFrameRenderer extends React.Component { } 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, 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() { @@ -640,6 +648,7 @@ export class DockedFrameRenderer extends React.Component { bringToFront={emptyFunction} addDocument={undefined} removeDocument={undefined} + ruleProvider={undefined} ContentScaling={this.contentScaling} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} @@ -652,6 +661,7 @@ export class DockedFrameRenderer extends React.Component { 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 c59107b53..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; @@ -149,7 +149,9 @@ export class CollectionSchemaCell extends React.Component { DataDoc: this.props.rowProps.original, fieldKey: this.props.rowProps.column.id as string, fieldExt: "", + ruleProvider: undefined, ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.CollectionView.props.Document, isSelected: returnFalse, select: emptyFunction, renderDepth: this.props.renderDepth + 1, @@ -171,7 +173,8 @@ export class CollectionSchemaCell extends React.Component { let onItemDown = (e: React.PointerEvent) => { if (fieldIsDoc) { SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); + this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, + this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); } }; let onPointerEnter = (e: React.PointerEvent): void => { @@ -235,13 +238,11 @@ export class CollectionSchemaCell extends React.Component { 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)); }} />
    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 { @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 9d83aa6c1..7bd2a1971 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -14,7 +14,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { Docs, DocumentOptions } from "../../documents/Documents"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { Gateway } from "../../northstar/manager/Gateway"; -import { SetupDrag, DragManager } from "../../util/DragManager"; +import { DragManager } from "../../util/DragManager"; import { CompileScript, ts, Transformer } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; @@ -32,6 +32,7 @@ import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, Collection import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { DocumentType } from "../../documents/DocumentTypes"; library.add(faCog, faPlus, faSortUp, faSortDown); @@ -161,6 +162,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined} childDocs={this.childDocs} renderDepth={this.props.renderDepth} + ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider} width={this.previewWidth} height={this.previewHeight} getTransform={this.getPreviewTransform} @@ -194,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} @@ -245,6 +248,7 @@ export interface SchemaTableProps { childDocs?: Doc[]; CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; ContainingCollectionView: Opt; + ContainingCollectionDoc: Opt; fieldKey: string; renderDepth: number; deleteDocument: (document: Doc) => boolean; @@ -252,7 +256,7 @@ export interface SchemaTableProps { ScreenToLocalTransform: () => Transform; active: () => boolean; onDrop: (e: React.DragEvent, 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; @@ -901,6 +905,7 @@ interface CollectionSchemaPreviewProps { fitToBox?: boolean; width: () => number; height: () => number; + ruleProvider: Doc | undefined; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView; onClick?: ScriptField; @@ -910,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; @@ -943,13 +948,12 @@ export class CollectionSchemaPreview extends React.Component { 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(); } @@ -995,12 +999,14 @@ export class CollectionSchemaPreview extends React.Component doc) { _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef(); _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(); } @@ -134,7 +128,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } - @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : ScriptCast(this.Document.onChildClick); } + @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); } getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) { let height = () => this.getDocHeight(layoutDoc); @@ -144,6 +138,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { DataDocument={dataDoc} showOverlays={this.overlays} renderDepth={this.props.renderDepth} + ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider} fitToBox={this.props.fitToBox} onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler} width={width} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index bc4fe7dd7..b3b7b40dd 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -155,6 +155,9 @@ export class CollectionStackingViewFieldColumn extends React.Component NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; + newDoc.heading = heading; return this.props.parent.props.addDocument(newDoc); } @@ -175,13 +178,9 @@ export class CollectionStackingViewFieldColumn extends React.Component(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; + private _childLayoutDisposer?: IReactionDisposer; + protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer && this.dropDisposer(); if (ele) { @@ -51,33 +54,35 @@ export function CollectionSubView(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(); + } - get childDocs() { - let self = this; - //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; + // 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 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 @@ -118,7 +123,7 @@ export function CollectionSubView(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; @@ -127,9 +132,11 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { if (de.data.dropAction || de.data.userDropAction) { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (de.data.moveDocument) { - let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; - added = movedDocs.reduce((added: boolean, d) => - de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false); + let movedDocs = de.data.draggedDocuments;// de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; + // note that it's possible the drag function might create a drop document that's not the same as the + // original dragged document. So we explicitly call addDocument() with a droppedDocument and + added = movedDocs.reduce((added: boolean, d, i) => + de.data.moveDocument(d, this.props.Document, (doc: Doc) => this.props.addDocument(de.data.droppedDocuments[i])) || added, false); } else { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } @@ -271,6 +278,10 @@ export function CollectionSubView(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 f5bb76966..e5313f68c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -37,9 +37,10 @@ export interface TreeViewProps { containingCollection: Doc; renderDepth: number; deleteDoc: (doc: Doc) => boolean; + 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; @@ -177,7 +178,7 @@ class TreeView extends React.Component { 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([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; return this.props.addDocument(doc); @@ -200,6 +201,7 @@ class TreeView extends React.Component { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" }); } ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" }); ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); e.stopPropagation(); e.preventDefault(); @@ -324,6 +326,7 @@ class TreeView extends React.Component { DataDocument={this.resolvedDataDoc} renderDepth={this.props.renderDepth} showOverlays={this.noOverlays} + ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider} fitToBox={this.boundsOfCollectionDocument !== undefined} width={this.docWidth} height={this.docHeight} @@ -344,7 +347,7 @@ class TreeView extends React.Component { @computed get renderBullet() { - return
    this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> + return
    { this.treeViewOpen = !this.treeViewOpen; e.stopPropagation(); })} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> {}
    ; } @@ -402,7 +405,7 @@ class TreeView extends React.Component {
    ; } public static GetChildElements( - docList: Doc[], + docs: Doc[], treeViewId: string, containingCollection: Doc, dataDoc: Doc | undefined, @@ -411,7 +414,7 @@ class TreeView extends React.Component { 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 }, @@ -422,19 +425,9 @@ class TreeView extends React.Component { 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); @@ -491,6 +484,7 @@ class TreeView extends React.Component { dataDoc={pair.data} containingCollection={containingCollection} treeViewId={treeViewId} + ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc} key={child[Id]} indentDocument={indent} renderDepth={renderDepth} @@ -544,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(); @@ -559,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"); } } @@ -610,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([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 94b49fb98..1f2dc9b5c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -105,6 +105,12 @@ export class CollectionView extends React.Component { subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" }); subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" }); subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" }); + subItems.push({ + description: "Stacking (AutoHeight)", event: () => { + this.props.Document.viewType = CollectionViewType.Stacking; + this.props.Document.autoHeight = true; + }, icon: "ellipsis-v" + }); subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { 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 { - 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 { @observable private _docs: { col: Doc, target: Doc }[] = []; @@ -83,7 +90,7 @@ export class ParentDocSelector extends React.Component { ); } return ( -

    ^

    @@ -92,3 +99,38 @@ export class ParentDocSelector extends React.Component { ); } } + +@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) : ( +
    + +
    + ); + } + return ( + + {this.hover ? (null) : } + {flyout} + + ); + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 2a64a7afb..cfd18ad35 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,8 +1,9 @@ .collectionfreeformlinkview-linkLine { stroke: black; transform: translate(10000px,10000px); - // opacity: 0.5; + opacity: 0.8; pointer-events: all; + stroke-width: 3px; } .collectionfreeformlinkview-linkCircle { stroke: rgb(0,0,0); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 12771d11e..df089eb00 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -50,7 +50,6 @@ export class CollectionFreeFormLinkView extends React.Component {/* 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 f7bda0a26..eb738d783 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,10 +8,10 @@ import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue, DateCast } from "../../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { Docs } from "../../../documents/Documents"; +import { Docs, DocumentOptions } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; @@ -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"; @@ -51,6 +51,9 @@ export const panZoomSchema = createSchema({ scale: "number", arrangeScript: ScriptField, arrangeInit: ScriptField, + useClusters: "boolean", + isRuleProvider: "boolean", + fitToBox: "boolean" }); export interface ViewDefBounds { @@ -161,6 +164,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)} />, @@ -181,8 +185,8 @@ 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) { @@ -190,35 +194,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _lastY: number = 0; private get _pwidth() { return this.props.PanelWidth(); } private get _pheight() { return this.props.PanelHeight(); } - private inkKey = "ink"; - 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; } @@ -249,7 +225,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return res; } - @computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; } + @computed get fitToBox() { return this.props.fitToBox || this.Document.fitToBox; } @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; } public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt') @@ -265,29 +241,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed - newBox.heading = 1; - for (let child of this.childDocs) { - if (child.heading === 1) { - newBox.heading = 2; - } + let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0; + if (heading === 0) { + let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 : + DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0); + heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } - PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { - if (!ruleProvider) ruleProvider = this.props.Document; - // saturation shift - // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor)); - // let hsl = Utils.RGBToHSL(back.r, back.g, back.b); - // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l }; - // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l))); - // OR transparency set - let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col); - - let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); - round && (Doc.GetProto(newBox).borderRounding = round); - newBox.ruleProvider = ruleProvider; - this.addDocument(newBox, false); - }); + !this.Document.isRuleProvider && (newBox.heading = heading); + this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); @@ -302,14 +264,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } public getActiveDocuments = () => { const curPage = FieldValue(this.Document.curPage, -1); - return this.childDocs.filter(doc => { - var page = NumCast(doc.page, -1); + return this.childLayoutPairs.filter(pair => { + var page = NumCast(pair.layout.page, -1); return page === curPage || page === -1; - }); + }).map(pair => pair.layout); } @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 }, @@ -342,8 +304,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 => { @@ -366,8 +328,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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; @@ -383,7 +345,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { tryDragCluster(e: PointerEvent) { let probe = this.getTransform().transformPoint(e.clientX, e.clientY); - let cluster = this.childDocs.reduce((cluster, cd) => { + let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { let cx = NumCast(cd.x) - this._clusterDistance; let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(cd.width) + 2 * this._clusterDistance; @@ -394,7 +356,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return cluster; }, -1); if (cluster !== -1) { - let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view let prevSelected = SelectionManager.SelectedDocuments(); @@ -403,13 +365,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { SelectionManager.DeselectAll(); prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); - let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); + 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 @@ -423,9 +383,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action - updateClusters() { + updateClusters(useClusters: boolean) { + this.Document.useClusters = useClusters; this.sets.length = 0; - this.childDocs.map(c => { + this.childLayoutPairs.map(pair => pair.layout).map(c => { let included = []; for (let i = 0; i < this.sets.length; i++) { for (let member of this.sets[i]) { @@ -453,20 +414,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action updateCluster(doc: Doc) { + let childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.props.Document.useClusters) { this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; this.sets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && this.boundsOverlap(doc, member)) { doc.cluster = i; } })); - if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) { + if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } this.sets.map((set, i) => { - if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) { + if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { doc.cluster = i; } }); @@ -481,22 +443,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } getClusterColor = (doc: Doc) => { - if (this.props.Document.useClusters) { - let cluster = NumCast(doc.cluster); + let clusterColor = ""; + let cluster = NumCast(doc.cluster); + if (this.Document.useClusters) { if (this.sets.length <= cluster) { - setTimeout(() => this.updateCluster(doc), 0);// this.updateClusters(), 0); - return ""; + setTimeout(() => this.updateCluster(doc), 0); + } else { + // 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; + // 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)); } - let set = this.sets.length > cluster ? this.sets[cluster] : undefined; - let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; - let clusterColor = colors[cluster % colors.length]; - set && set.filter(s => !s.isBackground).map(s => - s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); - set && set.filter(s => s.isBackground).map(s => - s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); - return clusterColor; } - return ""; + return clusterColor; } @action @@ -528,40 +490,39 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childDocs || []; + let docs = this.childLayoutPairs.map(pair => pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - // if (!this.isAnnotationOverlay) { - // PDFMenu.Instance.fadeOut(true); - // let minx = docs.length ? NumCast(docs[0].x) : 0; - // let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; - // let miny = docs.length ? NumCast(docs[0].y) : 0; - // let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; - // let ranges = docs.filter(doc => doc).reduce((range, doc) => { - // let x = NumCast(doc.x); - // let xe = x + NumCast(doc.width); - // let y = NumCast(doc.y); - // let ye = y + NumCast(doc.height); - // return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], - // [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; - // }, [[minx, maxx], [miny, maxy]]); - // let ink = Cast(this.fieldExtensionDoc.ink, InkField); - // if (ink && ink.inkData) { - // ink.inkData.forEach((value: StrokeData, key: string) => { - // let bounds = InkingCanvas.StrokeRect(value); - // ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; - // ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; - // }); - // } - - // 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; - // } + if (!this.isAnnotationOverlay) { + PDFMenu.Instance.fadeOut(true); + let minx = docs.length ? NumCast(docs[0].x) : 0; + let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; + let miny = docs.length ? NumCast(docs[0].y) : 0; + let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; + let ranges = docs.filter(doc => doc).reduce((range, doc) => { + let x = NumCast(doc.x); + let xe = x + NumCast(doc.width); + let y = NumCast(doc.y); + let ye = y + NumCast(doc.height); + return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], + [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; + }, [[minx, maxx], [miny, maxy]]); + let ink = Cast(this.fieldExtensionDoc.ink, InkField); + if (ink && ink.inkData) { + ink.inkData.forEach((value: StrokeData, key: string) => { + let bounds = InkingCanvas.StrokeRect(value); + ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; + ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; + }); + } + + 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; this._lastY = e.pageY; @@ -575,31 +536,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (BoolCast(this.props.Document.lockedPosition)) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); - return; } - - let childSelected = this.childDocs.some(doc => { - var dv = DocumentManager.Instance.getDocumentView(doc); - return dv && SelectionManager.IsSelected(dv) ? true : false; - }); - if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) { - return; - } - e.stopPropagation(); - - // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix. - // if (e.ctrlKey && this.props.Document.scrollHeight === undefined) { - // let deltaScale = (1 - (e.deltaY / coefficient)); - // let nw = this.nativeWidth * deltaScale; - // let nh = this.nativeHeight * deltaScale; - // if (nw && nh) { - // this.props.Document.nativeWidth = nw; - // this.props.Document.nativeHeight = nh; - // } - // e.preventDefault(); - // } - // else - { + else if (this.props.active()) { + e.stopPropagation(); let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); @@ -628,46 +567,38 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - onDrop = (e: React.DragEvent): void => { + onDrop = async (e: React.DragEvent): Promise => { var pt = this.getTransform().transformPoint(e.pageX, e.pageY); - super.onDrop(e, { x: pt[0], y: pt[1] }); - } - - onDragOver = (): void => { + await super.onDrop(e, { x: pt[0], y: pt[1] }); } bringToFront = (doc: Doc, sendToBack?: boolean) => { if (sendToBack || doc.isBackground) { doc.zIndex = 0; - return; } - const docs = this.childDocs; - docs.slice().sort((doc1, doc2) => { - if (doc1 === doc) return 1; - if (doc2 === doc) return -1; - return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); - }).forEach((doc, index) => doc.zIndex = index + 1); - doc.zIndex = docs.length + 1; + else { + const docs = this.childLayoutPairs.map(pair => pair.layout); + docs.slice().sort((doc1, doc2) => { + if (doc1 === doc) return 1; + if (doc2 === doc) return -1; + return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); + }).forEach((doc, index) => doc.zIndex = index + 1); + doc.zIndex = docs.length + 1; + } } - focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => { - const panX = this.Document.panX; - const panY = this.Document.panY; - const id = this.Document[Id]; + focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => { const state = HistoryUtil.getState(); - state.initializers = state.initializers || {}; // TODO This technically isn't correct if type !== "doc", as // currently nothing is done, but we should probably push a new state - if (state.type === "doc" && panX !== undefined && panY !== undefined) { - const init = state.initializers[id]; + if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) { + const init = state.initializers![this.Document[Id]]; if (!init) { - state.initializers[id] = { - panX, panY - }; + state.initializers![this.Document[Id]] = { panX: this.Document.panX, panY: this.Document.panY }; HistoryUtil.pushState(state); - } else if (init.panX !== panX || init.panY !== panY) { - init.panX = panX; - init.panY = panY; + } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) { + init.panX = this.Document.panX; + init.panY = this.Document.panY; HistoryUtil.pushState(state); } } @@ -675,8 +606,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; const newState = HistoryUtil.getState(); - (newState.initializers || (newState.initializers = {}))[id] = { panX: newPanX, panY: newPanY }; + 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"; @@ -684,7 +619,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) => { @@ -716,13 +659,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, - onClick: this.props.onClick, + ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves + onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, PanelWidth: childLayout[WidthSym], 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, @@ -741,6 +686,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, + ruleProvider: this.props.ruleProvider, onClick: this.props.onClick, ScreenToLocalTransform: this.getTransform, renderDepth: this.props.renderDepth, @@ -748,6 +694,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, @@ -760,13 +707,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); - if (!result.success) { - return {}; + 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" }; } - let doc = params.doc; - return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result; + 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: PivotView.PivotData[]) => { @@ -829,14 +776,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 docs = this.childDocs; - let overlayDocs = DocListCast(this.props.Document.localOverlays); - overlayDocs && docs.push(...overlayDocs); + let pairs = this.childLayoutPairs; let elements: ViewDefResult[] = []; if (initScript) { - const initResult = initScript.script.run({ docs, 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; @@ -844,25 +788,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { elements = this.viewDefsToJSX(views); } } - let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { - var page = NumCast(doc.page, -1); - if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { - let minim = BoolCast(doc.isMinimized); - if (minim === undefined || !minim) { - const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : - { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; - state = pos.state === undefined ? state : pos.state; - let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc); - if (pair.layout && !(pair.data instanceof Promise)) { - prev.push({ - ele: , - bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } - }); - } - } + let docviews = pairs.reduce((prev, pair) => { + var page = NumCast(pair.layout.page, -1); + 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: , + bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 } + }); } return prev; }, elements); @@ -872,22 +810,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed.struct get views() { - let source = this.elements; - return source.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); } - @action onCursorMove = (e: React.PointerEvent) => { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } - fitToContainer = async () => this.props.Document.fitToBox = !this.fitToBox; - arrangeContents = async () => { const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); UndoManager.RunInBatch(() => { @@ -912,98 +846,86 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, "arrange contents"); } + autoFormat = () => { + this.Document.isRuleProvider = !this.Document.isRuleProvider; + // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading. + this.Document.isRuleProvider && this.childLayoutPairs.map(pair => + // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template) + DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => { + let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); + let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout; + if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) { + Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor; + } + }) + ); + } + analyzeStrokes = async () => { - let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); - if (!data) { - return; + let data = Cast(this.fieldExtensionDoc.ink, InkField); + if (data) { + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData); } - let relevantKeys = ["inkAnalysis", "handwriting"]; - CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, relevantKeys, data.inkData); } onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; - if (this.childDocs.some(d => d.isTemplate)) { - layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); + if (this.childDocs.some(d => BoolCast(d.isTemplate))) { + layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); } layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); - layoutItems.push({ - description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, - event: async () => { - Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes - Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; - this.props.Document.useClusters = !this.props.Document.useClusters; - this.updateClusters(); - }, - icon: !this.props.Document.useClusters ? "braille" : "braille" - }); - this.props.Document.useClusters && layoutItems.push({ - description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, - event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, - icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard" - }); + layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); + layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" }); layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); layoutItems.push({ - description: "Import document", icon: "upload", event: () => { + description: "Import document", icon: "upload", event: ({ x, y }) => { const input = document.createElement("input"); input.type = "file"; input.accept = ".zip"; input.onchange = async _e => { - const files = input.files; - if (!files) return; - const file = files[0]; - let formData = new FormData(); - formData.append('file', file); - formData.append('remap', "true"); const upload = Utils.prepend("/uploadDoc"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json === "error") { - return; - } - const doc = await DocServer.GetRefField(json); - if (!doc || !(doc instanceof Doc)) { - return; + let formData = new FormData(); + const file = input.files && input.files[0]; + if (file) { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + if (doc instanceof Doc) { + const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument && this.props.addDocument(doc, false); + } + } } - const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY); - doc.x = x, doc.y = y; - this.props.addDocument && - this.props.addDocument(doc, false); }; input.click(); } }); - let noteItems: ContextMenuProps[] = []; - if (CurrentUserUtils.UserDocument) { - let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); - notes.map((node, i) => noteItems.push({ description: (i + 1) + ": " + StrCast(node.title), event: () => this.createText(i), icon: "eye" })); - } - layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" }); + layoutItems.push({ + description: "Add Note ...", + subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({ + description: (i + 1) + ": " + 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" }); } - createText = (noteStyle: number) => { - let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - if (CurrentUserUtils.UserDocument) { - let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); - let text = Docs.Create.TextDocument({ width: 200, height: 100, x: pt[0], y: pt[1], autoHeight: true, title: StrCast(notes[noteStyle % notes.length].title) }); - text.layout = notes[noteStyle % notes.length]; - this.addLiveTextBox(text); - } - } private childViews = () => [ , ...this.views ] - private overlayChildViews = () => { - return [...this.overlayViews]; - } public static AddCustomLayout(doc: Doc, dataKey: string): () => void { return () => { @@ -1034,15 +956,18 @@ 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 (
    + onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> @@ -1056,7 +981,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { - {this.overlayChildViews()} + {this.overlayViews}
    ); @@ -1077,7 +1002,6 @@ class CollectionFreeFormOverlayView extends React.Component boolean }> { @computed get backgroundView() { - let props = this.props; return (); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index fe48a3485..bbea4a555 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,28 +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"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -232,18 +228,14 @@ export class MarqueeView extends React.Component 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 @@ -287,7 +279,7 @@ export class MarqueeView extends React.Component let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map(); [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { - let bg = StrCast(child.backgroundColor); + let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); @@ -309,13 +301,13 @@ export class MarqueeView extends React.Component defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, width: bounds.width, height: bounds.height, - title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", + title: "a nested collection", }); let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; this.marqueeInkDelete(inkData); - if (e.key === "s") { + if (e.key === "s" || e.key === "S") { selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; @@ -324,39 +316,23 @@ export class MarqueeView extends React.Component return d; }); newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - newCollection.proto!.summaryDoc = summary; - selected = [newCollection]; + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + Doc.GetProto(summary).summarizedDocs = new List([newCollection]); newCollection.x = bounds.left + bounds.width; - summary.proto!.subBulletDocs = new List(selected); - summary.templates = new List([Templates.Bullet.Layout]); - 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; - this.props.addLiveTextDocument(container); - // }); - } else if (e.key === "S") { - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; - return d; - }); - newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - newCollection.proto!.summaryDoc = summary; - selected = [newCollection]; - newCollection.x = bounds.left + bounds.width; - //this.props.addDocument(newCollection, false); - summary.proto!.summarizedDocs = new List(selected); - summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" - summary.autoHeight = true; - - this.props.addLiveTextDocument(summary); + 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 "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" + this.props.addLiveTextDocument(summary); + } } else { - newCollection.ruleProvider = this.props.container.props.Document; this.props.addDocument(newCollection, false); this.props.selectDocuments([newCollection]); } diff --git a/src/client/views/document_templates/image_card/ImageCard.tsx b/src/client/views/document_templates/image_card/ImageCard.tsx index 9931515f3..868afc423 100644 --- a/src/client/views/document_templates/image_card/ImageCard.tsx +++ b/src/client/views/document_templates/image_card/ImageCard.tsx @@ -1,8 +1,5 @@ import * as React from 'react'; -import { DocComponent } from '../../DocComponent'; import { FieldViewProps } from '../../nodes/FieldView'; -import { createSchema, makeInterface } from '../../../../new_fields/Schema'; -import { createInterface } from 'readline'; import { ImageBox } from '../../nodes/ImageBox'; export default class ImageCard extends React.Component { diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index f8807641b..81b0249dd 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"; @@ -85,11 +85,10 @@ export class LinkFollowBox extends React.Component { } 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 => { @@ -172,7 +171,6 @@ export class LinkFollowBox extends React.Component { if (LinkFollowBox.destinationDoc) { let view: DocumentView | null = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc); view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); - SelectionManager.DeselectAll(); } } @@ -188,7 +186,6 @@ export class LinkFollowBox extends React.Component { let view = DocumentManager.Instance.getDocumentView(options.context); view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); this.highlightDoc(); - SelectionManager.DeselectAll(); } } @@ -198,9 +195,9 @@ export class LinkFollowBox extends React.Component { } - _addDocTab: (undefined | ((doc: Doc, dataDoc: Doc | undefined, where: string) => void)); + _addDocTab: (undefined | ((doc: Doc, dataDoc: Opt, where: string) => boolean)); - setAddDocTab = (addFunc: (doc: Doc, dataDoc: Doc | undefined, where: string) => void) => { + setAddDocTab = (addFunc: (doc: Doc, dataDoc: Opt, where: string) => boolean) => { this._addDocTab = addFunc; } @@ -214,7 +211,7 @@ export class LinkFollowBox extends React.Component { 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 }); @@ -227,7 +224,7 @@ export class LinkFollowBox extends React.Component { 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 +244,7 @@ export class LinkFollowBox extends React.Component { let sourceContext = await Cast(proto.sourceContext, Doc); 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); @@ -274,7 +271,7 @@ export class LinkFollowBox extends React.Component { 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(); @@ -291,7 +288,7 @@ export class LinkFollowBox extends React.Component { 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(); @@ -373,9 +370,9 @@ export class LinkFollowBox extends React.Component { 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)); } } } @@ -396,9 +393,8 @@ export class LinkFollowBox extends React.Component { @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; @@ -459,17 +455,15 @@ export class LinkFollowBox extends React.Component { @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 b6a24b0c8..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; } @@ -57,7 +55,7 @@ export class LinkMenuGroup extends React.Component { let opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); if (opp) return opp; }) as Doc[]; - let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined)); + let dragData = new DragManager.DocumentDragData(draggedDocs); DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, { handlers: { diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 19a0023e9..82fe3df23 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(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(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 (
    diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss new file mode 100644 index 000000000..c0d9e1267 --- /dev/null +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss @@ -0,0 +1,5 @@ +.collectionFreeFormDocumentView-container { + transform-origin: left top; + position: absolute; + background-color: transparent; +} \ No newline at end of file diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 07dd1cae7..9685f9bca 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,13 +1,14 @@ -import { computed } from "mobx"; +import { computed, action, observable, reaction, IReactionDisposer, trace } from "mobx"; import { observer } from "mobx-react"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types"; +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 { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; -import "./DocumentView.scss"; +import { percent2frac } from "../../../Utils"; +import { DocumentView, DocumentViewProps, documentSchema } from "./DocumentView"; +import "./CollectionFreeFormDocumentView.scss"; import React = require("react"); -import { Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -16,31 +17,34 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { width?: number; height?: number; jitterRotation: number; + transition?: string; } - -const schema = createSchema({ +export const positionSchema = createSchema({ zIndex: "number", + x: "number", + y: "number", + z: "number", }); -//TODO Types: The import order is wrong, so positionSchema is undefined -type FreeformDocument = makeInterface<[typeof schema, typeof positionSchema]>; -const FreeformDocument = makeInterface(schema, positionSchema); +export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; +export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer -export class CollectionFreeFormDocumentView extends DocComponent(FreeformDocument) { +export class CollectionFreeFormDocumentView extends DocComponent(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.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } - @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } - @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.Document.height || 0; } - @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); } - @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); } - @computed get scaleToOverridingWidth() { return this.width / NumCast(this.props.Document.width, this.width); } + @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); } + @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); } + @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); } @computed get renderScriptDim() { if (this.Document.renderScript) { - let someView = Cast(this.Document.someView, Doc); - let minimap = Cast(this.Document.minimap, Doc); + let someView = Cast(this.props.Document.someView, Doc); + let minimap = Cast(this.props.Document.minimap, Doc); if (someView instanceof Doc && minimap instanceof Doc) { let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2; let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2; @@ -52,34 +56,31 @@ export class CollectionFreeFormDocumentView extends DocComponent this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? this.width / this.nativeWidth : 1; + componentWillUnmount() { + this._disposer && this._disposer(); + } + componentDidMount() { + this._disposer = reaction(() => [this.props.Document.animateToPos, this.props.Document.isAnimating], + () => { + 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 }); + } + + contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect ? this.width / this.nativeWidth : 1; panelWidth = () => this.props.PanelWidth(); panelHeight = () => this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth) - animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { - this.props.bringToFront(this.props.Document); - let targetPos = [this.Document.x || 0, this.Document.y || 0]; - let iconPos = this.props.ScreenToLocalTransform().transformPoint(icon[0], icon[1]); - DocumentView.animateBetweenIconFunc(this.props.Document, - this.Document.width || 0, this.Document.height || 0, stime, maximizing, (progress: number) => { - let pval = maximizing ? - [iconPos[0] + (targetPos[0] - iconPos[0]) * progress, iconPos[1] + (targetPos[1] - iconPos[1]) * progress] : - [targetPos[0] + (iconPos[0] - targetPos[0]) * progress, targetPos[1] + (iconPos[1] - targetPos[1]) * progress]; - this.Document.x = progress === 1 ? targetPos[0] : pval[0]; - this.Document.y = progress === 1 ? targetPos[1] : pval[1]; - }); - } - borderRounding = () => { - let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding); + let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; + let br = StrCast(((this.layoutDoc.layout as Doc) || this.Document).borderRounding); + br = !br && ruleRounding ? ruleRounding : br; if (br.endsWith("%")) { - let percent = Number(br.substr(0, br.length - 1)) / 100; let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight)); - let minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight())); - return minDim; + return percent2frac(br) * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight())); } return undefined; } @@ -95,24 +96,21 @@ export class CollectionFreeFormDocumentView extends DocComponent
    ); 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 { - let field = this.props.Document.templates; - if (field && field instanceof List) { - return field; - } - return new List(); - } @computed get finalLayout() { return this.props.layoutKey === "overlayLayout" ? "
    " : this.layout; } @@ -107,7 +100,7 @@ export class DocumentContentsView extends React.Component 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 ; -// const LinkDoc = makeInterface(linkSchema); - export interface DocumentViewProps { ContainingCollectionView: Opt; + ContainingCollectionDoc: Opt; Document: Doc; DataDoc?: Doc; fitToBox?: boolean; @@ -93,47 +83,51 @@ export interface DocumentViewProps { renderDepth: number; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; ContentScaling: () => number; + 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; - animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void; + animateBetweenIcon?: (maximize: boolean, target: number[]) => void; ChromeHeight?: () => number; } -const schema = createSchema({ - layout: "string", - nativeWidth: "number", - nativeHeight: "number", - backgroundColor: "string", - opacity: "number", - hidden: "boolean", - onClick: ScriptField, -}); - -export const positionSchema = createSchema({ - nativeWidth: "number", - nativeHeight: "number", - width: "number", - height: "number", - x: "number", - y: "number", - z: "number", +export const documentSchema = createSchema({ + // 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) }); -export type PositionDocument = makeInterface<[typeof positionSchema]>; -export const PositionDocument = makeInterface(positionSchema); -type Document = makeInterface<[typeof schema]>; -const Document = makeInterface(schema); +type Document = makeInterface<[typeof documentSchema]>; +const Document = makeInterface(documentSchema); @observer export class DocumentView extends DocComponent(Document) { @@ -141,107 +135,41 @@ export class DocumentView extends DocComponent(Docu private _downY: number = 0; private _lastTap: number = 0; private _doubleTap = false; - private _hitExpander = false; private _hitTemplateDrag = false; private _mainCont = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; - _animateToIconDisposer?: IReactionDisposer; - _reactionDisposer?: IReactionDisposer; 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; } - screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); + @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) } - }); - } - // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes - this._reactionDisposer = reaction(() => [DocListCast(this.props.Document.maximizedDocs).map(md => md.title), - this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""], - () => { - let maxDoc = DocListCast(this.props.Document.maximizedDocs); - if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) { - this.props.Document.proto!.title = "-" + maxDoc[0].title + ".icon"; - } - let sumDoc = Cast(this.props.Document.summaryDoc, Doc); - if (sumDoc instanceof Doc && StrCast(this.props.Document.title).startsWith("-")) { - this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded"; - } - }, { fireImmediately: true }); - this._animateToIconDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => - (values instanceof List) && this.animateBetweenIcon(values, values[2], values[3] ? true : false) - , { fireImmediately: true }); + this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); DocumentManager.Instance.DocumentViews.push(this); } - animateBetweenIcon = (iconPos: number[], startTime: number, maximizing: boolean) => { - this.props.animateBetweenIcon ? this.props.animateBetweenIcon(iconPos, startTime, maximizing) : - DocumentView.animateBetweenIconFunc(this.props.Document, this.Document[WidthSym](), this.Document[HeightSym](), startTime, maximizing); - } - - public static animateBetweenIconFunc = (doc: Doc, width: number, height: number, stime: number, maximizing: boolean, cb?: (progress: number) => void) => { - setTimeout(() => { - let now = Date.now(); - let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1; - doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress; - doc.height = progress === 1 ? height : maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress; - cb && cb(progress); - if (now < stime + 200) { - DocumentView.animateBetweenIconFunc(doc, width, height, stime, maximizing, cb); - } - else { - doc.isMinimized = !maximizing; - doc.isIconAnimating = undefined; - } - doc.willMaximize = false; - }, - 2); - } @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._reactionDisposer && this._reactionDisposer(); - this._animateToIconDisposer && this._animateToIconDisposer(); this._dropDisposer && this._dropDisposer(); DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } - stopPropagation = (e: React.SyntheticEvent) => { - e.stopPropagation(); - } - - get dataDoc() { - 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, dragSubBullets: boolean, applyAsTemplate?: boolean) { + startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) { if (this._mainCont.current) { - let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])]; - let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])]; + let dragData = new DragManager.DocumentDragData([this.props.Document]); const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); - let dragData = new DragManager.DocumentDragData(allConnected, alldataConnected); - const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); + 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, { @@ -252,151 +180,79 @@ export class DocumentView extends DocComponent(Docu }); } } - toggleMinimized = async () => { - let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc); - if (minimizedDoc) { - let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint( - NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y)); - this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); - } - } - - static _undoBatch?: UndoManager.Batch = undefined; - @action - public collapseTargetsToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { - SelectionManager.DeselectAll(); - if (expandedDocs) { - if (!DocumentView._undoBatch) { - DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); - } - let isMinimized: boolean | undefined; - expandedDocs.map(maximizedDoc => { - let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); - if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { - if (isMinimized === undefined) { - isMinimized = BoolCast(maximizedDoc.isMinimized); - } - maximizedDoc.willMaximize = isMinimized; - maximizedDoc.isMinimized = false; - maximizedDoc.isIconAnimating = new List([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); - } - }); - setTimeout(() => { - DocumentView._undoBatch && DocumentView._undoBatch.end(); - DocumentView._undoBatch = undefined; - }, 500); - } - } onClick = async (e: React.MouseEvent) => { - if (e.nativeEvent.cancelBubble) return; // 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.props.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); - fullScreenAlias.templates = new List(); - Doc.UseDetailLayout(fullScreenAlias); - fullScreenAlias.showCaption = true; - 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); - } - else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] && - (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && - Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { - if (BoolCast(this.props.Document.ignoreClick)) { - return; + 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))); } - e.stopPropagation(); - SelectionManager.SelectDoc(this, e.ctrlKey); - let isExpander = (e.target as any).id === "isExpander"; - if (BoolCast(this.props.Document.isButton) || this.props.Document.type === DocumentType.BUTTON || isExpander) { - let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs); - 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 = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs; - expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; - expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; - // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; - if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents - SelectionManager.DeselectAll(); - let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace"); - let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); - if (altKey || ctrlKey) { - maxLocation = this.props.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 && !BoolCast(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)); - 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._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0; 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; @@ -413,11 +269,11 @@ export class DocumentView extends DocComponent(Docu document.removeEventListener("pointermove", this.onPointerMove); } else if (!e.cancelBubble && this.active) { - if (!this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) { - if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) { + 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); - this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, this._hitTemplateDrag); + this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); } } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers @@ -435,77 +291,72 @@ export class DocumentView extends DocComponent(Docu deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } @undoBatch - fieldsClicked = (): void => { - let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }); - this.props.addDocTab(kvp, this.dataDoc, "onRight"); - } + static makeNativeViewClicked = (doc: Doc): void => { swapViews(doc, "layoutNative", "layoutCustom"); } @undoBatch - makeBtnClicked = (): void => { - let doc = Doc.GetProto(this.props.Document); - if (doc.isButton || doc.onClick) { - doc.isButton = false; - doc.onClick = undefined; + static makeCustomViewClicked = async (doc: Doc, dataDoc: Opt) => { + if (doc.layoutCustom === undefined) { + Doc.GetProto(dataDoc || doc).layoutNative = Doc.MakeTitled("layoutNative"); + await swapViews(doc, "", "layoutNative"); + + const width = NumCast(doc.width); + const height = NumCast(doc.height); + const options = { title: "data", width, x: -width / 2, y: - height / 2, }; + let fieldTemplate = doc.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : + doc.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : + Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + + fieldTemplate.backgroundColor = doc.backgroundColor; + fieldTemplate.heading = 1; + fieldTemplate.autoHeight = true; + + let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) }); + + Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true); + Doc.ApplyTemplateTo(docTemplate, doc, undefined); + Doc.GetProto(dataDoc || doc).layoutCustom = Doc.MakeTitled("layoutCustom"); } else { - doc.isButton = true; + swapViews(doc, "layoutCustom", "layoutNative"); } - - // if (doc.isButton) { - // if (!doc.nativeWidth) { - // doc.nativeWidth = this.props.Document[WidthSym](); - // doc.nativeHeight = this.props.Document[HeightSym](); - // } - // } else { - // doc.nativeWidth = doc.nativeHeight = undefined; - // } } @undoBatch - public fullScreenClicked = (): void => { - CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this); - SelectionManager.DeselectAll(); + makeBtnClicked = (): void => { + if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) { + this.Document.isButton = false; + this.Document.ignoreClick = false; + this.Document.onClick = undefined; + } else { + this.Document.isButton = true; + } } @undoBatch @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)); } } @@ -513,9 +364,9 @@ export class DocumentView extends DocComponent(Docu onDrop = (e: React.DragEvent) => { let text = e.dataTransfer.getData("text/plain"); if (!e.isDefaultPrevented() && text && text.startsWith("(Docu @undoBatch @action freezeNativeDimensions = (): void => { - let proto = this.props.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document); - this.props.Document.autoHeight = proto.autoHeight = false; - proto.ignoreAspect = !BoolCast(proto.ignoreAspect); - if (!BoolCast(proto.ignoreAspect) && !proto.nativeWidth) { + let proto = this.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document); + proto.autoHeight = this.Document.autoHeight = false; + proto.ignoreAspect = !proto.ignoreAspect; + if (!proto.ignoreAspect && !proto.nativeWidth) { proto.nativeWidth = this.props.PanelWidth(); proto.nativeHeight = this.props.PanelHeight(); } } + + @undoBatch + @action + 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); + this.Document.isButton = true; + }); + } + } + @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.props.Document.title + ".portal") return true; - return false; - })) { - let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); - Doc.GetProto(this.props.Document).isButton = true; + setCustomView = (custom: boolean): void => { + if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { + 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 ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document); } } @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 () => { @@ -570,48 +433,6 @@ export class DocumentView extends DocComponent(Docu }); } - public static makeNativeViewClicked = undoBatch((document: Doc): void => { - document.customLayout = document.layout; - document.layout = document.nativeLayout; - document.type = document.nativeType; - document.nativeWidth = document.nativeNativeWidth; - document.nativeHeight = document.nativeNativeHeight; - document.ignoreAspect = document.nativeIgnoreAspect; - document.nativeLayout = undefined; - document.nativeNativeWidth = undefined; - document.nativeNativeHeight = undefined; - document.nativeIgnoreAspect = undefined; - }); - - public static makeCustomViewClicked = undoBatch((document: Doc, showTitle = undefined): void => { - document.nativeLayout = document.layout; - document.nativeType = document.type; - document.nativeNativeWidth = document.nativeWidth; - document.nativeNativeHeight = document.nativeHeight; - document.nativeIgnoreAspect = document.ignoreAspect; - PromiseValue(Cast(document.customLayout, Doc)).then(custom => { - if (custom) { - document.type = DocumentType.TEMPLATE; - document.layout = custom; - !custom.nativeWidth && (document.nativeWidth = 0); - !custom.nativeHeight && (document.nativeHeight = 0); - !custom.nativeWidth && (document.ignoreAspect = true); - } else { - let options = { title: "data", width: NumCast(document.width), height: NumCast(document.height) + 25, x: -NumCast(document.width) / 2, y: -NumCast(document.height) / 2, }; - let fieldTemplate = document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - - let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(document.title) + "layout", width: NumCast(document.width) + 20, height: Math.max(100, NumCast(document.height) + 45) }); - let metaKey = "data"; - let proto = Doc.GetProto(docTemplate); - Doc.MakeTemplate(fieldTemplate, metaKey, proto); - fieldTemplate.showTitle = showTitle; - - Doc.ApplyTemplateTo(docTemplate, document, undefined, false); - document.customLayout = document.layout; - } - }); - }); - @action onContextMenu = async (e: React.MouseEvent): Promise => { e.persist(); @@ -625,13 +446,14 @@ export class DocumentView extends DocComponent(Docu const cm = ContextMenu.Instance; let subitems: ContextMenuProps[] = []; - subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" }); - subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.dataDoc, "inTab"), icon: "folder" }); - subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "inTab"), icon: "folder" }); - subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.dataDoc, "onRight"), icon: "caret-square-right" }); - subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" }); - subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); + 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.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" }); + if (Cast(this.props.Document.data, ImageField)) { cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); } @@ -640,25 +462,17 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" }); cm.addItem({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" }); } - let existingMake = ContextMenu.Instance.findByDescription("Make..."); - let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; - makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); - makes.push({ description: "Custom Document View", event: () => DocumentView.makeCustomViewClicked(this.props.Document), icon: "concierge-bell" }); - makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document), icon: "concierge-bell" }); - makes.push({ description: "Into Portal", event: this.makeIntoPortal, icon: "window-restore" }); - makes.push({ description: this.layoutDoc.ignoreClick ? "Selectable" : "Unselectable", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); - !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); - 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: this.layoutDoc.ignoreClick ? "Select" : "Do Nothing", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); - onClicks.push({ description: this.props.Document.isButton || this.props.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" }); + 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"); } }); @@ -666,22 +480,19 @@ export class DocumentView extends DocComponent(Docu let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; - layoutItems.push({ description: this.props.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); - if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.layout instanceof Doc) { - layoutItems.push({ description: "Make View of Metadata Field", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document), icon: "concierge-bell" }); + 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: `${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.props.Document.ignoreAspect || !this.props.Document.nativeWidth || !this.props.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.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.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.props.Document.isTemplate) { - layoutItems.push({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" }); - } - if (this.props.Document.type !== DocumentType.COL && this.props.Document.type !== DocumentType.TEMPLATE) { - layoutItems.push({ description: "Use Custom Layout", event: () => DocumentView.makeCustomViewClicked(this.props.Document), icon: "concierge-bell" }); - } else if (this.props.Document.nativeLayout) { + if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { + layoutItems.push({ description: "Use Custom Layout", event: () => DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); + } else if (this.props.Document.layoutNative) { layoutItems.push({ description: "Use Native Layout", event: () => DocumentView.makeNativeViewClicked(this.props.Document), icon: "concierge-bell" }); } !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); @@ -697,19 +508,18 @@ export class DocumentView extends DocComponent(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(, { 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" }); runInAction(() => { if (!ClientUtils.RELEASE) { @@ -754,80 +564,90 @@ export class DocumentView extends DocComponent(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 (); + // 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() { - // 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 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() { - let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ? - this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : - StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); - let foregroundColor = StrCast(this.layoutDoc.color); - var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%"; - var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; - let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; - let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); - let showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : StrCast(this.layoutDoc.showCaption); - 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"; - if (!showCaption && str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption"; - }); - } - let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" - {StrCast(this.props.Document.search_fields)} + 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.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.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.getLayoutPropStr("borderRounding") || ruleRounding; + const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; + const searchHighlight = (!this.Document.searchFields ? (null) : +
    + {this.Document.searchFields} +
    ); + const captionView = (!showCaption ? (null) : +
    + +
    ); + const titleView = (!showTitle ? (null) : +
    + StrCast(this.Document[showTitle])} + SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true} + />
    ); + const contents = (); return (
    (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.props.Document.search_fields ?
    - {this.contents} - {searchHighlight} -
    : - this.contents : -
    -
    - {this.contents} + this.Document.searchFields ? + (
    + {contents} + {searchHighlight} +
    ) + : + contents + : +
    +
    + {contents}
    - {!showTitle ? (null) : -
    - 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} - /> -
    - } - {!showCaption ? (null) : -
    - -
    - } + {titleView} + {captionView} {searchHighlight}
    }
    ); } -} \ No newline at end of file +} + +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 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) { + 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 1f2c88086..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(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], [undefined]), 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 d9774303b..49fc2263d 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -30,6 +30,8 @@ export interface FieldViewProps { leaveNativeSize?: boolean; fitToBox?: boolean; ContainingCollectionView: Opt; + ContainingCollectionDoc: Opt; + ruleProvider: Doc | undefined; Document: Doc; DataDoc?: Doc; onClick?: ScriptField; @@ -37,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.scss b/src/client/views/nodes/FormattedTextBox.scss index d7ac7a9c5..0d7277cbe 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -4,7 +4,6 @@ width: 100%; height: 100%; min-height: 100%; - font-family: $serif; } .ProseMirror:focus { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8f0f142c4..cb9fecfc5 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -41,7 +41,7 @@ import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import * as _ from "lodash"; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { inputRules } from 'prosemirror-inputrules'; -import { select } from 'async'; +import { DocumentButtonBar } from '../DocumentButtonBar'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -81,15 +81,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _linkClicked = ""; private _nodeClicked: any; private _undoTyping?: UndoManager.Batch; - private _reactionDisposer: Opt; private _searchReactionDisposer?: Lambda; + private _reactionDisposer: Opt; private _textReactionDisposer: Opt; private _heightReactionDisposer: Opt; + private _rulesReactionDisposer: Opt; private _proxyReactionDisposer: Opt; private _pullReactionDisposer: Opt; private _pushReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; + @observable private _fontSize = 13; + @observable private _fontFamily = "Arial"; + @observable private _fontAlign = ""; @observable private _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; public static SelectOnLoad = ""; @@ -121,14 +125,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch public setFontColor(color: string) { - this._editorView!.state.storedMarks; - if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false; - if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) { + let view = this._editorView!; + if (view.state.selection.from === view.state.selection.to) return false; + if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { this.props.Document.color = color; } - let colorMark = this._editorView!.state.schema.mark(this._editorView!.state.schema.marks.pFontColor, { color: color }); - this._editorView!.dispatch(this._editorView!.state.tr.addMark(this._editorView!.state.selection.from, - this._editorView!.state.selection.to, colorMark)); + let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); + view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); return true; } @@ -142,10 +145,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 @@ -170,6 +172,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + linkOnDeselect: Map = new Map(); + + doLinkOnDeselect() { + Array.from(this.linkOnDeselect.entries()).map(entry => { + let key = entry[0]; + let value = entry[1]; + let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + DocServer.GetRefField(value).then(doc => { + DocServer.GetRefField(id).then(linkDoc => { + this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); + DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); + if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } + else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true); + }); + }); + }); + this.linkOnDeselect.clear(); + } + dispatchTransaction = (tx: Transaction) => { if (this._editorView) { let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); @@ -184,15 +205,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (split.length > 1 && split[1]) { let key = split[0]; let value = split[split.length - 1]; + this.linkOnDeselect.set(key, value); let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - DocServer.GetRefField(value).then(doc => { - DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); - 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); - }); - }); const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); const mval = this._editorView.state.schema.marks.metadataVal.create(); let offset = (tx.selection.to === range!.end - 1 ? -1 : 0); @@ -256,12 +271,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } }); - // const fieldkey = 'search_string'; - // if (Object.keys(this.props.Document).indexOf(fieldkey) !== -1) { - // this.props.Document[fieldkey] = undefined; - // } - // else this.props.Document.proto![fieldkey] = undefined; - // } } } setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { @@ -292,16 +301,29 @@ 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 && StrCast(draggedDoc.layout) !== "") { - this.props.Document.layout = draggedDoc; - draggedDoc.isTemplate = true; + 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); + layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); + this.props.Document.layout = layoutDelegateToOverrideFieldKey; + } else { + this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + } + } e.stopPropagation(); } } } recordKeyHandler = (e: KeyboardEvent) => { - if (this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { + if (SelectionManager.SelectedDocuments().length && this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { if (e.key === "R" && e.altKey) { e.stopPropagation(); e.preventDefault(); @@ -380,6 +402,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentDidMount() { + if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { @@ -410,8 +433,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); } @@ -421,8 +444,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(); } } @@ -462,6 +485,39 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); + + + this._rulesReactionDisposer = reaction(() => { + let ruleProvider = this.props.ruleProvider; + let heading = NumCast(this.props.Document.heading); + if (ruleProvider instanceof Doc) { + return { + align: StrCast(ruleProvider["ruleAlign_" + heading], ""), + font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"), + size: NumCast(ruleProvider["ruleSize_" + heading], 13) + }; + } + return undefined; + }, + action((rules: any) => { + this._fontFamily = rules ? rules.font : "Arial"; + this._fontSize = rules ? rules.size : 13; + rules && setTimeout(() => { + const view = this._editorView!; + if (this._proseRef) { + let n = new NodeSelection(view.state.doc.resolve(0)); + if (this._editorView!.state.doc.textContent === "") { + view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))). + replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true)); + } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) { + view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align })); + } + this.tryUpdateHeight(); + } + }, 0); + }), { fireImmediately: true } + ); + setTimeout(() => this.tryUpdateHeight(), 0); } @@ -481,7 +537,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 = () => { @@ -529,7 +585,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else { delete dataDoc[GoogleRef]; } - DocumentDecorations.Instance.startPullOutcome(pullSuccess); + DocumentButtonBar.Instance.startPullOutcome(pullSuccess); } checkState = (exportState: Opt, dataDoc: Doc) => { @@ -538,7 +594,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let equalTitles = dataDoc.title === exportState.title; let unchanged = equalContent && equalTitles; dataDoc.unchanged = unchanged; - DocumentDecorations.Instance.setPullState(unchanged); + DocumentButtonBar.Instance.setPullState(unchanged); } } @@ -585,7 +641,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); @@ -666,27 +722,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe else if (this.props.isOverlay) this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - let heading = this.props.Document.heading; - if (heading && selectOnLoad) { - PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { - if (ruleProvider) { - let align = StrCast(ruleProvider["ruleAlign_" + heading]); - let font = StrCast(ruleProvider["ruleFont_" + heading]); - let size = NumCast(ruleProvider["ruleSize_" + heading]); - if (align) { - let tr = this._editorView!.state.tr; - tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). - replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: align }), true). - setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); - this._editorView!.dispatch(tr); - } - let sm = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - size && (sm = [...sm, schema.marks.pFontSize.create({ fontSize: size })]); - font && (sm = [...sm, this.getFont(font)]); - this._editorView!.dispatch(this._editorView!.state.tr.setStoredMarks(sm)); - } - }); - } } getFont(font: string) { switch (font) { @@ -702,6 +737,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentWillUnmount() { + this._rulesReactionDisposer && this._rulesReactionDisposer(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); this._textReactionDisposer && this._textReactionDisposer(); @@ -857,6 +893,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._undoTyping.end(); this._undoTyping = undefined; } + this.doLinkOnDeselect(); } onKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Escape") { @@ -866,7 +903,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); @@ -877,7 +914,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)); @@ -885,12 +922,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - render() { 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"; + let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground + ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); return (
    this._entered = true)} onPointerLeave={action(() => this._entered = false)} > -
    +
    ); } diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 7e78ec684..63a504d1a 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -12,6 +12,8 @@ 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 } from "../../util/Scripting"; +import { ComputedField } from "../../../new_fields/ScriptField"; library.add(faCaretUp); @@ -27,6 +29,25 @@ export class IconBox extends React.Component { @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "

    Error loading icon data

    "; } @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); } + public static summaryTitleScript(inputDoc: Doc) { + const sumDoc = Cast(inputDoc.summaryDoc, Doc) as Doc; + if (sumDoc && StrCast(sumDoc.title).startsWith("-")) { + return sumDoc.title + ".expanded"; + } + return "???"; + } + public static titleScript(inputDoc: Doc) { + const maxDoc = DocListCast(inputDoc.maximizedDocs); + if (maxDoc.length === 1) { + return maxDoc[0].title + ".icon"; + } + return maxDoc.length > 1 ? "-multiple-.icon" : "???"; + } + + public static AutomaticTitle(doc: Doc) { + Doc.GetProto(doc).title = ComputedField.MakeFunction('iconTitle(this);'); + } + public static DocumentIcon(layout: string) { let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : layout.indexOf("ImageBox") !== -1 ? faImage : @@ -38,35 +59,20 @@ export class IconBox extends React.Component { } setLabelField = (): void => { - this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel); - } - setUseOwnTitleField = (): void => { - this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle); + this.props.Document.hideLabel = !this.props.Document.hideLabel; } specificContextMenu = (): void => { - ContextMenu.Instance.addItem({ - description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon", - event: this.setLabelField, - icon: "tag" - }); - let maxDocs = DocListCast(this.props.Document.maximizedDocs); - if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) { - ContextMenu.Instance.addItem({ - description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label", - event: this.setUseOwnTitleField, - icon: "text-height" - }); + let cm = ContextMenu.Instance; + cm.addItem({ description: this.props.Document.hideLabel ? "Show label with icon" : "Remove label from icon", event: this.setLabelField, icon: "tag" }); + if (!this.props.Document.hideLabel) { + cm.addItem({ description: "Use Target Title", event: () => IconBox.AutomaticTitle(this.props.Document), icon: "text-height" }); } } @observable _panelWidth: number = 0; @observable _panelHeight: number = 0; render() { - let labelField = StrCast(this.props.Document.labelField); - let hideLabel = BoolCast(this.props.Document.hideLabel); - let maxDocs = DocListCast(this.props.Document.maximizedDocs); - let firstDoc = maxDocs.length ? maxDocs[0] : undefined; - let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle) ? firstDoc[labelField] : this.props.Document.title); + let label = this.props.Document.hideLabel ? "" : this.props.Document.title; return (
    {this.minimizedIcon} @@ -82,4 +88,6 @@ export class IconBox extends React.Component {
    ); } -} \ No newline at end of file +} +Scripting.addGlobal(function iconTitle(doc: any) { return IconBox.titleScript(doc); }); +Scripting.addGlobal(function summaryTitle(doc: any) { return IconBox.summaryTitleScript(doc); }); \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 649d2d056..1645c4ffd 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(ImageDocument) { @@ -65,7 +64,9 @@ export class ImageBox extends DocComponent(ImageD private dropDisposer?: DragManager.DragDropDisposer; @observable private hoverActive = false; - @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); } protected createDropTarget = (ele: HTMLDivElement) => { if (this.dropDisposer) { @@ -81,32 +82,18 @@ export class ImageBox extends DocComponent(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 } } @@ -243,9 +230,7 @@ export class ImageBox extends DocComponent(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"; @@ -261,13 +246,8 @@ export class ImageBox extends DocComponent(ImageD onDotDown(index: number) { this.Document.curPage = index; } - - @computed get fieldExtensionDoc() { - return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); - } - @computed private get url() { - let data = Cast(Doc.GetProto(this.props.Document).data, ImageField); + let data = Cast(Doc.GetProto(this.props.Document)[this.props.fieldKey], ImageField); return data ? data.url.href : undefined; } @@ -407,7 +387,6 @@ export class ImageBox extends DocComponent(ImageD // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight()); // let w = bptX - sptX; - let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx let nativeWidth = FieldValue(this.Document.nativeWidth, pw); let nativeHeight = FieldValue(this.Document.nativeHeight, 0); let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; @@ -433,13 +412,13 @@ export class ImageBox extends DocComponent(ImageD if (!this.props.Document.ignoreAspect && !this.props.leaveNativeSize) this.resize(srcpath, this.props.Document); return ( -
    this.hoverActive = true)} onPointerLeave={action(() => this.hoverActive = false)} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
    - { } 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 { let i = 0; const self = this; for (let key of Object.keys(ids).slice().sort()) { - rows.push( { if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1); @@ -198,7 +198,7 @@ export class KeyValueBox extends React.Component { return; } let previousViewType = fieldTemplate.viewType; - Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(parentStackingDoc)); + Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc)); previousViewType && (fieldTemplate.viewType = previousViewType); Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index a27dbd83d..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, where: string) => boolean; } @observer export class KeyValuePair extends React.Component { @@ -45,7 +46,7 @@ export class KeyValuePair extends React.Component { 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,8 @@ export class KeyValuePair extends React.Component { Document: this.props.doc, DataDoc: this.props.doc, ContainingCollectionView: undefined, + ContainingCollectionDoc: undefined, + ruleProvider: undefined, fieldKey: this.props.keyName, fieldExt: "", isSelected: returnFalse, @@ -66,7 +69,7 @@ export class KeyValuePair extends React.Component { 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(PdfDocument) { public static LayoutString() { return FieldView.LayoutString(PDFBox); } - - @observable private _flyout: boolean = false; - @observable private _alt = false; - @observable private _pdf: Opt; - - @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 = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _keyValue: string = ""; @@ -47,16 +35,26 @@ export class PDFBox extends DocComponent(PdfDocumen private _valueRef: React.RefObject = React.createRef(); private _scriptRef: React.RefObject = React.createRef(); + @observable private _flyout: boolean = false; + @observable private _alt = false; + @observable private _pdf: Opt; + + @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(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(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(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(PdfDocumen return !this.props.active() ? (null) : (
    e.stopPropagation()}>
  • +
  • ; @@ -520,12 +521,8 @@ export class MainView extends React.Component { /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ @computed get miscButtons() { - let logoutRef = React.createRef(); - return [ this.isSearchVisible ?
    : null, -
    -
    ]; } diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 0e7e0afa7..6f5abd05b 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,8 +1,5 @@ @import "../../views/globalCssVariables.scss"; -.collectiondockingview-content { - height: 100%; -} .lm_active .messageCounter{ color:white; background: #999999; @@ -21,7 +18,7 @@ .collectiondockingview-container { width: 100%; - height: 100%; + height:100%; border-style: solid; border-width: $COLLECTION_BORDER_WIDTH; position: absolute; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index b6bc4b4ba..b047e77a8 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -32,6 +32,7 @@ import React = require("react"); import { ButtonSelector } from './ParentDocumentSelector'; import { DocumentType } from '../../documents/DocumentTypes'; library.add(faFile); +const _global = (window /* browser */ || global /* node */) as any; @observer export class CollectionDockingView extends React.Component { @@ -534,12 +535,11 @@ interface DockedFrameProps { } @observer export class DockedFrameRenderer extends React.Component { - _mainCont: HTMLDivElement | undefined = undefined; + _mainCont: HTMLDivElement | null = null; @observable private _panelWidth = 0; @observable private _panelHeight = 0; @observable private _document: Opt; @observable private _dataDoc: Opt; - @observable private _isActive: boolean = false; get _stack(): any { @@ -577,6 +577,13 @@ export class DockedFrameRenderer extends React.Component { } componentDidMount() { + let observer = new _global.ResizeObserver(action((entries: any) => { + for (let entry of entries) { + this._panelWidth = entry.contentRect.width; + this._panelHeight = entry.contentRect.height; + } + })); + observer.observe(this.props.glContainer._element[0]); this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.on("tab", this.onActiveContentItemChanged); this.onActiveContentItemChanged(); @@ -595,15 +602,16 @@ export class DockedFrameRenderer extends React.Component { } } - panelWidth = () => this._document!.ignoreAspect ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth())); - panelHeight = () => this._document!.ignoreAspect ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight())); + panelWidth = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth())); + panelHeight = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight())); - nativeWidth = () => !this._document!.ignoreAspect ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0; - nativeHeight = () => !this._document!.ignoreAspect ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0; + nativeWidth = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0; + nativeHeight = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0; contentScaling = () => { if (this._document!.type === DocumentType.PDF) { - if (this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) { + if ((this._document && this._document.fitWidth) || + this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) { return this._panelWidth / NumCast(this._document!.nativeWidth); } else { return this._panelHeight / NumCast(this._document!.nativeHeight); @@ -639,13 +647,10 @@ export class DockedFrameRenderer extends React.Component { return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); } } - @computed get docView() { - if (!this._document) { - return (null); - } - let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc; - return { getScale={returnOne} />; } - @computed get content() { - return ( -
    { - this._mainCont = ref; - if (ref) { - this._panelWidth = Number(getComputedStyle(ref).width!.replace("px", "")); - this._panelHeight = Number(getComputedStyle(ref).height!.replace("px", "")); - } - })} - style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> - {this.docView} -
    ); - } - render() { - if (!this._isActive || !this._document) return null; - let theContent = this.content; - return !this._document ? (null) : - { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}> - {({ measureRef }) =>
    - {theContent} -
    } -
    ; + return (!this._isActive || !this._document) ? (null) : + (
    this._mainCont = ref} + style={{ + transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`, + height: this._document && this._document.fitWidth ? undefined : "100%" + }}> + {this.docView(this._document)} +
    ); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 721732774..4b260d111 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -401,27 +401,34 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } SelectionManager.DeselectAll(); - const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; - const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; - const newState = HistoryUtil.getState(); - newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; - HistoryUtil.pushState(newState); - - let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType }; - - this.setPan(newPanX, newPanY); - this.Document.panTransformType = "Ease"; - this.props.focus(this.props.Document); - willZoom && this.setScaleToZoom(doc, scale); - - afterFocus && setTimeout(() => { - if (afterFocus && afterFocus()) { - this.Document.panX = savedState.px; - this.Document.panY = savedState.py; - this.Document.scale = savedState.s; - this.Document.panTransformType = savedState.pt; - } - }, 1000); + if (this.props.Document.scrollHeight) { + let annotOn = Cast(doc.annotationOn, Doc) as Doc; + let offset = annotOn && (NumCast(annotOn.height) / 2); + this.props.Document.scrollY = NumCast(doc.y) - offset; + } else { + const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; + const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; + const newState = HistoryUtil.getState(); + newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; + HistoryUtil.pushState(newState); + + let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType }; + + this.setPan(newPanX, newPanY); + this.Document.panTransformType = "Ease"; + this.props.focus(this.props.Document); + willZoom && this.setScaleToZoom(doc, scale); + + afterFocus && setTimeout(() => { + if (afterFocus && afterFocus()) { + this.Document.panX = savedState.px; + this.Document.panY = savedState.py; + this.Document.scale = savedState.s; + this.Document.panTransformType = savedState.pt; + } + }, 1000); + } + } setScaleToZoom = (doc: Doc, scale: number = 0.5) => { @@ -449,7 +456,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { PanelHeight: childLayout[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.ContainingCollectionDoc, + ContainingCollectionDoc: this.props.Document, focus: this.focusDocument, backgroundColor: this.getClusterColor, parentActive: this.props.active, diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 44611869e..82193aefa 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -188,16 +188,13 @@ export class MarqueeView extends React.Component @action onPointerUp = (e: PointerEvent): void => { if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); - // console.log("pointer up!"); if (this._visible) { - // console.log("visible"); let mselect = this.marqueeSelect(); if (!e.shiftKey) { SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } - //console.log("invisible"); this.cleanupInteractions(true); if (e.altKey) { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c3d2c9e51..c4fed200f 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -77,7 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; - let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout as Doc : undefined; + let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout : undefined; let br = StrCast((ld || this.props.Document).borderRounding); br = !br && ruleRounding ? ruleRounding : br; if (br.endsWith("%")) { @@ -100,7 +100,7 @@ export class CollectionFreeFormDocumentView extends DocComponent this.dataProvider ? this.dataProvider.width : this.panelWidth(); + finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth(); finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight(); render() { @@ -124,7 +124,7 @@ export class CollectionFreeFormDocumentView extends DocComponent
    diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6ee88f834..bd702c6a2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -223,15 +223,18 @@ export class DocumentView extends DocComponent(Docu else if (linkedDocs.length) { SelectionManager.DeselectAll(); let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); + let second = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor2 as Doc, this.props.Document) && !d.anchor2anchored); let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + let secondUnshown = second.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor1 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]]; + if (secondUnshown.length) second = [secondUnshown[0]]; + let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor1 as Doc] : undefined; // @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)) { + if (linkedFwdDocs && !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, @@ -601,7 +604,7 @@ export class DocumentView extends DocComponent(Docu 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` : nativeWidth !== "100%" ? nativeWidth : "100%"; + const nativeHeight = this.Document.ignoreAspect || this.props.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%"; 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"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 923dd1544..565fb0505 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -815,6 +815,40 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); } + let ctrlKey = e.ctrlKey; + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { + e.preventDefault(); + } + } + + onPointerUp = (e: React.PointerEvent): void => { + FormattedTextBoxComment.textBox = this; + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + e.stopPropagation(); + } + } + + @action + onFocused = (e: React.FocusEvent): void => { + document.removeEventListener("keypress", this.recordKeyHandler); + document.addEventListener("keypress", this.recordKeyHandler); + this.tryUpdateHeight(); + if (!this.props.isOverlay) { + FormattedTextBox.InputBoxOverlay = this; + } else { + if (this._ref.current) { + this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll; + } + } + } + onPointerWheel = (e: React.WheelEvent): void => { + // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time + if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + e.stopPropagation(); + } + } + + onClick = (e: React.MouseEvent): void => { let ctrlKey = e.ctrlKey; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; @@ -829,8 +863,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); if (node) { let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); - href = link && link.attrs.href; - location = link && link.attrs.location; + if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). + href = link && link.attrs.href; + location = link && link.attrs.location; + } } if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { @@ -848,7 +884,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return; } } - if (targetContext) { + if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) { DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } else if (jumpToDoc) { DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); @@ -870,39 +906,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - e.preventDefault(); - } - } - - onPointerUp = (e: React.PointerEvent): void => { - FormattedTextBoxComment.textBox = this; - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { - e.stopPropagation(); - } - } - - @action - onFocused = (e: React.FocusEvent): void => { - document.removeEventListener("keypress", this.recordKeyHandler); - document.addEventListener("keypress", this.recordKeyHandler); - this.tryUpdateHeight(); - if (!this.props.isOverlay) { - FormattedTextBox.InputBoxOverlay = this; - } else { - if (this._ref.current) { - this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll; - } - } - } - onPointerWheel = (e: React.WheelEvent): void => { - // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time - if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { - e.stopPropagation(); - } - } - - onClick = (e: React.MouseEvent): void => { // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. if (this.props.isSelected() && e.nativeEvent.offsetX < 40) { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 624593245..004f50590 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -38,6 +38,7 @@ library.add(faFileAudio, faAsterisk); export const pageSchema = createSchema({ curPage: "number", + fitWidth: "boolean" }); interface Window { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 0fcbaaa7c..fe71e76fd 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -20,6 +20,9 @@ import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); import { undoBatch } from '../../util/UndoManager'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ContextMenu } from '../ContextMenu'; +import { Utils } from '../../../Utils'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @@ -58,7 +61,7 @@ export class PDFBox extends DocComponent(PdfDocumen this.Document.nativeWidth = nw * 96 / 72; this.Document.nativeHeight = this.Document.nativeHeight ? nw * 96 / 72 * oldaspect : nh * 96 / 72; } - this.Document.height = this.Document[WidthSym]() * (nh / nw); + !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw)); } public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); } @@ -165,14 +168,23 @@ export class PDFBox extends DocComponent(PdfDocumen
    ); } + specificContextMenu = (e: React.MouseEvent): void => { + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); + let funcs: ContextMenuProps[] = []; + pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" }); + + ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); + } + render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive"); return (!(pdfUrl instanceof PdfField) || !this._pdf ?
    {`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}
    : -
    { +
    { let hit = document.elementFromPoint(e.clientX, e.clientY); - if (hit && hit.localName === "span" && this.props.isSelected()) { + if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation e.button === 0 && e.stopPropagation(); } }}> @@ -182,7 +194,7 @@ export class PDFBox extends DocComponent(PdfDocumen Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling} addDocTab={this.props.addDocTab} GoToPage={this.gotoPage} pinToPres={this.props.pinToPres} addDocument={this.props.addDocument} - ScreenToLocalTransform={this.props.ScreenToLocalTransform} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} /> {this.settingsPanel()} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 5ad4ffd48..13fd8ea98 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -9,14 +9,12 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, returnOne, Utils } from "../../../Utils"; +import smoothScroll, { Utils, emptyFunction, returnOne } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompiledScript, CompileScript } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import Annotation from "./Annotation"; import PDFMenu from "./PDFMenu"; import "./PDFViewer.scss"; import React = require("react"); @@ -24,7 +22,8 @@ import * as rp from "request-promise"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; -import { SelectionManager } from "../../util/SelectionManager"; +import Annotation from "./Annotation"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); @@ -41,6 +40,7 @@ interface IViewerProps { PanelWidth: () => number; PanelHeight: () => number; ContentScaling: () => number; + select: (isCtrlPressed: boolean) => void; renderDepth: number; isSelected: () => boolean; loaded: (nw: number, nh: number, np: number) => void; @@ -106,11 +106,20 @@ export class PDFViewer extends React.Component { // file address of the pdf this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`))); runInAction(() => this._showWaiting = this._showCover = true); - this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => { - this.setupPdfJsViewer(); - this._selectionReactionDisposer && this._selectionReactionDisposer(); - this._selectionReactionDisposer = undefined; - }) + this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.setupPdfJsViewer()); + this._reactionDisposer = reaction( + () => this.props.Document.scrollY, + (scrollY) => { + if (scrollY !== undefined) { + if (this._showCover || this._showWaiting) { + this.setupPdfJsViewer(); + } + this._mainCont.current && smoothScroll(1000, this._mainCont.current, NumCast(this.props.Document.scrollY) || 0); + this.props.Document.scrollY = undefined; + } + }, + { fireImmediately: true } + ); } componentWillUnmount = () => { @@ -153,12 +162,14 @@ export class PDFViewer extends React.Component { i === this.props.pdf.numPages - 1 && this.props.loaded((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i); })))); - Doc.GetProto(this.props.Document).scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0); + Doc.GetProto(this.props.Document).scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; } } @action setupPdfJsViewer = async () => { + this._selectionReactionDisposer && this._selectionReactionDisposer(); + this._selectionReactionDisposer = undefined; this._showWaiting = true; this.props.setPdfViewer(this); await this.initialLoad(); @@ -180,10 +191,6 @@ export class PDFViewer extends React.Component { }), { fireImmediately: true } ); - this._reactionDisposer = reaction( - () => this.props.Document.panY, - () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.props.Document.panY) || 0, behavior: "auto" }) - ); document.removeEventListener("copy", this.copy); document.addEventListener("copy", this.copy); @@ -228,10 +235,9 @@ export class PDFViewer extends React.Component { annoDocs.push(annoDoc); annoDoc.isButton = true; anno.remove(); - // this.props.addDocument && this.props.addDocument(annoDoc, false); mainAnnoDoc = annoDoc; + mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); mainAnnoDocProto.y = annoDoc.y; - mainAnnoDocProto = Doc.GetProto(annoDoc); } else { this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => { let annoDoc = new Doc(); @@ -381,7 +387,7 @@ export class PDFViewer extends React.Component { this._downX = e.clientX; this._downY = e.clientY; if (NumCast(this.props.Document.scale, 1) !== 1) return; - if (e.button !== 0 && this.active()) { + if ((e.button !== 0 || e.altKey) && this.active()) { this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true); } this._marqueeing = false; @@ -638,18 +644,15 @@ export class PDFViewer extends React.Component { } @computed get annotationLayer() { - trace(); return
    {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => )}
    ; } @computed get pdfViewerDiv() { - trace(); return
    ; } @computed get standinViews() { - trace(); return <> {this._showCover ? this.getCoverImage() : (null)} {this._showWaiting ? : (null)} @@ -710,7 +713,7 @@ class PdfViewerMarquee extends React.Component { width: `${this.props.width()}px`, height: `${this.props.height()}px`, border: `${this.props.width() === 0 ? "" : "2px dashed black"}` }}> -
    +
    ; } } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 1b3c8b0b0..79b73aba8 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -344,7 +344,7 @@ export namespace Doc { let list = Cast(target[key], listSpec(Doc)); if (list) { if (allowDuplicates !== true) { - let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1); + let pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1); if (pind !== -1) { list.splice(pind, 1); } -- cgit v1.2.3-70-g09d2 From d7f515f32de780884774e7bbdcc1cfe78733af45 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 30 Sep 2019 14:01:59 -0400 Subject: a bunch of changes to presentation view ... more coming. --- src/client/documents/DocumentTypes.ts | 2 +- src/client/documents/Documents.ts | 5 +- src/client/views/MainView.tsx | 1 - src/client/views/nodes/DocumentContentsView.tsx | 31 ++-- src/client/views/nodes/PresBox.tsx | 165 ++++++++------------- .../views/presentationview/PresentationElement.tsx | 19 +-- .../views/presentationview/PresentationList.tsx | 9 -- .../presentationview/PresentationModeMenu.scss | 30 ---- .../presentationview/PresentationModeMenu.tsx | 101 ------------- src/new_fields/Doc.ts | 22 ++- 10 files changed, 105 insertions(+), 280 deletions(-) delete mode 100644 src/client/views/presentationview/PresentationModeMenu.scss delete mode 100644 src/client/views/presentationview/PresentationModeMenu.tsx (limited to 'src/new_fields') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 381981e1b..8ec6e9642 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -19,5 +19,5 @@ export enum DocumentType { YOUTUBE = "youtube", DRAGBOX = "dragbox", PRES = "presentation", - LINKFOLLOW = "linkfollow", + LINKFOLLOW = "linkfollow" } \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d1ec2ac39..01c36182f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -46,8 +46,6 @@ import { ComputedField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; import { LinkFollowBox } from "../views/linking/LinkFollowBox"; -//import { PresBox } from "../views/nodes/PresBox"; -//import { PresField } from "../../new_fields/PresField"; var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -176,7 +174,7 @@ export namespace Docs { }], [DocumentType.LINKFOLLOW, { layout: { view: LinkFollowBox } - }] + }], ]); // All document prototypes are initialized with at least these values @@ -459,7 +457,6 @@ export namespace Docs { export function LinkFollowBoxDocument(options?: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) }); } - export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index dadff21a7..35d527c91 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -634,7 +634,6 @@ export class MainView extends React.Component { {this.mainContent} - {PresBox.miniPresentation} {this.nodesMenu()} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 3c3cc0d91..d035cbe18 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,37 +1,34 @@ -import { computed, trace } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; +import { Doc } from "../../../new_fields/Doc"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { Cast } from "../../../new_fields/Types"; +import { OmitKeys, Without } from "../../../Utils"; +import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; +import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; +import { LinkFollowBox } from "../linking/LinkFollowBox"; +import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; +import { ButtonBox } from "./ButtonBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; -import { FormattedTextBox } from "./FormattedTextBox"; -import { ImageBox } from "./ImageBox"; import { DragBox } from "./DragBox"; -import { ButtonBox } from "./ButtonBox"; -import { PresBox } from "./PresBox"; -import { LinkFollowBox } from "../linking/LinkFollowBox"; +import { FieldView, FieldViewProps } from "./FieldView"; +import { FormattedTextBox } from "./FormattedTextBox"; import { IconBox } from "./IconBox"; +import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; +import { PresBox } from "./PresBox"; import { VideoBox } from "./VideoBox"; -import { FieldView } from "./FieldView"; import { WebBox } from "./WebBox"; -import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; -import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; import React = require("react"); -import { FieldViewProps } from "./FieldView"; -import { Without, OmitKeys } from "../../../Utils"; -import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; -import { List } from "../../../new_fields/List"; -import { Doc } from "../../../new_fields/Doc"; -import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { fromPromise } from "mobx-utils"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index d246da87a..e2dac4fd3 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -2,20 +2,19 @@ import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, observable, runInAction, computed } from "mobx"; +import { action, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { listSpec } from "../../../new_fields/Schema"; import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; import { ContextMenu } from "../ContextMenu"; -import PresentationElement from "../presentationview/PresentationElement"; import PresentationViewList from "../presentationview/PresentationList"; import "../presentationview/PresentationView.scss"; import { FieldView, FieldViewProps } from './FieldView'; -import PresModeMenu from "../presentationview/PresentationModeMenu"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; library.add(faArrowLeft); library.add(faArrowRight); @@ -31,31 +30,13 @@ export class PresBox extends React.Component { public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } public static Instance: PresBox; - //mapping from docs to their rendered component - @observable _presElementsMappings: Map = new Map(); //variable that holds all the docs in the presentation @observable _childrenDocs: Doc[] = []; - @observable _opacity = 1; - @observable _persistOpacity = true; - @observable _labelOpacity = 0; // whether presentation view has been minimized @observable _presMode = false; @observable public static CurrentPresentation: PresBox; - @computed static get miniPresentation() { - let next = () => PresBox.CurrentPresentation.next(); - let back = () => PresBox.CurrentPresentation.back(); - let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres(); - let closePresMode = action(() => { - PresBox.CurrentPresentation._presMode = false; - CollectionDockingView.AddRightSplit(PresBox.CurrentPresentation.props.Document, undefined); - }); - return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation._presMode ? (null) : - ; - } - //initilize class variables constructor(props: FieldViewProps) { super(props); @@ -71,7 +52,7 @@ export class PresBox extends React.Component { let nextSelected = current + 1; for (; nextSelected < presDocs.length - 1; nextSelected++) { - if (!this._presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) { + if (!presDocs[nextSelected + 1].groupButton) { break; } } @@ -89,18 +70,16 @@ export class PresBox extends React.Component { let prevSelected = current; let zoomOut: boolean = false; - //checking if this presentation id is mapped to a group, if so chosing the first element in group - let presDocs = DocListCast(this.props.Document.data); + let presDocs = await DocListCastAsync(this.props.Document[this.props.fieldKey]); let currentsArray: Doc[] = []; - for (; prevSelected > 0 && presDocs[prevSelected].groupButton; prevSelected--) { + for (; presDocs && prevSelected > 0 && presDocs[prevSelected].groupButton; prevSelected--) { currentsArray.push(presDocs[prevSelected]); } prevSelected = Math.max(0, prevSelected - 1); //checking if any of the group members had used zooming in currentsArray.forEach((doc: Doc) => { - //let presElem: PresentationElement | undefined = this.presElementsMappings.get(doc); - if (this._presElementsMappings.get(doc)!.props.document.showButton) { + if (doc.showButton) { zoomOut = true; return; } @@ -109,7 +88,7 @@ export class PresBox extends React.Component { // if a group set that flag to zero or a single element //If so making sure to zoom out, which goes back to state before zooming action if (current > 0) { - if (zoomOut || this._presElementsMappings.get(docAtCurrent)!.showButton) { + if (zoomOut || docAtCurrent.showButton) { let prevScale = NumCast(this._childrenDocs[prevSelected].viewScale, null); let curScale = DocumentManager.Instance.getScaleOfDocView(this._childrenDocs[current]); if (prevScale !== undefined && prevScale !== curScale) { @@ -127,20 +106,20 @@ export class PresBox extends React.Component { * Hide Until Presented, Hide After Presented, Fade After Presented */ showAfterPresented = (index: number) => { - this._presElementsMappings.forEach((presElem, doc) => { + this._childrenDocs.forEach((doc, ind) => { //the order of cases is aligned based on priority - if (presElem.props.document.hideTillShownButton) { - if (this._childrenDocs.indexOf(doc) <= index) { + if (doc.hideTillShownButton) { + if (ind <= index) { doc.opacity = 1; } } - if (presElem.props.document.hideAfterButton) { - if (this._childrenDocs.indexOf(doc) < index) { + if (doc.hideAfterButton) { + if (ind < index) { doc.opacity = 0; } } - if (presElem.props.document.fadeButton) { - if (this._childrenDocs.indexOf(doc) < index) { + if (doc.fadeButton) { + if (ind < index) { doc.opacity = 0.5; } } @@ -153,21 +132,21 @@ export class PresBox extends React.Component { * Hide Until Presented, Hide After Presented, Fade After Presented */ hideIfNotPresented = (index: number) => { - this._presElementsMappings.forEach((presElem, key) => { + this._childrenDocs.forEach((key, ind) => { //the order of cases is aligned based on priority - if (presElem.props.document.hideAfterButton) { - if (this._childrenDocs.indexOf(key) >= index) { + if (key.hideAfterButton) { + if (ind >= index) { key.opacity = 1; } } - if (presElem.props.document.fadeButton) { - if (this._childrenDocs.indexOf(key) >= index) { + if (key.fadeButton) { + if (ind >= index) { key.opacity = 1; } } - if (presElem.props.document.hideTillShownButton) { - if (this._childrenDocs.indexOf(key) > index) { + if (key.hideTillShownButton) { + if (ind > index) { key.opacity = 0; } } @@ -187,18 +166,18 @@ export class PresBox extends React.Component { let nextSelected = presDocs.indexOf(curDoc); let currentDocGroups: Doc[] = []; for (; nextSelected < presDocs.length - 1; nextSelected++) { - if (!this._presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) { + if (!presDocs[nextSelected + 1].groupButton) { break; } currentDocGroups.push(presDocs[nextSelected]); } currentDocGroups.forEach((doc: Doc, index: number) => { - if (this._presElementsMappings.get(doc)!.navButton) { + if (doc.navButton) { docToJump = doc; willZoom = false; } - if (this._presElementsMappings.get(doc)!.showButton) { + if (doc.showButton) { docToJump = doc; willZoom = true; } @@ -207,9 +186,9 @@ export class PresBox extends React.Component { //docToJump stayed same meaning, it was not in the group or was the last element in the group if (docToJump === curDoc) { //checking if curDoc has navigation open - if (this._presElementsMappings.get(curDoc)!.navButton) { + if (curDoc.navButton) { DocumentManager.Instance.jumpToDocument(curDoc, false); - } else if (this._presElementsMappings.get(curDoc)!.showButton) { + } else if (curDoc.showButton) { let curScale = DocumentManager.Instance.getScaleOfDocView(this._childrenDocs[fromDoc]); //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(curDoc, true); @@ -250,15 +229,13 @@ export class PresBox extends React.Component { } /** - * The function that removes a doc from a presentation. It also makes sure to - * do necessary updates to backUps and mappings stored locally. + * The function that removes a doc from a presentation. */ @action public RemoveDoc = async (index: number) => { const value = FieldValue(Cast(this.props.Document.data, listSpec(Doc))); // don't replace with DocListCast -- we need to modify the document's actual stored list if (value) { - //removing the Presentation Element from the document and update mappings - this._presElementsMappings.delete(await value.splice(index, 1)[0]); + value.splice(index, 1); } } @@ -302,14 +279,6 @@ export class PresBox extends React.Component { this._childrenDocs = docList; } - //The function that is called to render the play or pause button depending on - //if presentation is running or not. - renderPlayPauseButton = () => { - return ; - } - //The function that starts or resets presentaton functionally, depending on status flag. @action startOrResetPres = () => { @@ -340,18 +309,18 @@ export class PresBox extends React.Component { //The function that starts the presentation, also checking if actions should be applied //directly at start. startPresentation = (startIndex: number) => { - this._presElementsMappings.forEach((component, doc) => { - if (component.props.document.hideTillShownButton) { + this._childrenDocs.map(doc => { + if (doc.hideTillShownButton) { if (this._childrenDocs.indexOf(doc) > startIndex) { doc.opacity = 0; } } - if (component.props.document.hideAfterButton) { + if (doc.hideAfterButton) { if (this._childrenDocs.indexOf(doc) < startIndex) { doc.opacity = 0; } } - if (component.props.document.fadeButton) { + if (doc.fadeButton) { if (this._childrenDocs.indexOf(doc) < startIndex) { doc.opacity = 0.5; } @@ -359,13 +328,18 @@ export class PresBox extends React.Component { }); } - addPressElem = (keyDoc: Doc, elem: PresentationElement) => { - this._presElementsMappings.set(keyDoc, elem); - } - - minimize = undoBatch(action(() => { - this._presMode = true; - this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); + toggleMinimize = undoBatch(action((e: React.PointerEvent) => { + if (this.props.Document.minimizedView) { + this.props.Document.minimizedView = false; + Doc.RemoveDocFromList((CurrentUserUtils.UserDocument.overlays as Doc), "data", this.props.Document); + CollectionDockingView.AddRightSplit(this.props.Document, this.props.DataDoc); + } else { + this.props.Document.minimizedView = true; + this.props.Document.x = e.clientX + 25; + this.props.Document.y = e.clientY - 25; + this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); + Doc.AddDocToList((CurrentUserUtils.UserDocument.overlays as Doc), "data", this.props.Document); + } })); specificContextMenu = (e: React.MouseEvent): void => { @@ -374,37 +348,24 @@ export class PresBox extends React.Component { render() { return ( -
    !this._persistOpacity && (this._opacity = 1))} onContextMenu={this.specificContextMenu} - onPointerLeave={action(() => !this._persistOpacity && (this._opacity = 0.4))} - style={{ width: "100%", opacity: this._opacity, }}> -
    - - {this.renderPlayPauseButton()} - - +
    +
    + + + +
    - ) => { - this._persistOpacity = e.target.checked; - this._opacity = this._persistOpacity ? 1 : 0.4; - })} - checked={this._persistOpacity} - style={{ position: "absolute", bottom: 5, left: 5 }} - onPointerEnter={action(() => this._labelOpacity = 1)} - onPointerLeave={action(() => this._labelOpacity = 0)} - /> -

    opacity {this._persistOpacity ? "persistent" : "on focus"}

    - this._presElementsMappings.clear()} - /> + {this.props.Document.minimizedView ? (null) : + }
    ); } diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 126d62c52..6e8c28d34 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons'; -import { faArrowRight, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDown, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed } from "mobx"; import { observer } from "mobx-react"; @@ -22,7 +22,7 @@ library.add(fileSolid); library.add(faLocationArrow); library.add(fileRegular as any); library.add(faSearch); -library.add(faArrowRight); +library.add(faArrowDown); interface PresentationElementProps { mainDocument: Doc; @@ -33,7 +33,6 @@ interface PresentationElementProps { allListElements: Doc[]; presStatus: boolean; removeDocByRef(doc: Doc): boolean; - PresElementsMappings: Map; } /** @@ -59,14 +58,14 @@ export default class PresentationElement extends React.Component { + onExpandTabClick = (e: React.MouseEvent) => { e.stopPropagation(); - - this.openRightButton = !this.openRightButton; + this.embedInline = !this.embedInline } /** @@ -416,7 +413,7 @@ export default class PresentationElement extends React.Component e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}> - +
    {this.renderEmbeddedInline()} diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx index da48a856a..483461e5a 100644 --- a/src/client/views/presentationview/PresentationList.tsx +++ b/src/client/views/presentationview/PresentationList.tsx @@ -12,11 +12,9 @@ interface PresListProps { mainDocument: Doc; deleteDocument(index: number): void; gotoDocument(index: number, fromDoc: number): Promise; - PresElementsMappings: Map; setChildrenDocs: (docList: Doc[]) => void; presStatus: boolean; removeDocByRef(doc: Doc): boolean; - clearElemMap(): void; } @@ -45,16 +43,10 @@ export default class PresentationViewList extends React.Component const children = DocListCast(this.props.mainDocument.data); this.initializeScaleViews(children); this.props.setChildrenDocs(children); - this.props.clearElemMap(); return (
    {children.map((doc: Doc, index: number) => { - if (e && e !== null) { - this.props.PresElementsMappings.set(doc, e); - } - }} key={doc[Id]} mainDocument={this.props.mainDocument} document={doc} @@ -64,7 +56,6 @@ export default class PresentationViewList extends React.Component allListElements={children} presStatus={this.props.presStatus} removeDocByRef={this.props.removeDocByRef} - PresElementsMappings={this.props.PresElementsMappings} /> )}
    diff --git a/src/client/views/presentationview/PresentationModeMenu.scss b/src/client/views/presentationview/PresentationModeMenu.scss deleted file mode 100644 index 336f43d20..000000000 --- a/src/client/views/presentationview/PresentationModeMenu.scss +++ /dev/null @@ -1,30 +0,0 @@ -.presMenu-cont { - position: fixed; - z-index: 10000; - height: 35px; - background: #323232; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - border-radius: 0px 6px 6px 6px; - overflow: hidden; - display: flex; - - .presMenu-button { - background-color: transparent; - width: 35px; - height: 35px; - } - - .presMenu-button:hover { - background-color: #121212; - } - - .presMenu-dragger { - height: 100%; - transition: width .2s; - background-image: url("https://logodix.com/logo/1020374.png"); - background-size: 90% 100%; - background-repeat: no-repeat; - background-position: left center; - } - -} \ No newline at end of file diff --git a/src/client/views/presentationview/PresentationModeMenu.tsx b/src/client/views/presentationview/PresentationModeMenu.tsx deleted file mode 100644 index e4123a1fe..000000000 --- a/src/client/views/presentationview/PresentationModeMenu.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React = require("react"); -import { observable, action, runInAction } from "mobx"; -import "./PresentationModeMenu.scss"; -import { observer } from "mobx-react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - - -export interface PresModeMenuProps { - next: () => void; - back: () => void; - presStatus: boolean; - startOrResetPres: () => void; - closePresMode: () => void; -} - -/** - * This class is responsible for modeling of the Presentation Mode Menu. The menu allows - * user to navigate through presentation elements, and start/stop the presentation. - */ -@observer -export default class PresModeMenu extends React.Component { - - @observable private _top: number = 20; - @observable private _left: number = window.innerWidth - 160; - @observable private _opacity: number = 1; - @observable private _transition: string = "opacity 0.5s"; - @observable private _transitionDelay: string = ""; - private _offsetY: number = 0; - private _offsetX: number = 0; - - - private _mainCont: React.RefObject = React.createRef(); - - /** - * The function that changes the coordinates of the menu, depending on the - * movement of the mouse when it's being dragged. - */ - @action - dragging = (e: PointerEvent) => { - this._left = e.pageX - this._offsetX; - this._top = e.pageY - this._offsetY; - - e.stopPropagation(); - e.preventDefault(); - } - - /** - * The function that removes the event listeners that are responsible for - * dragging of the menu. - */ - dragEnd = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.dragging); - document.removeEventListener("pointerup", this.dragEnd); - e.stopPropagation(); - e.preventDefault(); - } - - /** - * The function that starts the dragging of the presentation mode menu. When - * the lines on further right are clicked on. - */ - dragStart = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.dragging); - document.addEventListener("pointermove", this.dragging); - document.removeEventListener("pointerup", this.dragEnd); - document.addEventListener("pointerup", this.dragEnd); - - this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX; - this._offsetY = e.nativeEvent.offsetY; - - e.stopPropagation(); - e.preventDefault(); - } - - /** - * The function that is responsible for rendering the play or pause button, depending on the - * status of the presentation. - */ - renderPlayPauseButton = () => { - if (this.props.presStatus) { - return ; - } else { - return ; - } - } - - render() { - return ( -
    - - {this.renderPlayPauseButton()} - - -
    -
    - ); - } -} \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 79b73aba8..4a03fed08 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -337,11 +337,25 @@ export namespace Doc { export function IndexOf(toFind: Doc, list: Doc[]) { 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) { - Doc.GetProto(target)[key] = new List(); + export function RemoveDocFromList(listDoc: Doc, key: string, doc: Doc) { + if (listDoc[key] === undefined) { + Doc.GetProto(listDoc)[key] = new List(); } - let list = Cast(target[key], listSpec(Doc)); + let list = Cast(listDoc[key], listSpec(Doc)); + if (list) { + let ind = list.indexOf(doc); + if (ind !== -1) { + list.splice(ind, 1); + return true; + } + } + return false; + } + export function AddDocToList(listDoc: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { + if (listDoc[key] === undefined) { + Doc.GetProto(listDoc)[key] = new List(); + } + let list = Cast(listDoc[key], listSpec(Doc)); if (list) { if (allowDuplicates !== true) { let pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1); -- cgit v1.2.3-70-g09d2 From 69e4a936c4eb0cc2e35e4e7f3258aed1f72b8da7 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 1 Oct 2019 15:10:22 -0400 Subject: fixed a bunch of pdf related things with stacking views. --- .../views/collections/CollectionDockingView.tsx | 9 ++-- .../views/collections/CollectionSchemaView.tsx | 6 +-- .../views/collections/CollectionStackingView.tsx | 6 +-- src/client/views/nodes/DocumentView.tsx | 54 ++++++++++++++++------ src/client/views/nodes/PDFBox.tsx | 16 +++++-- src/client/views/pdf/PDFViewer.tsx | 17 +++++-- src/new_fields/Doc.ts | 1 + 7 files changed, 78 insertions(+), 31 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 246546c9e..98aff41d3 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -3,7 +3,7 @@ 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, computed, runInAction } from "mobx"; +import { action, Lambda, observable, reaction, computed, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; @@ -659,7 +659,10 @@ export class DockedFrameRenderer extends React.Component { return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); } } - docView(document: Doc) { + + @computed get docView() { + if (!this._document) return (null); + const document = this._document; let resolvedDataDoc = document.layout instanceof Doc ? document : this._dataDoc; return { transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`, height: this._document && this._document.fitWidth ? undefined : "100%" }}> - {this.docView(this._document)} + {this.docView}
    ); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 853808335..24f585a07 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -958,10 +958,10 @@ export class CollectionSchemaPreview extends React.Component this.nativeWidth ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); - private PanelHeight = () => this.nativeHeight ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); + private PanelWidth = () => this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); + private PanelHeight = () => this.nativeHeight && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling()); - get centeringOffset() { return this.nativeWidth ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; } + get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; } @action onPreviewScriptChange = (e: React.ChangeEvent) => { this.props.setPreviewScript(e.currentTarget.value); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e2020601b..4b81db3a6 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,7 +1,7 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { CursorProperty } from "csstype"; -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 Switch from 'rc-switch'; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; @@ -133,7 +133,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) { let height = () => this.getDocHeight(layoutDoc); let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]()); - return doc) { if (!(d.nativeWidth && !d.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(d[WidthSym](), wid); return wid * aspect; } - return d.fitWidth ? Math.min(this.props.PanelHeight() - 2 * this.yMargin, d[HeightSym]()) : d[HeightSym](); + return d.fitWidth ? this.props.PanelHeight() - 2 * this.yMargin : d[HeightSym](); } columnDividerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 779e701ea..ded50890d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, runInAction } from "mobx"; +import { action, computed, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; @@ -601,6 +601,39 @@ export class DocumentView extends DocComponent(Docu return (showTitle ? 25 : 0) + 1; } + childScaling = () => (this.props.Document.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); + @computed get contents() { + return (); + } 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; @@ -610,8 +643,8 @@ export class DocumentView extends DocComponent(Docu 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.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%"; + const nativeWidth = this.props.Document.fitWidth ? this.props.PanelWidth() : this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%"; + const nativeHeight = this.props.Document.fitWidth ? this.props.PanelHeight() : this.Document.ignoreAspect || this.props.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%"; 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"); @@ -645,13 +678,6 @@ export class DocumentView extends DocComponent(Docu SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true} />
    ); - const contents = (); return (
    (Docu background: backgroundColor, width: nativeWidth, height: nativeHeight, - transform: `scale(${this.props.ContentScaling()})`, + transform: `scale(${this.props.Document.fitWidth ? 1 : this.props.ContentScaling()})`, opacity: this.Document.opacity }} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} @@ -675,15 +701,15 @@ export class DocumentView extends DocComponent(Docu {!showTitle && !showCaption ? this.Document.searchFields ? (
    - {contents} + {this.contents} {searchHighlight}
    ) : - contents + this.contents :
    - {contents} + {this.contents}
    {titleView} {captionView} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3014af944..3ad0b4010 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -35,6 +35,7 @@ export class PDFBox extends DocComponent(PdfDocumen private _scriptValue: string = ""; private _searchString: string = ""; private _isChildActive = false; + private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title private _pdfViewer: PDFViewer | undefined; private _keyRef: React.RefObject = React.createRef(); private _valueRef: React.RefObject = React.createRef(); @@ -176,20 +177,25 @@ export class PDFBox extends DocComponent(PdfDocumen ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } - render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive"); - return (!(pdfUrl instanceof PdfField) || !this._pdf || this.props.ScreenToLocalTransform().Scale > 6 ? + let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf; + if (!noPdf && (this.props.isSelected() || this.props.ScreenToLocalTransform().Scale < 2.5)) this._everActive = true; + return (!this._everActive ?
    {` ${this.props.Document.title}`}
    : -
    { +
    { let hit = document.elementFromPoint(e.clientX, e.clientY); if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation e.button === 0 && e.stopPropagation(); } }}> - (PdfDocumen pinToPres={this.props.pinToPres} addDocument={this.props.addDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} - fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} /> + fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this.props.ScreenToLocalTransform().Scale < 2.5 ? true : false} /> {this.settingsPanel()}
    ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 3bf53340b..91a8cbf9f 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -41,6 +41,7 @@ interface IViewerProps { PanelHeight: () => number; ContentScaling: () => number; select: (isCtrlPressed: boolean) => void; + startupLive: boolean; renderDepth: number; isSelected: () => boolean; loaded: (nw: number, nh: number, np: number) => void; @@ -75,6 +76,7 @@ export class PDFViewer extends React.Component { @observable private _zoomed = 1; public pdfViewer: any; + private _retries = 0; // number of times tried to create the PDF viewer private _isChildActive = false; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); private _annotationLayer: React.RefObject = React.createRef(); @@ -84,7 +86,6 @@ export class PDFViewer extends React.Component { private _filterReactionDisposer?: IReactionDisposer; private _viewer: React.RefObject = React.createRef(); private _mainCont: React.RefObject = React.createRef(); - private _marquee: React.RefObject = React.createRef(); private _selectionText: string = ""; private _startX: number = 0; private _startY: number = 0; @@ -106,7 +107,7 @@ export class PDFViewer extends React.Component { // file address of the pdf this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`))); runInAction(() => this._showWaiting = this._showCover = true); - this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.setupPdfJsViewer()); + this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.setupPdfJsViewer(), { fireImmediately: this.props.startupLive }); this._reactionDisposer = reaction( () => this.props.Document.scrollY, (scrollY) => { @@ -199,6 +200,17 @@ export class PDFViewer extends React.Component { this.gotoPage(NumCast(this.props.Document.curPage, 1)); })); document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false)); + this.createPdfViewer(); + } + + createPdfViewer() { + if (!this._mainCont.current) { + if (this._retries < 5) { + this._retries++; + setTimeout(() => this.createPdfViewer(), 1000); + } + return; + } var pdfLinkService = new PDFJSViewer.PDFLinkService(); let pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, @@ -664,7 +676,6 @@ export class PDFViewer extends React.Component { marqueeY = () => this._marqueeY; marqueeing = () => this._marqueeing; render() { - trace(); return (
    {this.pdfViewerDiv} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 4a03fed08..a68692732 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -712,6 +712,7 @@ export namespace Doc { } 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 getAlias(doc: any) { return Doc.MakeAlias(doc); }); Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); Scripting.addGlobal(function aliasDocs(field: any) { return new List(field.map((d: any) => Doc.MakeAlias(d))); }); Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 128b8f61a8e3c7557bf7c3424a76f9c61ad6a56b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 1 Oct 2019 21:13:02 -0400 Subject: fixed issues with isMinimized and with iconBoxs being too big. --- package.json | 2 +- src/client/views/collections/CollectionSchemaView.tsx | 4 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 10 +++++++--- src/client/views/nodes/PDFBox.scss | 2 +- src/client/views/nodes/PDFBox.tsx | 12 ++++++++---- src/new_fields/Doc.ts | 10 ++-------- 7 files changed, 22 insertions(+), 20 deletions(-) (limited to 'src/new_fields') diff --git a/package.json b/package.json index e516a6979..9d12f51b0 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "react-mosaic": "0.0.20", "react-simple-dropdown": "^3.2.3", "react-split-pane": "^0.1.85", - "react-table": "^6.9.2", + "react-table": "^6.10.3", "readline": "^1.3.0", "request": "^2.88.0", "request-promise": "^4.2.4", diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 24f585a07..52a41fc84 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -309,11 +309,11 @@ export class SchemaTable extends React.Component { return resized; }, [] as { id: string, value: number }[]); } - @computed get sorted(): { id: string, desc?: true }[] { + @computed get sorted(): { id: string, desc: boolean }[] { return this.columns.reduce((sorted, shf) => { shf.desc && sorted.push({ id: shf.heading, desc: shf.desc }); return sorted; - }, [] as { id: string, desc?: true }[]); + }, [] as { id: string, desc: boolean }[]); } @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 27b9a06f5..98730fe13 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -105,7 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); } - public isCurrent(doc: Doc) { return !this.props.Document.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); } + public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); } public getActiveDocuments = () => { return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ded50890d..a903f8fe2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -635,6 +635,7 @@ export class DocumentView extends DocComponent(Docu DataDoc={this.props.DataDoc} />); } render() { + let animDims = this.props.Document.animateToDimensions ? Array.from(Cast(this.props.Document.animateToDimensions, listSpec("number"))!) : undefined; 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.setsLayoutProp("backgroundColor"); @@ -644,7 +645,7 @@ export class DocumentView extends DocComponent(Docu ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document); const nativeWidth = this.props.Document.fitWidth ? this.props.PanelWidth() : this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%"; - const nativeHeight = this.props.Document.fitWidth ? this.props.PanelHeight() : this.Document.ignoreAspect || this.props.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%"; + const nativeHeight = this.props.Document.fitWidth ? this.props.PanelHeight() : 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.Document) : undefined; const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle"); const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); @@ -678,6 +679,9 @@ export class DocumentView extends DocComponent(Docu SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true} />
    ); + let animheight = animDims ? animDims[1] : nativeHeight; + let animwidth = animDims ? animDims[0] : nativeWidth; + return (
    (Docu outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px", border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined, background: backgroundColor, - width: nativeWidth, - height: nativeHeight, + width: animwidth, + height: animheight, transform: `scale(${this.props.Document.fitWidth ? 1 : this.props.ContentScaling()})`, opacity: this.Document.opacity }} diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 3f5f47e05..6393f21f7 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -15,7 +15,7 @@ width: 100%; height: 100%; background: lightslategray; - .pdfBox-title-cont { + .pdfBox-title-cont, .pdfBox-cont-interactive{ width: 150%; height: 100%; position: relative; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3ad0b4010..a3b375be7 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -183,11 +183,15 @@ export class PDFBox extends DocComponent(PdfDocumen let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf; if (!noPdf && (this.props.isSelected() || this.props.ScreenToLocalTransform().Scale < 2.5)) this._everActive = true; return (!this._everActive ? -
    - {` ${this.props.Document.title}`}
    : +
    +
    + {` ${this.props.Document.title}`} +
    +
    :
    { let hit = document.elementFromPoint(e.clientX, e.clientY); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index a68692732..aeffc81c4 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -144,14 +144,8 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; - public [WidthSym] = () => { - 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 animDims = this[SelfProxy].animateToDimensions ? Array.from(Cast(this[SelfProxy].animateToDimensions, listSpec("number"))!) : undefined; - return animDims ? animDims[1] : NumCast(this[SelfProxy].height); - } + public [WidthSym] = () => NumCast(this[SelfProxy].width); + public [HeightSym] = () => NumCast(this[SelfProxy].height); [ToScriptString]() { return "invalid"; -- cgit v1.2.3-70-g09d2 From 9427474b473d70974784a1517a1be902fb8d18ee Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 2 Oct 2019 18:26:55 -0400 Subject: many fixes to pdfs, linking, annotations, presentations. --- src/client/documents/Documents.ts | 24 ++-- src/client/util/DocumentManager.ts | 125 +++++++++++---------- src/client/util/RichTextSchema.tsx | 9 +- src/client/util/SharingManager.tsx | 2 +- .../views/collections/CollectionBaseView.tsx | 9 +- .../views/collections/CollectionDockingView.tsx | 10 +- src/client/views/collections/CollectionSubView.tsx | 1 + .../views/collections/CollectionTreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +- src/client/views/linking/LinkFollowBox.tsx | 11 +- src/client/views/nodes/DocumentView.tsx | 44 ++------ src/client/views/nodes/FormattedTextBox.tsx | 79 ++++++------- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 17 ++- src/client/views/nodes/PresBox.tsx | 24 ++-- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 17 ++- src/client/views/pdf/PDFMenu.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 72 +++++------- .../views/presentationview/PresElementBox.tsx | 32 +++--- src/new_fields/Doc.ts | 2 + 21 files changed, 224 insertions(+), 276 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4d9698532..2d323ea4b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -653,32 +653,30 @@ export namespace DocUtils { } }); } - export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) { - let sv = DocumentManager.Instance.getDocumentView(source); - if (sv && sv.props.ContainingCollectionDoc === target) return; - if (target === CurrentUserUtils.UserDocument) return undefined; + export function MakeLink(source: {doc:Doc,ctx?:Doc}, target: {doc:Doc,ctx?:Doc}, title: string = "", description: string = "", id?: string, anchored1?: boolean) { + let sv = DocumentManager.Instance.getDocumentView(source.doc); + if (sv && sv.props.ContainingCollectionDoc === target.doc) return; + if (target.doc === CurrentUserUtils.UserDocument) return undefined; let linkDocProto = new Doc(id, true); UndoManager.RunInBatch(() => { linkDocProto.type = DocumentType.LINK; - linkDocProto.targetContext = targetContext; - linkDocProto.sourceContext = sourceContext; - linkDocProto.title = title === "" ? source.title + " to " + target.title : title; + linkDocProto.targetContext = target.ctx; + linkDocProto.sourceContext = source.ctx; + linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title; linkDocProto.linkDescription = description; - linkDocProto.anchor1 = source; - linkDocProto.anchor1Page = source.curPage; + linkDocProto.anchor1 = source.doc; linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor1anchored = anchored1; - linkDocProto.anchor2 = target; - linkDocProto.anchor2Page = target.curPage; + linkDocProto.anchor2 = target.doc; linkDocProto.anchor2Groups = new List([]); LinkManager.Instance.addLink(linkDocProto); - Doc.GetProto(source).links = ComputedField.MakeFunction("links(this)"); - Doc.GetProto(target).links = ComputedField.MakeFunction("links(this)"); + Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)"); + Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)"); }, "make link"); return linkDocProto; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4e6968f08..4ebcdf83c 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,7 +1,7 @@ import { action, computed, observable } from 'mobx'; import { Doc, DocListCastAsync } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; -import { Cast, NumCast } from '../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionPDFView } from '../views/collections/CollectionPDFView'; import { CollectionVideoView } from '../views/collections/CollectionVideoView'; @@ -56,9 +56,9 @@ export class DocumentManager { return this.getDocumentViewsById(doc[Id]); } - public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null { + public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | undefined { - let toReturn: DocumentView | null = null; + let toReturn: DocumentView | undefined; let passes = preferredCollection ? [preferredCollection, undefined] : [undefined]; for (let pass of passes) { @@ -81,10 +81,14 @@ export class DocumentManager { return toReturn; } - public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null { + public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | undefined { return this.getDocumentViewById(toFind[Id], preferredCollection); } + public getFirstDocumentView(toFind: Doc): DocumentView | undefined { + const views = this.getDocumentViews(toFind); + return views.length ? views[0] : undefined; + } public getDocumentViews(toFind: Doc): DocumentView[] { let toReturn: DocumentView[] = []; @@ -127,72 +131,70 @@ export class DocumentManager { return pairs; } - - @undoBatch - public jumpToDocument = async (docDelegate: Doc, willZoom: boolean, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number, docContext?: Doc): Promise => { - let doc = Doc.GetProto(docDelegate); - const contextDoc = await Cast(doc.annotationOn, Doc); - if (contextDoc) { - contextDoc.scrollY = NumCast(doc.y) - NumCast(contextDoc.height) / 2 * 72 / 96; - } - - let docView: DocumentView | null; - // using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed - if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) { - Doc.BrushDoc(docView.props.Document); - if (linkPage !== undefined) docView.props.Document.curPage = linkPage; - UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus"); + public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, closeContextIfNotFound: boolean = false): Promise => { + const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + const annotatedDoc = await Cast(targetDoc.annotationOn, Doc); + if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? + annotatedDoc && docView.props.focus(annotatedDoc, false); + docView.props.focus(targetDoc, willZoom); } else { - 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; - - if (!forceDockFunc && docContext && (targetContextView = DocumentManager.Instance.getDocumentView(docContext))) { - docContext.panTransformType = "Ease"; - targetContextView.props.focus(docDelegate, willZoom); - } else { - (dockFunc || CollectionDockingView.AddRightSplit)(docContext, undefined); - setTimeout(() => { - 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.AddRightSplit)(actualDoc, undefined); - } + const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; + const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; + const targetDocContext = (annotatedDoc ? annotatedDoc : contextDoc); + + if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default + (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); } else { - let contextView: DocumentView | null; - Doc.BrushDoc(docDelegate); - let contextViews = DocumentManager.Instance.getDocumentViews(contextDoc); - if (!forceDockFunc && contextViews.length) { - contextView = contextViews[0]; - SelectionManager.SelectDoc(contextView, false); // force unrendered annotations to be created - contextDoc.panTransformType = "Ease"; + const targetDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext); + if (targetDocContextView) { // we have a context view and aren't forced to create a new one ... focus on the context + targetDocContext.panTransformType = "Ease"; + targetDocContext.scrollY = 0; + targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); + + // now find the target document within the context setTimeout(() => { - SelectionManager.DeselectDoc(contextView!); - docView = DocumentManager.Instance.getDocumentView(docDelegate); - docView && contextView!.props.focus(contextView!.props.Document, willZoom); - docView && UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus"); + const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc); + if (retryDocView) { + retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context + } else { + if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); + (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target + } }, 0); - } else { - (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined); + } else { // there's no context view so we need to create one first and try again + targetDocContext.scrollY = 0; + (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext, undefined); setTimeout(() => { - this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage); - }, 1000); + const foundTargetDocContextView = DocumentManager.Instance.getDocumentView(targetDocContext); + if (foundTargetDocContextView) { // we should always find a target context here.... + this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, true); // so call jump to doc again and if the doc isn't found, it will be created. + } + }, 2000); // the long timeout gives the context view a chance to create its children. think pdf's which need to be activated to render their annotations. } } } } + public async FollowLink(doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { + let linkDocs = LinkManager.Instance.getAllRelatedLinks(doc); + SelectionManager.DeselectAll(); + let firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored); + let secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored); + const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); + let first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; + let second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; + let linkFollowDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor2 as Doc] : undefined; + let linkFollowDocContexts = first.length ? [await (first[0].targetContext) as Doc, await (first[0].sourceContext) as Doc] : second.length ? [await (second[0].sourceContext) as Doc, await (second[0].targetContext) as Doc] : [undefined, undefined]; + if (linkFollowDocs && !linkFollowDocs.some(l => l instanceof Promise)) { + let maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); + let targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; + DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, + // 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: Doc) => focus(doc, maxLocation), targetContext); + } + } + @action zoomIntoScale = (docDelegate: Doc, scale: number) => { let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate)); @@ -202,8 +204,7 @@ export class DocumentManager { getScaleOfDocView = (docDelegate: Doc) => { let doc = Doc.GetProto(docDelegate); - let docView: DocumentView | null; - docView = DocumentManager.Instance.getDocumentView(doc); + const docView = DocumentManager.Instance.getDocumentView(doc); if (docView) { return docView.props.getScale(); } else { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 64821d8db..49bd93942 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -627,17 +627,16 @@ export class ImageResizeView { let jumpToDoc = await Cast(linkDoc.anchor2, Doc); if (jumpToDoc) { if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); return; } } if (targetContext) { - DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); + DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); } else if (jumpToDoc) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); } else { - DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); + DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); } } }); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index f427e40b1..1cde2aa8e 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -192,7 +192,7 @@ export default class SharingManager extends React.Component<{}> { onClick={() => { let context: Opt; if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) { - DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, undefined, undefined, context.props.Document); + DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document); } }} onPointerEnter={action(() => { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index e928887e2..38d050e5c 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -5,7 +5,7 @@ import { Doc, DocListCast } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { listSpec } from '../../../new_fields/Schema'; -import { BoolCast, Cast, NumCast, PromiseValue, StrCast } from '../../../new_fields/Types'; +import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from '../../../new_fields/Types'; import { DocumentManager } from '../../util/DocumentManager'; import { SelectionManager } from '../../util/SelectionManager'; import { ContextMenu } from '../ContextMenu'; @@ -130,8 +130,11 @@ export class CollectionBaseView extends React.Component { let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); 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)); + PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => { + if (Doc.AreProtosEqual(annotationOn, FieldValue(Cast(this.dataDoc.extendsDoc, Doc)))) { + Doc.GetProto(doc).annotationOn = undefined; + } + }); if (index !== -1) { value.splice(index, 1); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 98aff41d3..14e513157 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -574,8 +574,8 @@ export class DockedFrameRenderer extends React.Component { let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc; if (curPres) { let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" }); - Doc.GetProto(pinDoc).target = doc; - Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.target instanceof Doc) && this.target.title.toString()'); + Doc.GetProto(pinDoc).presentationTargetDoc = doc; + Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()'); const data = Cast(curPres.data, listSpec(Doc)); if (data) { data.push(pinDoc); @@ -614,8 +614,8 @@ export class DockedFrameRenderer extends React.Component { } } - panelWidth = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth())); - panelHeight = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight())); + panelWidth = () => this._document && this._document.maxWidth ? Math.min(Math.max(NumCast(this._document.width), NumCast(this._document.nativeWidth)), this._panelWidth) : this._panelWidth; + panelHeight = () => this._panelHeight; nativeWidth = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0; nativeHeight = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0; @@ -644,7 +644,7 @@ export class DockedFrameRenderer extends React.Component { } return Transform.Identity(); } - get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() / this.ScreenToLocalTransform().Scale) / 2 : 0; } + get previewPanelCenteringOffset() { return this.nativeWidth() && !this._document!.ignoreAspect ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; } addDocTab = (doc: Doc, dataDoc: Opt, location: string) => { SelectionManager.DeselectAll(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 155f2718b..28d1eb384 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -31,6 +31,7 @@ export interface CollectionViewProps extends FieldViewProps { moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; PanelWidth: () => number; PanelHeight: () => number; + VisibleHeight?: () => number; chromeCollapsed: boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ffd1f9170..882a0f144 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -217,7 +217,7 @@ class TreeView extends React.Component { if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.document; - DocUtils.MakeLink(sourceDoc, destDoc); + DocUtils.MakeLink({doc:sourceDoc}, {doc:destDoc}); e.stopPropagation(); } if (de.data instanceof DragManager.DocumentDragData) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index eb40e0bcb..4bdede48a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -403,8 +403,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { SelectionManager.DeselectAll(); if (this.props.Document.scrollHeight) { let annotOn = Cast(doc.annotationOn, Doc) as Doc; - let offset = annotOn && (NumCast(annotOn.height) / 2 * 72 / 96); - this.props.Document.scrollY = NumCast(doc.y) - offset; + if (!annotOn) { + this.props.focus(doc); + } else { + let contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height); + let offset = annotOn && (contextHgt / 2 * 96 / 72); + this.props.Document.scrollY = NumCast(doc.y) - offset; + } } else { const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; @@ -416,6 +421,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.setPan(newPanX, newPanY); this.Document.panTransformType = "Ease"; + Doc.BrushDoc(this.props.Document); this.props.focus(this.props.Document); willZoom && this.setScaleToZoom(doc, scale); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 72fff8e53..53b720a9e 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -171,7 +171,7 @@ export class LinkFollowBox extends React.Component { @undoBatch openFullScreen = () => { if (LinkFollowBox.destinationDoc) { - let view: DocumentView | null = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc); + let view = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc); view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); } } @@ -250,10 +250,10 @@ export class LinkFollowBox extends React.Component { let dockingFunc = (document: Doc) => { (LinkFollowBox._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); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, async document => dockingFunc(document), targetContext); } else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, document => dockingFunc(sourceContext!)); if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) { if (guid) { let views = DocumentManager.Instance.getDocumentViews(jumpToDoc); @@ -264,12 +264,11 @@ export class LinkFollowBox extends React.Component { } } else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, - NumCast((LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 ? LinkFollowBox.linkDoc.anchor2Page : LinkFollowBox.linkDoc.anchor1Page))); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom); } else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, dockingFunc); } this.highlightDoc(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a903f8fe2..3273abc1d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -206,7 +206,7 @@ export class DocumentView extends DocComponent(Docu 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 linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); let expandedDocs: Doc[] = []; expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; @@ -223,28 +223,10 @@ export class DocumentView extends DocComponent(Docu expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); } } - else if (linkedDocs.length) { - SelectionManager.DeselectAll(); - let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); - let second = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor2 as Doc, this.props.Document) && !d.anchor2anchored); - let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); - let secondUnshown = second.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); - if (firstUnshown.length) first = [firstUnshown[0]]; - if (secondUnshown.length) second = [secondUnshown[0]]; - let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor1 as Doc] : undefined; - - // @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 && !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); - } + else if (linkDocs.length) { + DocumentManager.Instance.FollowLink(this.props.Document, + (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)), + ctrlKey, altKey, this.props.ContainingCollectionDoc); } } @@ -352,15 +334,9 @@ export class DocumentView extends DocComponent(Docu 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 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; - annotations && annotations.forEach(anno => anno.target = targetDoc); - - DocUtils.MakeLink(sourceDoc, targetDoc, this.props.ContainingCollectionDoc, `Link from ${StrCast(sourceDoc.title)}`); + (de.data as any).linkedToDoc = true; + + DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`); } if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) { Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document); @@ -371,7 +347,7 @@ export class DocumentView extends DocComponent(Docu // 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, undefined, "in-text link being created")); // TODODO this is where in text links get passed + (de.data.linkDocument = DocUtils.MakeLink({ doc: de.data.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed } } @@ -407,7 +383,7 @@ export class DocumentView extends DocComponent(Docu 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); + DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link"); this.Document.isButton = true; }); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 449f56b16..749886d9a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -9,7 +9,7 @@ import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { RichTextField } from "../../../new_fields/RichTextField"; @@ -230,7 +230,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true); + else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id, true); }); }); }); @@ -342,7 +342,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] }))); - DocUtils.MakeLink(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]); + DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); this.tryUpdateHeight(); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { @@ -667,55 +667,42 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { let cbe = event as ClipboardEvent; - let docId: string; - let regionId: string; - if (!cbe.clipboardData) { - return false; - } - let linkId: string; - docId = cbe.clipboardData.getData("dash/pdfOrigin"); - regionId = cbe.clipboardData.getData("dash/pdfRegion"); - if (!docId || !regionId) { - return false; - } - - DocServer.GetRefField(docId).then(doc => { - DocServer.GetRefField(regionId).then(region => { - if (!(doc instanceof Doc) || !(region instanceof Doc)) { - return; - } - - let annotations = DocListCast(region.annotations); - annotations.forEach(anno => anno.target = this.props.Document); - let fieldExtDoc = Doc.fieldExtensionDoc(doc, "data"); - let targetAnnotations = DocListCast(fieldExtDoc.annotations); - if (targetAnnotations) { - targetAnnotations.push(region); - fieldExtDoc.annotations = new List(targetAnnotations); - } + const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin"); + const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion"); + if (pdfDocId && pdfRegionId) { + DocServer.GetRefField(pdfDocId).then(pdfDoc => { + DocServer.GetRefField(pdfRegionId).then(pdfRegion => { + if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { + setTimeout(async () => { + let targetAnnotations = await DocListCastAsync(Doc.fieldExtensionDoc(pdfDoc, "data").annotations);// bcz: NO... this assumes the pdf is using its 'data' field. need to have the PDF's view handle updating its own annotations + targetAnnotations && targetAnnotations.push(pdfRegion); + }); - let link = DocUtils.MakeLink(this.props.Document, region, doc); - if (link) { - cbe.clipboardData!.setData("dash/linkDoc", link[Id]); - linkId = link[Id]; - let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title))); - slice = new Slice(frag, slice.openStart, slice.openEnd); - var tr = view.state.tr.replaceSelection(slice); - view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); - } + let link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link"); + if (link) { + cbe.clipboardData!.setData("dash/linkDoc", link[Id]); + let linkId = link[Id]; + let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); + slice = new Slice(frag, slice.openStart, slice.openEnd); + var tr = view.state.tr.replaceSelection(slice); + view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); + } + } + }); }); - }); + return true; + } + return false; - return true; function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { const nodes: Node[] = []; frag.forEach(node => nodes.push(marker(node))); return Fragment.fromArray(nodes); } - function addLinkMark(node: Node, title: string) { + function addLinkMark(node: Node, title: string, linkId: string) { if (!node.isText) { - const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title)); + const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); return node.copy(content); } const marks = [...node.marks]; @@ -879,16 +866,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (jumpToDoc) { if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); return; } } if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), undefined, targetContext); + DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), targetContext); } else if (jumpToDoc) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); + DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } else { - DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); + DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } } }); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 9e9fe1324..fe4f75cad 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -299,7 +299,7 @@ export class ImageBox extends DocComponent(ImageD let rotation = NumCast(this.dataDoc.rotation) % 180; let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; let aspect = realsize.height / realsize.width; - if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) { + if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(1 - NumCast(layoutdoc.nativeHeight) / NumCast(layoutdoc.nativeWidth) / (realsize.height / realsize.width)) > 0.1)) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; layoutdoc.nativeHeight = realsize.height; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 617b580dd..88cd2cdc4 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -57,11 +57,8 @@ export class PDFBox extends DocComponent(PdfDocumen } loaded = (nw: number, nh: number, np: number) => { this.dataDoc.numPages = np; - if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) { - let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); - this.Document.nativeWidth = nw * 96 / 72; - this.Document.nativeHeight = this.Document.nativeHeight ? nw * 96 / 72 * oldaspect : nh * 96 / 72; - } + this.Document.nativeWidth = nw * 96 / 72; + this.Document.nativeHeight = nh * 96 / 72; !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw)); } @@ -177,12 +174,14 @@ export class PDFBox extends DocComponent(PdfDocumen ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } + _initialScale: number | undefined; render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive"); let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf; - if (!noPdf && (this.props.isSelected() || this.props.ScreenToLocalTransform().Scale < 2.5)) this._everActive = true; - return (!this._everActive ? + if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale; + if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true; + return (noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
    {` ${this.props.Document.title}`} @@ -203,11 +202,11 @@ export class PDFBox extends DocComponent(PdfDocumen setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView} renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling} - addDocTab={this.props.addDocTab} GoToPage={this.gotoPage} + addDocTab={this.props.addDocTab} GoToPage={this.gotoPage} focus={this.props.focus} pinToPres={this.props.pinToPres} addDocument={this.props.addDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} - fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this.props.ScreenToLocalTransform().Scale < 2.5 ? true : false} /> + fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} /> {this.settingsPanel()}
    ); } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index f44ca99b9..180ed9032 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -43,8 +43,8 @@ export class PresBox extends React.Component { value.forEach((item, i) => { if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) { let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" }); - Doc.GetProto(pinDoc).target = item; - Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.target instanceof Doc) && this.target.title.toString()'); + Doc.GetProto(pinDoc).presentationTargetDoc = item; + Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()'); value.splice(i, 1, pinDoc); } }); @@ -124,13 +124,13 @@ export class PresBox extends React.Component { this.childDocs.forEach((doc, ind) => { //the order of cases is aligned based on priority if (doc.hideTillShownButton && ind <= index) { - (doc.target as Doc).opacity = 1; + (doc.presentationTargetDoc as Doc).opacity = 1; } if (doc.hideAfterButton && ind < index) { - (doc.target as Doc).opacity = 0; + (doc.presentationTargetDoc as Doc).opacity = 0; } if (doc.fadeButton && ind < index) { - (doc.target as Doc).opacity = 0.5; + (doc.presentationTargetDoc as Doc).opacity = 0.5; } }); } @@ -145,13 +145,13 @@ export class PresBox extends React.Component { //the order of cases is aligned based on priority if (key.hideAfterButton && ind >= index) { - (key.target as Doc).opacity = 1; + (key.presentationTargetDoc as Doc).opacity = 1; } if (key.fadeButton && ind >= index) { - (key.target as Doc).opacity = 1; + (key.presentationTargetDoc as Doc).opacity = 1; } if (key.hideTillShownButton && ind > index) { - (key.target as Doc).opacity = 0; + (key.presentationTargetDoc as Doc).opacity = 0; } }); } @@ -162,7 +162,7 @@ export class PresBox extends React.Component { * te option open, navigates to that element. */ navigateToElement = async (curDoc: Doc, fromDocIndex: number) => { - let fromDoc = this.childDocs[fromDocIndex].target as Doc; + let fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc; let docToJump = curDoc; let willZoom = false; @@ -190,7 +190,7 @@ export class PresBox extends React.Component { //docToJump stayed same meaning, it was not in the group or was the last element in the group if (docToJump === curDoc) { //checking if curDoc has navigation open - let target = await curDoc.target as Doc; + let target = await curDoc.presentationTargetDoc as Doc; if (curDoc.navButton) { DocumentManager.Instance.jumpToDocument(target, false); } else if (curDoc.showButton) { @@ -210,8 +210,8 @@ export class PresBox extends React.Component { let curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc); //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(await docToJump.target as Doc, willZoom); - let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.target as Doc); + await DocumentManager.Instance.jumpToDocument(await docToJump.presentationTargetDoc as Doc, willZoom); + let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc); curDoc.viewScale = newScale; //saving the scale that user was on if (curScale !== 1) { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index b7d9a1eab..573197117 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -129,7 +129,7 @@ export class VideoBox extends DocComponent(VideoD width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" }); this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); - DocUtils.MakeLink(imageSummary, this.props.Document); + DocUtils.MakeLink({doc:imageSummary}, {doc: this.props.Document}, "snapshot from " + this.props.Document.title, "video frame snapshot"); } }); } diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 3ed85f6a5..f7f52b3ef 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,7 +1,7 @@ import React = require("react"); import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym, WidthSym, Opt, DocListCastAsync } 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"; @@ -14,6 +14,7 @@ interface IAnnotationProps { fieldExtensionDoc: Doc; addDocTab: (document: Doc, dataDoc: Opt, where: string) => boolean; pinToPres: (document: Doc) => void; + focus: (doc: Doc) => void; } export default class Annotation extends React.Component { @@ -60,6 +61,7 @@ class RegionAnnotation extends React.Component { } componentWillUnmount() { + this._brushDisposer && this._brushDisposer(); this._reactionDisposer && this._reactionDisposer(); } @@ -94,14 +96,11 @@ class RegionAnnotation extends React.Component { PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); } else if (e.button === 0) { - let targetDoc = await Cast(this.props.document.target, Doc); - if (targetDoc) { - let context = await Cast(targetDoc.targetContext, Doc); - if (context) { - DocumentManager.Instance.jumpToDocument(targetDoc, false, false, - ((doc) => this.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")), - undefined, undefined); - } + let annoGroup = await Cast(this.props.document.group, Doc); + if (annoGroup) { + DocumentManager.Instance.FollowLink(annoGroup, + (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "onRight" : "inTab"), + false, false, undefined); } } } diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 2202351ee..e62542014 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -31,7 +31,7 @@ export default class PDFMenu extends React.Component { @observable public Pinned: boolean = false; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; - public Highlight: (d: Doc | undefined, color: string) => void = emptyFunction; + public Highlight: (color: string) => void = emptyFunction; public Delete: () => void = emptyFunction; public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; public AddTag: (key: string, value: string) => boolean = returnFalse; @@ -156,11 +156,11 @@ export default class PDFMenu extends React.Component { @action highlightClicked = (e: React.MouseEvent) => { if (!this.Pinned) { - this.Highlight(undefined, "#f4f442"); + this.Highlight("#f4f442"); } else { this.Highlighting = !this.Highlighting; - this.Highlight(undefined, "#f4f442"); + this.Highlight("#f4f442"); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 33226eac4..20dfc4d8c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -10,7 +10,6 @@ import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import smoothScroll, { Utils, emptyFunction, returnOne } from "../../../Utils"; -import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompiledScript, CompileScript } from "../../util/Scripting"; @@ -44,6 +43,7 @@ interface IViewerProps { select: (isCtrlPressed: boolean) => void; startupLive: boolean; renderDepth: number; + focus: (doc: Doc) => void; isSelected: () => boolean; loaded: (nw: number, nh: number, np: number) => void; active: () => boolean; @@ -108,7 +108,8 @@ export class PDFViewer extends React.Component { // file address of the pdf this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`))); runInAction(() => this._showWaiting = this._showCover = true); - this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.props.isSelected() && SelectionManager.SelectedDocuments().length === 1 && this.setupPdfJsViewer(), { fireImmediately: this.props.startupLive }); + this.props.startupLive && this.setupPdfJsViewer(); + this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => (this.props.isSelected() && SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(), { fireImmediately: true }); this._reactionDisposer = reaction( () => this.props.Document.scrollY, (scrollY) => { @@ -136,19 +137,11 @@ export class PDFViewer extends React.Component { if (this.props.active() && e.clipboardData) { e.clipboardData.setData("text/plain", this._selectionText); e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); - e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument(undefined, "#0390fc")[Id]); + e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument("#0390fc")[Id]); e.preventDefault(); } } - paste = (e: ClipboardEvent) => { - if (e.clipboardData && e.clipboardData.getData("dash/pdfOrigin") === this.props.Document[Id]) { - let linkDocId = e.clipboardData.getData("dash/linkDoc"); - linkDocId && DocServer.GetRefField(linkDocId).then(async (link) => - (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), "#0390fc", false))); - } - } - setSelectionText = (text: string) => this._selectionText = text; @action @@ -194,13 +187,6 @@ export class PDFViewer extends React.Component { { fireImmediately: true } ); - document.removeEventListener("copy", this.copy); - document.addEventListener("copy", this.copy); - document.addEventListener("pagesinit", action(() => { - this.pdfViewer.currentScaleValue = this._zoomed = 1; - this.gotoPage(NumCast(this.props.Document.curPage, 1)); - })); - document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false)); this.createPdfViewer(); } @@ -212,6 +198,13 @@ export class PDFViewer extends React.Component { } return; } + document.removeEventListener("copy", this.copy); + document.addEventListener("copy", this.copy); + document.addEventListener("pagesinit", action(() => { + this.pdfViewer.currentScaleValue = this._zoomed = 1; + this.gotoPage(NumCast(this.props.Document.curPage, 1)); + })); + document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false)); var pdfLinkService = new PDFJSViewer.PDFLinkService(); let pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, @@ -229,19 +222,18 @@ export class PDFViewer extends React.Component { } @action - makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => { + makeAnnotationDocument = (color: string): Doc => { let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); let annoDocs: Doc[] = []; let minY = Number.MAX_VALUE; - if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1 && !createLink) { + if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1) { let anno = this._savedAnnotations.values()[0][0]; let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) }); if (anno.style.left) annoDoc.x = parseInt(anno.style.left); if (anno.style.top) annoDoc.y = parseInt(anno.style.top); if (anno.style.height) annoDoc.height = parseInt(anno.style.height); if (anno.style.width) annoDoc.width = parseInt(anno.style.width); - annoDoc.target = sourceDoc; annoDoc.group = mainAnnoDoc; annoDoc.color = color; annoDoc.type = AnnotationTypes.Region; @@ -258,7 +250,6 @@ export class PDFViewer extends React.Component { if (anno.style.top) annoDoc.y = parseInt(anno.style.top); if (anno.style.height) annoDoc.height = parseInt(anno.style.height); if (anno.style.width) annoDoc.width = parseInt(anno.style.width); - annoDoc.target = sourceDoc; annoDoc.group = mainAnnoDoc; annoDoc.color = color; annoDoc.type = AnnotationTypes.Region; @@ -272,9 +263,6 @@ export class PDFViewer extends React.Component { } mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); mainAnnoDocProto.annotationOn = this.props.Document; - if (sourceDoc && createLink) { - DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`); - } this._savedAnnotations.clear(); this.Index = -1; return mainAnnoDoc; @@ -529,7 +517,7 @@ export class PDFViewer extends React.Component { } if (PDFMenu.Instance.Highlighting) { - this.highlight(undefined, "goldenrod"); + this.highlight("goldenrod"); } else { PDFMenu.Instance.StartDrag = this.startDrag; @@ -540,9 +528,9 @@ export class PDFViewer extends React.Component { } @action - highlight = (targetDoc: Doc | undefined, color: string) => { + highlight = (color: string) => { // creates annotation documents for current highlights - let annotationDoc = this.makeAnnotationDocument(targetDoc, color, false); + let annotationDoc = this.makeAnnotationDocument(color); Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc); return annotationDoc; } @@ -555,20 +543,14 @@ export class PDFViewer extends React.Component { startDrag = (e: PointerEvent, ele: HTMLElement): void => { e.preventDefault(); e.stopPropagation(); - let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); - targetDoc.targetPage = this.getPageFromScroll(this._marqueeY); - let annotationDoc = this.highlight(undefined, "red"); - annotationDoc.linkedToDoc = false; + let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title }); + let annotationDoc = this.highlight("red"); let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { handlers: { - dragComplete: () => { - if (!annotationDoc.linkedToDoc) { - let annotations = DocListCast(annotationDoc.annotations); - annotations && annotations.forEach(anno => anno.target = targetDoc); - DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`); - } - } + dragComplete: () => !(dragData as any).linkedToDoc && + DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${StrCast(this.props.Document.title)}`, "link from PDF") + }, hideSource: false }); @@ -602,6 +584,7 @@ export class PDFViewer extends React.Component { @action.bound removeDocument(doc: Doc): boolean { + Doc.GetProto(doc).annotationOn = undefined; //TODO This won't create the field if it doesn't already exist let targetDataDoc = this.props.fieldExtensionDoc; let targetField = this.props.fieldExt; @@ -634,10 +617,10 @@ export class PDFViewer extends React.Component { getCoverImage = () => { if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) { - setTimeout(() => { + setTimeout((() => { this.props.Document.height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width; this.props.Document.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width; - }, 0); + }).bind(this), 0); } let nativeWidth = NumCast(this.props.Document.nativeWidth); let nativeHeight = NumCast(this.props.Document.nativeHeight); @@ -659,7 +642,7 @@ export class PDFViewer extends React.Component { @computed get annotationLayer() { return
    {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => - )} + )}
    ; } @computed get pdfViewerDiv() { @@ -686,7 +669,8 @@ export class PDFViewer extends React.Component { setPreviewCursor={this.setPreviewCursor} PanelHeight={() => NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))} PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : NumCast(this.props.Document.nativeWidth)} - focus={emptyFunction} + VisibleHeight={() => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96} + focus={this.props.focus} isSelected={this.props.isSelected} select={emptyFunction} active={this.active} @@ -694,7 +678,7 @@ export class PDFViewer extends React.Component { whenActiveChanged={this.whenActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} - addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }} + addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }} CollectionView={this.props.ContainingCollectionView} ScreenToLocalTransform={this.scrollXf} ruleProvider={undefined} diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index de3242d32..daf000dc7 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -63,13 +63,11 @@ export class PresElementBox extends React.Component { this.hideTillShownButton = !this.hideTillShownButton; if (!this.hideTillShownButton) { if (this.myIndex >= this.currentIndex) { - (this.props.Document.target as Doc).opacity = 1; + (this.props.Document.presentationTargetDoc as Doc).opacity = 1; } } else { - if (this.presentationDoc.presStatus) { - if (this.myIndex > this.currentIndex) { - (this.props.Document.target as Doc).opacity = 0; - } + if (this.presentationDoc.presStatus && this.myIndex > this.currentIndex) { + (this.props.Document.presentationTargetDoc as Doc).opacity = 0; } } } @@ -85,14 +83,12 @@ export class PresElementBox extends React.Component { this.hideAfterButton = !this.hideAfterButton; if (!this.hideAfterButton) { if (this.myIndex <= this.currentIndex) { - (this.props.Document.target as Doc).opacity = 1; + (this.props.Document.presentationTargetDoc as Doc).opacity = 1; } } else { if (this.fadeButton) this.fadeButton = false; - if (this.presentationDoc.presStatus) { - if (this.myIndex < this.currentIndex) { - (this.props.Document.target as Doc).opacity = 0; - } + if (this.presentationDoc.presStatus && this.myIndex < this.currentIndex) { + (this.props.Document.presentationTargetDoc as Doc).opacity = 0; } } } @@ -108,14 +104,12 @@ export class PresElementBox extends React.Component { this.fadeButton = !this.fadeButton; if (!this.fadeButton) { if (this.myIndex <= this.currentIndex) { - (this.props.Document.target as Doc).opacity = 1; + (this.props.Document.presentationTargetDoc as Doc).opacity = 1; } } else { this.hideAfterButton = false; - if (this.presentationDoc.presStatus) { - if (this.myIndex < this.currentIndex) { - (this.props.Document.target as Doc).opacity = 0.5; - } + if (this.presentationDoc.presStatus && (this.myIndex < this.currentIndex)) { + (this.props.Document.presentationTargetDoc as Doc).opacity = 0.5; } } } @@ -162,7 +156,7 @@ export class PresElementBox extends React.Component { * presentation element. */ renderEmbeddedInline = () => { - if (!this.embedOpen || !(this.props.Document.target instanceof Doc)) { + if (!this.embedOpen || !(this.props.Document.presentationTargetDoc instanceof Doc)) { return (null); } @@ -175,8 +169,8 @@ export class PresElementBox extends React.Component { width: propDocWidth === 0 ? "auto" : propDocWidth * scale(), }}> { let pbi = "presElementBox-interaction"; return (
    { p.focus(p.Document); e.stopPropagation(); }}> {treecontainer ? (null) : <> diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index aeffc81c4..58304cebb 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -665,10 +665,12 @@ export namespace Doc { export function BrushDoc(doc: Doc) { brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true); + return doc; } export function UnBrushDoc(doc: Doc) { brushManager.BrushedDoc.delete(doc); brushManager.BrushedDoc.delete(Doc.GetDataDoc(doc)); + return doc; } export class HighlightBrush { -- cgit v1.2.3-70-g09d2 From 456e9120857f20fb609ab13bb07cbd8a2d2f850b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 3 Oct 2019 00:25:44 -0400 Subject: cleaned up link following code. changed opening in place behavior to not open if view already exists. fixed formattedText box scrolling. fixed clicking on image in text box. more... --- src/client/util/DocumentManager.ts | 49 +++++---- src/client/util/RichTextSchema.tsx | 26 +---- src/client/util/SearchUtil.ts | 1 - src/client/views/MetadataEntryMenu.tsx | 2 +- src/client/views/linking/LinkFollowBox.tsx | 82 ++++----------- src/client/views/nodes/DocumentView.scss | 1 + src/client/views/nodes/DocumentView.tsx | 10 +- src/client/views/nodes/FormattedTextBox.tsx | 152 ++++++++++++---------------- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 2 +- src/new_fields/Doc.ts | 17 +++- 11 files changed, 148 insertions(+), 196 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4ebcdf83c..305a77b14 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -12,6 +12,7 @@ import { undoBatch, UndoManager } from './UndoManager'; import { Scripting } from './Scripting'; import { List } from '../../new_fields/List'; import { SelectionManager } from './SelectionManager'; +import { notDeepEqual } from 'assert'; export class DocumentManager { @@ -131,12 +132,12 @@ export class DocumentManager { return pairs; } - public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, closeContextIfNotFound: boolean = false): Promise => { + public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false): Promise => { const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc); const annotatedDoc = await Cast(targetDoc.annotationOn, Doc); if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? annotatedDoc && docView.props.focus(annotatedDoc, false); - docView.props.focus(targetDoc, willZoom); + docView.props.focus(docView.props.Document, willZoom); } else { const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; @@ -160,38 +161,52 @@ export class DocumentManager { if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target } + const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + finalDocView && (finalDocView.Document.scrollToLinkID = linkId); + finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); }, 0); } else { // there's no context view so we need to create one first and try again targetDocContext.scrollY = 0; (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext, undefined); setTimeout(() => { const foundTargetDocContextView = DocumentManager.Instance.getDocumentView(targetDocContext); - if (foundTargetDocContextView) { // we should always find a target context here.... - this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, true); // so call jump to doc again and if the doc isn't found, it will be created. + if (foundTargetDocContextView) { // we might be lucky and the context loads right away + this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true); // so call jump to doc again and if the doc isn't found, it will be created. + } else { + setTimeout(() => { // if not, wait a bit to see if the context can be loaded (e.g., a PDF). + const foundTargetDocContextView = DocumentManager.Instance.getDocumentView(targetDocContext); + if (foundTargetDocContextView) { // now we should always find a target context here.... + this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true); // so call jump to doc again and if the doc isn't found, it will be created. + } + }, 2000) } - }, 2000); // the long timeout gives the context view a chance to create its children. think pdf's which need to be activated to render their annotations. + }, 0); } } } + const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + finalDocView && (finalDocView.Document.scrollToLinkID = linkId); + finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); } - public async FollowLink(doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { - let linkDocs = LinkManager.Instance.getAllRelatedLinks(doc); + public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { + const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc); SelectionManager.DeselectAll(); - let firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored); - let secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored); + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored); const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); - let first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; - let second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; - let linkFollowDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor2 as Doc] : undefined; - let linkFollowDocContexts = first.length ? [await (first[0].targetContext) as Doc, await (first[0].sourceContext) as Doc] : second.length ? [await (second[0].sourceContext) as Doc, await (second[0].targetContext) as Doc] : [undefined, undefined]; - if (linkFollowDocs && !linkFollowDocs.some(l => l instanceof Promise)) { - let maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); - let targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; + const first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; + const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; + const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined; + const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined; + const linkFollowDocContexts = first.length ? [await first[0].targetContext as Doc, await first[0].sourceContext as Doc] : second.length ? [await second[0].sourceContext as Doc, await second[0].targetContext as Doc] : [undefined, undefined]; + if (linkFollowDocs && linkDoc) { + const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); + const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, // 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: Doc) => focus(doc, maxLocation), targetContext); + (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]); } } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 49bd93942..066266873 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -13,6 +13,7 @@ import { Cast, NumCast } from "../../new_fields/Types"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; import { times } from "async"; +import { LinkManager } from "./LinkManager"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -619,27 +620,10 @@ export class ImageResizeView { if (!view.isOverlay || e.ctrlKey) { e.preventDefault(); e.stopPropagation(); - DocServer.GetRefField(node.attrs.docid).then(async linkDoc => { - const location = node.attrs.location; - if (linkDoc instanceof Doc) { - let proto = Doc.GetProto(linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let jumpToDoc = await Cast(linkDoc.anchor2, Doc); - if (jumpToDoc) { - if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); - return; - } - } - if (targetContext) { - DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); - } else if (jumpToDoc) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); - } else { - DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); - } - } - }); + DocServer.GetRefField(node.attrs.docid).then(async linkDoc => + (linkDoc instanceof Doc) && + DocumentManager.Instance.FollowLink(linkDoc, (view.state.schema as any).Document, + document => addDocTab(document, undefined, node.attrs.location ? node.attrs.location : "inTab"), false)); } }; this._handle.onpointerdown = function (e: any) { diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index d8b9dbec6..e37f1f90d 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -3,7 +3,6 @@ import { DocServer } from '../DocServer'; import { Doc } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { Utils } from '../../Utils'; -import { ResultParameters } from '../northstar/model/idea/idea'; import { DocumentType } from '../documents/DocumentTypes'; export namespace SearchUtil { diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index f1b101b8e..41453f8b2 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -3,7 +3,7 @@ import "./MetadataEntryMenu.scss"; import { observer } from 'mobx-react'; import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx'; import { KeyValueBox } from './nodes/KeyValueBox'; -import { Doc, Field, DocListCast, DocListCastAsync } from '../../new_fields/Doc'; +import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc'; import * as Autosuggest from 'react-autosuggest'; import { undoBatch } from '../util/UndoManager'; import { emptyFunction } from '../../Utils'; diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 53b720a9e..2bff3ded4 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -152,21 +152,7 @@ export class LinkFollowBox extends React.Component { this.resetPan(); } - unhighlight = () => { - Doc.UnhighlightAll(); - document.removeEventListener("pointerdown", this.unhighlight); - } - - @action - highlightDoc = () => { - if (LinkFollowBox.destinationDoc) { - document.removeEventListener("pointerdown", this.unhighlight); - Doc.HighlightDoc(LinkFollowBox.destinationDoc); - window.setTimeout(() => { - document.addEventListener("pointerdown", this.unhighlight); - }, 10000); - } - } + highlightDoc = () => LinkFollowBox.destinationDoc && Doc.linkFollowHighlight(LinkFollowBox.destinationDoc); @undoBatch openFullScreen = () => { @@ -235,44 +221,11 @@ export class LinkFollowBox extends React.Component { @undoBatch jumpToLink = async (options: { shouldZoom: boolean }) => { - if (LinkFollowBox.destinationDoc && LinkFollowBox.linkDoc) { - let jumpToDoc: Doc = LinkFollowBox.destinationDoc; - let pdfDoc = FieldValue(Cast(LinkFollowBox.destinationDoc, Doc)); - if (pdfDoc) { - jumpToDoc = pdfDoc; - } - let proto = Doc.GetProto(LinkFollowBox.linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let sourceContext = await Cast(proto.sourceContext, Doc); - let guid = StrCast(LinkFollowBox.linkDoc[Id]); - const shouldZoom = options ? options.shouldZoom : false; - - let dockingFunc = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; - - if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, async document => dockingFunc(document), targetContext); - } - else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, document => dockingFunc(sourceContext!)); - if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) { - if (guid) { - let views = DocumentManager.Instance.getDocumentViews(jumpToDoc); - views.length && (views[0].props.Document.scrollToLinkID = guid); - } else { - jumpToDoc.linkHref = Utils.prepend("/doc/" + StrCast(LinkFollowBox.linkDoc[Id])); - } - } - } - else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom); - - } - else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, dockingFunc); - } + if (LinkFollowBox.sourceDoc && LinkFollowBox.linkDoc) { + let focus = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + //let focus = (doc: Doc, maxLocation: string) => this.props.focus(docthis.props.focus(LinkFollowBox.destinationDoc, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)); - this.highlightDoc(); - SelectionManager.DeselectAll(); + DocumentManager.Instance.FollowLink(LinkFollowBox.linkDoc, LinkFollowBox.sourceDoc, focus, options && options.shouldZoom, false, undefined); } } @@ -310,20 +263,23 @@ export class LinkFollowBox extends React.Component { openLinkInPlace = (options: { shouldZoom: boolean }) => { if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) { - let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); - let y = NumCast(LinkFollowBox.sourceDoc.y); - let x = NumCast(LinkFollowBox.sourceDoc.x); + if (this.sourceView && this.sourceView.props.addDocument) { + let destViews = DocumentManager.Instance.getDocumentViews(LinkFollowBox.destinationDoc); + if (!destViews.find(dv => dv.props.ContainingCollectionView === this.sourceView!.props.ContainingCollectionView)) { + let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); + let y = NumCast(LinkFollowBox.sourceDoc.y); + let x = NumCast(LinkFollowBox.sourceDoc.x); - let width = NumCast(LinkFollowBox.sourceDoc.width); - let height = NumCast(LinkFollowBox.sourceDoc.height); + let width = NumCast(LinkFollowBox.sourceDoc.width); + let height = NumCast(LinkFollowBox.sourceDoc.height); - alias.x = x + width + 30; - alias.y = y; - alias.width = width; - alias.height = height; + alias.x = x + width + 30; + alias.y = y; + alias.width = width; + alias.height = height; - if (this.sourceView && this.sourceView.props.addDocument) { - this.sourceView.props.addDocument(alias, false); + this.sourceView.props.addDocument(alias, false); + } } this.jumpToLink({ shouldZoom: options.shouldZoom }); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 4ea200e6d..b3e7898c1 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -5,6 +5,7 @@ top: 0; left:0; border-radius: inherit; + transition : outline .3s linear; // background: $light-color; //overflow: hidden; transform-origin: left top; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3273abc1d..67c3fe6e7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -224,7 +224,7 @@ export class DocumentView extends DocComponent(Docu } } else if (linkDocs.length) { - DocumentManager.Instance.FollowLink(this.props.Document, + DocumentManager.Instance.FollowLink(undefined, this.props.Document, (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)), ctrlKey, altKey, this.props.ContainingCollectionDoc); } @@ -658,6 +658,8 @@ export class DocumentView extends DocComponent(Docu let animheight = animDims ? animDims[1] : nativeHeight; let animwidth = animDims ? animDims[0] : nativeWidth; + const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; + const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"]; return (
    (Docu 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", - border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined, + outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", + border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, background: backgroundColor, width: animwidth, height: animheight, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 749886d9a..9347868b3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,47 +1,46 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; +import _ from "lodash"; import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; +import { inputRules } from 'prosemirror-inputrules'; import { keymap } from "prosemirror-keymap"; -import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state"; +import { Fragment, Mark, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model"; +import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; +import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; -import { List } from '../../../new_fields/List'; import { RichTextField } from "../../../new_fields/RichTextField"; -import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types"; +import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Utils, numberRange, timenow } from '../../../Utils'; +import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { numberRange, timenow, Utils } from '../../../Utils'; +import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { ImageResizeView, schema, SummarizedView, OrderedListView, FootnoteView } from "../../util/RichTextSchema"; +import { FootnoteView, ImageResizeView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; +import { DocumentButtonBar } from '../DocumentButtonBar'; +import { DocumentDecorations } from '../DocumentDecorations'; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; +import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; import React = require("react"); -import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; -import { DocumentDecorations } from '../DocumentDecorations'; -import { DictationManager } from '../../util/DictationManager'; -import { ReplaceStep } from 'prosemirror-transform'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { RichTextUtils } from '../../../new_fields/RichTextUtils'; -import _ from "lodash"; -import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; -import { inputRules } from 'prosemirror-inputrules'; -import { DocumentButtonBar } from '../DocumentButtonBar'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -142,51 +141,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } FormattedTextBox.Instance = this; - this._scrollToRegionReactionDisposer = reaction( - () => StrCast(this.props.Document.scrollToLinkID), - async (scrollToLinkID) => { - let findLinkFrag = (frag: Fragment, editor: EditorView) => { - const nodes: Node[] = []; - frag.forEach((node, index) => { - let examinedNode = findLinkNode(node, editor); - if (examinedNode && examinedNode.textContent) { - nodes.push(examinedNode); - start += index; - } - }); - return { frag: Fragment.fromArray(nodes), start: start }; - }; - let findLinkNode = (node: Node, editor: EditorView) => { - if (!node.isText) { - const content = findLinkFrag(node.content, editor); - return node.copy(content.frag); - } - const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); - return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; - }; - - let start = -1; - if (this._editorView && scrollToLinkID) { - let editor = this._editorView; - let ret = findLinkFrag(editor.state.doc.content, editor); - - if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) { - let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start - if (ret.frag.firstChild) { - selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected - } - editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); - const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); - setTimeout(() => this.unhighlightSearchTerms(), 2000); - } - this.props.Document.scrollToLinkID = undefined; - } - - }, - { fireImmediately: true } - ); } public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @@ -341,8 +295,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let url = de.data.urlField.url.href; let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] }))); - DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); + let link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); + link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: link[Id] }))); this.tryUpdateHeight(); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { @@ -424,6 +378,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe _keymap: any = undefined; @computed get config() { this._keymap = buildKeymap(schema); + (schema as any).Document = this.props.Document; return { schema, plugins: this.props.isOverlay ? [ @@ -563,6 +518,51 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, 0); }), { fireImmediately: true } ); + this._scrollToRegionReactionDisposer = reaction( + () => StrCast(this.props.Document.scrollToLinkID), + async (scrollToLinkID) => { + let findLinkFrag = (frag: Fragment, editor: EditorView) => { + const nodes: Node[] = []; + frag.forEach((node, index) => { + let examinedNode = findLinkNode(node, editor); + if (examinedNode && examinedNode.textContent) { + nodes.push(examinedNode); + start += index; + } + }); + return { frag: Fragment.fromArray(nodes), start: start }; + }; + let findLinkNode = (node: Node, editor: EditorView) => { + if (!node.isText) { + const content = findLinkFrag(node.content, editor); + return node.copy(content.frag); + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); + return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; + }; + + let start = -1; + if (this._editorView && scrollToLinkID) { + let editor = this._editorView; + let ret = findLinkFrag(editor.state.doc.content, editor); + + if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) { + let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start + if (ret.frag.firstChild) { + selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + } + editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); + const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); + setTimeout(() => this.unhighlightSearchTerms(), 2000); + } + this.props.Document.scrollToLinkID = undefined; + } + + }, + { fireImmediately: true } + ); setTimeout(() => this.tryUpdateHeight(), 0); } @@ -858,27 +858,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (href.indexOf(Utils.prepend("/doc/")) === 0) { this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; if (this._linkClicked) { - DocServer.GetRefField(this._linkClicked).then(async linkDoc => { - if (linkDoc instanceof Doc) { - let proto = Doc.GetProto(linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let jumpToDoc = await Cast(linkDoc.anchor2, Doc); - - if (jumpToDoc) { - if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); - return; - } - } - if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), targetContext); - } else if (jumpToDoc) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); - } else { - DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); - } - } - }); + DocServer.GetRefField(this._linkClicked).then(async linkDoc => + (linkDoc instanceof Doc) && + DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false)); e.stopPropagation(); e.preventDefault(); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index fe4f75cad..a198a0764 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -299,7 +299,7 @@ export class ImageBox extends DocComponent(ImageD let rotation = NumCast(this.dataDoc.rotation) % 180; let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; let aspect = realsize.height / realsize.width; - if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(1 - NumCast(layoutdoc.nativeHeight) / NumCast(layoutdoc.nativeWidth) / (realsize.height / realsize.width)) > 0.1)) { + if (layoutdoc.width && (Math.abs(1 - NumCast(layoutdoc.height) / NumCast(layoutdoc.width) / (realsize.height / realsize.width)) > 0.1)) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; layoutdoc.nativeHeight = realsize.height; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index f7f52b3ef..98e04d93e 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -98,7 +98,7 @@ class RegionAnnotation extends React.Component { else if (e.button === 0) { let annoGroup = await Cast(this.props.document.group, Doc); if (annoGroup) { - DocumentManager.Instance.FollowLink(annoGroup, + DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "onRight" : "inTab"), false, false, undefined); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 58304cebb..6acc6e1ca 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -637,7 +637,7 @@ export namespace Doc { export function isBrushedHighlightedDegree(doc: Doc) { if (Doc.IsHighlighted(doc)) { - return 3; + return 6; } else { return Doc.IsBrushedDegree(doc); @@ -673,6 +673,21 @@ export namespace Doc { return doc; } + export function linkFollowUnhighlight() { + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", linkFollowUnhighlight); + } + + let dt = 0; + export function linkFollowHighlight(destDoc: Doc) { + linkFollowUnhighlight(); + Doc.HighlightDoc(destDoc); + document.removeEventListener("pointerdown", linkFollowUnhighlight); + document.addEventListener("pointerdown", linkFollowUnhighlight); + let x = dt = Date.now(); + window.setTimeout(() => dt == x && linkFollowUnhighlight(), 5000); + } + export class HighlightBrush { @observable HighlightedDoc: Map = new Map(); } -- cgit v1.2.3-70-g09d2 From d611773fc805082a935cae49723d516ce66e1a14 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 4 Oct 2019 23:45:01 -0400 Subject: more pdf cleanup. fix to mix-multiply-mode for better highlighters/opacity. small text box fixes. --- src/client/util/DocumentManager.ts | 2 +- src/client/util/RichTextSchema.tsx | 26 ++- src/client/util/SelectionManager.ts | 2 - src/client/views/DocumentButtonBar.tsx | 1 - src/client/views/DocumentDecorations.tsx | 1 - src/client/views/MainView.tsx | 3 +- .../collectionFreeForm/MarqueeView.scss | 2 +- src/client/views/nodes/Annotation.tsx | 117 ------------ src/client/views/nodes/FormattedTextBox.tsx | 7 +- src/client/views/pdf/Annotation.scss | 3 +- src/client/views/pdf/Annotation.tsx | 4 +- src/client/views/pdf/PDFMenu.tsx | 10 +- src/client/views/pdf/PDFViewer.scss | 6 +- src/client/views/pdf/PDFViewer.tsx | 198 +++++++++------------ src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 2 +- 16 files changed, 117 insertions(+), 269 deletions(-) delete mode 100644 src/client/views/nodes/Annotation.tsx (limited to 'src/new_fields') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ffd311665..6fe97edbb 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -139,7 +139,7 @@ export class DocumentManager { const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); finalDocView && (finalDocView.Document.scrollToLinkID = linkId); finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); - } + }; const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc); const annotatedDoc = await Cast(targetDoc.annotationOn, Doc); if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 53eaf9ce2..528c0000b 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -654,17 +654,17 @@ export class ImageResizeView { this._img.onclick = function (e: any) { e.stopPropagation(); e.preventDefault(); - if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) - view.dispatch( - view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); - } + if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) { + view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); + } + }; this._img.onpointerdown = function (e: any) { if (e.ctrlKey) { e.preventDefault(); e.stopPropagation(); DocServer.GetRefField(node.attrs.docid).then(async linkDoc => (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, (view.state.schema as any).Document, + DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document, document => addDocTab(document, undefined, node.attrs.location ? node.attrs.location : "inTab"), false)); } }; @@ -730,7 +730,7 @@ export class DashDocView { this._dashSpan.style.width = node.attrs.width; this._dashSpan.style.height = node.attrs.height; this._dashSpan.style.position = "absolute"; - this._dashSpan.style.display = "inline-block" + this._dashSpan.style.display = "inline-block"; this._handle.style.position = "absolute"; this._handle.style.width = "20px"; this._handle.style.height = "20px"; @@ -771,16 +771,10 @@ export class DashDocView { this._dashSpan.onclick = function (e: any) { FormattedTextBox.firstTarget && FormattedTextBox.firstTarget(); e.stopPropagation(); - } - this._dashSpan.onkeydown = function (e: any) { - e.stopPropagation(); - } - this._dashSpan.onkeypress = function (e: any) { - e.stopPropagation(); - } - this._dashSpan.onkeyup = function (e: any) { - e.stopPropagation(); - } + }; + this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index a02a270ee..df1b46b33 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -27,7 +27,6 @@ export namespace SelectionManager { } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false)); manager.SelectedDocuments = [docView]; - FormattedTextBox.InputBoxOverlay = undefined; } } @action @@ -42,7 +41,6 @@ export namespace SelectionManager { DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments = []; - FormattedTextBox.InputBoxOverlay = undefined; } } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index e57745b86..9e2d41621 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -140,7 +140,6 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], 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: { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 26ffaf3a6..9acb77ce2 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -466,7 +466,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> break; } - if (!this._resizing) runInAction(() => FormattedTextBox.InputBoxOverlay = undefined); SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 545f99a41..3b0457dff 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,6 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv, faChevronRight, faEllipsisV } from '@fortawesome/free-solid-svg-icons'; -import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv, faChevronRight, faEllipsisV } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 7dc54ea79..04f6ec2ad 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -11,7 +11,7 @@ } .marqueeView:focus-within { - overflow: visible; + overflow: hidden; } .marquee { border-style: dashed; diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx deleted file mode 100644 index 3e4ed6bf1..000000000 --- a/src/client/views/nodes/Annotation.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import "./ImageBox.scss"; -import React = require("react"); -import { observer } from "mobx-react"; -import { observable, action } from 'mobx'; -import 'react-pdf/dist/Page/AnnotationLayer.css'; - -interface IProps { - Span: HTMLSpanElement; - X: number; - Y: number; - Highlights: any[]; - Annotations: any[]; - CurrAnno: any[]; - -} - -/** - * Annotation class is used to take notes on a particular highlight. You can also change highlighted span's color - * Improvements to be made: Removing the annotation when onRemove is called. (Removing this, not just the highlighted span). - * Also need to support multiline highlighting - * - * Written by: Andrew Kim - */ -@observer -export class Annotation extends React.Component { - - /** - * changes color of the span (highlighted section) - */ - onColorChange = (e: React.PointerEvent) => { - if (e.currentTarget.innerHTML === "r") { - this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"; - } else if (e.currentTarget.innerHTML === "b") { - this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"; - } else if (e.currentTarget.innerHTML === "y") { - this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"; - } else if (e.currentTarget.innerHTML === "g") { - this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"; - } - - } - - /** - * removes the highlighted span. Supposed to remove Annotation too, but I don't know how to unmount this - */ - @action - onRemove = (e: any) => { - let index: number = -1; - //finding the highlight in the highlight array - this.props.Highlights.forEach((e) => { - for (const span of e.spans) { - if (span === this.props.Span) { - index = this.props.Highlights.indexOf(e); - this.props.Highlights.splice(index, 1); - } - } - }); - - //removing from CurrAnno and Annotation array - this.props.Annotations.splice(index, 1); - this.props.CurrAnno.pop(); - - //removing span from div - if (this.props.Span.parentElement) { - let nodesArray = this.props.Span.parentElement.childNodes; - nodesArray.forEach((e) => { - if (e === this.props.Span) { - if (this.props.Span.parentElement) { - this.props.Highlights.forEach((item) => { - if (item === e) { - item.remove(); - } - }); - e.remove(); - } - } - }); - } - - - } - - render() { - return ( -
    -
    - -
    - - - - -
    - -
    -
    - -
    -
    - - ); - } -} \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 05904e1e7..2b6a86aed 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -291,7 +291,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (de.data.urlField && link) { let url: string = de.data.urlField.url.href; let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; - node = model.create({ src: url, docid: link[Id] }) + node = model.create({ src: url, docid: link[Id] }); } else { node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), @@ -798,7 +798,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._editorView!.focus(); } } - } + }; } onPointerUp = (e: React.PointerEvent): void => { @@ -807,9 +807,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); } } - + static InputBoxOverlay: any = null; @action onFocused = (e: React.FocusEvent): void => { + FormattedTextBox.InputBoxOverlay = this; document.removeEventListener("keypress", this.recordKeyHandler); document.addEventListener("keypress", this.recordKeyHandler); this.tryUpdateHeight(); diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss index 0c6df74f0..cc326eb93 100644 --- a/src/client/views/pdf/Annotation.scss +++ b/src/client/views/pdf/Annotation.scss @@ -2,6 +2,5 @@ pointer-events: all; user-select: none; position: absolute; - background-color: red; - opacity: 0.1; + background-color: rgba(146, 245, 95, 0.467); } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 134e757d1..4bb166ffe 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -122,9 +122,9 @@ class RegionAnnotation extends React.Component { left: this.props.x, width: this.props.width, height: this.props.height, - transition: "opacity 0.5s", opacity: this._brushed ? 0.5 : undefined, - backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.color) + backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor), + transition: "opacity 0.5s", }} />); } } \ No newline at end of file diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index e62542014..1e3320069 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -4,7 +4,7 @@ import { observable, action, } from "mobx"; import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { emptyFunction, returnFalse } from "../../../Utils"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../new_fields/Doc"; @observer export default class PDFMenu extends React.Component { @@ -31,7 +31,7 @@ export default class PDFMenu extends React.Component { @observable public Pinned: boolean = false; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; - public Highlight: (color: string) => void = emptyFunction; + public Highlight: (color: string) => Opt = (color: string) => undefined; public Delete: () => void = emptyFunction; public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; public AddTag: (key: string, value: string) => boolean = returnFalse; @@ -155,12 +155,8 @@ export default class PDFMenu extends React.Component { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Pinned) { - this.Highlight("#f4f442"); - } - else { + if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { this.Highlighting = !this.Highlighting; - this.Highlight("#f4f442"); } } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index a71e4f81e..c77cee792 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -16,6 +16,7 @@ mix-blend-mode: multiply; opacity: 0.9; } + .textLayer ::selection { background: yellow; } // should match the backgroundColor in createAnnotation() .textLayer .highlight { background-color: yellow; } @@ -51,10 +52,9 @@ pointer-events: none; mix-blend-mode: multiply; - .pdfPage-annotationBox { + .pdfViewer-annotationBox { position: absolute; - background-color: red; - opacity: 0.1; + background-color: rgba(245, 230, 95, 0.616); } } .pdfViewer-waiting { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 1b76ddbdc..4516e9904 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -75,8 +75,9 @@ export class PDFViewer extends React.Component { @observable private _showWaiting = true; @observable private _showCover = false; @observable private _zoomed = 1; + @observable private _scrollTop = 0; - public pdfViewer: any; + private _pdfViewer: any; private _retries = 0; // number of times tried to create the PDF viewer private _isChildActive = false; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); @@ -149,15 +150,16 @@ export class PDFViewer extends React.Component { copy = (e: ClipboardEvent) => { if (this.props.active() && e.clipboardData) { - e.clipboardData.setData("text/plain", this._selectionText); - e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); - e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument("#0390fc")[Id]); + let annoDoc = this.makeAnnotationDocument("#0390fc"); + if (annoDoc) { + e.clipboardData.setData("text/plain", this._selectionText); + e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); + e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]); + } e.preventDefault(); } } - setSelectionText = (text: string) => this._selectionText = text; - @action initialLoad = async () => { if (this._pageSizes.length === 0) { @@ -215,7 +217,7 @@ export class PDFViewer extends React.Component { document.removeEventListener("copy", this.copy); document.addEventListener("copy", this.copy); document.addEventListener("pagesinit", action(() => { - this.pdfViewer.currentScaleValue = this._zoomed = 1; + this._pdfViewer.currentScaleValue = this._zoomed = 1; this.gotoPage(NumCast(this.props.Document.curPage, 1)); })); document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false)); @@ -223,36 +225,35 @@ export class PDFViewer extends React.Component { let pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, }); - this.pdfViewer = new PDFJSViewer.PDFViewer({ + this._pdfViewer = new PDFJSViewer.PDFViewer({ container: this._mainCont.current, viewer: this._viewer.current, linkService: pdfLinkService, findController: pdfFindController, renderer: "canvas", }); - pdfLinkService.setViewer(this.pdfViewer); + pdfLinkService.setViewer(this._pdfViewer); pdfLinkService.setDocument(this.props.pdf, null); - this.pdfViewer.setDocument(this.props.pdf); + this._pdfViewer.setDocument(this.props.pdf); } @action - makeAnnotationDocument = (color: string): Doc => { + makeAnnotationDocument = (color: string): Opt => { + if (this._savedAnnotations.size() === 0) return undefined; let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); let annoDocs: Doc[] = []; let minY = Number.MAX_VALUE; - if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1) { + if ((this._savedAnnotations.values()[0][0] as any).marqueeing) { let anno = this._savedAnnotations.values()[0][0]; - let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) }); + let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + StrCast(this.props.Document.title) }); if (anno.style.left) annoDoc.x = parseInt(anno.style.left); if (anno.style.top) annoDoc.y = parseInt(anno.style.top); if (anno.style.height) annoDoc.height = parseInt(anno.style.height); if (anno.style.width) annoDoc.width = parseInt(anno.style.width); annoDoc.group = mainAnnoDoc; - annoDoc.color = color; - annoDoc.type = AnnotationTypes.Region; - annoDocs.push(annoDoc); annoDoc.isButton = true; + annoDocs.push(annoDoc); anno.remove(); mainAnnoDoc = annoDoc; mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); @@ -265,8 +266,7 @@ export class PDFViewer extends React.Component { if (anno.style.height) annoDoc.height = parseInt(anno.style.height); if (anno.style.width) annoDoc.width = parseInt(anno.style.width); annoDoc.group = mainAnnoDoc; - annoDoc.color = color; - annoDoc.type = AnnotationTypes.Region; + annoDoc.backgroundColor = color; annoDocs.push(annoDoc); anno.remove(); (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY)); @@ -307,7 +307,7 @@ export class PDFViewer extends React.Component { @action gotoPage = (p: number) => { - this.pdfViewer && this.pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); + this._pdfViewer && this._pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); } @action @@ -317,25 +317,11 @@ export class PDFViewer extends React.Component { Doc.linkFollowHighlight(scrollToAnnotation); } - sendAnnotations = (page: number) => { - return this._savedAnnotations.getValue(page); - } - - receiveAnnotations = (annotations: HTMLDivElement[], page: number) => { - if (page === -1) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, annotations)); - } - else { - this._savedAnnotations.setValue(page, annotations); - } - } - @observable scrollTop = 0; @action onScroll = (e: React.UIEvent) => { - this.scrollTop = this._mainCont.current!.scrollTop; - this.pdfViewer && (this.props.Document.curPage = this.pdfViewer.currentPageNumber); + this._scrollTop = this._mainCont.current!.scrollTop; + this._pdfViewer && (this.props.Document.curPage = this._pdfViewer.currentPageNumber); } // get the page index that the vertical offset passed in is on @@ -355,6 +341,8 @@ export class PDFViewer extends React.Component { div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString(); } this._annotationLayer.current.append(div); + div.style.backgroundColor = "yellow"; + div.style.opacity = "0.5"; let savedPage = this._savedAnnotations.getValue(page); if (savedPage) { savedPage.push(div); @@ -371,8 +359,8 @@ export class PDFViewer extends React.Component { if (!searchString) { fwd ? this.nextAnnotation() : this.prevAnnotation(); } - else if (this.pdfViewer._pageViewsReady) { - this.pdfViewer.findController.executeCommand('findagain', { + else if (this._pdfViewer._pageViewsReady) { + this._pdfViewer.findController.executeCommand('findagain', { caseSensitive: false, findPrevious: !fwd, highlightAll: true, @@ -382,7 +370,7 @@ export class PDFViewer extends React.Component { } else if (this._mainCont.current) { let executeFind = () => { - this.pdfViewer.findController.executeCommand('find', { + this._pdfViewer.findController.executeCommand('find', { caseSensitive: false, findPrevious: !fwd, highlightAll: true, @@ -406,30 +394,24 @@ export class PDFViewer extends React.Component { } this._marqueeing = false; if (!e.altKey && e.button === 0 && this.active()) { + // clear out old marquees and initialize menu for new selection PDFMenu.Instance.StartDrag = this.startDrag; PDFMenu.Instance.Highlight = this.highlight; PDFMenu.Instance.Snippet = this.createSnippet; PDFMenu.Instance.Status = "pdf"; PDFMenu.Instance.fadeOut(true); + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, [])); if (e.target && (e.target as any).parentElement.className === "textLayer") { - if (!e.ctrlKey) { - this.receiveAnnotations([], -1); - } + // start selecting text if mouse down on textLayer spans } - else { + else if (this._mainCont.current) { // set marquee x and y positions to the spatially transformed position - if (this._mainCont.current) { - let boundingRect = this._mainCont.current.getBoundingClientRect(); - this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width); - this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop; - } + let boundingRect = this._mainCont.current.getBoundingClientRect(); + this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width); + this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop; + this._marqueeHeight = this._marqueeWidth = 0; this._marqueeing = true; - let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox"); - if (marquees && marquees.length) { // make a copy of the marquee - let marquee = marquees[0] as HTMLDivElement; - marquee.style.opacity = "0.2"; - } - this.receiveAnnotations([], -1); } document.removeEventListener("pointermove", this.onSelectMove); document.addEventListener("pointermove", this.onSelectMove); @@ -464,13 +446,13 @@ export class PDFViewer extends React.Component { let clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { let rect = clientRects.item(i); - if (rect/* && rect.width !== this._mainCont.current.getBoundingClientRect().width && rect.height !== this._mainCont.current.getBoundingClientRect().height / this.props.pdf.numPages*/) { + if (rect) { let scaleY = this._mainCont.current.offsetHeight / boundingRect.height; let scaleX = this._mainCont.current.offsetWidth / boundingRect.width; if (rect.width !== this._mainCont.current.clientWidth && - (i == 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) { + (i === 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) { let annoBox = document.createElement("div"); - annoBox.className = "pdfPage-annotationBox"; + annoBox.className = "pdfViewer-annotationBox"; // transforms the positions from screen onto the pdf div annoBox.style.top = ((rect.top - boundingRect.top) * scaleY + this._mainCont.current.scrollTop).toString(); annoBox.style.left = ((rect.left - boundingRect.left) * scaleX).toString(); @@ -481,8 +463,7 @@ export class PDFViewer extends React.Component { } } } - let text = selRange.cloneContents().textContent; - text && this.setSelectionText(text); + this._selectionText = selRange.cloneContents().textContent || ""; // clear selection if (sel.empty) { // Chrome @@ -494,22 +475,22 @@ export class PDFViewer extends React.Component { @action onSelectEnd = (e: PointerEvent): void => { + this._savedAnnotations.clear(); if (this._marqueeing) { if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox"); - if (marquees && marquees.length) { // make a copy of the marquee + if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation. + let style = (marquees[0] as HTMLDivElement).style; let copy = document.createElement("div"); - let marquee = marquees[0] as HTMLDivElement; - let style = marquee.style; copy.style.left = style.left; copy.style.top = style.top; copy.style.width = style.width; copy.style.height = style.height; copy.style.border = style.border; copy.style.opacity = style.opacity; - copy.className = "pdfPage-annotationBox"; + (copy as any).marqueeing = true; + copy.className = "pdfViewer-annotationBox"; this.createAnnotation(copy, this.getPageFromScroll(this._marqueeY)); - marquee.style.opacity = "0"; } if (!e.ctrlKey) { @@ -518,8 +499,7 @@ export class PDFViewer extends React.Component { } PDFMenu.Instance.jumpTo(e.clientX, e.clientY); } - - this._marqueeHeight = this._marqueeWidth = 0; + this._marqueeing = false; } else { let sel = window.getSelection(); @@ -531,7 +511,7 @@ export class PDFViewer extends React.Component { } if (PDFMenu.Instance.Highlighting) { - this.highlight("goldenrod"); + this.highlight("rgba(245, 230, 95, 0.616)"); // when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up } else { PDFMenu.Instance.StartDrag = this.startDrag; @@ -545,7 +525,7 @@ export class PDFViewer extends React.Component { highlight = (color: string) => { // creates annotation documents for current highlights let annotationDoc = this.makeAnnotationDocument(color); - Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc); + annotationDoc && Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc); return annotationDoc; } @@ -558,16 +538,18 @@ export class PDFViewer extends React.Component { e.preventDefault(); e.stopPropagation(); let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title }); - let annotationDoc = this.highlight("red"); - let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); - DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { - handlers: { - dragComplete: () => !(dragData as any).linkedToDoc && - DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${StrCast(this.props.Document.title)}`, "link from PDF") - - }, - hideSource: false - }); + const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); + if (annotationDoc) { + let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); + DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { + handlers: { + dragComplete: () => !(dragData as any).linkedToDoc && + DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${StrCast(this.props.Document.title)}`, "link from PDF") + + }, + hideSource: false + }); + } } createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => { @@ -609,7 +591,7 @@ export class PDFViewer extends React.Component { return true; } scrollXf = () => { - return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.scrollTop) : this.props.ScreenToLocalTransform(); + return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform(); } setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => { this._setPreviewCursor = func; @@ -647,9 +629,9 @@ export class PDFViewer extends React.Component { onZoomWheel = (e: React.WheelEvent) => { e.stopPropagation(); if (e.ctrlKey) { - let curScale = Number(this.pdfViewer.currentScaleValue); - this.pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000)); - this._zoomed = Number(this.pdfViewer.currentScaleValue); + let curScale = Number(this._pdfViewer.currentScaleValue); + this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000)); + this._zoomed = Number(this._pdfViewer.currentScaleValue); } } @@ -657,29 +639,7 @@ export class PDFViewer extends React.Component { return
    {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => )} -
    ; - } - @computed get pdfViewerDiv() { - return
    ; - } - @computed get standinViews() { - return <> - {this._showCover ? this.getCoverImage() : (null)} - {this._showWaiting ? : (null)} - ; - } - marqueeWidth = () => this._marqueeWidth; - marqueeHeight = () => this._marqueeHeight; - marqueeX = () => this._marqueeX; - marqueeY = () => this._marqueeY; - marqueeing = () => this._marqueeing; - visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96; - render() { - return (
    - {this.pdfViewerDiv} - - {this.annotationLayer} -
    +
    NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))} @@ -702,7 +662,29 @@ export class PDFViewer extends React.Component { chromeCollapsed={true}>
    +
    ; + } + @computed get pdfViewerDiv() { + return
    ; + } + @computed get standinViews() { + return <> + {this._showCover ? this.getCoverImage() : (null)} + {this._showWaiting ? : (null)} + ; + } + marqueeWidth = () => this._marqueeWidth; + marqueeHeight = () => this._marqueeHeight; + marqueeX = () => this._marqueeX; + marqueeY = () => this._marqueeY; + marqueeing = () => this._marqueeing; + visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96; + render() { + return (
    + {this.pdfViewerDiv} + {this.annotationLayer} {this.standinViews} +
    ); } } @@ -722,11 +704,9 @@ class PdfViewerMarquee extends React.Component { style={{ left: `${this.props.x()}px`, top: `${this.props.y()}px`, width: `${this.props.width()}px`, height: `${this.props.height()}px`, - border: `${this.props.width() === 0 ? "" : "2px dashed black"}` + border: `${this.props.width() === 0 ? "" : "2px dashed black"}`, + opacity: 0.2 }}>
    ; } -} - - -export enum AnnotationTypes { Region } +} \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6acc6e1ca..7e37eba84 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -685,7 +685,7 @@ export namespace Doc { document.removeEventListener("pointerdown", linkFollowUnhighlight); document.addEventListener("pointerdown", linkFollowUnhighlight); let x = dt = Date.now(); - window.setTimeout(() => dt == x && linkFollowUnhighlight(), 5000); + window.setTimeout(() => dt === x && linkFollowUnhighlight(), 5000); } export class HighlightBrush { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index b2509a4f1..0fbfbf2f3 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -112,7 +112,7 @@ export class CurrentUserUtils { if (sidebar) { sidebar.backgroundColor = "lightgrey"; } - }) + }); if (doc.overlays === undefined) { const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" }); -- cgit v1.2.3-70-g09d2 From f9916faa215297d434aab2357b98d2c4b1fdcb92 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 5 Oct 2019 13:02:43 -0400 Subject: started cleaning up videos. restored link follwing to timecodes. changed to currentTimecode from curPage --- src/client/documents/Documents.ts | 11 +++-- src/client/util/DictationManager.ts | 1 - src/client/util/DocumentManager.ts | 7 ++- src/client/views/InkingCanvas.tsx | 6 +-- .../views/collections/CollectionBaseView.tsx | 4 +- .../views/collections/CollectionSchemaView.tsx | 2 +- .../views/collections/CollectionVideoView.tsx | 6 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 6 +-- src/client/views/nodes/VideoBox.tsx | 57 ++++++++++++---------- src/new_fields/InkField.ts | 2 +- 13 files changed, 58 insertions(+), 50 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 71b9038d4..98f56af01 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -74,6 +74,7 @@ export interface DocumentOptions { backgroundLayout?: string; chromeStatus?: string; curPage?: number; + currentTimecode?: number; documentText?: string; borderRounding?: string; schemaColumns?: List; @@ -120,7 +121,7 @@ export namespace Docs { }], [DocumentType.IMG, { layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, - options: { curPage: 0 } + options: {} }], [DocumentType.WEB, { layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, @@ -136,7 +137,7 @@ export namespace Docs { }], [DocumentType.VID, { layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType }, - options: { curPage: 0 }, + options: { currentTimecode: 0 }, }], [DocumentType.AUDIO, { layout: { view: AudioBox }, @@ -662,15 +663,17 @@ export namespace DocUtils { UndoManager.RunInBatch(() => { linkDocProto.type = DocumentType.LINK; - linkDocProto.targetContext = target.ctx; - linkDocProto.sourceContext = source.ctx; linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title; linkDocProto.linkDescription = description; linkDocProto.anchor1 = source.doc; + linkDocProto.anchor1Context = source.ctx; + linkDocProto.anchor1Timecode = source.doc.currentTimecode; linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2 = target.doc; + linkDocProto.anchor2Context = target.ctx; linkDocProto.anchor2Groups = new List([]); + linkDocProto.anchor2Timecode = target.doc.currentTimecode; LinkManager.Instance.addLink(linkDocProto); diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index cebb56bbe..3b2307073 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -336,7 +336,6 @@ export namespace DictationManager { let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" }); newBox.autoHeight = true; let proto = newBox.proto!; - proto.page = -1; let prompt = "Press alt + r to start dictating here..."; let head = 3; let anchor = head + prompt.length; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 6fe97edbb..83a69465f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, trace } from 'mobx'; import { Doc, DocListCastAsync } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { Cast, NumCast, StrCast } from '../../new_fields/Types'; @@ -196,10 +196,13 @@ export class DocumentManager { const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined; const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined; - const linkFollowDocContexts = first.length ? [await first[0].targetContext as Doc, await first[0].sourceContext as Doc] : second.length ? [await second[0].sourceContext as Doc, await second[0].targetContext as Doc] : [undefined, undefined]; + const linkFollowDocContexts = first.length ? [await first[0].anchor2Context as Doc, await first[0].anchor1Context as Doc] : second.length ? [await second[0].anchor1Context as Doc, await second[0].anchor2Context as Doc] : [undefined, undefined]; + const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2Timecode), NumCast(first[0].anchor1Timecode)] : second.length ? [NumCast(second[0].anchor1Timecode), NumCast(second[0].anchor2Timecode)] : [undefined, undefined]; if (linkFollowDocs && linkDoc) { const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; + const target = linkFollowDocs[reverse ? 1 : 0]; + target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]); DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]); } } diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 1cfa8d644..9ab320eab 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -87,7 +87,7 @@ export class InkingCanvas extends React.Component { color: InkingControl.Instance.selectedColor, width: InkingControl.Instance.selectedWidth, tool: InkingControl.Instance.selectedTool, - page: NumCast(this.props.Document.curPage, -1) + displayTimecode: NumCast(this.props.Document.currentTimecode, -1) }); this.inkData = data; } @@ -150,9 +150,9 @@ export class InkingCanvas extends React.Component { @computed get drawnPaths() { - let curPage = NumCast(this.props.Document.curPage, -1); + let curTimecode = NumCast(this.props.Document.currentTimecode, -1); let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { - if (strokeData.page === -1 || (Math.abs(Math.round(strokeData.page) - Math.round(curPage)) < 3)) { + if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) { paths.push( { @action.bound addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { - var curPage = NumCast(this.props.Document.curPage, -1); - Doc.GetProto(doc).page = curPage; + var curTime = NumCast(this.props.Document.currentTimecode, -1); + curTime !== -1 && (doc.displayTimecode = curTime); if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 52a41fc84..1ba35a52b 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -51,7 +51,7 @@ const columnTypes: Map = new Map([ ["title", ColumnType.String], ["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number], ["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], - ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number] + ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number] ]); @observer diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 5185d9d0e..3d898b7de 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -21,7 +21,7 @@ export class CollectionVideoView extends React.Component { } private get uIButtons() { let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); - let curTime = NumCast(this.props.Document.curPage); + let curTime = NumCast(this.props.Document.currentTimecode); return ([
    {"" + Math.round(curTime)} {" " + Math.round((curTime - Math.trunc(curTime)) * 100)} @@ -85,7 +85,7 @@ export class CollectionVideoView extends React.Component { onPointerMove = (e: PointerEvent) => { this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY); if (this._videoBox) { - this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.curPage, 0) + Math.sign(e.movementX) * 0.0333)); + this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333)); } e.stopImmediatePropagation(); } @@ -94,7 +94,7 @@ export class CollectionVideoView extends React.Component { document.removeEventListener("pointermove", this.onPointerMove, true); document.removeEventListener("pointerup", this.onPointerUp, true); InkingControl.Instance.switchTool(InkTool.None); - this._isclick < 10 && (this.props.Document.curPage = 0); + this._isclick < 10 && (this.props.Document.currentTimecode = 0); } setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7b29f2c2a..bfd3e6481 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -105,7 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); } - public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); } + public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } public getActiveDocuments = () => { return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index bf154d37d..eaf65b88c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -285,7 +285,7 @@ export class MarqueeView extends React.Component this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; + d.displayTimecode = undefined; return d; }); } diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 2bff3ded4..b18aa5d63 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -128,7 +128,7 @@ export class LinkFollowBox extends React.Component { runInAction(async () => { this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest })); this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); - let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc; + let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc; runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest })); }); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 88cd2cdc4..1f3887608 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -65,9 +65,9 @@ export class PDFBox extends DocComponent(PdfDocumen public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); } public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); } public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); } - public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); } + public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); } public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); }; - public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); } + public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); } @undoBatch @action @@ -128,7 +128,7 @@ export class PDFBox extends DocComponent(PdfDocumen
    e.stopPropagation()}>
    - this.gotoPage(Number(e.currentTarget.value))} style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }} onClick={action(() => this._pageControls = !this._pageControls)} /> diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 573197117..e83aa8bea 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -3,7 +3,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction, import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { InkTool } from "../../../new_fields/InkField"; -import { makeInterface } from "../../../new_fields/Schema"; +import { makeInterface, createSchema } from "../../../new_fields/Schema"; import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; @@ -16,16 +16,19 @@ import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; 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 { Doc } from "../../../new_fields/Doc"; import { ScriptField } from "../../../new_fields/ScriptField"; +import { positionSchema } from "./CollectionFreeFormDocumentView"; var path = require('path'); -type VideoDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>; -const VideoDocument = makeInterface(documentSchema, pageSchema); +export const timeSchema = createSchema({ + currentTimecode: "number", +}); +type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>; +const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); library.add(faVideo); @@ -97,11 +100,11 @@ export class VideoBox extends DocComponent(VideoD } @action public Snapshot() { - let width = NumCast(this.props.Document.width); - let height = NumCast(this.props.Document.height); + let width = this.Document.width || 0; + let height = this.Document.height || 0; var canvas = document.createElement('canvas'); canvas.width = 640; - canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth); + canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { ctx.rect(0, 0, canvas.width, canvas.height); @@ -112,24 +115,25 @@ export class VideoBox extends DocComponent(VideoD if (!this._videoRef) { // can't find a way to take snapshots of videos let b = Docs.Create.ButtonDocument({ - x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), - width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString() + x: (this.Document.x || 0) + width, y: (this.Document.y || 0), + width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString() }); - b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`); + b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`); } else { //convert to desired file format var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, - let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, ""); - VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { + let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString())); + VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => { if (returnedFilename) { let url = this.choosePath(Utils.prepend(returnedFilename)); let imageSummary = Docs.Create.ImageDocument(url, { - x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), - width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" + x: (this.Document.x || 0) + width, y: (this.Document.y || 0), + width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-" }); + imageSummary.isButton = true; this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); - DocUtils.MakeLink({doc:imageSummary}, {doc: this.props.Document}, "snapshot from " + this.props.Document.title, "video frame snapshot"); + DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot"); } }); } @@ -137,8 +141,8 @@ export class VideoBox extends DocComponent(VideoD @action updateTimecode = () => { - this.player && (this.props.Document.curPage = this.player.currentTime); - this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime()); + this.player && (this.Document.currentTimecode = this.player.currentTime); + this._youtubePlayer && (this.Document.currentTimecode = this._youtubePlayer.getCurrentTime()); } componentDidMount() { @@ -146,12 +150,12 @@ export class VideoBox extends DocComponent(VideoD if (this.youtubeVideoId) { let youtubeaspect = 400 / 315; - var nativeWidth = FieldValue(this.Document.nativeWidth, 0); - var nativeHeight = FieldValue(this.Document.nativeHeight, 0); + var nativeWidth = (this.Document.nativeWidth || 0); + var nativeHeight = (this.Document.nativeHeight || 0); if (!nativeWidth || !nativeHeight) { if (!this.Document.nativeWidth) this.Document.nativeWidth = 600; this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect; - this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect; + this.Document.height = (this.Document.width || 0) / youtubeaspect; } } } @@ -168,10 +172,9 @@ export class VideoBox extends DocComponent(VideoD if (vref) { this._videoRef!.ontimeupdate = this.updateTimecode; vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); - if (this._reactionDisposer) this._reactionDisposer(); - this._reactionDisposer = reaction(() => this.props.Document.curPage, () => - !this.Playing && (vref.currentTime = this.Document.curPage || 0) - , { fireImmediately: true }); + this._reactionDisposer && this._reactionDisposer(); + this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0, + time => !this.Playing && (vref.currentTime = time), { fireImmediately: true }); } } @@ -242,7 +245,7 @@ export class VideoBox extends DocComponent(VideoD let onYoutubePlayerReady = (event: any) => { this._reactionDisposer && this._reactionDisposer(); this._youtubeReactionDisposer && this._youtubeReactionDisposer(); - this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 0)); + this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0)); this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => { let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting; iframe.style.pointerEvents = interactive ? "all" : "none"; @@ -263,9 +266,9 @@ export class VideoBox extends DocComponent(VideoD this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); - let start = untracked(() => Math.round(NumCast(this.props.Document.curPage))); + let start = untracked(() => Math.round(this.Document.currentTimecode || 0)); return ; } diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts index 8f64c1c2e..e381d0218 100644 --- a/src/new_fields/InkField.ts +++ b/src/new_fields/InkField.ts @@ -16,7 +16,7 @@ export interface StrokeData { color: string; width: string; tool: InkTool; - page: number; + displayTimecode: number; } export type InkData = Map; -- cgit v1.2.3-70-g09d2 From 3b471ca0673945c33be74970db223ea411cfa0ae Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 8 Oct 2019 22:49:16 -0400 Subject: sharing fixes and cleanup --- src/client/util/SharingManager.scss | 4 ++ src/client/util/SharingManager.tsx | 79 ++++++++++++++++++++----------------- src/new_fields/Doc.ts | 4 ++ 3 files changed, 50 insertions(+), 37 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index 9a4c5db30..dec9f751a 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -2,6 +2,10 @@ display: flex; flex-direction: column; + .focus-span { + text-decoration: underline; + } + p { font-size: 20px; text-align: left; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index b5e29d050..ad03245b0 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -2,7 +2,7 @@ import { observable, runInAction, action, autorun } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; -import { Doc, Opt, DocListCastAsync } from "../../new_fields/Doc"; +import { Doc, Opt, DocListCastAsync, DocCastAsync } from "../../new_fields/Doc"; import { DocServer } from "../DocServer"; import { Cast, StrCast } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; @@ -70,8 +70,9 @@ export default class SharingManager extends React.Component<{}> { return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; } - public open = (target: DocumentView) => { + public open = action((target: DocumentView) => { SelectionManager.DeselectAll(); + this.users = []; this.populateUsers().then(action(() => { this.targetDocView = target; this.targetDoc = target.props.Document; @@ -81,7 +82,7 @@ export default class SharingManager extends React.Component<{}> { this.sharingDoc = new Doc; } })); - } + }); public close = action(() => { this.isOpen = false; @@ -125,38 +126,26 @@ export default class SharingManager extends React.Component<{}> { return Promise.all(evaluating); } - setInternalSharing = async (validated: ValidatedUser, state: string) => { - if (!this.sharingDoc) { - return console.log("SHARING ABORTED!"); - } - const { user, notificationDoc } = validated; - this.sharingDoc[user.userDocumentId] = state; - let target = this.targetDoc; - if (!target) { - return console.log("SharingManager trying to share an undefined document!!"); - } - const data = await DocListCastAsync(notificationDoc.data); - if (!data) { - console.log("UNABLE TO ACCESS NOTIFICATION DATA"); - return; - } - console.log(`Attempting to set permissions to ${state} for the document ${target[Id]}`); - if (state !== SharingPermissions.None) { - const sharedDoc = Doc.MakeAlias(target); - if (data) { - data.push(sharedDoc); - } else { - notificationDoc.data = new List([sharedDoc]); + setInternalSharing = async (recipient: ValidatedUser, state: string) => { + const { user, notificationDoc } = recipient; + const target = this.targetDoc!; + const manager = this.sharingDoc!; + const key = user.userDocumentId; + const storage = "data"; + if (state === SharingPermissions.None) { + const metadata = (await DocCastAsync(manager[key])); + if (metadata) { + let sharedAlias = (await DocCastAsync(metadata.sharedAlias))!; + Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias); + manager[key] = undefined; } } else { - let dataDocs = (await Promise.all(data.map(doc => doc))).map(doc => Doc.GetProto(doc)); - if (dataDocs.includes(target)) { - console.log("Searching in ", dataDocs, "for", target); - dataDocs.splice(dataDocs.indexOf(target), 1); - console.log("SUCCESSFULLY UNSHARED DOC"); - } else { - console.log("DIDN'T THINK WE HAD IT, SO NOT SUCCESSFULLY UNSHARED"); - } + const sharedAlias = Doc.MakeAlias(target); + Doc.AddDocToList(notificationDoc, storage, sharedAlias); + const metadata = new Doc; + metadata.permissions = state; + metadata.sharedAlias = sharedAlias; + manager[key] = metadata; } } @@ -197,6 +186,7 @@ export default class SharingManager extends React.Component<{}> { let title = this.targetDoc ? StrCast(this.targetDoc.title) : ""; return ( { let context: Opt; @@ -222,6 +212,18 @@ export default class SharingManager extends React.Component<{}> { ); } + private computePermissions = (userKey: string) => { + const sharingDoc = this.sharingDoc; + if (!sharingDoc) { + return SharingPermissions.None; + } + const metadata = sharingDoc[userKey] as Doc; + if (!metadata) { + return SharingPermissions.None; + } + return StrCast(metadata.permissions, SharingPermissions.None)!; + } + private get sharingInterface() { const existOtherUsers = this.users.length > 0; return ( @@ -260,17 +262,20 @@ export default class SharingManager extends React.Component<{}> {
    {!existOtherUsers ? "There are no other users in your database." : this.users.map(({ user, notificationDoc }) => { + const userKey = user.userDocumentId; + const permissions = this.computePermissions(userKey); + const color = ColorMapping.get(permissions); return (