From ae62e415d18b83fb1ef6b4a8f438b1d7e8d3157a Mon Sep 17 00:00:00 2001 From: HJF Bulterman Date: Fri, 9 Aug 2019 11:31:57 -0400 Subject: initial commit - individual presentation document view showing but with errors --- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FieldView.tsx | 5 + src/client/views/nodes/PresBox.tsx | 880 ++++++++++++++++++++++++ 4 files changed, 888 insertions(+), 2 deletions(-) create mode 100644 src/client/views/nodes/PresBox.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 91d4fb524..f74336cdd 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -12,6 +12,7 @@ import "./DocumentView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; import { ButtonBox } from "./ButtonBox"; +import { PresBox } from "./PresBox"; import { IconBox } from "./IconBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; @@ -98,7 +99,7 @@ export class DocumentContentsView extends React.Component 7) return (null); if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null); return (Docu subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); - cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); + cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //change this to cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); let makes: ContextMenuProps[] = []; makes.push({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ffaee8042..b1030d19d 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -18,6 +18,7 @@ import { ImageBox } from "./ImageBox"; import { PDFBox } from "./PDFBox"; import { VideoBox } from "./VideoBox"; import { Id } from "../../../new_fields/FieldSymbols"; +import { PresBox } from "./PresBox"; // // these properties get assigned through the render() method of the DocumentView when it creates this node. @@ -54,6 +55,7 @@ export interface FieldViewProps { export class FieldView extends React.Component { public static LayoutString(fieldType: { name: string }, fieldStr: string = "data", fieldExt: string = "") { return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} fieldExt={"${fieldExt}"} />`; + //"" } @computed @@ -75,6 +77,9 @@ export class FieldView extends React.Component { else if (field instanceof ImageField) { return ; } + // else if (field instaceof PresBox) { + // return ; + // } else if (field instanceof IconField) { return ; } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx new file mode 100644 index 000000000..2feb32693 --- /dev/null +++ b/src/client/views/nodes/PresBox.tsx @@ -0,0 +1,880 @@ +import React = require("react"); +import { FieldViewProps, FieldView } from './FieldView'; +import { observer } from "mobx-react"; +import { observable, action, runInAction, reaction, autorun, computed } from "mobx"; +import "../presentationview/PresentationView.scss"; +import { DocumentManager } from "../../util/DocumentManager"; +import { Utils } from "../../../Utils"; +import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { List } from "../../../new_fields/List"; +import PresentationElement, { buttonIndex } from "../presentationview/PresentationElement"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit } from '@fortawesome/free-solid-svg-icons'; +import { Docs } from "../../documents/Documents"; +import { undoBatch, UndoManager } from "../../util/UndoManager"; +import PresentationViewList from "../presentationview/PresentationList"; + +library.add(faArrowLeft); +library.add(faArrowRight); +library.add(faPlay); +library.add(faStop); +library.add(faPlus); +library.add(faTimes); +library.add(faMinus); +library.add(faEdit); + + +export interface PresViewProps { + Documents: List; +} + +const expandedWidth = 400; + +@observer +export class PresBox extends React.Component { //FieldViewProps? + + @computed + private get presentationDocs() { + let source = Doc.GetProto(this.props.Document); + return DocListCast(source.data); + } + + public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } + + //public static Instance: PresentationView; + public static Instance: PresBox; + + //Mapping from presentation ids to a list of doc that represent a group + @observable groupMappings: Map = new Map(); + //mapping from docs to their rendered component + @observable presElementsMappings: Map = new Map(); + //variable that holds all the docs in the presentation + @observable childrenDocs: Doc[] = []; + //variable to hold if presentation is started + @observable presStatus: boolean = false; + //back-up so that presentation stays the way it's when refreshed + @observable presGroupBackUp: Doc = new Doc(); + @observable presButtonBackUp: Doc = new Doc(); + + //Keeping track of the doc for the current presentation + @observable curPresentation: Doc = new Doc(); + //Mapping of guids to presentations. + @observable presentationsMapping: Map = new Map(); + //Mapping of presentations to guid, so that select option values can be given. + @observable presentationsKeyMapping: Map = new Map(); + //Variable to keep track of guid of the current presentation + @observable currentSelectedPresValue: string | undefined; + //A flag to keep track if title input is open, which is used in rendering. + @observable PresTitleInputOpen: boolean = false; + //Variable that holds reference to title input, so that new presentations get titles assigned. + @observable titleInputElement: HTMLInputElement | undefined; + @observable PresTitleChangeOpen: boolean = false; + + @observable opacity = 1; + @observable persistOpacity = true; + @observable labelOpacity = 0; + + //initilize class variables + constructor(props: FieldViewProps) { //FieldViewProps? + super(props); + //PresentationView.Instance = this; + PresBox.Instance = this; + } + + @action + toggle = (forcedValue: boolean | undefined) => { + if (forcedValue !== undefined) { + this.curPresentation.width = forcedValue ? expandedWidth : 0; + } else { + this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth; + } + } + + //The first lifecycle function that gets called to set up the current presentation. + async componentWillMount() { + this.presentationDocs.forEach(async (doc, index: number) => { + + //For each presentation received from mainContainer, a mapping is created. + let curDoc: Doc = await doc; + let newGuid = Utils.GenerateGuid(); + this.presentationsKeyMapping.set(curDoc, newGuid); + this.presentationsMapping.set(newGuid, curDoc); + + //The Presentation at first index gets set as default start presentation + if (index === 0) { + runInAction(() => this.currentSelectedPresValue = newGuid); + runInAction(() => this.curPresentation = curDoc); + } + }); + } + + //Second lifecycle function that gets called when component mounts. It makes sure to + //get the back-up information from previous session for the current presentation. + async componentDidMount() { + let docAtZero = await this.presentationDocs[0]; + runInAction(() => this.curPresentation = docAtZero); + + this.setPresentationBackUps(); + + } + + + /** + * The function that retrieves the backUps for the current Presentation if present, + * otherwise initializes. + */ + setPresentationBackUps = async () => { + //getting both backUp documents + + let castedGroupBackUp = Cast(this.curPresentation.presGroupBackUp, Doc); + let castedButtonBackUp = Cast(this.curPresentation.presButtonBackUp, Doc); + //if instantiated before + if (castedGroupBackUp instanceof Promise) { + castedGroupBackUp.then(doc => { + let toAssign = doc ? doc : new Doc(); + this.curPresentation.presGroupBackUp = toAssign; + runInAction(() => this.presGroupBackUp = toAssign); + if (doc) { + if (toAssign[Id] === doc[Id]) { + this.retrieveGroupMappings(); + } + } + }); + + //if never instantiated a store doc yet + } else if (castedGroupBackUp instanceof Doc) { + let castedDoc: Doc = await castedGroupBackUp; + runInAction(() => this.presGroupBackUp = castedDoc); + this.retrieveGroupMappings(); + } else { + runInAction(() => { + let toAssign = new Doc(); + this.presGroupBackUp = toAssign; + this.curPresentation.presGroupBackUp = toAssign; + + }); + + } + //if instantiated before + if (castedButtonBackUp instanceof Promise) { + castedButtonBackUp.then(doc => { + let toAssign = doc ? doc : new Doc(); + this.curPresentation.presButtonBackUp = toAssign; + runInAction(() => this.presButtonBackUp = toAssign); + }); + + //if never instantiated a store doc yet + } else if (castedButtonBackUp instanceof Doc) { + let castedDoc: Doc = await castedButtonBackUp; + runInAction(() => this.presButtonBackUp = castedDoc); + + } else { + runInAction(() => { + let toAssign = new Doc(); + this.presButtonBackUp = toAssign; + this.curPresentation.presButtonBackUp = toAssign; + }); + + } + + + //storing the presentation status,ie. whether it was stopped or playing + let presStatusBackUp = BoolCast(this.curPresentation.presStatus); + runInAction(() => this.presStatus = presStatusBackUp); + } + + /** + * This is the function that is called to retrieve the groups that have been stored and + * push them to the groupMappings. + */ + retrieveGroupMappings = async () => { + let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); + if (castedGroupDocs !== undefined) { + castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { + let castedGrouping = await DocListCastAsync(groupDoc.grouping); + let castedKey = StrCast(groupDoc.presentIdStore, null); + if (castedGrouping) { + castedGrouping.forEach((doc: Doc) => { + doc.presentId = castedKey; + }); + } + if (castedGrouping !== undefined && castedKey !== undefined) { + this.groupMappings.set(castedKey, castedGrouping); + } + }); + } + } + + //observable means render is re-called every time variable is changed + @observable + collapsed: boolean = false; + closePresentation = action(() => this.curPresentation.width = 0); + next = async () => { + const current = NumCast(this.curPresentation.selectedDoc); + //asking to get document at current index + let docAtCurrentNext = await this.getDocAtIndex(current + 1); + if (docAtCurrentNext === undefined) { + return; + } + //asking for it's presentation id + let curNextPresId = StrCast(docAtCurrentNext.presentId); + let nextSelected = current + 1; + + //if curDoc is in a group, selection slides until last one, if not it's next one + if (this.groupMappings.has(curNextPresId)) { + let currentsArray = this.groupMappings.get(StrCast(docAtCurrentNext.presentId))!; + nextSelected = current + currentsArray.length - currentsArray.indexOf(docAtCurrentNext); + + //end of grup so go beyond + if (nextSelected === current) nextSelected = current + 1; + } + + this.gotoDocument(nextSelected, current); + + } + back = async () => { + const current = NumCast(this.curPresentation.selectedDoc); + //requesting for the doc at current index + let docAtCurrent = await this.getDocAtIndex(current); + if (docAtCurrent === undefined) { + return; + } + + //asking for its presentation id. + let curPresId = StrCast(docAtCurrent.presentId); + let prevSelected = current - 1; + let zoomOut: boolean = false; + + //checking if this presentation id is mapped to a group, if so chosing the first element in group + if (this.groupMappings.has(curPresId)) { + let currentsArray = this.groupMappings.get(StrCast(docAtCurrent.presentId))!; + prevSelected = current - currentsArray.length + (currentsArray.length - currentsArray.indexOf(docAtCurrent)) - 1; + //end of grup so go beyond + if (prevSelected === current) prevSelected = current - 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)!.selected[buttonIndex.Show]) { + zoomOut = true; + return; + } + }); + + } + + // 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)!.selected[buttonIndex.Show]) { + let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null); + let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]); + if (prevScale !== undefined) { + if (prevScale !== curScale) { + DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); + } + } + } + } + this.gotoDocument(prevSelected, current); + + } + + /** + * This is the method that checks for the actions that need to be performed + * after the document has been presented, which involves 3 button options: + * Hide Until Presented, Hide After Presented, Fade After Presented + */ + showAfterPresented = (index: number) => { + this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { + let selectedButtons: boolean[] = presElem.selected; + //the order of cases is aligned based on priority + if (selectedButtons[buttonIndex.HideTillPressed]) { + if (this.childrenDocs.indexOf(key) <= index) { + key.opacity = 1; + } + } + if (selectedButtons[buttonIndex.HideAfter]) { + if (this.childrenDocs.indexOf(key) < index) { + key.opacity = 0; + } + } + if (selectedButtons[buttonIndex.FadeAfter]) { + if (this.childrenDocs.indexOf(key) < index) { + key.opacity = 0.5; + } + } + }); + } + + /** + * This is the method that checks for the actions that need to be performed + * before the document has been presented, which involves 3 button options: + * Hide Until Presented, Hide After Presented, Fade After Presented + */ + hideIfNotPresented = (index: number) => { + this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { + let selectedButtons: boolean[] = presElem.selected; + + //the order of cases is aligned based on priority + + if (selectedButtons[buttonIndex.HideAfter]) { + if (this.childrenDocs.indexOf(key) >= index) { + key.opacity = 1; + } + } + if (selectedButtons[buttonIndex.FadeAfter]) { + if (this.childrenDocs.indexOf(key) >= index) { + key.opacity = 1; + } + } + if (selectedButtons[buttonIndex.HideTillPressed]) { + if (this.childrenDocs.indexOf(key) > index) { + key.opacity = 0; + } + } + }); + } + + /** + * This method makes sure that cursor navigates to the element that + * has the option open and last in the group. If not in the group, and it has + * te option open, navigates to that element. + */ + navigateToElement = async (curDoc: Doc, fromDoc: number) => { + let docToJump: Doc = curDoc; + let curDocPresId = StrCast(curDoc.presentId, null); + let willZoom: boolean = false; + + //checking if in group + if (curDocPresId !== undefined) { + if (this.groupMappings.has(curDocPresId)) { + let currentDocGroup = this.groupMappings.get(curDocPresId)!; + currentDocGroup.forEach((doc: Doc, index: number) => { + let selectedButtons: boolean[] = this.presElementsMappings.get(doc)!.selected; + if (selectedButtons[buttonIndex.Navigate]) { + docToJump = doc; + willZoom = false; + } + if (selectedButtons[buttonIndex.Show]) { + docToJump = doc; + willZoom = true; + } + }); + } + + } + //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 curDocButtons = this.presElementsMappings.get(curDoc)!.selected; + if (curDocButtons[buttonIndex.Navigate]) { + DocumentManager.Instance.jumpToDocument(curDoc, false); + } else if (curDocButtons[buttonIndex.Show]) { + 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); + let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); + curDoc.viewScale = newScale; + + //saving the scale user was on before zooming in + if (curScale !== 1) { + this.childrenDocs[fromDoc].viewScale = curScale; + } + + } + return; + } + 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(docToJump, willZoom); + let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); + curDoc.viewScale = newScale; + //saving the scale that user was on + if (curScale !== 1) { + this.childrenDocs[fromDoc].viewScale = curScale; + } + + } + + /** + * Async function that supposedly return the doc that is located at given index. + */ + getDocAtIndex = async (index: number) => { + const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); + if (!list) { + return undefined; + } + if (index < 0 || index >= list.length) { + return undefined; + } + + this.curPresentation.selectedDoc = index; + //awaiting async call to finish to get Doc instance + const doc = await list[index]; + return doc; + } + + /** + * The function that removes a doc from a presentation. It also makes sure to + * do necessary updates to backUps and mappings stored locally. + */ + @action + public RemoveDoc = async (index: number) => { + const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); + if (value) { + let removedDoc = await value.splice(index, 1)[0]; + + //removing the Presentation Element stored for it + this.presElementsMappings.delete(removedDoc); + + let removedDocPresentId = StrCast(removedDoc.presentId); + + //Removing it from local mapping of the groups + if (this.groupMappings.has(removedDocPresentId)) { + let removedDocsGroup = this.groupMappings.get(removedDocPresentId); + if (removedDocsGroup) { + removedDocsGroup.splice(removedDocsGroup.indexOf(removedDoc), 1); + if (removedDocsGroup.length === 0) { + this.groupMappings.delete(removedDocPresentId); + } + } + } + + //removing it from the backUp of selected Buttons + // let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); + // if (castedList) { + // castedList.forEach(async (doc, indexOfDoc) => { + // let curDoc = await doc; + // let curDocId = StrCast(curDoc.docId); + // if (curDocId === removedDoc[Id]) { + // if (castedList) { + // castedList.splice(indexOfDoc, 1); + // return; + // } + // } + // }); + + // } + //removing it from the backUp of selected Buttons + + let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); + if (castedList) { + for (let doc of castedList) { + let curDoc = await doc; + let curDocId = StrCast(curDoc.docId); + if (curDocId === removedDoc[Id]) { + castedList.splice(castedList.indexOf(curDoc), 1); + break; + + } + } + } + + //removing it from the backup of groups + let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); + if (castedGroupDocs) { + castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { + let castedKey = StrCast(groupDoc.presentIdStore, null); + if (castedKey === removedDocPresentId) { + let castedGrouping = await DocListCastAsync(groupDoc.grouping); + if (castedGrouping) { + castedGrouping.splice(castedGrouping.indexOf(removedDoc), 1); + if (castedGrouping.length === 0) { + castedGroupDocs!.splice(castedGroupDocs!.indexOf(groupDoc), 1); + } + } + } + + }); + + } + + + } + } + + public removeDocByRef = (doc: Doc) => { + let indexOfDoc = this.childrenDocs.indexOf(doc); + const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); + if (value) { + value.splice(indexOfDoc, 1)[0]; + } + //this.RemoveDoc(indexOfDoc, true); + if (indexOfDoc !== - 1) { + return true; + } + return false; + } + + //The function that is called when a document is clicked or reached through next or back. + //it'll also execute the necessary actions if presentation is playing. + @action + public gotoDocument = async (index: number, fromDoc: number) => { + const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); + if (!list) { + return; + } + if (index < 0 || index >= list.length) { + return; + } + this.curPresentation.selectedDoc = index; + + if (!this.presStatus) { + this.presStatus = true; + this.startPresentation(index); + } + + const doc = await list[index]; + if (this.presStatus) { + this.navigateToElement(doc, fromDoc); + this.hideIfNotPresented(index); + this.showAfterPresented(index); + } + + } + + //Function that is called to resetGroupIds, so that documents get new groupIds at + //first load, when presentation is changed. + resetGroupIds = async () => { + let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); + if (castedGroupDocs !== undefined) { + castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { + let castedGrouping = await DocListCastAsync(groupDoc.grouping); + if (castedGrouping) { + castedGrouping.forEach((doc: Doc) => { + doc.presentId = Utils.GenerateGuid(); + }); + } + }); + } + runInAction(() => this.groupMappings = new Map()); + } + + /** + * Adds a document to the presentation view + **/ + @undoBatch + @action + public PinDoc(doc: Doc) { + //add this new doc to props.Document + const data = Cast(this.curPresentation.data, listSpec(Doc)); + if (data) { + data.push(doc); + } else { + this.curPresentation.data = new List([doc]); + } + + this.toggle(true); + } + + //Function that sets the store of the children docs. + @action + setChildrenDocs = (docList: Doc[]) => { + this.childrenDocs = docList; + } + + //The function that is called to render the play or pause button depending on + //if presentation is running or not. + renderPlayPauseButton = () => { + if (this.presStatus) { + return ; + } else { + return ; + } + } + + //The function that starts or resets presentaton functionally, depending on status flag. + @action + startOrResetPres = () => { + if (this.presStatus) { + this.resetPresentation(); + } else { + this.presStatus = true; + this.startPresentation(0); + const current = NumCast(this.curPresentation.selectedDoc); + this.gotoDocument(0, current); + } + this.curPresentation.presStatus = this.presStatus; + } + + //The function that resets the presentation by removing every action done by it. It also + //stops the presentaton. + @action + resetPresentation = () => { + this.childrenDocs.forEach((doc: Doc) => { + doc.opacity = 1; + doc.viewScale = 1; + }); + this.curPresentation.selectedDoc = 0; + this.presStatus = false; + this.curPresentation.presStatus = this.presStatus; + if (this.childrenDocs.length === 0) { + return; + } + DocumentManager.Instance.zoomIntoScale(this.childrenDocs[0], 1); + } + + + //The function that starts the presentation, also checking if actions should be applied + //directly at start. + startPresentation = (startIndex: number) => { + let selectedButtons: boolean[]; + this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => { + selectedButtons = component.selected; + if (selectedButtons[buttonIndex.HideTillPressed]) { + if (this.childrenDocs.indexOf(doc) > startIndex) { + doc.opacity = 0; + } + + } + if (selectedButtons[buttonIndex.HideAfter]) { + if (this.childrenDocs.indexOf(doc) < startIndex) { + doc.opacity = 0; + } + } + if (selectedButtons[buttonIndex.FadeAfter]) { + if (this.childrenDocs.indexOf(doc) < startIndex) { + doc.opacity = 0.5; + } + } + + }); + + } + + /** + * The function that is called to add a new presentation to the presentationView. + * It sets up te mappings and local copies of it. Resets the groupings and presentation. + * Makes the new presentation current selected, and retrieve the back-Ups if present. + */ + @action + addNewPresentation = (presTitle: string) => { + //creating a new presentation doc + let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle }); + let presDocs = Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc)); + presDocs && presDocs.push(newPresentationDoc); + + //setting that new doc as current + this.curPresentation = newPresentationDoc; + + //storing the doc in local copies for easier access + let newGuid = Utils.GenerateGuid(); + this.presentationsMapping.set(newGuid, newPresentationDoc); + this.presentationsKeyMapping.set(newPresentationDoc, newGuid); + + //resetting the previous presentation's actions so that new presentation can be loaded. + this.resetGroupIds(); + this.resetPresentation(); + this.presElementsMappings = new Map(); + this.currentSelectedPresValue = newGuid; + this.setPresentationBackUps(); + + } + + /** + * The function that is called to change the current selected presentation. + * Changes the presentation, also resetting groupings and presentation in process. + * Plus retrieving the backUps for the newly selected presentation. + */ + @action + getSelectedPresentation = (e: React.ChangeEvent) => { + //get the guid of the selected presentation + let selectedGuid = e.target.value; + //set that as current presentation + this.curPresentation = this.presentationsMapping.get(selectedGuid)!; + + //reset current Presentations local things so that new one can be loaded + this.resetGroupIds(); + this.resetPresentation(); + this.presElementsMappings = new Map(); + this.currentSelectedPresValue = selectedGuid; + this.setPresentationBackUps(); + + + } + + /** + * The function that is called to render either select for presentations, or title inputting. + */ + renderSelectOrPresSelection = () => { + let presentationList = this.presentationDocs; + if (this.PresTitleInputOpen || this.PresTitleChangeOpen) { + return this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />; + } else { + return ; + } + } + + /** + * The function that is called on enter press of title input. It gives the + * new presentation the title user entered. If nothing is entered, gives a default title. + */ + @action + submitPresentationTitle = (e: React.KeyboardEvent) => { + if (e.keyCode === 13) { + let presTitle = this.titleInputElement!.value; + this.titleInputElement!.value = ""; + if (this.PresTitleInputOpen) { + if (presTitle === "") { + presTitle = "Presentation"; + } + this.PresTitleInputOpen = false; + this.addNewPresentation(presTitle); + } else if (this.PresTitleChangeOpen) { + this.PresTitleChangeOpen = false; + this.changePresentationTitle(presTitle); + } + } + } + + /** + * The function that is called to remove a presentation from all its copies, and the main Container's + * list. Sets up the next presentation as current. + */ + @action + removePresentation = async () => { + if (this.presentationsMapping.size !== 1) { + let presentationList = this.presentationDocs; + let batch = UndoManager.StartBatch("presRemoval"); + + //getting the presentation that will be removed + let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!); + //that presentation is removed + presentationList!.splice(presentationList!.indexOf(removedDoc!), 1); + + //its mappings are removed from local copies + this.presentationsKeyMapping.delete(removedDoc!); + this.presentationsMapping.delete(this.currentSelectedPresValue!); + + //the next presentation is set as current + let remainingPresentations = this.presentationsMapping.values(); + let nextDoc = remainingPresentations.next().value; + this.curPresentation = nextDoc; + + + //Storing these for being able to undo changes + let curGuid = this.currentSelectedPresValue!; + let curPresStatus = this.presStatus; + + //resetting the groups and presentation actions so that next presentation gets loaded + this.resetGroupIds(); + this.resetPresentation(); + this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); + this.setPresentationBackUps(); + + //Storing for undo + let currentGroups = this.groupMappings; + let curPresElemMapping = this.presElementsMappings; + + //Event to undo actions that are not related to doc directly, aka. local things + UndoManager.AddEvent({ + undo: action(() => { + this.curPresentation = removedDoc!; + this.presentationsMapping.set(curGuid, removedDoc!); + this.presentationsKeyMapping.set(removedDoc!, curGuid); + this.currentSelectedPresValue = curGuid; + + this.presStatus = curPresStatus; + this.groupMappings = currentGroups; + this.presElementsMappings = curPresElemMapping; + this.setPresentationBackUps(); + + }), + redo: action(() => { + this.curPresentation = nextDoc; + this.presStatus = false; + this.presentationsKeyMapping.delete(removedDoc!); + this.presentationsMapping.delete(curGuid); + this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); + this.setPresentationBackUps(); + + }), + }); + + batch.end(); + } + } + + /** + * The function that is called to change title of presentation to what user entered. + */ + @undoBatch + changePresentationTitle = (newTitle: string) => { + if (newTitle === "") { + return; + } + this.curPresentation.title = newTitle; + } + + addPressElem = (keyDoc: Doc, elem: PresentationElement) => { + this.presElementsMappings.set(keyDoc, elem); + } + + + render() { + + let width = NumCast(this.curPresentation.width); + + return ( +
!this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}> +
+ {this.renderSelectOrPresSelection()} + {/**this.closePresentation CLICK does not work?! Also without the*/} + + + +
+
+ + {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()} + /> +
+ ); + } + + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 0af800d2edf120ab2a20842ed67ea7d7616996b9 Mon Sep 17 00:00:00 2001 From: HJF Bulterman Date: Fri, 9 Aug 2019 15:37:49 -0400 Subject: working for about 85% --- src/client/views/MainView.tsx | 27 +++++++++++++++++----- .../views/collections/CollectionDockingView.tsx | 9 ++++++++ src/client/views/nodes/DocumentView.tsx | 3 ++- src/client/views/nodes/PresBox.tsx | 7 +++--- 4 files changed, 36 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 14cfc6792..cb81f9aad 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -247,7 +247,6 @@ export class MainView extends React.Component { @computed get dockingContent() { let flyoutWidth = this.flyoutWidth; let mainCont = this.mainContainer; - let castRes = mainCont ? FieldValue(Cast(mainCont.presentationView, listSpec(Doc))) : undefined; return {({ measureRef }) =>
@@ -271,7 +270,7 @@ export class MainView extends React.Component { zoomToScale={emptyFunction} getScale={returnOne} />} - {castRes ? : null} + {/* {presentationDoc ? : null} */}
}
; @@ -385,13 +384,11 @@ export class MainView extends React.Component { let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })); let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })); - let addPresentationNode = action(() => Docs.Create.PresDocument(new List([Docs.Create.TreeDocument([], { title: "Presentation" })]))); - let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "object-group", "Add Collection", addColNode], [React.createRef(), "bolt", "Add Button", addButtonDocument], // [React.createRef(), "clone", "Add Docking Frame", addDockingNode], - [React.createRef(), "cloud-upload-alt", "Import Directory", addPresentationNode], //remove at some point in favor of addImportCollectionNode + [React.createRef(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode ]; if (!ClientUtils.RELEASE) btns.unshift([React.createRef(), "cat", "Add Cat Image", addImageNode]); @@ -402,7 +399,7 @@ export class MainView extends React.Component {
  • -
  • +
  • {btns.map(btn => @@ -453,6 +450,24 @@ export class MainView extends React.Component { this.isSearchVisible = !this.isSearchVisible; } + togglePresentationView = () => { + let presDoc = this.presentationDoc; + if (!presDoc) { + return; + } + let isOpen = CollectionDockingView.Instance.Has(presDoc); + if (isOpen) { + CollectionDockingView.Instance.CloseRightSplit(presDoc); + } else { + CollectionDockingView.Instance.AddRightSplit(presDoc, undefined); + } + } + + private get presentationDoc() { + let mainCont = this.mainContainer; + return mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined; + } + render() { return (
    diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1859ebee7..bd83a46a3 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -162,6 +162,14 @@ export class CollectionDockingView extends React.Component { + let docs = Cast(this.props.Document.data, listSpec(Doc)); + if (!docs) { + return false; + } + return docs.includes(document); + } + // // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // @@ -525,6 +533,7 @@ export class DockedFrameRenderer extends React.Component { this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.on("tab", this.onActiveContentItemChanged); this.onActiveContentItemChanged(); + // setTimeout(() => MainView.Instance.openPresentationView(), 2000); } componentWillUnmount() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0ca303dde..6d4c18050 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,6 +41,7 @@ import { ClientUtils } from '../../util/ClientUtils'; import { EditableView } from '../EditableView'; import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons'; import { DocumentDecorations } from '../DocumentDecorations'; +import { PresBox } from './PresBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -553,7 +554,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" }); cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); - cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //change this to + cm.addItem({ description: "Pin to Presentation", event: () => PresBox.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //this should work, and it does! A miracle! cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); let makes: ContextMenuProps[] = []; makes.push({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 2feb32693..8316c4469 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -32,7 +32,7 @@ export interface PresViewProps { Documents: List; } -const expandedWidth = 400; +const expandedWidth = 450; @observer export class PresBox extends React.Component { //FieldViewProps? @@ -825,10 +825,11 @@ export class PresBox extends React.Component { //FieldViewProps? render() { - let width = NumCast(this.curPresentation.width); + let width = "100%"; //NumCast(this.curPresentation.width) + console.log("The width is: " + width); return ( -
    !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}> +
    !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease", pointerEvents: "all" }}>
    {this.renderSelectOrPresSelection()} {/**this.closePresentation CLICK does not work?! Also without the*/} -- cgit v1.2.3-70-g09d2 From 2b829d1028a61869858ecd48e2f1801819e17488 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 13 Aug 2019 22:15:47 -0400 Subject: robust google doc export api and cleanup --- .../apis/google_docs/GoogleApiClientUtils.ts | 185 ++++++++++++--------- src/client/views/MainView.tsx | 11 +- src/client/views/nodes/FormattedTextBox.tsx | 24 ++- src/new_fields/RichTextField.ts | 13 ++ src/server/index.ts | 25 ++- 5 files changed, 162 insertions(+), 96 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 5e974b2e7..dda36f05a 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,7 +1,8 @@ import { docs_v1 } from "googleapis"; import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; -import { Opt } from "../../../new_fields/Doc"; +import { Opt, Doc } from "../../../new_fields/Doc"; +import { isArray } from "util"; export namespace GoogleApiClientUtils { @@ -9,15 +10,12 @@ export namespace GoogleApiClientUtils { export enum Actions { Create = "create", - Retrieve = "retrieve" + Retrieve = "retrieve", + Update = "update" } export namespace Utils { - export const fromRgb = (red: number, green: number, blue: number) => { - return { color: { rgbColor: { red, green, blue } } }; - }; - export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => { let fragments: string[] = []; if (document.body && document.body.content) { @@ -36,55 +34,36 @@ export namespace GoogleApiClientUtils { return removeNewlines ? text.ReplaceAll("\n", "") : text; }; - } - - export const ExampleDocumentSchema = { - title: "This is a Google Doc Created From Dash Web", - body: { - content: [ - { - endIndex: 1, - sectionBreak: { - sectionStyle: { - columnSeparatorStyle: "NONE", - contentDirection: "LEFT_TO_RIGHT" + export const EndOf = (schema: docs_v1.Schema$Document): Opt => { + if (schema.body && schema.body.content) { + let paragraphs = schema.body.content.filter(el => el.paragraph); + if (paragraphs.length) { + let target = paragraphs[paragraphs.length - 1]; + if (target.paragraph && target.paragraph.elements) { + length = target.paragraph.elements.length; + if (length) { + let final = target.paragraph.elements[length - 1]; + return final.endIndex ? final.endIndex - 1 : undefined; } } - }, - { - paragraph: { - elements: [ - { - textRun: { - content: "And this is its bold, blue text!!!\n", - textStyle: { - bold: true, - backgroundColor: Utils.fromRgb(0, 0, 1) - } - } - } - ] - } - }, - { - paragraph: { - elements: [ - { - textRun: { - content: "And this is its bold, blue text!!!\n", - textStyle: { - bold: true, - backgroundColor: Utils.fromRgb(0, 0, 1) - } - } - } - ] - } - }, + } + } + }; - ] as docs_v1.Schema$StructuralElement[] - } - } as docs_v1.Schema$Document; + } + + export interface ReadOptions { + documentId: string; + removeNewlines?: boolean; + } + + export interface WriteOptions { + documentId?: string; + title?: string; + content: string | string[]; + index?: number; + store?: { receiver: Doc, key: string }; + } /** * After following the authentication routine, which connects this API call to the current signed in account @@ -96,45 +75,95 @@ export namespace GoogleApiClientUtils { * actual document body and its styling! * @returns the documentId of the newly generated document, or undefined if the creation process fails. */ - export const Create = async (schema?: docs_v1.Schema$Document): Promise => { + const Create = async (title?: string): Promise => { let path = RouteStore.googleDocs + Actions.Create; - let parameters = { requestBody: schema || ExampleDocumentSchema }; - let generatedId: string | undefined; + let parameters = { + requestBody: { + title: title || `Dash Export (${new Date().toDateString()})` + } + }; try { - generatedId = await PostToServer(path, parameters); - } catch (e) { - console.error(e); - generatedId = undefined; - } finally { - return generatedId; + let schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + return schema.documentId; + } catch { + return undefined; } }; - export interface ReadOptions { - documentId: string; - removeNewlines?: boolean; - } + const Retrieve = async (documentId: string): Promise => { + let path = RouteStore.googleDocs + Actions.Retrieve; + let parameters = { + documentId + }; + try { + let schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + return schema; + } catch { + return undefined; + } + }; + + const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise => { + let path = RouteStore.googleDocs + Actions.Update; + let parameters = { + documentId, + requestBody: { + requests + } + }; + try { + let replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters); + console.log(replies); + return replies; + } catch { + return undefined; + } + }; - export const Read = async (options: ReadOptions): Promise> => { + export const Read = async (options: ReadOptions): Promise => { return Retrieve(options.documentId).then(schema => { return schema ? Utils.extractText(schema, options.removeNewlines) : undefined; }); }; - export const Retrieve = async (documentId: string): Promise> => { - let path = RouteStore.googleDocs + Actions.Retrieve; - let parameters = { documentId }; - let schema: Opt; - try { - schema = await PostToServer(path, parameters); - } catch (e) { - console.error(e); - schema = undefined; - } finally { - return schema; - } + export const ReadLines = async (options: ReadOptions) => { + return Retrieve(options.documentId).then(schema => { + if (!schema) { + return undefined; + } + let lines = Utils.extractText(schema).split("\n"); + return options.removeNewlines ? lines.filter(line => line.length) : lines; + }); }; + export const Write = async (options: WriteOptions): Promise => { + let target = options.documentId; + if (!target) { + if (!(target = await Create(options.title))) { + return undefined; + } + } + let index = options.index; + if (!index) { + let schema = await Retrieve(target); + if (!schema || !(index = Utils.EndOf(schema))) { + return undefined; + } + } + let text = options.content; + let request = { + insertText: { + text: isArray(text) ? text.join("\n") : text, + location: { index } + } + }; + return Update(target, [request]).then(res => { + if (res && options.store) { + options.store.receiver[options.store.key] = res.documentId; + } + return res; + }); + }; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 705eacc0c..7b15e9624 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -87,11 +87,6 @@ export class MainView extends React.Component { if (!("presentationView" in doc)) { doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })]); } - if (!("googleDocId" in doc)) { - GoogleApiClientUtils.Docs.Create().then(id => { - id && (doc.googleDocId = id); - }); - } CurrentUserUtils.UserDocument.activeWorkspace = doc; } } @@ -158,9 +153,9 @@ export class MainView extends React.Component { reaction(() => this.mainContainer, () => { let main = this.mainContainer, documentId; if (main && (documentId = StrCast(main.googleDocId))) { - GoogleApiClientUtils.Docs.Read({ documentId }).then(text => { - text && Utils.CopyText(text); - console.log(text); + let options = { documentId, removeNewlines: true }; + GoogleApiClientUtils.Docs.ReadLines(options).then(lines => { + console.log(lines); }); } }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 44b5d2c21..8c2af7c9e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons'; +import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; @@ -38,9 +38,10 @@ import { For } from 'babel-types'; import { DateField } from '../../../new_fields/DateField'; import { Utils } from '../../../Utils'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; +import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils'; library.add(faEdit); -library.add(faSmile, faTextHeight); +library.add(faSmile, faTextHeight, faUpload); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -58,6 +59,8 @@ const richTextSchema = createSchema({ documentText: "string" }); +const googleDocKey = "googleDocId"; + type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); @@ -661,7 +664,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" }); + if (!(googleDocKey in Doc.GetProto(this.props.Document))) { + ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.exportToGoogleDoc, icon: "upload" }); + } } + + exportToGoogleDoc = () => { + let dataDoc = Doc.GetProto(this.props.Document); + let data = Cast(dataDoc.data, RichTextField); + let content: string | undefined; + if (data && (content = data.plainText())) { + GoogleApiClientUtils.Docs.Write({ + title: StrCast(dataDoc.title), + store: { receiver: dataDoc, key: googleDocKey }, + content + }); + } + } + render() { let self = this; let style = this.props.isOverlay ? "scroll" : "hidden"; diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 89799b2af..dc66813e0 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -9,6 +9,7 @@ import { scriptingGlobal } from "../client/util/Scripting"; export class RichTextField extends ObjectField { @serializable(true) readonly Data: string; + private Extractor = /,\"text\":\"([^\"\}]*)\"\}/g; constructor(data: string) { super(); @@ -22,4 +23,16 @@ export class RichTextField extends ObjectField { [ToScriptString]() { return `new RichTextField("${this.Data}")`; } + + plainText = () => { + let contents = ""; + let matches: RegExpExecArray | null; + let considering = this.Data; + while ((matches = this.Extractor.exec(considering)) !== null) { + contents += matches[1]; + considering = considering.substring(matches.index + matches[0].length); + this.Extractor.lastIndex = 0; + } + return contents.length ? contents : undefined; + } } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 4ccb61681..abaa29658 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -42,6 +42,7 @@ import AdmZip from 'adm-zip'; import * as YoutubeApi from "./apis/youtube/youtubeApiSample"; import { Response } from 'express-serve-static-core'; import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils"; +import { GaxiosResponse } from 'gaxios'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -170,6 +171,13 @@ const read_text_file = (relativePath: string) => { }); }; +const write_text_file = (relativePath: string, contents: any) => { + let target = path.join(__dirname, relativePath); + return new Promise((resolve, reject) => { + fs.writeFile(target, contents, (err) => err ? reject(err) : resolve()); + }); +}; + app.get("/version", (req, res) => { exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => { if (err) { @@ -790,21 +798,22 @@ const credentials = path.join(__dirname, "./credentials/google_docs_credentials. const token = path.join(__dirname, "./credentials/google_docs_token.json"); app.post(RouteStore.googleDocs + ":action", (req, res) => { - let parameters = req.body; - GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { - let results: Promise | undefined; + let results: Promise | undefined; + let documents = endpoint.documents; + let parameters = req.body; switch (req.params.action) { case "create": - results = endpoint.documents.create(parameters).then(generated => generated.data.documentId); + results = documents.create(parameters); break; case "retrieve": - results = endpoint.documents.get(parameters).then(response => response.data); + results = documents.get(parameters); + break; + case "update": + results = documents.batchUpdate(parameters); break; - default: - results = undefined; } - results && results.then(final => res.send(final)); + !results ? res.send(undefined) : results.then(response => res.send(response.data)); }); }); -- cgit v1.2.3-70-g09d2 From dc766185075b5861686c68a704a8e49213eacbc6 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 14 Aug 2019 01:00:29 -0400 Subject: cleanup and streamlined / robust type requirements --- .../apis/google_docs/GoogleApiClientUtils.ts | 88 +++++++++++----------- src/client/views/MainView.tsx | 12 --- src/client/views/nodes/FormattedTextBox.tsx | 20 ++--- src/new_fields/RichTextField.ts | 2 +- 4 files changed, 58 insertions(+), 64 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index dda36f05a..f4fb87e0b 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -17,32 +17,32 @@ export namespace GoogleApiClientUtils { export namespace Utils { export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => { - let fragments: string[] = []; + const fragments: string[] = []; if (document.body && document.body.content) { - for (let element of document.body.content) { + for (const element of document.body.content) { if (element.paragraph && element.paragraph.elements) { - for (let inner of element.paragraph.elements) { + for (const inner of element.paragraph.elements) { if (inner && inner.textRun) { - let fragment = inner.textRun.content; + const fragment = inner.textRun.content; fragment && fragments.push(fragment); } } } } } - let text = fragments.join(""); + const text = fragments.join(""); return removeNewlines ? text.ReplaceAll("\n", "") : text; }; export const EndOf = (schema: docs_v1.Schema$Document): Opt => { if (schema.body && schema.body.content) { - let paragraphs = schema.body.content.filter(el => el.paragraph); + const paragraphs = schema.body.content.filter(el => el.paragraph); if (paragraphs.length) { - let target = paragraphs[paragraphs.length - 1]; + const target = paragraphs[paragraphs.length - 1]; if (target.paragraph && target.paragraph.elements) { length = target.paragraph.elements.length; if (length) { - let final = target.paragraph.elements[length - 1]; + const final = target.paragraph.elements[length - 1]; return final.endIndex ? final.endIndex - 1 : undefined; } } @@ -52,17 +52,25 @@ export namespace GoogleApiClientUtils { } + export type IdHandler = (id: DocumentId) => any; + export interface CreateOptions { + handler: IdHandler; + // if excluded, will use a default title annotated with the current date + title?: string; + } + export interface ReadOptions { documentId: string; + // if exluded, will preserve newlines removeNewlines?: boolean; } + export type DocumentId = string; export interface WriteOptions { - documentId?: string; - title?: string; content: string | string[]; + reference: DocumentId | CreateOptions; + // if excluded, will compute the last index of the document and append the content there index?: number; - store?: { receiver: Doc, key: string }; } /** @@ -70,33 +78,36 @@ export namespace GoogleApiClientUtils { * 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 schema whatever subset of a docs_v1.Schema$Document is required to properly initialize your - * Google Doc. This schema defines all aspects of a Google Doc, from the title to headers / footers to the - * actual document body and its styling! + * @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. */ - const Create = async (title?: string): Promise => { - let path = RouteStore.googleDocs + Actions.Create; - let parameters = { + const Create = async (options: CreateOptions): Promise => { + const path = RouteStore.googleDocs + Actions.Create; + const parameters = { requestBody: { - title: title || `Dash Export (${new Date().toDateString()})` + title: options.title || `Dash Export (${new Date().toDateString()})` } }; try { - let schema: docs_v1.Schema$Document = await PostToServer(path, parameters); - return schema.documentId; + const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + const generatedId = schema.documentId; + if (generatedId) { + options.handler(generatedId); + return generatedId; + } } catch { return undefined; } }; const Retrieve = async (documentId: string): Promise => { - let path = RouteStore.googleDocs + Actions.Retrieve; - let parameters = { + const path = RouteStore.googleDocs + Actions.Retrieve; + const parameters = { documentId }; try { - let schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); return schema; } catch { return undefined; @@ -104,16 +115,15 @@ export namespace GoogleApiClientUtils { }; const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise => { - let path = RouteStore.googleDocs + Actions.Update; - let parameters = { + const path = RouteStore.googleDocs + Actions.Update; + const parameters = { documentId, requestBody: { requests } }; try { - let replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters); - console.log(replies); + const replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters); return replies; } catch { return undefined; @@ -131,38 +141,32 @@ export namespace GoogleApiClientUtils { if (!schema) { return undefined; } - let lines = Utils.extractText(schema).split("\n"); + const lines = Utils.extractText(schema).split("\n"); return options.removeNewlines ? lines.filter(line => line.length) : lines; }); }; export const Write = async (options: WriteOptions): Promise => { - let target = options.documentId; - if (!target) { - if (!(target = await Create(options.title))) { - return undefined; - } + let documentId: string | undefined; + const ref = options.reference; + if (!(documentId = typeof ref === "string" ? ref : await Create(ref))) { + return undefined; } let index = options.index; if (!index) { - let schema = await Retrieve(target); + let schema = await Retrieve(documentId); if (!schema || !(index = Utils.EndOf(schema))) { return undefined; } } - let text = options.content; - let request = { + const text = options.content; + const request = { insertText: { text: isArray(text) ? text.join("\n") : text, location: { index } } }; - return Update(target, [request]).then(res => { - if (res && options.store) { - options.store.receiver[options.store.key] = res.documentId; - } - return res; - }); + return Update(documentId, [request]); }; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7b15e9624..77f0e3d60 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -149,18 +149,6 @@ export class MainView extends React.Component { }, { fireImmediately: true }); } - componentDidMount() { - reaction(() => this.mainContainer, () => { - let main = this.mainContainer, documentId; - if (main && (documentId = StrCast(main.googleDocId))) { - let options = { documentId, removeNewlines: true }; - GoogleApiClientUtils.Docs.ReadLines(options).then(lines => { - console.log(lines); - }); - } - }); - } - componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.globalPointerDown); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8c2af7c9e..50ec27259 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -670,16 +670,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } exportToGoogleDoc = () => { - let dataDoc = Doc.GetProto(this.props.Document); - let data = Cast(dataDoc.data, RichTextField); - let content: string | undefined; - if (data && (content = data.plainText())) { - GoogleApiClientUtils.Docs.Write({ - title: StrCast(dataDoc.title), - store: { receiver: dataDoc, key: googleDocKey }, - content - }); + const dataDoc = Doc.GetProto(this.props.Document); + const data = Cast(dataDoc.data, RichTextField); + if (!data) { + return; } + GoogleApiClientUtils.Docs.Write({ + reference: { + title: StrCast(dataDoc.title), + handler: id => dataDoc[googleDocKey] = id + }, + content: data.plainText() + }); } render() { diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index dc66813e0..3e8803a34 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -33,6 +33,6 @@ export class RichTextField extends ObjectField { considering = considering.substring(matches.index + matches[0].length); this.Extractor.lastIndex = 0; } - return contents.length ? contents : undefined; + return contents; } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 48fcec82fa384ec260a02965f9f78c2e41256dd9 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 14 Aug 2019 03:41:11 -0400 Subject: clean up and regex improvement --- .../apis/google_docs/GoogleApiClientUtils.ts | 111 ++++++++++++--------- src/client/views/nodes/FormattedTextBox.tsx | 15 ++- src/new_fields/RichTextField.ts | 4 +- src/server/index.ts | 35 ++++--- 4 files changed, 99 insertions(+), 66 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index f4fb87e0b..c6c7d7bd4 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,7 +1,7 @@ import { docs_v1 } from "googleapis"; import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; -import { Opt, Doc } from "../../../new_fields/Doc"; +import { Opt } from "../../../new_fields/Doc"; import { isArray } from "util"; export namespace GoogleApiClientUtils { @@ -14,9 +14,42 @@ export namespace GoogleApiClientUtils { Update = "update" } + export type DocumentId = string; + export type Reference = DocumentId | CreateOptions; + export type TextContent = string | string[]; + export type IdHandler = (id: DocumentId) => any; + + export type CreationResult = Opt; + export type RetrievalResult = Opt; + export type UpdateResult = Opt; + export type ReadLinesResult = Opt; + export type ReadResult = Opt; + + export interface CreateOptions { + handler: IdHandler; // callback to process the documentId of the newly created Google Doc + title?: string; // if excluded, will use a default title annotated with the current date + } + + export interface RetrieveOptions { + documentId: DocumentId; + } + + export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; + + export interface WriteOptions { + content: TextContent; + reference: Reference; + index?: number; // if excluded, will compute the last index of the document and append the content there + } + + export interface UpdateOptions { + documentId: DocumentId; + requests: docs_v1.Schema$Request[]; + } + export namespace Utils { - export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => { + export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): string => { const fragments: string[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { @@ -34,7 +67,7 @@ export namespace GoogleApiClientUtils { return removeNewlines ? text.ReplaceAll("\n", "") : text; }; - export const EndOf = (schema: docs_v1.Schema$Document): Opt => { + export const endOf = (schema: docs_v1.Schema$Document): number | undefined => { if (schema.body && schema.body.content) { const paragraphs = schema.body.content.filter(el => el.paragraph); if (paragraphs.length) { @@ -50,27 +83,8 @@ export namespace GoogleApiClientUtils { } }; - } + export const initialize = async (reference: Reference) => typeof reference === "string" ? reference : create(reference); - export type IdHandler = (id: DocumentId) => any; - export interface CreateOptions { - handler: IdHandler; - // if excluded, will use a default title annotated with the current date - title?: string; - } - - export interface ReadOptions { - documentId: string; - // if exluded, will preserve newlines - removeNewlines?: boolean; - } - - export type DocumentId = string; - export interface WriteOptions { - content: string | string[]; - reference: DocumentId | CreateOptions; - // if excluded, will compute the last index of the document and append the content there - index?: number; } /** @@ -82,7 +96,7 @@ export namespace GoogleApiClientUtils { * 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. */ - const Create = async (options: CreateOptions): Promise => { + export const create = async (options: CreateOptions): Promise => { const path = RouteStore.googleDocs + Actions.Create; const parameters = { requestBody: { @@ -101,43 +115,40 @@ export namespace GoogleApiClientUtils { } }; - const Retrieve = async (documentId: string): Promise => { + export const retrieve = async (options: RetrieveOptions): Promise => { const path = RouteStore.googleDocs + Actions.Retrieve; - const parameters = { - documentId - }; try { - const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + const schema: RetrievalResult = await PostToServer(path, options); return schema; } catch { return undefined; } }; - const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise => { + export const update = async (options: UpdateOptions): Promise => { const path = RouteStore.googleDocs + Actions.Update; const parameters = { - documentId, + documentId: options.documentId, requestBody: { - requests + requests: options.requests } }; try { - const replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters); + const replies: UpdateResult = await PostToServer(path, parameters); return replies; } catch { return undefined; } }; - export const Read = async (options: ReadOptions): Promise => { - return Retrieve(options.documentId).then(schema => { + export const read = async (options: ReadOptions): Promise => { + return retrieve(options).then(schema => { return schema ? Utils.extractText(schema, options.removeNewlines) : undefined; }); }; - export const ReadLines = async (options: ReadOptions) => { - return Retrieve(options.documentId).then(schema => { + export const readLines = async (options: ReadOptions): Promise => { + return retrieve(options).then(schema => { if (!schema) { return undefined; } @@ -146,27 +157,29 @@ export namespace GoogleApiClientUtils { }); }; - export const Write = async (options: WriteOptions): Promise => { - let documentId: string | undefined; - const ref = options.reference; - if (!(documentId = typeof ref === "string" ? ref : await Create(ref))) { + export const write = async (options: WriteOptions): Promise => { + const documentId = await Utils.initialize(options.reference); + if (!documentId) { return undefined; } let index = options.index; if (!index) { - let schema = await Retrieve(documentId); - if (!schema || !(index = Utils.EndOf(schema))) { + let schema = await retrieve({ documentId }); + if (!schema || !(index = Utils.endOf(schema))) { return undefined; } } const text = options.content; - const request = { - insertText: { - text: isArray(text) ? text.join("\n") : text, - location: { index } - } + const updateOptions = { + documentId, + requests: [{ + insertText: { + text: isArray(text) ? text.join("\n") : text, + location: { index } + } + }] }; - return Update(documentId, [request]); + return update(updateOptions); }; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 50ec27259..46aed9b2d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -288,6 +288,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + componentWillMount() { + this.pollExportedCounterpart(); + } + + pollExportedCounterpart = async () => { + let dataDoc = Doc.GetProto(this.props.Document); + let documentId = StrCast(dataDoc[googleDocKey]); + if (documentId) { + let contents = await GoogleApiClientUtils.Docs.read({ documentId }); + contents ? console.log(contents) : delete dataDoc[googleDocKey]; + } + } + componentDidMount() { const config = { schema, @@ -675,7 +688,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!data) { return; } - GoogleApiClientUtils.Docs.Write({ + GoogleApiClientUtils.Docs.write({ reference: { title: StrCast(dataDoc.title), handler: id => dataDoc[googleDocKey] = id diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 3e8803a34..4f782816c 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -9,7 +9,7 @@ import { scriptingGlobal } from "../client/util/Scripting"; export class RichTextField extends ObjectField { @serializable(true) readonly Data: string; - private Extractor = /,\"text\":\"([^\"\}]*)\"\}/g; + private Extractor = /,\"text\":\"([^\}]*)\"\}/g; constructor(data: string) { super(); @@ -33,6 +33,6 @@ export class RichTextField extends ObjectField { considering = considering.substring(matches.index + matches[0].length); this.Extractor.lastIndex = 0; } - return contents; + return contents.ReplaceAll("\\", ""); } } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index abaa29658..ef1829f30 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -43,6 +43,8 @@ import * as YoutubeApi from "./apis/youtube/youtubeApiSample"; import { Response } from 'express-serve-static-core'; import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils"; import { GaxiosResponse } from 'gaxios'; +import { Opt } from '../new_fields/Doc'; +import { docs_v1 } from 'googleapis'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -797,23 +799,28 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json"); const token = path.join(__dirname, "./credentials/google_docs_token.json"); +type ApiResponse = Promise; +type ApiHandler = (endpoint: docs_v1.Resource$Documents, parameters: any) => ApiResponse; +type Action = "create" | "retrieve" | "update"; + +const EndpointHandlerMap = new Map([ + ["create", (api, params) => api.create(params)], + ["retrieve", (api, params) => api.get(params)], + ["update", (api, params) => api.batchUpdate(params)], +]); + app.post(RouteStore.googleDocs + ":action", (req, res) => { GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { - let results: Promise | undefined; - let documents = endpoint.documents; - let parameters = req.body; - switch (req.params.action) { - case "create": - results = documents.create(parameters); - break; - case "retrieve": - results = documents.get(parameters); - break; - case "update": - results = documents.batchUpdate(parameters); - break; + let handler = EndpointHandlerMap.get(req.params.action); + if (handler) { + let execute = handler(endpoint.documents, req.body).then( + response => res.send(response.data), + rejection => res.send(rejection) + ); + execute.catch(exception => res.send(exception)); + return; } - !results ? res.send(undefined) : results.then(response => res.send(response.data)); + res.send(undefined); }); }); -- cgit v1.2.3-70-g09d2 From 9ea032b9ab14ba17511b1014044dba0236e93837 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 14 Aug 2019 10:48:23 -0400 Subject: refactored plain text parsing and setting --- src/client/views/nodes/FormattedTextBox.tsx | 13 +++++--- src/new_fields/RichTextField.ts | 50 ++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 15 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 46aed9b2d..5ba2aa0cf 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,7 +12,7 @@ import { EditorView } from "prosemirror-view"; import { Doc, Opt, DocListCast } from "../../../new_fields/Doc"; import { Id, Copy } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; -import { RichTextField } from "../../../new_fields/RichTextField"; +import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; @@ -296,8 +296,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let dataDoc = Doc.GetProto(this.props.Document); let documentId = StrCast(dataDoc[googleDocKey]); if (documentId) { - let contents = await GoogleApiClientUtils.Docs.read({ documentId }); - contents ? console.log(contents) : delete dataDoc[googleDocKey]; + let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); + if (exportState) { + let data = Cast(dataDoc.data, RichTextField); + data && data[FromPlainText](exportState); + } else { + delete dataDoc[googleDocKey]; + } } } @@ -693,7 +698,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe title: StrCast(dataDoc.title), handler: id => dataDoc[googleDocKey] = id }, - content: data.plainText() + content: data[ToPlainText]() }); } diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 4f782816c..9d8a1cecb 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -4,12 +4,14 @@ 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"); + @scriptingGlobal @Deserializable("RichTextField") export class RichTextField extends ObjectField { @serializable(true) - readonly Data: string; - private Extractor = /,\"text\":\"([^\}]*)\"\}/g; + Data: string; constructor(data: string) { super(); @@ -24,15 +26,41 @@ export class RichTextField extends ObjectField { return `new RichTextField("${this.Data}")`; } - plainText = () => { - let contents = ""; - let matches: RegExpExecArray | null; - let considering = this.Data; - while ((matches = this.Extractor.exec(considering)) !== null) { - contents += matches[1]; - considering = considering.substring(matches.index + matches[0].length); - this.Extractor.lastIndex = 0; + [ToPlainText]() { + let content = JSON.parse(this.Data).doc.content; + let paragraphs = content.filter((item: any) => item.type === "paragraph"); + let output = ""; + for (let i = 0; i < paragraphs.length; i++) { + let paragraph = paragraphs[i]; + if (paragraph.content) { + output += paragraph.content.map((block: any) => block.text).join(""); + } else { + output += i > 0 && paragraphs[i - 1].content ? "\n\n" : "\n"; + } } - return contents.ReplaceAll("\\", ""); + return output; + } + + [FromPlainText](plainText: string) { + let elements = plainText.split("\n"); + let parsed = JSON.parse(this.Data); + parsed.doc.content = elements.map(text => { + let paragraph: any = { type: "paragraph" }; + if (text.length) { + paragraph.content = [{ + type: "text", + marks: [], + text + }]; + } + return paragraph; + }); + parsed.selection = { + type: "text", + anchor: 0, + head: 0 + }; + this.Data = JSON.stringify(parsed); } + } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 13d2be7f66c01ee9f09cb65f567eb3da760df670 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 14 Aug 2019 15:18:13 -0400 Subject: semi-synchronous editing between dash notes and google docs --- package.json | 2 + .../apis/google_docs/GoogleApiClientUtils.ts | 40 ++++++++++++++------ src/client/views/nodes/FormattedTextBox.tsx | 44 ++++++++++++++-------- src/new_fields/RichTextField.ts | 25 +++++++----- 4 files changed, 76 insertions(+), 35 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package.json b/package.json index 326468ab9..7a629d813 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@types/cookie-parser": "^1.4.1", "@types/cookie-session": "^2.0.36", "@types/d3-format": "^1.3.1", + "@types/diff": "^4.0.2", "@types/dotenv": "^6.1.1", "@types/express": "^4.16.1", "@types/express-flash": "0.0.0", @@ -127,6 +128,7 @@ "cookie-session": "^2.0.0-beta.3", "crypto-browserify": "^3.11.0", "d3-format": "^1.3.2", + "diff": "^4.0.1", "dotenv": "^8.0.0", "express": "^4.16.4", "express-flash": "0.0.2", diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index c6c7d7bd4..c1cbba31f 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -14,6 +14,11 @@ export namespace GoogleApiClientUtils { Update = "update" } + export enum WriteMode { + Insert, + Replace + } + export type DocumentId = string; export type Reference = DocumentId | CreateOptions; export type TextContent = string | string[]; @@ -37,6 +42,7 @@ export namespace GoogleApiClientUtils { export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; export interface WriteOptions { + mode: WriteMode; content: TextContent; reference: Reference; index?: number; // if excluded, will compute the last index of the document and append the content there @@ -158,28 +164,40 @@ export namespace GoogleApiClientUtils { }; export const write = async (options: WriteOptions): Promise => { + const requests: docs_v1.Schema$Request[] = []; const documentId = await Utils.initialize(options.reference); if (!documentId) { return undefined; } let index = options.index; - if (!index) { + const mode = options.mode; + if (!(index && mode === WriteMode.Insert)) { let schema = await retrieve({ documentId }); if (!schema || !(index = Utils.endOf(schema))) { return undefined; } } - const text = options.content; - const updateOptions = { - documentId, - requests: [{ - insertText: { - text: isArray(text) ? text.join("\n") : text, - location: { index } + if (mode === WriteMode.Replace) { + requests.push({ + deleteContentRange: { + range: { + startIndex: 1, + endIndex: index + } } - }] - }; - return update(updateOptions); + }); + index = 1; + } + const text = options.content; + requests.push({ + insertText: { + text: isArray(text) ? text.join("\n") : text, + location: { index } + } + }); + let replies = await update({ documentId, requests }); + console.log(replies); + return replies; }; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 5ba2aa0cf..ad29ce775 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,7 +12,7 @@ import { EditorView } from "prosemirror-view"; import { Doc, Opt, DocListCast } from "../../../new_fields/Doc"; import { Id, Copy } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; -import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; +import { RichTextField, ToGoogleDocText, FromGoogleDocText } from "../../../new_fields/RichTextField"; import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; @@ -39,6 +39,7 @@ import { DateField } from '../../../new_fields/DateField'; import { Utils } from '../../../Utils'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils'; +import * as diff from "diff"; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -299,7 +300,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); if (exportState) { let data = Cast(dataDoc.data, RichTextField); - data && data[FromPlainText](exportState); + if (data) { + dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState)); + } } else { delete dataDoc[googleDocKey]; } @@ -629,6 +632,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._undoTyping.end(); this._undoTyping = undefined; } + this.updateGoogleDoc(); } public _undoTyping?: UndoManager.Batch; onKeyPress = (e: React.KeyboardEvent) => { @@ -683,23 +687,33 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" }); if (!(googleDocKey in Doc.GetProto(this.props.Document))) { - ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.exportToGoogleDoc, icon: "upload" }); + ContextMenu.Instance.addItem({ + description: "Export to Google Doc...", + event: this.updateGoogleDoc, + icon: "upload" + }); } } - exportToGoogleDoc = () => { - const dataDoc = Doc.GetProto(this.props.Document); - const data = Cast(dataDoc.data, RichTextField); - if (!data) { - return; + updateGoogleDoc = () => { + let modes = GoogleApiClientUtils.Docs.WriteMode; + let mode = modes.Replace; + let reference: Opt = Cast(this.dataDoc[googleDocKey], "string"); + if (!reference) { + mode = modes.Insert; + reference = { + title: StrCast(this.dataDoc.title), + handler: id => this.dataDoc[googleDocKey] = id + }; + } + const data = Cast(this.dataDoc.data, RichTextField); + if (data) { + GoogleApiClientUtils.Docs.write({ + mode, + content: data[ToGoogleDocText](), + reference + }); } - GoogleApiClientUtils.Docs.write({ - reference: { - title: StrCast(dataDoc.title), - handler: id => dataDoc[googleDocKey] = id - }, - content: data[ToPlainText]() - }); } render() { diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index f0284b1b8..92b19b921 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -4,14 +4,14 @@ 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"); +export const ToGoogleDocText = Symbol("PlainText"); +export const FromGoogleDocText = Symbol("PlainText"); @scriptingGlobal @Deserializable("RichTextField") export class RichTextField extends ObjectField { @serializable(true) - Data: string; + readonly Data: string; constructor(data: string) { super(); @@ -26,24 +26,28 @@ export class RichTextField extends ObjectField { return `new RichTextField("${this.Data}")`; } - [ToPlainText]() { + [ToGoogleDocText]() { let content = JSON.parse(this.Data).doc.content; let paragraphs = content.filter((item: any) => item.type === "paragraph"); let output = ""; for (let i = 0; i < paragraphs.length; i++) { let paragraph = paragraphs[i]; + let addNewLine = i > 0 ? paragraphs[i - 1].content : false; if (paragraph.content) { output += paragraph.content.map((block: any) => block.text).join(""); } else { - output += i > 0 && paragraphs[i - 1].content ? "\n\n" : "\n"; + output += "\n"; } + addNewLine && (output += "\n"); } return output; } - [FromPlainText](plainText: string) { + [FromGoogleDocText](plainText: string) { let elements = plainText.split("\n"); + !elements[elements.length - 1].length && elements.pop(); let parsed = JSON.parse(this.Data); + let blankCount = 0; parsed.doc.content = elements.map(text => { let paragraph: any = { type: "paragraph" }; if (text.length) { @@ -52,15 +56,18 @@ export class RichTextField extends ObjectField { marks: [], text }]; + } else { + blankCount++; } return paragraph; }); + let selection = plainText.length + 2 * blankCount; parsed.selection = { type: "text", - anchor: plainText.length, - head: plainText.length + anchor: selection, + head: selection }; - this.Data = JSON.stringify(parsed); + return JSON.stringify(parsed); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 9d76e8c2f318b4c6a4f941e6d2c8e795bc93f372 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Thu, 15 Aug 2019 16:15:05 -0400 Subject: updated webbox --- src/client/views/nodes/WebBox.tsx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ff5297783..91170e99a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -17,6 +17,7 @@ import { RefField } from "../../../new_fields/RefField"; import { ObjectField } from "../../../new_fields/ObjectField"; import { updateSourceFile } from "typescript"; import { KeyValueBox } from "./KeyValueBox"; +import { setReactionScheduler } from "mobx/lib/internal"; @observer export class WebBox extends React.Component { @@ -38,6 +39,8 @@ export class WebBox extends React.Component { this.props.Document.height = NumCast(this.props.Document.width) / youtubeaspect; } } + + this.setURL(); } @action @@ -50,15 +53,13 @@ export class WebBox extends React.Component { const script = KeyValueBox.CompileKVPScript(`new WebField("${this.url}")`); if (!script) return; KeyValueBox.ApplyKVPScript(this.props.Document, "data", script); - let mod = document.getElementById("webpage-input"); - if (mod) mod.style.display = "none"; } - @computed - get getURL() { + @action + setURL() { let urlField: FieldResult = Cast(this.props.Document.data, WebField) - if (urlField) return urlField.url.toString(); - return ""; + if (urlField) this.url = urlField.url.toString(); + else this.url = ""; } onValueKeyDown = async (e: React.KeyboardEvent) => { @@ -86,10 +87,9 @@ export class WebBox extends React.Component {
    diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ad29ce775..bc057bb5f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -60,7 +60,7 @@ const richTextSchema = createSchema({ documentText: "string" }); -const googleDocKey = "googleDocId"; +const googleDocId = "googleDocId"; type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); @@ -84,6 +84,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _proxyReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + private isOpening = false; @observable _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; @@ -295,17 +296,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe pollExportedCounterpart = async () => { let dataDoc = Doc.GetProto(this.props.Document); - let documentId = StrCast(dataDoc[googleDocKey]); + let documentId = StrCast(dataDoc[googleDocId]); if (documentId) { let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); if (exportState) { let data = Cast(dataDoc.data, RichTextField); if (data) { + this.isOpening = true; dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState)); } } else { - delete dataDoc[googleDocKey]; + delete dataDoc[googleDocId]; } + this.tryUpdateHeight(); } } @@ -346,9 +349,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined; return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; }, - field2 => { - this._editorView && !this._applyingChange && - this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2))); + incomingValue => { + if (this._editorView && !this._applyingChange) { + let updatedState = JSON.parse(incomingValue); + this._editorView.updateState(EditorState.fromJSON(config, updatedState)); + // manually sets cursor selection at the end of the text on focus + if (this.isOpening) { + this.isOpening = false; + let end = this._editorView.state.doc.content.size - 1; + updatedState.selection = { type: "text", anchor: end, head: end }; + this._editorView.updateState(EditorState.fromJSON(config, updatedState)); + } + } } ); @@ -686,7 +698,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" }); - if (!(googleDocKey in Doc.GetProto(this.props.Document))) { + if (!(googleDocId in Doc.GetProto(this.props.Document))) { ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.updateGoogleDoc, @@ -698,21 +710,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe updateGoogleDoc = () => { let modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[googleDocKey], "string"); + let reference: Opt = Cast(this.dataDoc[googleDocId], "string"); if (!reference) { mode = modes.Insert; reference = { title: StrCast(this.dataDoc.title), - handler: id => this.dataDoc[googleDocKey] = id + handler: id => this.dataDoc[googleDocId] = id }; } - const data = Cast(this.dataDoc.data, RichTextField); - if (data) { - GoogleApiClientUtils.Docs.write({ - mode, - content: data[ToGoogleDocText](), - reference - }); + if (this._editorView) { + let data = Cast(this.dataDoc.data, RichTextField); + let content = data ? data[ToGoogleDocText]() : this._editorView.state.doc.textContent; + GoogleApiClientUtils.Docs.write({ reference, content, mode }); } } diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 8963682c3..ec08293e9 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -6,6 +6,8 @@ import { scriptingGlobal } from "../client/util/Scripting"; export const ToGoogleDocText = Symbol("PlainText"); export const FromGoogleDocText = Symbol("PlainText"); +const delimiter = "\n"; +const joiner = ""; @scriptingGlobal @Deserializable("RichTextField") @@ -27,45 +29,39 @@ export class RichTextField extends ObjectField { } [ToGoogleDocText]() { - let state = JSON.parse(this.Data); - let text = state.doc.textBetween(0, state.doc.content.size, "\n\n"); - console.log(text); + // 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"); - let output = ""; - for (let i = 0; i < paragraphs.length; i++) { - let paragraph = paragraphs[i]; - let addNewLine = i > 0 ? paragraphs[i - 1].content : false; - if (paragraph.content) { - output += paragraph.content.map((block: any) => block.text).join(""); - } else { - output += i === 0 ? "" : "\n"; - } - addNewLine && (output += "\n"); - } - return output; + + // 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. Trim the last newline, an artifact. + let textParagraphs = paragraphs.map(concatenateParagraph); + return textParagraphs.join(joiner).trimEnd(delimiter); } [FromGoogleDocText](plainText: string) { - let elements = plainText.split("\n"); + // 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" }; - if (text.length) { - paragraph.content = [{ - type: "text", - marks: [], - text - }]; - } + text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break return paragraph; }); - parsed.selection = { - type: "text", - anchor: 1, - head: 1 - }; + + // 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); } -- cgit v1.2.3-70-g09d2 From 0e4729a8d634c67a3575761784b840a28694ba7a Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 19 Aug 2019 15:55:36 -0400 Subject: fixed treeview dragging. got rid of extra stuff from presentationview --- src/client/documents/Documents.ts | 2 +- src/client/views/MainView.tsx | 62 +- .../views/collections/CollectionDockingView.tsx | 27 +- .../views/collections/CollectionSchemaCells.tsx | 2 + .../views/collections/CollectionSchemaView.tsx | 4 + src/client/views/collections/CollectionSubView.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 18 +- .../CollectionFreeFormRemoteCursors.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 + src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/KeyValuePair.tsx | 1 + src/client/views/nodes/LinkMenu.tsx | 3 - src/client/views/nodes/PresBox.tsx | 249 +----- src/client/views/pdf/Annotation.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 1 + .../views/presentationview/PresentationElement.tsx | 3 +- .../views/presentationview/PresentationList.tsx | 5 +- .../views/presentationview/PresentationView.scss | 4 +- .../views/presentationview/PresentationView.tsx | 994 --------------------- src/client/views/search/SearchItem.tsx | 1 + src/new_fields/Doc.ts | 55 +- src/new_fields/util.ts | 3 +- .../authentication/models/current_user_utils.ts | 23 +- 24 files changed, 172 insertions(+), 1304 deletions(-) delete mode 100644 src/client/views/presentationview/PresentationView.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c551fd2a3..47df17329 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -290,7 +290,7 @@ export namespace Docs { const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); if (!("author" in protoProps)) { - protoProps.author = CurrentUserUtils.email; + protoProps.author = Doc.CurrentUserEmail; } if (!("creationDate" in protoProps)) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7119a4fc3..7b7a5542d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -12,7 +12,6 @@ import { List } from '../../new_fields/List'; import { Id } from '../../new_fields/FieldSymbols'; import { InkTool } from '../../new_fields/InkField'; import { listSpec } from '../../new_fields/Schema'; -import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField'; import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; @@ -24,7 +23,7 @@ import { DictationManager } from '../util/DictationManager'; import { SetupDrag } from '../util/DragManager'; import { HistoryUtil } from '../util/History'; import { Transform } from '../util/Transform'; -import { UndoManager } from '../util/UndoManager'; +import { UndoManager, undoBatch } from '../util/UndoManager'; import { CollectionBaseView } from './collections/CollectionBaseView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionTreeView } from './collections/CollectionTreeView'; @@ -37,15 +36,16 @@ import { MainOverlayTextBox } from './MainOverlayTextBox'; import { DocumentView } from './nodes/DocumentView'; import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; -import { PresentationView } from './presentationview/PresentationView'; import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; +import { DocumentManager } from '../util/DocumentManager'; +import PresModeMenu from './presentationview/PresentationModeMenu'; +import { PresBox } from './nodes/PresBox'; @observer export class MainView extends React.Component { public static Instance: MainView; @observable addMenuToggle = React.createRef(); - @observable private _workspacesShown: boolean = false; @observable public pwidth: number = 0; @observable public pheight: number = 0; @@ -82,10 +82,6 @@ export class MainView extends React.Component { public isPointerDown = false; private set mainContainer(doc: Opt) { if (doc) { - if (!("presentationView" in doc)) { - let initialDoc = Docs.Create.TreeDocument([], { title: "Presentation" }); - doc.presentationView = Docs.Create.PresDocument(new List([initialDoc])); - } CurrentUserUtils.UserDocument.activeWorkspace = doc; } } @@ -322,6 +318,7 @@ export class MainView extends React.Component { DataDoc={undefined} addDocument={undefined} addDocTab={emptyFunction} + pinToPres={emptyFunction} onClick={undefined} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} @@ -339,7 +336,6 @@ export class MainView extends React.Component { zoomToScale={emptyFunction} getScale={returnOne} />} - {/* {presentationDoc ? : null} */}
    } ; @@ -386,6 +382,7 @@ export class MainView extends React.Component { DataDoc={undefined} addDocument={undefined} addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} removeDocument={undefined} onClick={undefined} ScreenToLocalTransform={Transform.Identity} @@ -443,26 +440,24 @@ export class MainView extends React.Component { let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; - // let addDockingNode = action(() => Docs.Create.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" })); - let addSchemaNode = action(() => Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], [], { width: 200, height: 200, title: "a schema collection" })); - //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" })); - // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" })); let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); + let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })); let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })); let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" })); let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })); let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })); - let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; - let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" })); + // let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; + // let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" })); let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "object-group", "Add Collection", addColNode], + [React.createRef(), "table", "Add Presentation Trail", addPresNode], [React.createRef(), "globe-asia", "Add Website", addWebNode], [React.createRef(), "bolt", "Add Button", addButtonDocument], // [React.createRef(), "clone", "Add Docking Frame", addDockingNode], [React.createRef(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode - [React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], + //[React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], [React.createRef(), "file", "Add Document Dragger", addDragboxNode] ]; if (!ClientUtils.RELEASE) btns.unshift([React.createRef(), "cat", "Add Cat Image", addImageNode]); @@ -489,7 +484,7 @@ export class MainView extends React.Component {
    • -
    • +
    • {btns.map(btn => @@ -543,30 +538,20 @@ export class MainView extends React.Component { @observable isSearchVisible = false; @action.bound toggleSearch = () => { - // console.log("search toggling") this.isSearchVisible = !this.isSearchVisible; } togglePresentationView = () => { - let presDoc = this.presentationDoc; - if (!presDoc) { - return; - } - let isOpen = CollectionDockingView.Instance.Has(presDoc); - if (isOpen) { - return; - // CollectionDockingView.Instance.CloseRightSplit(presDoc); - //why?? It's throwing an error that seems impossible to fix. - //ToDo: - } else { - CollectionDockingView.Instance.AddRightSplit(presDoc, undefined); + if (CurrentUserUtils.UserDocument.curPresentation) { + let isOpen = DocumentManager.Instance.getDocumentView(CurrentUserUtils.UserDocument.curPresentation as Doc); + if (isOpen) { + CollectionDockingView.Instance.CloseRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc); + } else { + CollectionDockingView.Instance.AddRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc, undefined); + } } } - private get presentationDoc() { - let mainCont = this.mainContainer; - return mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined; - } private get dictationOverlay() { let display = this.dictationOverlayVisible; let success = this.dictationSuccess; @@ -592,12 +577,21 @@ export class MainView extends React.Component { ); } + @computed get miniPresentation() { + let next = () => PresBox.CurrentPresentation.next(); + let back = () => PresBox.CurrentPresentation.back(); + let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres(); + let closePresMode = action(() => {PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document)}); + return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : + } + render() { return (
      {this.dictationOverlay} {this.mainContent} + {this.miniPresentation} {this.nodesMenu()} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 3fcc61b76..47dfeb169 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -29,6 +29,8 @@ 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'; library.add(faFile); @observer @@ -543,11 +545,31 @@ export class DockedFrameRenderer extends React.Component { })); } + /** + * Adds a document to the presentation view + **/ + @undoBatch + @action + public PinDoc(doc: Doc) { + if (doc.type === DocumentType.PRES) { + MainView.Instance.toggleMiniPresentation() + } + //add this new doc to props.Document + let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc; + if (curPres) { + const data = Cast(curPres.data, listSpec(Doc)); + if (data) { + data.push(doc); + } else { + curPres.data = new List([doc]); + } + } + } + componentDidMount() { this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.on("tab", this.onActiveContentItemChanged); this.onActiveContentItemChanged(); - // setTimeout(() => MainView.Instance.openPresentationView(), 2000); } componentWillUnmount() { @@ -592,6 +614,8 @@ export class DockedFrameRenderer extends React.Component { MainView.Instance.openWorkspace(doc); } else if (location === "onRight") { CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); + } else if (location === "close") { + CollectionDockingView.Instance.CloseRightSplit(doc); } else { CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); } @@ -618,6 +642,7 @@ export class DockedFrameRenderer extends React.Component { focus={emptyFunction} backgroundColor={returnEmptyString} addDocTab={this.addDocTab} + pinToPres={this.PinDoc} ContainingCollectionView={undefined} zoomToScale={emptyFunction} getScale={returnOne} />; diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 7e3061354..551b485e7 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -40,6 +40,7 @@ export interface CellProps { fieldKey: string; renderDepth: number; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; isFocused: boolean; changeFocusedCellByIndex: (row: number, col: number) => void; @@ -160,6 +161,7 @@ export class CollectionSchemaCell extends React.Component { PanelHeight: returnZero, PanelWidth: returnZero, addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, ContentScaling: returnOne }; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 4537dcc85..221908dd2 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -251,6 +251,7 @@ export interface SchemaTableProps { active: () => boolean; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; isSelected: () => boolean; isFocused: (document: Doc) => boolean; setFocused: (document: Doc) => void; @@ -377,6 +378,7 @@ export class SchemaTable extends React.Component { fieldKey: this.props.fieldKey, renderDepth: this.props.renderDepth, addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, moveDocument: this.props.moveDocument, setIsEditing: this.setCellIsEditing, isEditable: isEditable, @@ -907,6 +909,7 @@ interface CollectionSchemaPreviewProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; setPreviewScript: (script: string) => void; previewScript?: string; } @@ -997,6 +1000,7 @@ export class CollectionSchemaPreview extends React.Component(schemaCtor: (doc: Doc) => T) { let ind; let doc = this.props.Document; let id = CurrentUserUtils.id; - let email = CurrentUserUtils.email; + let email = Doc.CurrentUserEmail; let pos = { x: position[0], y: position[1] }; if (id && email) { const proto = Doc.GetProto(doc); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 7424cc186..6b9cd57b3 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -39,6 +39,7 @@ export interface TreeViewProps { moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; panelWidth: () => number; panelHeight: () => number; addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean; @@ -259,10 +260,10 @@ class TreeView extends React.Component { if (contents instanceof Doc || Cast(contents, listSpec(Doc))) { let remDoc = (doc: Doc) => this.remove(doc, key); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); } else { contentElement = { const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; if (expandKey !== undefined) { let remDoc = (doc: Doc) => this.remove(doc, expandKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); let docs = expandKey === "links" ? this.childLinks : this.childDocs; return
        {!docs ? (null) : TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
      ; } else if (this.treeViewExpandedView === "fields") { @@ -319,6 +320,7 @@ class TreeView extends React.Component { active={this.props.active} whenActiveChanged={emptyFunction as any} addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} setPreviewScript={emptyFunction}>
      ; @@ -395,6 +397,7 @@ class TreeView extends React.Component { move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void, + pinToPres: (document: Doc) => void, screenToLocalXf: () => Transform, outerXf: () => { translateX: number, translateY: number }, active: () => boolean, @@ -471,6 +474,7 @@ class TreeView extends React.Component { moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} + pinToPres={pinToPres} ScreenToLocalTransform={screenToLocalXf} outerXf={outerXf} parentKey={key} @@ -554,7 +558,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { render() { Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); let dropAction = StrCast(this.props.Document.dropAction) as dropActionType; - let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); + let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, !BoolCast(this.props.Document.stackingHeadersSortDescending)); let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); return !this.childDocs ? (null) : (
      ([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; - Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true); + Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, !BoolCast(this.props.Document.stackingHeadersSortDescending)); }} /> {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)} {this.props.Document.allowClear ? this.renderClearButton : (null)}
        { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, - moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth) + moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth) }
      diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 3193f5624..b8148852d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -55,7 +55,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void; zoomToScale: (scale: number) => void; backgroundColor: (doc: Doc) => string | undefined; @@ -614,7 +614,7 @@ export class DocumentView extends DocComponent(Docu let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; analyzers.push({ description: "Transcribe Speech", event: this.listen, icon: "microphone" }); !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); - cm.addItem({ description: "Pin to Presentation", event: () => PresBox.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! + 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: () => { @@ -661,7 +661,7 @@ export class DocumentView extends DocComponent(Docu try { let stuff = await rp.get(Utils.prepend(RouteStore.getUsers)); const users: User[] = JSON.parse(stuff); - usersMenu = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({ + usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({ description: email, event: async () => { const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc); if (!userDocument) { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index cae975f30..f0f1b3b73 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -39,6 +39,7 @@ export interface FieldViewProps { selectOnLoad: boolean; addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; removeDocument?: (document: Doc) => boolean; moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 534a42efc..8001b24a7 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -68,6 +68,7 @@ export class KeyValuePair extends React.Component { PanelWidth: returnZero, PanelHeight: returnZero, addDocTab: returnZero, + pinToPres: returnZero, ContentScaling: returnOne }; let contents = ; diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 1a4af04f8..1908889e9 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -12,9 +12,6 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; library.add(faTrash); -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { DocumentType } from "../../documents/Documents"; interface Props { docView: DocumentView; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 91c141540..cc042e008 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,22 +1,22 @@ import React = require("react"); -import { FieldViewProps, FieldView } from './FieldView'; +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, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { observable, action, runInAction, reaction, autorun, computed } from "mobx"; -import "../presentationview/PresentationView.scss"; -import { DocumentManager } from "../../util/DocumentManager"; -import { Utils } from "../../../Utils"; import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils } from "../../../Utils"; +import { DocumentManager } from "../../util/DocumentManager"; +import { undoBatch } from "../../util/UndoManager"; import PresentationElement, { buttonIndex } from "../presentationview/PresentationElement"; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit } from '@fortawesome/free-solid-svg-icons'; -import { Docs } from "../../documents/Documents"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; import PresentationViewList from "../presentationview/PresentationList"; +import "../presentationview/PresentationView.scss"; +import { FieldView, FieldViewProps } from './FieldView'; +import { ContextMenu } from "../ContextMenu"; library.add(faArrowLeft); library.add(faArrowRight); @@ -37,17 +37,14 @@ const expandedWidth = 450; @observer export class PresBox extends React.Component { //FieldViewProps? - @computed - private get presentationDocs() { - let source = Doc.GetProto(this.props.Document); - return DocListCast(source.data); - } public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } - //public static Instance: PresentationView; public static Instance: PresBox; + //Keeping track of the doc for the current presentation -- bcz: keeping a list of current presentations shouldn't be needed. Let users create them, store them, as they see fit. + @computed get curPresentation() { return this.props.Document; } + //Mapping from presentation ids to a list of doc that represent a group @observable groupMappings: Map = new Map(); //mapping from docs to their rendered component @@ -59,9 +56,6 @@ export class PresBox extends React.Component { //FieldViewProps? //back-up so that presentation stays the way it's when refreshed @observable presGroupBackUp: Doc = new Doc(); @observable presButtonBackUp: Doc = new Doc(); - - //Keeping track of the doc for the current presentation - @observable curPresentation: Doc = new Doc(); //Mapping of guids to presentations. @observable presentationsMapping: Map = new Map(); //Mapping of presentations to guid, so that select option values can be given. @@ -77,12 +71,14 @@ export class PresBox extends React.Component { //FieldViewProps? @observable opacity = 1; @observable persistOpacity = true; @observable labelOpacity = 0; + @observable presMode = false; + + @observable public static CurrentPresentation: PresBox; //initilize class variables - constructor(props: FieldViewProps) { //FieldViewProps? + constructor(props: FieldViewProps) { super(props); - //PresentationView.Instance = this; - PresBox.Instance = this; + runInAction(() => PresBox.CurrentPresentation = this); } @action @@ -94,32 +90,10 @@ export class PresBox extends React.Component { //FieldViewProps? } } - //The first lifecycle function that gets called to set up the current presentation. - async componentWillMount() { - this.presentationDocs.forEach(async (doc, index: number) => { - - //For each presentation received from mainContainer, a mapping is created. - let curDoc: Doc = await doc; - let newGuid = Utils.GenerateGuid(); - this.presentationsKeyMapping.set(curDoc, newGuid); - this.presentationsMapping.set(newGuid, curDoc); - - //The Presentation at first index gets set as default start presentation - if (index === 0) { - runInAction(() => this.currentSelectedPresValue = newGuid); - runInAction(() => this.curPresentation = curDoc); - } - }); - } - - //Second lifecycle function that gets called when component mounts. It makes sure to + //Second lifecycle function that gets called when component mounts. It makes sure toS //get the back-up information from previous session for the current presentation. async componentDidMount() { - let docAtZero = await this.presentationDocs[0]; - runInAction(() => this.curPresentation = docAtZero); - this.setPresentationBackUps(); - } @@ -212,7 +186,6 @@ export class PresBox extends React.Component { //FieldViewProps? //observable means render is re-called every time variable is changed @observable collapsed: boolean = false; - closePresentation = action(() => this.curPresentation.width = 0); next = async () => { const current = NumCast(this.curPresentation.selectedDoc); //asking to get document at current index @@ -556,23 +529,6 @@ export class PresBox extends React.Component { //FieldViewProps? runInAction(() => this.groupMappings = new Map()); } - /** - * Adds a document to the presentation view - **/ - @undoBatch - @action - public PinDoc(doc: Doc) { - //add this new doc to props.Document - const data = Cast(this.curPresentation.data, listSpec(Doc)); - if (data) { - data.push(doc); - } else { - this.curPresentation.data = new List([doc]); - } - - this.toggle(true); - } - //Function that sets the store of the children docs. @action setChildrenDocs = (docList: Doc[]) => { @@ -648,72 +604,15 @@ export class PresBox extends React.Component { //FieldViewProps? } - /** - * The function that is called to add a new presentation to the presentationView. - * It sets up te mappings and local copies of it. Resets the groupings and presentation. - * Makes the new presentation current selected, and retrieve the back-Ups if present. - */ - @action - addNewPresentation = (presTitle: string) => { - //creating a new presentation doc - let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle }); - let presDocs = Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc)); - presDocs && presDocs.push(newPresentationDoc); - - //setting that new doc as current - this.curPresentation = newPresentationDoc; - - //storing the doc in local copies for easier access - let newGuid = Utils.GenerateGuid(); - this.presentationsMapping.set(newGuid, newPresentationDoc); - this.presentationsKeyMapping.set(newPresentationDoc, newGuid); - - //resetting the previous presentation's actions so that new presentation can be loaded. - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = newGuid; - this.setPresentationBackUps(); - - } - - /** - * The function that is called to change the current selected presentation. - * Changes the presentation, also resetting groupings and presentation in process. - * Plus retrieving the backUps for the newly selected presentation. - */ - @action - getSelectedPresentation = (e: React.ChangeEvent) => { - //get the guid of the selected presentation - let selectedGuid = e.target.value; - //set that as current presentation - this.curPresentation = this.presentationsMapping.get(selectedGuid)!; - - //reset current Presentations local things so that new one can be loaded - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = selectedGuid; - this.setPresentationBackUps(); - - - } /** * The function that is called to render either select for presentations, or title inputting. */ renderSelectOrPresSelection = () => { - let presentationList = this.presentationDocs; if (this.PresTitleInputOpen || this.PresTitleChangeOpen) { return this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />; } else { - return ; + return (null); } } @@ -726,87 +625,12 @@ export class PresBox extends React.Component { //FieldViewProps? if (e.keyCode === 13) { let presTitle = this.titleInputElement!.value; this.titleInputElement!.value = ""; - if (this.PresTitleInputOpen) { - if (presTitle === "") { - presTitle = "Presentation"; - } - this.PresTitleInputOpen = false; - this.addNewPresentation(presTitle); - } else if (this.PresTitleChangeOpen) { + if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; this.changePresentationTitle(presTitle); } } } - - /** - * The function that is called to remove a presentation from all its copies, and the main Container's - * list. Sets up the next presentation as current. - */ - @action - removePresentation = async () => { - if (this.presentationsMapping.size !== 1) { - let presentationList = this.presentationDocs; - let batch = UndoManager.StartBatch("presRemoval"); - - //getting the presentation that will be removed - let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!); - //that presentation is removed - presentationList!.splice(presentationList.indexOf(removedDoc!), 1); - - //its mappings are removed from local copies - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(this.currentSelectedPresValue!); - - //the next presentation is set as current - let remainingPresentations = this.presentationsMapping.values(); - let nextDoc = remainingPresentations.next().value; - this.curPresentation = nextDoc; - - - //Storing these for being able to undo changes - let curGuid = this.currentSelectedPresValue!; - let curPresStatus = this.presStatus; - - //resetting the groups and presentation actions so that next presentation gets loaded - this.resetGroupIds(); - this.resetPresentation(); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - //Storing for undo - let currentGroups = this.groupMappings; - let curPresElemMapping = this.presElementsMappings; - - //Event to undo actions that are not related to doc directly, aka. local things - UndoManager.AddEvent({ - undo: action(() => { - this.curPresentation = removedDoc!; - this.presentationsMapping.set(curGuid, removedDoc!); - this.presentationsKeyMapping.set(removedDoc!, curGuid); - this.currentSelectedPresValue = curGuid; - - this.presStatus = curPresStatus; - this.groupMappings = currentGroups; - this.presElementsMappings = curPresElemMapping; - this.setPresentationBackUps(); - - }), - redo: action(() => { - this.curPresentation = nextDoc; - this.presStatus = false; - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(curGuid); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - }), - }); - - batch.end(); - } - } - /** * The function that is called to change title of presentation to what user entered. */ @@ -822,26 +646,23 @@ export class PresBox extends React.Component { //FieldViewProps? this.presElementsMappings.set(keyDoc, elem); } + specificContextMenu = (e: React.MouseEvent): void => { + ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" }); + ContextMenu.Instance.addItem({ + description: "Toggle Minimized Mode", event: action(() => { + this.presMode = !this.presMode; + if (this.presMode) this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); + }), icon: "asterisk" + }); + } render() { let width = "100%"; //NumCast(this.curPresentation.width) - return ( -
      !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease", pointerEvents: "all" }}> -
      - {this.renderSelectOrPresSelection()} - {/**this.closePresentation CLICK does not work?! Also without the*/} - - - -
      +
      !this.persistOpacity && (this.opacity = 1))} onContextMenu={this.specificContextMenu} + onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} + style={{ width: width, opacity: this.opacity, }}>
      {this.renderPlayPauseButton()} diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 7ba7b6d14..6f77a0a5b 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -6,10 +6,10 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { DocumentManager } from "../../util/DocumentManager"; -import { PresentationView } from "../presentationview/PresentationView"; import PDFMenu from "./PDFMenu"; import "./Annotation.scss"; import { scale } from "./PDFViewer"; +import { PresBox } from "../nodes/PresBox"; interface IAnnotationProps { anno: Doc; @@ -18,6 +18,7 @@ interface IAnnotationProps { fieldExtensionDoc: Doc; scrollTo?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; } export default class Annotation extends React.Component { @@ -37,6 +38,7 @@ interface IRegionAnnotationProps { fieldExtensionDoc: Doc; scrollTo?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; document: Doc; } @@ -81,7 +83,7 @@ class RegionAnnotation extends React.Component { pinToPres = () => { let group = FieldValue(Cast(this.props.document.group, Doc)); - group && PresentationView.Instance.PinDoc(group); + group && this.props.pinToPres(group); } @action diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 08674720d..258e218f0 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -36,6 +36,7 @@ interface IViewerProps { active: () => boolean; setPanY?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; } diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index d98b66324..912970a50 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -15,7 +15,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { ContextMenu } from "../ContextMenu"; import { Transform } from "../../util/Transform"; import { DocumentView } from "../nodes/DocumentView"; -import { DocumentType } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import React = require("react"); @@ -839,6 +839,7 @@ export default class PresentationElement extends React.Component 350} PanelHeight={() => 90} diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx index e853c4070..288ade042 100644 --- a/src/client/views/presentationview/PresentationList.tsx +++ b/src/client/views/presentationview/PresentationList.tsx @@ -6,10 +6,7 @@ import { Utils } from "../../../Utils"; import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { NumCast, StrCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; -import PresentationElement, { buttonIndex } from "./PresentationElement"; -import "../../../new_fields/Doc"; - - +import PresentationElement from "./PresentationElement"; interface PresListProps { diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index 65b09c833..4f5858f20 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -7,7 +7,9 @@ top: 0; bottom: 0; letter-spacing: 2px; - + overflow: hidden; + transition: 0.7s opacity ease; + pointer-events: all; } .presentationView-item { diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx deleted file mode 100644 index bea70f00b..000000000 --- a/src/client/views/presentationview/PresentationView.tsx +++ /dev/null @@ -1,994 +0,0 @@ -import { observer } from "mobx-react"; -import React = require("react"); -import { observable, action, runInAction, reaction, autorun } from "mobx"; -import "./PresentationView.scss"; -import { DocumentManager } from "../../util/DocumentManager"; -import { Utils } from "../../../Utils"; -import { Doc, DocListCast, DocListCastAsync, WidthSym } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import PresentationElement, { buttonIndex } from "./PresentationElement"; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit, faEye } from '@fortawesome/free-solid-svg-icons'; -import { Docs } from "../../documents/Documents"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import PresentationViewList from "./PresentationList"; -import PresModeMenu from "./PresentationModeMenu"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; - -library.add(faArrowLeft); -library.add(faArrowRight); -library.add(faPlay); -library.add(faStop); -library.add(faPlus); -library.add(faTimes); -library.add(faMinus); -library.add(faEdit); -library.add(faEye); - - -export interface PresViewProps { - Documents: List; -} - -const expandedWidth = 400; -const presMinWidth = 300; - -@observer -export class PresentationView extends React.Component { - public static Instance: PresentationView; - - //Mapping from presentation ids to a list of doc that represent a group - @observable groupMappings: Map = new Map(); - //mapping from docs to their rendered component - @observable presElementsMappings: Map = new Map(); - //variable that holds all the docs in the presentation - @observable childrenDocs: Doc[] = []; - //variable to hold if presentation is started - @observable presStatus: boolean = false; - //back-up so that presentation stays the way it's when refreshed - @observable presGroupBackUp: Doc = new Doc(); - @observable presButtonBackUp: Doc = new Doc(); - - //Keeping track of the doc for the current presentation - @observable curPresentation: Doc = new Doc(); - //Mapping of guids to presentations. - @observable presentationsMapping: Map = new Map(); - //Mapping of presentations to guid, so that select option values can be given. - @observable presentationsKeyMapping: Map = new Map(); - //Variable to keep track of guid of the current presentation - @observable currentSelectedPresValue: string | undefined; - //A flag to keep track if title input is open, which is used in rendering. - @observable PresTitleInputOpen: boolean = false; - //Variable that holds reference to title input, so that new presentations get titles assigned. - @observable titleInputElement: HTMLInputElement | undefined; - @observable PresTitleChangeOpen: boolean = false; - @observable presMode: boolean = false; - - - @observable opacity = 1; - @observable persistOpacity = true; - @observable labelOpacity = 0; - - //initilize class variables - constructor(props: PresViewProps) { - super(props); - PresentationView.Instance = this; - } - - @action - toggle = (forcedValue: boolean | undefined) => { - if (forcedValue !== undefined) { - this.curPresentation.width = forcedValue ? expandedWidth : 0; - } else { - this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth; - } - } - - //The first lifecycle function that gets called to set up the current presentation. - async componentWillMount() { - - this.props.Documents.forEach(async (doc, index: number) => { - - //For each presentation received from mainContainer, a mapping is created. - let curDoc: Doc = await doc; - let newGuid = Utils.GenerateGuid(); - this.presentationsKeyMapping.set(curDoc, newGuid); - this.presentationsMapping.set(newGuid, curDoc); - - //The Presentation at first index gets set as default start presentation - if (index === 0) { - runInAction(() => this.currentSelectedPresValue = newGuid); - runInAction(() => this.curPresentation = curDoc); - } - }); - } - - //Second lifecycle function that gets called when component mounts. It makes sure to - //get the back-up information from previous session for the current presentation. - async componentDidMount() { - let docAtZero = await this.props.Documents[0]; - runInAction(() => this.curPresentation = docAtZero); - - this.setPresentationBackUps(); - - } - - - /** - * The function that retrieves the backUps for the current Presentation if present, - * otherwise initializes. - */ - setPresentationBackUps = async () => { - //getting both backUp documents - - let castedGroupBackUp = Cast(this.curPresentation.presGroupBackUp, Doc); - let castedButtonBackUp = Cast(this.curPresentation.presButtonBackUp, Doc); - //if instantiated before - if (castedGroupBackUp instanceof Promise) { - castedGroupBackUp.then(doc => { - let toAssign = doc ? doc : new Doc(); - this.curPresentation.presGroupBackUp = toAssign; - runInAction(() => this.presGroupBackUp = toAssign); - if (doc) { - if (toAssign[Id] === doc[Id]) { - this.retrieveGroupMappings(); - } - } - }); - - //if never instantiated a store doc yet - } else if (castedGroupBackUp instanceof Doc) { - let castedDoc: Doc = await castedGroupBackUp; - runInAction(() => this.presGroupBackUp = castedDoc); - this.retrieveGroupMappings(); - } else { - runInAction(() => { - let toAssign = new Doc(); - this.presGroupBackUp = toAssign; - this.curPresentation.presGroupBackUp = toAssign; - - }); - - } - //if instantiated before - if (castedButtonBackUp instanceof Promise) { - castedButtonBackUp.then(doc => { - let toAssign = doc ? doc : new Doc(); - this.curPresentation.presButtonBackUp = toAssign; - runInAction(() => this.presButtonBackUp = toAssign); - }); - - //if never instantiated a store doc yet - } else if (castedButtonBackUp instanceof Doc) { - let castedDoc: Doc = await castedButtonBackUp; - runInAction(() => this.presButtonBackUp = castedDoc); - - } else { - runInAction(() => { - let toAssign = new Doc(); - this.presButtonBackUp = toAssign; - this.curPresentation.presButtonBackUp = toAssign; - }); - - } - - - //storing the presentation status,ie. whether it was stopped or playing - let presStatusBackUp = BoolCast(this.curPresentation.presStatus); - runInAction(() => this.presStatus = presStatusBackUp); - } - - /** - * This is the function that is called to retrieve the groups that have been stored and - * push them to the groupMappings. - */ - retrieveGroupMappings = async () => { - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs !== undefined) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - let castedKey = StrCast(groupDoc.presentIdStore, null); - if (castedGrouping) { - castedGrouping.forEach((doc: Doc) => { - doc.presentId = castedKey; - }); - } - if (castedGrouping !== undefined && castedKey !== undefined) { - this.groupMappings.set(castedKey, castedGrouping); - } - }); - } - } - - //observable means render is re-called every time variable is changed - @observable - collapsed: boolean = false; - closePresentation = action(() => this.curPresentation.width = 0); - next = async () => { - const current = NumCast(this.curPresentation.selectedDoc); - //asking to get document at current index - let docAtCurrentNext = await this.getDocAtIndex(current + 1); - if (docAtCurrentNext === undefined) { - return; - } - //asking for it's presentation id - let curNextPresId = StrCast(docAtCurrentNext.presentId); - let nextSelected = current + 1; - - //if curDoc is in a group, selection slides until last one, if not it's next one - if (this.groupMappings.has(curNextPresId)) { - let currentsArray = this.groupMappings.get(StrCast(docAtCurrentNext.presentId))!; - nextSelected = current + currentsArray.length - currentsArray.indexOf(docAtCurrentNext); - - //end of grup so go beyond - if (nextSelected === current) nextSelected = current + 1; - } - - this.gotoDocument(nextSelected, current); - - } - back = async () => { - const current = NumCast(this.curPresentation.selectedDoc); - //requesting for the doc at current index - let docAtCurrent = await this.getDocAtIndex(current); - if (docAtCurrent === undefined) { - return; - } - - //asking for its presentation id. - let curPresId = StrCast(docAtCurrent.presentId); - let prevSelected = current - 1; - let zoomOut: boolean = false; - - //checking if this presentation id is mapped to a group, if so chosing the first element in group - if (this.groupMappings.has(curPresId)) { - let currentsArray = this.groupMappings.get(StrCast(docAtCurrent.presentId))!; - prevSelected = current - currentsArray.length + (currentsArray.length - currentsArray.indexOf(docAtCurrent)) - 1; - //end of grup so go beyond - if (prevSelected === current) prevSelected = current - 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)!.selected[buttonIndex.Show]) { - zoomOut = true; - return; - } - }); - - } - - // 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)!.selected[buttonIndex.Show]) { - let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null); - let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]); - if (prevScale !== undefined) { - if (prevScale !== curScale) { - DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); - } - } - } - } - this.gotoDocument(prevSelected, current); - - } - - /** - * This is the method that checks for the actions that need to be performed - * after the document has been presented, which involves 3 button options: - * Hide Until Presented, Hide After Presented, Fade After Presented - */ - showAfterPresented = (index: number) => { - this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { - let selectedButtons: boolean[] = presElem.selected; - //the order of cases is aligned based on priority - if (selectedButtons[buttonIndex.HideTillPressed]) { - if (this.childrenDocs.indexOf(key) <= index) { - key.opacity = 1; - } - } - if (selectedButtons[buttonIndex.HideAfter]) { - if (this.childrenDocs.indexOf(key) < index) { - key.opacity = 0; - } - } - if (selectedButtons[buttonIndex.FadeAfter]) { - if (this.childrenDocs.indexOf(key) < index) { - key.opacity = 0.5; - } - } - }); - } - - /** - * This is the method that checks for the actions that need to be performed - * before the document has been presented, which involves 3 button options: - * Hide Until Presented, Hide After Presented, Fade After Presented - */ - hideIfNotPresented = (index: number) => { - this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { - let selectedButtons: boolean[] = presElem.selected; - - //the order of cases is aligned based on priority - - if (selectedButtons[buttonIndex.HideAfter]) { - if (this.childrenDocs.indexOf(key) >= index) { - key.opacity = 1; - } - } - if (selectedButtons[buttonIndex.FadeAfter]) { - if (this.childrenDocs.indexOf(key) >= index) { - key.opacity = 1; - } - } - if (selectedButtons[buttonIndex.HideTillPressed]) { - if (this.childrenDocs.indexOf(key) > index) { - key.opacity = 0; - } - } - }); - } - - /** - * This method makes sure that cursor navigates to the element that - * has the option open and last in the group. If not in the group, and it has - * te option open, navigates to that element. - */ - navigateToElement = async (curDoc: Doc, fromDoc: number) => { - let docToJump: Doc = curDoc; - let curDocPresId = StrCast(curDoc.presentId, null); - let willZoom: boolean = false; - - //checking if in group - if (curDocPresId !== undefined) { - if (this.groupMappings.has(curDocPresId)) { - let currentDocGroup = this.groupMappings.get(curDocPresId)!; - currentDocGroup.forEach((doc: Doc, index: number) => { - let selectedButtons: boolean[] = this.presElementsMappings.get(doc)!.selected; - if (selectedButtons[buttonIndex.Navigate]) { - docToJump = doc; - willZoom = false; - } - if (selectedButtons[buttonIndex.Show]) { - docToJump = doc; - willZoom = true; - } - }); - } - - } - //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 curDocButtons = this.presElementsMappings.get(curDoc)!.selected; - if (curDocButtons[buttonIndex.Navigate]) { - this.jumpToTabOrRight(curDocButtons, curDoc); - } else if (curDocButtons[buttonIndex.Show]) { - let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]); - if (curDocButtons[buttonIndex.OpenRight]) { - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(curDoc, true); - } else { - await DocumentManager.Instance.jumpToDocument(curDoc, true, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); - } - - let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); - curDoc.viewScale = newScale; - - //saving the scale user was on before zooming in - if (curScale !== 1) { - this.childrenDocs[fromDoc].viewScale = curScale; - } - - } - return; - } - let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]); - let curDocButtons = this.presElementsMappings.get(docToJump)!.selected; - - - if (curDocButtons[buttonIndex.OpenRight]) { - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(docToJump, willZoom); - } else { - await DocumentManager.Instance.jumpToDocument(docToJump, willZoom, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); - } - let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); - curDoc.viewScale = newScale; - //saving the scale that user was on - if (curScale !== 1) { - this.childrenDocs[fromDoc].viewScale = curScale; - } - - } - - /** - * This function checks if right option is clicked on a presentation element, if not it does open it as a tab - * with help of CollectionDockingView. - */ - jumpToTabOrRight = (curDocButtons: boolean[], curDoc: Doc) => { - if (curDocButtons[buttonIndex.OpenRight]) { - DocumentManager.Instance.jumpToDocument(curDoc, false); - } else { - DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); - } - } - - /** - * Async function that supposedly return the doc that is located at given index. - */ - getDocAtIndex = async (index: number) => { - const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (!list) { - return undefined; - } - if (index < 0 || index >= list.length) { - return undefined; - } - - this.curPresentation.selectedDoc = index; - //awaiting async call to finish to get Doc instance - const doc = await list[index]; - return doc; - } - - /** - * The function that removes a doc from a presentation. It also makes sure to - * do necessary updates to backUps and mappings stored locally. - */ - @action - public RemoveDoc = async (index: number) => { - const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (value) { - let removedDoc = await value.splice(index, 1)[0]; - - //removing the Presentation Element stored for it - this.presElementsMappings.delete(removedDoc); - - let removedDocPresentId = StrCast(removedDoc.presentId); - - //Removing it from local mapping of the groups - if (this.groupMappings.has(removedDocPresentId)) { - let removedDocsGroup = this.groupMappings.get(removedDocPresentId); - if (removedDocsGroup) { - removedDocsGroup.splice(removedDocsGroup.indexOf(removedDoc), 1); - if (removedDocsGroup.length === 0) { - this.groupMappings.delete(removedDocPresentId); - } - } - } - - - let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - if (castedList) { - for (let doc of castedList) { - let curDoc = await doc; - let curDocId = StrCast(curDoc.docId); - if (curDocId === removedDoc[Id]) { - castedList.splice(castedList.indexOf(curDoc), 1); - break; - - } - } - } - - //removing it from the backup of groups - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedKey = StrCast(groupDoc.presentIdStore, null); - if (castedKey === removedDocPresentId) { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - if (castedGrouping) { - castedGrouping.splice(castedGrouping.indexOf(removedDoc), 1); - if (castedGrouping.length === 0) { - castedGroupDocs!.splice(castedGroupDocs!.indexOf(groupDoc), 1); - } - } - } - - }); - - } - - - } - } - - /** - * An alternative remove method that removes a doc from presentation by its actual - * reference. - */ - public removeDocByRef = (doc: Doc) => { - let indexOfDoc = this.childrenDocs.indexOf(doc); - const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (value) { - value.splice(indexOfDoc, 1)[0]; - } - if (indexOfDoc !== - 1) { - return true; - } - return false; - } - - //The function that is called when a document is clicked or reached through next or back. - //it'll also execute the necessary actions if presentation is playing. - @action - public gotoDocument = async (index: number, fromDoc: number) => { - const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (!list) { - return; - } - if (index < 0 || index >= list.length) { - return; - } - this.curPresentation.selectedDoc = index; - - if (!this.presStatus) { - this.presStatus = true; - this.startPresentation(index); - } - - const doc = await list[index]; - if (this.presStatus) { - this.navigateToElement(doc, fromDoc); - this.hideIfNotPresented(index); - this.showAfterPresented(index); - } - - } - - //Function that is called to resetGroupIds, so that documents get new groupIds at - //first load, when presentation is changed. - resetGroupIds = async () => { - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs !== undefined) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - if (castedGrouping) { - castedGrouping.forEach((doc: Doc) => { - doc.presentId = Utils.GenerateGuid(); - }); - } - }); - } - runInAction(() => this.groupMappings = new Map()); - } - - /** - * Adds a document to the presentation view - **/ - @undoBatch - @action - public PinDoc(doc: Doc) { - //add this new doc to props.Document - const data = Cast(this.curPresentation.data, listSpec(Doc)); - if (data) { - data.push(doc); - } else { - this.curPresentation.data = new List([doc]); - } - - this.toggle(true); - } - - //Function that sets the store of the children docs. - @action - setChildrenDocs = (docList: Doc[]) => { - this.childrenDocs = docList; - } - - //The function that is called to render the play or pause button depending on - //if presentation is running or not. - renderPlayPauseButton = () => { - if (this.presStatus) { - return ; - } else { - return ; - } - } - - //The function that starts or resets presentaton functionally, depending on status flag. - @action - startOrResetPres = async () => { - if (this.presStatus) { - this.resetPresentation(); - } else { - this.presStatus = true; - let startIndex = await this.findStartDocument(); - this.startPresentation(startIndex); - const current = NumCast(this.curPresentation.selectedDoc); - this.gotoDocument(startIndex, current); - } - this.curPresentation.presStatus = this.presStatus; - } - - /** - * This method is called to find the start document of presentation. So - * that when user presses on play, the correct presentation element will be - * selected. - */ - findStartDocument = async () => { - let docAtZero = await this.getDocAtIndex(0); - if (docAtZero === undefined) { - return 0; - } - let docAtZeroPresId = StrCast(docAtZero.presentId); - - if (this.groupMappings.has(docAtZeroPresId)) { - let group = this.groupMappings.get(docAtZeroPresId)!; - let lastDoc = group[group.length - 1]; - return this.childrenDocs.indexOf(lastDoc); - } else { - return 0; - } - } - - //The function that resets the presentation by removing every action done by it. It also - //stops the presentaton. - @action - resetPresentation = () => { - this.childrenDocs.forEach((doc: Doc) => { - doc.opacity = 1; - doc.viewScale = 1; - }); - this.curPresentation.selectedDoc = 0; - this.presStatus = false; - this.curPresentation.presStatus = this.presStatus; - if (this.childrenDocs.length === 0) { - return; - } - DocumentManager.Instance.zoomIntoScale(this.childrenDocs[0], 1); - } - - - //The function that starts the presentation, also checking if actions should be applied - //directly at start. - startPresentation = (startIndex: number) => { - let selectedButtons: boolean[]; - this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => { - selectedButtons = component.selected; - if (selectedButtons[buttonIndex.HideTillPressed]) { - if (this.childrenDocs.indexOf(doc) > startIndex) { - doc.opacity = 0; - } - - } - if (selectedButtons[buttonIndex.HideAfter]) { - if (this.childrenDocs.indexOf(doc) < startIndex) { - doc.opacity = 0; - } - } - if (selectedButtons[buttonIndex.FadeAfter]) { - if (this.childrenDocs.indexOf(doc) < startIndex) { - doc.opacity = 0.5; - } - } - - }); - - } - - /** - * The function that is called to add a new presentation to the presentationView. - * It sets up te mappings and local copies of it. Resets the groupings and presentation. - * Makes the new presentation current selected, and retrieve the back-Ups if present. - */ - @action - addNewPresentation = (presTitle: string) => { - //creating a new presentation doc - let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle }); - this.props.Documents.push(newPresentationDoc); - - //setting that new doc as current - this.curPresentation = newPresentationDoc; - - //storing the doc in local copies for easier access - let newGuid = Utils.GenerateGuid(); - this.presentationsMapping.set(newGuid, newPresentationDoc); - this.presentationsKeyMapping.set(newPresentationDoc, newGuid); - - //resetting the previous presentation's actions so that new presentation can be loaded. - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = newGuid; - this.setPresentationBackUps(); - - } - - /** - * The function that is called to change the current selected presentation. - * Changes the presentation, also resetting groupings and presentation in process. - * Plus retrieving the backUps for the newly selected presentation. - */ - @action - getSelectedPresentation = (e: React.ChangeEvent) => { - //get the guid of the selected presentation - let selectedGuid = e.target.value; - //set that as current presentation - this.curPresentation = this.presentationsMapping.get(selectedGuid)!; - - //reset current Presentations local things so that new one can be loaded - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = selectedGuid; - this.setPresentationBackUps(); - - - } - - /** - * The function that is called to render either select for presentations, or title inputting. - */ - renderSelectOrPresSelection = () => { - let presentationList = DocListCast(this.props.Documents); - if (this.PresTitleInputOpen || this.PresTitleChangeOpen) { - return this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />; - } else { - return ; - } - } - - /** - * The function that is called on enter press of title input. It gives the - * new presentation the title user entered. If nothing is entered, gives a default title. - */ - @action - submitPresentationTitle = (e: React.KeyboardEvent) => { - if (e.keyCode === 13) { - let presTitle = this.titleInputElement!.value; - this.titleInputElement!.value = ""; - if (this.PresTitleInputOpen) { - if (presTitle === "") { - presTitle = "Presentation"; - } - this.PresTitleInputOpen = false; - this.addNewPresentation(presTitle); - } else if (this.PresTitleChangeOpen) { - this.PresTitleChangeOpen = false; - this.changePresentationTitle(presTitle); - } - } - } - - /** - * The function that is called to remove a presentation from all its copies, and the main Container's - * list. Sets up the next presentation as current. - */ - @action - removePresentation = async () => { - if (this.presentationsMapping.size !== 1) { - let presentationList = Cast(this.props.Documents, listSpec(Doc)); - let batch = UndoManager.StartBatch("presRemoval"); - - //getting the presentation that will be removed - let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!); - //that presentation is removed - presentationList!.splice(presentationList!.indexOf(removedDoc!), 1); - - //its mappings are removed from local copies - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(this.currentSelectedPresValue!); - - //the next presentation is set as current - let remainingPresentations = this.presentationsMapping.values(); - let nextDoc = remainingPresentations.next().value; - this.curPresentation = nextDoc; - - - //Storing these for being able to undo changes - let curGuid = this.currentSelectedPresValue!; - let curPresStatus = this.presStatus; - - //resetting the groups and presentation actions so that next presentation gets loaded - this.resetGroupIds(); - this.resetPresentation(); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - //Storing for undo - let currentGroups = this.groupMappings; - let curPresElemMapping = this.presElementsMappings; - - //Event to undo actions that are not related to doc directly, aka. local things - UndoManager.AddEvent({ - undo: action(() => { - this.curPresentation = removedDoc!; - this.presentationsMapping.set(curGuid, removedDoc!); - this.presentationsKeyMapping.set(removedDoc!, curGuid); - this.currentSelectedPresValue = curGuid; - - this.presStatus = curPresStatus; - this.groupMappings = currentGroups; - this.presElementsMappings = curPresElemMapping; - this.setPresentationBackUps(); - - }), - redo: action(() => { - this.curPresentation = nextDoc; - this.presStatus = false; - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(curGuid); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - }), - }); - - batch.end(); - } - } - - /** - * The function that is called to change title of presentation to what user entered. - */ - @undoBatch - changePresentationTitle = (newTitle: string) => { - if (newTitle === "") { - return; - } - this.curPresentation.title = newTitle; - } - - /** - * On pointer down element that is catched on resizer of te - * presentation view. Sets up the event listeners to change the size with - * mouse move. - */ - _downsize = 0; - onPointerDown = (e: React.PointerEvent) => { - this._downsize = e.clientX; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - e.preventDefault(); - } - /** - * Changes the size of the presentation view, with mouse move. - * Minimum size is set to 300, so that every button is visible. - */ - @action - onPointerMove = (e: PointerEvent) => { - - this.curPresentation.width = Math.max(window.innerWidth - e.clientX, presMinWidth); - } - - /** - * The method that is called on pointer up event. It checks if the button is just - * clicked so that presentation view will be closed. The way it's done is to check - * for minimal pixel change like 4, and accept it as it's just a click on top of the dragger. - */ - @action - onPointerUp = (e: PointerEvent) => { - if (Math.abs(e.clientX - this._downsize) < 4) { - let presWidth = NumCast(this.curPresentation.width); - if (presWidth - presMinWidth !== 0) { - this.curPresentation.width = 0; - } - if (presWidth === 0) { - this.curPresentation.width = presMinWidth; - } - } - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } - - /** - * This function is a setter that opens up the - * presentation mode, by setting it's render flag - * to true. It also closes the presentation view. - */ - @action - openPresMode = () => { - if (!this.presMode) { - this.curPresentation.width = 0; - this.presMode = true; - } - } - - /** - * This function closes the presentation mode by setting its - * render flag to false. It also opens up the presentation view. - * By setting it to it's minimum size. - */ - @action - closePresMode = () => { - if (this.presMode) { - this.presMode = false; - this.curPresentation.width = presMinWidth; - } - - } - - /** - * Function that is called to render the presentation mode, depending on its flag. - */ - renderPresMode = () => { - if (this.presMode) { - return ; - } else { - return (null); - } - - } - - render() { - - let width = NumCast(this.curPresentation.width); - - return ( -
      -
      !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflowY: "scroll", overflowX: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}> -
      - {this.renderSelectOrPresSelection()} - - - - - -
      -
      - - {this.renderPlayPauseButton()} - -
      - - this.presElementsMappings.clear()} - /> - ) => { - 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.renderPresMode()} - -
      - ); - } -} diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 4bb2ed55b..41fc49c2e 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -203,6 +203,7 @@ export class SearchItem extends React.Component { removeDocument={returnFalse} ScreenToLocalTransform={Transform.Identity} addDocTab={returnFalse} + pinToPres={returnFalse} renderDepth={1} PanelWidth={returnXDimension} PanelHeight={returnYDimension} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index a8b616565..31f1f7a12 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -1,19 +1,18 @@ -import { observable, action, runInAction, ObservableMap } from "mobx"; -import { serializable, primitive, map, alias, list, PropSchema, custom } from "serializr"; -import { autoObject, SerializationHelper, Deserializable, afterDocDeserialize } from "../client/util/SerializationHelper"; +import { observable, ObservableMap, runInAction } from "mobx"; +import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; -import { setter, getter, getField, updateFunction, deleteProperty, makeEditable, makeReadOnly } from "./util"; -import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast, BoolCast, StrCast } from "./Types"; -import { listSpec } from "./Schema"; -import { ObjectField } from "./ObjectField"; -import { RefField, FieldId } from "./RefField"; -import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id, Copy } from "./FieldSymbols"; -import { scriptingGlobal, CompileScript, Scripting } from "../client/util/Scripting"; -import { List } from "./List"; import { DocumentType } from "../client/documents/DocumentTypes"; -import { ComputedField, ScriptField } from "./ScriptField"; +import { CompileScript, 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"; +import { ObjectField } from "./ObjectField"; import { PrefetchProxy, ProxyField } from "./Proxy"; -//import { CurrentUserUtils } from "../server/authentication/models/current_user_utils"; +import { FieldId, RefField } from "./RefField"; +import { listSpec } from "./Schema"; +import { ComputedField } from "./ScriptField"; +import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types"; +import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -72,6 +71,7 @@ export const HeightSym = Symbol("Height"); export const UpdatingFromServer = Symbol("UpdatingFromServer"); const CachedUpdates = Symbol("Cached updates"); + function fetchProto(doc: Doc) { const proto = doc.proto; if (proto instanceof Promise) { @@ -151,10 +151,10 @@ export class Doc extends RefField { } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; - + public static CurrentUserEmail: string = ""; public async [HandleUpdate](diff: any) { const set = diff.$set; - const sameAuthor = this.author === "foo@bar.com";//CurrentUserUtils.email; + const sameAuthor = this.author === Doc.CurrentUserEmail; if (set) { for (const key in set) { if (!key.startsWith("fields.")) { @@ -327,14 +327,12 @@ export namespace Doc { return Array.from(results); } - export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean) { + export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { if (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"); 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 +340,18 @@ export namespace Doc { } } if (first) { - console.log("is first"); list.splice(0, 0, doc); } else { - 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); + if (ind === -1) { + if (reversed) list.splice(0, 0, doc); + else list.push(doc); + } + else { + if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc); + else list.splice(before ? ind : ind + 1, 0, doc); + } } } return true; @@ -595,10 +596,14 @@ export namespace Doc { }); } - export class DocBrush { + + export class DocData { + @observable _user_doc: Doc = undefined!; @observable BrushedDoc: ObservableMap = new ObservableMap(); } - const manager = new DocBrush(); + const manager = new DocData(); + export function UserDoc(): Doc { return manager._user_doc; } + export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } export function IsBrushed(doc: Doc) { return manager.BrushedDoc.has(doc) || manager.BrushedDoc.has(Doc.GetDataDoc(doc)); } diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index c546e2aac..04194509c 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -7,7 +7,6 @@ import { ObjectField } from "./ObjectField"; import { action } from "mobx"; import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; -import { CurrentUserUtils } from "../server/authentication/models/current_user_utils"; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -61,7 +60,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } const writeMode = DocServer.getFieldWriteMode(prop as string); const fromServer = target[UpdatingFromServer]; - const sameAuthor = fromServer || (receiver.author === CurrentUserUtils.email); + const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail); const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly); const writeToServer = sameAuthor || (writeMode === DocServer.WriteMode.Default); if (writeToDoc) { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index f36f5b73d..508655605 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -10,20 +10,17 @@ import { CollectionView } from "../../../client/views/collections/CollectionView import { Doc } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; -import { RouteStore } from "../../RouteStore"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; +import { RouteStore } from "../../RouteStore"; export class CurrentUserUtils { - private static curr_email: string; private static curr_id: string; - @observable private static user_document: Doc; //TODO tfs: these should be temporary... private static mainDocId: string | undefined; - public static get email() { return this.curr_email; } public static get id() { return this.curr_id; } - @computed public static get UserDocument() { return this.user_document; } + @computed public static get UserDocument() { return Doc.UserDoc(); } public static get MainDocId() { return this.mainDocId; } public static set MainDocId(id: string | undefined) { this.mainDocId = id; } @@ -32,7 +29,7 @@ export class CurrentUserUtils { doc.viewType = CollectionViewType.Tree; doc.dropAction = "alias"; doc.layout = CollectionView.LayoutString(); - doc.title = this.email; + doc.title = Doc.CurrentUserEmail this.updateUserDocument(doc); doc.data = new List(); doc.gridGap = 5; @@ -58,6 +55,12 @@ export class CurrentUserUtils { recentlyClosed.boxShadow = "0 0"; doc.recentlyClosed = recentlyClosed; } + 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; @@ -85,15 +88,15 @@ export class CurrentUserUtils { public static async loadUserDocument({ id, email }: { id: string, email: string }) { this.curr_id = id; - this.curr_email = email; + Doc.CurrentUserEmail = email; await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => { if (id) { return DocServer.GetRefField(id).then(async field => { if (field instanceof Doc) { await this.updateUserDocument(field); - runInAction(() => this.user_document = field); + runInAction(() => Doc.SetUserDoc(field)); } else { - runInAction(() => this.user_document = this.createUserDocument(id)); + runInAction(() => Doc.SetUserDoc(this.createUserDocument(id))); } }); } else { -- cgit v1.2.3-70-g09d2 From 8225f3d2f6647b4163f7fe056af73c86f85c28d2 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 19 Aug 2019 17:00:59 -0400 Subject: cleaned up some buttons --- src/client/DocServer.ts | 2 + src/client/views/MainView.tsx | 45 +++------------------- .../views/collections/CollectionTreeView.tsx | 4 +- src/client/views/collections/CollectionView.tsx | 6 ++- src/client/views/nodes/DocumentView.tsx | 40 +++++++++++++------ 5 files changed, 43 insertions(+), 54 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index bf5168c22..2cec1046b 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -33,6 +33,8 @@ export namespace DocServer { LivePlayground = 3, } + export let AclsMode = WriteMode.Default; + const fieldWriteModes: { [field: string]: WriteMode } = {}; const docsWithUpdates: { [field: string]: Set } = {}; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7b7a5542d..b27b91c12 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faTable, faThumbtack, faTree, 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 } 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'; @@ -38,7 +38,6 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; -import { DocumentManager } from '../util/DocumentManager'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; @@ -172,7 +171,7 @@ export class MainView extends React.Component { library.add(faCat); library.add(faFilePdf); library.add(faObjectGroup); - library.add(faTable); + library.add(faTv); library.add(faGlobeAsia); library.add(faUndoAlt); library.add(faRedoAlt); @@ -452,30 +451,15 @@ export class MainView extends React.Component { let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "object-group", "Add Collection", addColNode], - [React.createRef(), "table", "Add Presentation Trail", addPresNode], + [React.createRef(), "tv", "Add Presentation Trail", addPresNode], [React.createRef(), "globe-asia", "Add Website", addWebNode], [React.createRef(), "bolt", "Add Button", addButtonDocument], - // [React.createRef(), "clone", "Add Docking Frame", addDockingNode], + [React.createRef(), "file", "Add Document Dragger", addDragboxNode], [React.createRef(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode //[React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], - [React.createRef(), "file", "Add Document Dragger", addDragboxNode] ]; if (!ClientUtils.RELEASE) btns.unshift([React.createRef(), "cat", "Add Cat Image", addImageNode]); - const setWriteMode = (mode: DocServer.WriteMode) => { - console.log(DocServer.WriteMode[mode]); - const mode1 = mode; - const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; - DocServer.setFieldWriteMode("x", mode1); - DocServer.setFieldWriteMode("y", mode1); - DocServer.setFieldWriteMode("width", mode1); - DocServer.setFieldWriteMode("height", mode1); - - DocServer.setFieldWriteMode("panX", mode2); - DocServer.setFieldWriteMode("panY", mode2); - DocServer.setFieldWriteMode("scale", mode2); - DocServer.setFieldWriteMode("viewType", mode2); - }; return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 20, bottom: 20 }} > @@ -484,7 +468,6 @@ export class MainView extends React.Component {
      • -
      • {btns.map(btn => @@ -493,13 +476,6 @@ export class MainView extends React.Component {
      )} -
    • - {ClientUtils.RELEASE ? [] : [ -
    • , -
    • , -
    • , -
    • - ]}
    • diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 077f3f941..70c0632d1 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -16,7 +16,7 @@ import { DragManager } from "../../util/DragManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; -import { FormattedTextBox } from "../nodes/FormattedTextBox"; +import { FormattedTextBox, Blank } from "../nodes/FormattedTextBox"; import { CollectionPDFView } from "./CollectionPDFView"; import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; @@ -206,7 +206,17 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 })); return; } - + let matches: RegExpExecArray | null; + if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) { + let newBox = Docs.Create.TextDocument({ ...options, width: 600, height: 400, title: "Fetching title from Google Docs..." }); + let proto = newBox.proto!; + proto.autoHeight = true; + proto.googleDocId = matches[2]; + proto.data = "Fetching contents from Google Docs..."; + proto.backgroundColor = "#eeeeff"; + this.props.addDocument(newBox); + return; + } let batch = UndoManager.StartBatch("collection view drop"); let promises: Promise[] = []; // tslint:disable-next-line:prefer-for-of diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index bc057bb5f..406c2c4a6 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx"; +import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace, untracked } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -47,6 +47,8 @@ library.add(faSmile, faTextHeight, faUpload); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // +export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; + export interface FormattedTextBoxProps { isOverlay?: boolean; hideOnLeave?: boolean; @@ -84,7 +86,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _proxyReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - private isOpening = false; + private isGoogleDocsUpdate = false; @observable _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; @@ -290,28 +292,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - componentWillMount() { - this.pollExportedCounterpart(); - } - - pollExportedCounterpart = async () => { - let dataDoc = Doc.GetProto(this.props.Document); - let documentId = StrCast(dataDoc[googleDocId]); - if (documentId) { - let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); - if (exportState) { - let data = Cast(dataDoc.data, RichTextField); - if (data) { - this.isOpening = true; - dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState)); - } - } else { - delete dataDoc[googleDocId]; - } - this.tryUpdateHeight(); - } - } - componentDidMount() { const config = { schema, @@ -347,23 +327,32 @@ 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 : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; + return field ? field.Data : Blank; }, incomingValue => { if (this._editorView && !this._applyingChange) { let updatedState = JSON.parse(incomingValue); this._editorView.updateState(EditorState.fromJSON(config, updatedState)); // manually sets cursor selection at the end of the text on focus - if (this.isOpening) { - this.isOpening = false; + if (this.isGoogleDocsUpdate) { + this.isGoogleDocsUpdate = false; let end = this._editorView.state.doc.content.size - 1; updatedState.selection = { type: "text", anchor: end, head: end }; this._editorView.updateState(EditorState.fromJSON(config, updatedState)); } + this.tryUpdateHeight(); } } ); + reaction(() => this.props.Document.pullFromGoogleDocsTrigger, () => { + this.pullFromGoogleDoc(); + }); + + reaction(() => this.props.Document.pushToGoogleDocsTrigger, () => { + this.pushToGoogleDoc(); + }); + this._textReactionDisposer = reaction( () => this.extensionDoc, () => { @@ -391,6 +380,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); + + ["pushToGoogleDocsTrigger", "pullFromGoogleDocsTrigger"].map(key => { + let doc = this.props.Document; + if (doc[key] === undefined) { + untracked(() => doc[key] = false); + } + }); } clipboardTextSerializer = (slice: Slice): string => { @@ -644,7 +640,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._undoTyping.end(); this._undoTyping = undefined; } - this.updateGoogleDoc(); } public _undoTyping?: UndoManager.Batch; onKeyPress = (e: React.KeyboardEvent) => { @@ -701,13 +696,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!(googleDocId in Doc.GetProto(this.props.Document))) { ContextMenu.Instance.addItem({ description: "Export to Google Doc...", - event: this.updateGoogleDoc, + event: this.pushToGoogleDoc, icon: "upload" }); } } - updateGoogleDoc = () => { + pushToGoogleDoc = () => { let modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; let reference: Opt = Cast(this.dataDoc[googleDocId], "string"); @@ -725,6 +720,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + pullFromGoogleDoc = async () => { + let dataDoc = Doc.GetProto(this.props.Document); + let documentId = StrCast(dataDoc[googleDocId]); + if (documentId) { + let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); + if (exportState && exportState.body && exportState.title) { + let data = Cast(dataDoc.data, RichTextField); + if (data) { + this.isGoogleDocsUpdate = true; + dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState.body)); + dataDoc.title = exportState.title; + } + } else { + delete dataDoc[googleDocId]; + } + } + } + + render() { let self = this; let style = this.props.isOverlay ? "scroll" : "hidden"; -- cgit v1.2.3-70-g09d2 From 7f6a802ad6306e55332cf2fd50084de80f935650 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Mon, 19 Aug 2019 20:46:26 -0400 Subject: a few link behaviors are done --- src/client/views/DocumentDecorations.tsx | 1 + src/client/views/SearchItem.tsx | 67 -------------------- src/client/views/nodes/LinkMenu.tsx | 1 + src/client/views/nodes/LinkMenuGroup.tsx | 10 ++- src/client/views/nodes/LinkMenuItem.tsx | 103 ++++++++++++++++++++++++++++++- 5 files changed, 112 insertions(+), 70 deletions(-) delete mode 100644 src/client/views/SearchItem.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index aae7f0d3f..3fdda00cf 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -685,6 +685,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let linkButton = null; if (SelectionManager.SelectedDocuments().length > 0) { let selFirst = SelectionManager.SelectedDocuments()[0]; + let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length; linkButton = ( { - - onClick = () => { - DocumentManager.Instance.jumpToDocument(this.props.doc, false); - } - - //needs help - // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "

      Error loading icon data

      "; } - - - public static DocumentIcon(layout: string) { - let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : - layout.indexOf("ImageBox") !== -1 ? faImage : - layout.indexOf("Formatted") !== -1 ? faStickyNote : - layout.indexOf("Video") !== -1 ? faFilm : - layout.indexOf("Collection") !== -1 ? faObjectGroup : - faCaretUp; - return ; - } - onPointerEnter = (e: React.PointerEvent) => { - Doc.BrushDoc(this.props.doc); - } - onPointerLeave = (e: React.PointerEvent) => { - Doc.UnBrushDoc(this.props.doc); - } - - collectionRef = React.createRef(); - startDocDrag = () => { - let doc = this.props.doc; - const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); - if (isProto) { - return Doc.MakeDelegate(doc); - } else { - return Doc.MakeAlias(doc); - } - } - render() { - return ( -
      -
      title: {this.props.doc.title}
      - {/*
      Type: {this.props.doc.layout}
      */} - {/*
      {SearchItem.DocumentIcon(this.layout)}
      */} -
      - ); - } -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 1a4af04f8..fe7d88457 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -42,6 +42,7 @@ export class LinkMenu extends React.Component { linkItems.push( void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + docView: DocumentView; + } @observer @@ -83,9 +85,13 @@ export class LinkMenuGroup extends React.Component { let groupItems = this.props.group.map(linkDoc => { let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); if (destination && this.props.sourceDoc) { - return ; + linkDoc={linkDoc} + sourceDoc={this.props.sourceDoc} + destinationDoc={destination} + showEditor={this.props.showEditor} />; } }); diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx index a119eb39b..caae88943 100644 --- a/src/client/views/nodes/LinkMenuItem.tsx +++ b/src/client/views/nodes/LinkMenuItem.tsx @@ -13,6 +13,8 @@ import { LinkManager } from '../../util/LinkManager'; import { DragLinkAsDocument } from '../../util/DragManager'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { SelectionManager } from '../../util/SelectionManager'; +import { CollectionViewType } from '../collections/CollectionBaseView'; +import { DocumentView } from './DocumentView'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -53,21 +55,117 @@ export class LinkMenuItem extends React.Component { if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!); + console.log("1") } else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!)); + console.log("2") } else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); + console.log("3") + } else { DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc); + console.log("4") + + } + } + + // NOT DONE? + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColRight = ({ col, target }: { col: Doc, target: Doc }) => { + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; } + CollectionDockingView.Instance.AddRightSplit(col, undefined); } + // DONE + // this opens the linked doc in a right split, NOT in its collection + @undoBatch + openLinkRight = () => { + let alias = Doc.MakeAlias(this.props.destinationDoc); + CollectionDockingView.Instance.AddRightSplit(alias, undefined); + SelectionManager.DeselectAll(); + + } + + // NOT DONE + // this is the standard "follow link" (jump to document) + // taken from follow link + @undoBatch + jumpToLink = async (shouldZoom: boolean = false) => { + let jumpToDoc = this.props.destinationDoc; + let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); + if (pdfDoc) { + jumpToDoc = pdfDoc; + } + let proto = Doc.GetProto(this.props.linkDoc); + let targetContext = await Cast(proto.targetContext, Doc); + let sourceContext = await Cast(proto.sourceContext, Doc); + let self = this; + + + let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + + if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext!); + } + else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + } + else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); + + } + else { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); + } + } + + // DONE + // opens link in new tab (not in a collection) + // this opens it full screen, do we need a separate full screen option? + @undoBatch + openLinkTab = () => { + let fullScreenAlias = Doc.MakeAlias(this.props.destinationDoc); + this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + //opens link in new tab in collection + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { + + } + + // this will open a link next to the source doc + @undoBatch + openLinkInPlace = () => { + let alias = Doc.MakeAlias(this.props.destinationDoc); + let y = this.props.sourceDoc.y; + let x = this.props.sourceDoc.x; + + console.log(x, y); + } + + //set this to be the default link behavior, can be any of the above + private defaultLinkBehavior: any = this.openLinkInPlace; + onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); this.props.showEditor(this.props.linkDoc); + SelectionManager.DeselectAll(); } renderMetadata = (): JSX.Element => { @@ -127,7 +225,10 @@ export class LinkMenuItem extends React.Component { {canExpand ?
      this.toggleShowMore()}>
      : <>}
      -
      + {/* Original */} + {/*
      */} + {/* New */} +
      {this._showMore ? this.renderMetadata() : <>} -- cgit v1.2.3-70-g09d2 From 43ea15b087ec923e9bd54001c7bd06c5e08efdb7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 19 Aug 2019 22:02:21 -0400 Subject: presentation view fixes --- src/client/views/MainOverlayTextBox.tsx | 3 ++- src/client/views/collections/CollectionDockingView.tsx | 6 +++--- src/client/views/collections/CollectionSchemaView.tsx | 2 ++ src/client/views/collections/CollectionStackingView.tsx | 1 + src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/PresBox.tsx | 12 ++++++------ src/client/views/presentationview/PresentationView.scss | 2 +- src/client/views/search/IconBar.tsx | 1 - 8 files changed, 16 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index b14a1e0ea..9fe435bc5 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -146,7 +146,8 @@ export class MainOverlayTextBox extends React.Component onClick={undefined} isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} - ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} /> + ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} + pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
    diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 47dfeb169..dc0cbda0b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -551,9 +551,6 @@ export class DockedFrameRenderer extends React.Component { @undoBatch @action public PinDoc(doc: Doc) { - if (doc.type === DocumentType.PRES) { - MainView.Instance.toggleMiniPresentation() - } //add this new doc to props.Document let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc; if (curPres) { @@ -563,6 +560,9 @@ export class DockedFrameRenderer extends React.Component { } else { curPres.data = new List([doc]); } + if (!DocumentManager.Instance.getDocumentView(curPres)) { + this.addDocTab(curPres, undefined, "onRight"); + } } } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 221908dd2..4008dea51 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -171,6 +171,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { active={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} setPreviewScript={this.setPreviewScript} previewScript={this.previewScript} /> @@ -200,6 +201,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { active={this.props.active} onDrop={this.onDrop} addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} isSelected={this.props.isSelected} isFocused={this.isFocused} setFocused={this.setFocused} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 8e7fe9a82..1f7ef0689 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -155,6 +155,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { active={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} setPreviewScript={emptyFunction} previewScript={undefined}> ; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 6450cb826..18f82ff47 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -198,7 +198,7 @@ export class PDFBox extends DocComponent(PdfDocumen {this.settingsPanel()}
); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index cc042e008..112d39c32 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -646,14 +646,13 @@ export class PresBox extends React.Component { //FieldViewProps? this.presElementsMappings.set(keyDoc, elem); } + minimize = undoBatch(action(() => { + this.presMode = true; + this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); + })); + specificContextMenu = (e: React.MouseEvent): void => { ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" }); - ContextMenu.Instance.addItem({ - description: "Toggle Minimized Mode", event: action(() => { - this.presMode = !this.presMode; - if (this.presMode) this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); - }), icon: "asterisk" - }); } render() { @@ -667,6 +666,7 @@ export class PresBox extends React.Component { //FieldViewProps? {this.renderPlayPauseButton()} + Date: Mon, 19 Aug 2019 22:39:56 -0400 Subject: fixed push pull UI --- .../apis/google_docs/GoogleApiClientUtils.ts | 3 + src/client/views/DocumentDecorations.tsx | 14 ++- src/client/views/nodes/FormattedTextBox.tsx | 122 +++++++++++---------- src/new_fields/RichTextField.ts | 8 +- 4 files changed, 81 insertions(+), 66 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 55b4a76f8..821c52270 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -4,6 +4,9 @@ import { RouteStore } from "../../../server/RouteStore"; import { Opt } from "../../../new_fields/Doc"; import { isArray } from "util"; +export const Pulls = "googleDocsPullCount"; +export const Pushes = "googleDocsPushCount"; + export namespace GoogleApiClientUtils { export namespace Docs { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 80d4ecb9b..797b43add 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -30,6 +30,7 @@ import { ObjectField } from '../../new_fields/ObjectField'; 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'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -626,7 +627,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (!canPush) return (null); return (
-
thisDoc.pushToGoogleDocsTrigger = !thisDoc.pushToGoogleDocsTrigger}> +
{ + DocumentDecorations.hasPushedHack = false; + thisDoc[Pushes] = NumCast(thisDoc[Pushes]) + 1; + }}>
@@ -639,13 +643,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (!canPull) return (null); return (
-
thisDoc.pullFromGoogleDocsTrigger = !thisDoc.pullFromGoogleDocsTrigger}> +
{ + DocumentDecorations.hasPulledHack = false; + thisDoc[Pulls] = NumCast(thisDoc[Pulls]) + 1; + }}>
); } + public static hasPushedHack = false; + public static hasPulledHack = false; + considerTooltip = () => { let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 406c2c4a6..d2eb71350 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,7 +12,7 @@ import { EditorView } from "prosemirror-view"; import { Doc, Opt, DocListCast } from "../../../new_fields/Doc"; import { Id, Copy } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; -import { RichTextField, ToGoogleDocText, FromGoogleDocText } from "../../../new_fields/RichTextField"; +import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; @@ -30,16 +30,14 @@ import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; -import { Templates } from '../Templates'; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -import { For } from 'babel-types'; import { DateField } from '../../../new_fields/DateField'; import { Utils } from '../../../Utils'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; -import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils'; -import * as diff from "diff"; +import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; +import { DocumentDecorations } from '../DocumentDecorations'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -84,6 +82,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _searchReactionDisposer?: Lambda; private _textReactionDisposer: Opt; private _proxyReactionDisposer: Opt; + private pullReactionDisposer: Opt; + private pushReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } private isGoogleDocsUpdate = false; @@ -345,13 +345,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } ); - reaction(() => this.props.Document.pullFromGoogleDocsTrigger, () => { - this.pullFromGoogleDoc(); - }); + this.pullReactionDisposer = reaction( + () => this.props.Document[Pulls], + () => { + if (!DocumentDecorations.hasPulledHack) { + DocumentDecorations.hasPulledHack = true; + this.pullFromGoogleDoc(); + } + } + ); - reaction(() => this.props.Document.pushToGoogleDocsTrigger, () => { - this.pushToGoogleDoc(); - }); + this.pushReactionDisposer = reaction( + () => this.props.Document[Pushes], + () => { + if (!DocumentDecorations.hasPushedHack) { + DocumentDecorations.hasPushedHack = true; + this.pushToGoogleDoc(); + } + } + ); this._textReactionDisposer = reaction( () => this.extensionDoc, @@ -380,13 +392,44 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); + } - ["pushToGoogleDocsTrigger", "pullFromGoogleDocsTrigger"].map(key => { - let doc = this.props.Document; - if (doc[key] === undefined) { - untracked(() => doc[key] = false); - } - }); + pushToGoogleDoc = () => { + let modes = GoogleApiClientUtils.Docs.WriteMode; + let mode = modes.Replace; + let reference: Opt = Cast(this.dataDoc[googleDocId], "string"); + if (!reference) { + mode = modes.Insert; + reference = { + title: StrCast(this.dataDoc.title), + handler: id => this.dataDoc[googleDocId] = id + }; + } + if (this._editorView) { + let data = Cast(this.dataDoc.data, RichTextField); + let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent; + GoogleApiClientUtils.Docs.write({ reference, content, mode }); + } + } + + pullFromGoogleDoc = async () => { + let dataDoc = Doc.GetProto(this.props.Document); + let documentId = StrCast(dataDoc[googleDocId]); + if (documentId) { + let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); + UndoManager.RunInBatch(() => { + if (exportState && exportState.body && exportState.title) { + let data = Cast(dataDoc.data, RichTextField); + if (data) { + this.isGoogleDocsUpdate = true; + dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); + dataDoc.title = exportState.title; + } + } else { + delete dataDoc[googleDocId]; + } + }, Pulls); + } } clipboardTextSerializer = (slice: Slice): string => { @@ -516,6 +559,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); this._textReactionDisposer && this._textReactionDisposer(); + this.pushReactionDisposer && this.pushReactionDisposer(); + this.pullReactionDisposer && this.pullReactionDisposer(); } onPointerDown = (e: React.PointerEvent): void => { @@ -693,49 +738,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" }); - if (!(googleDocId in Doc.GetProto(this.props.Document))) { - ContextMenu.Instance.addItem({ - description: "Export to Google Doc...", - event: this.pushToGoogleDoc, - icon: "upload" - }); - } - } - - pushToGoogleDoc = () => { - let modes = GoogleApiClientUtils.Docs.WriteMode; - let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[googleDocId], "string"); - if (!reference) { - mode = modes.Insert; - reference = { - title: StrCast(this.dataDoc.title), - handler: id => this.dataDoc[googleDocId] = id - }; - } - if (this._editorView) { - let data = Cast(this.dataDoc.data, RichTextField); - let content = data ? data[ToGoogleDocText]() : this._editorView.state.doc.textContent; - GoogleApiClientUtils.Docs.write({ reference, content, mode }); - } - } - - pullFromGoogleDoc = async () => { - let dataDoc = Doc.GetProto(this.props.Document); - let documentId = StrCast(dataDoc[googleDocId]); - if (documentId) { - let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); - if (exportState && exportState.body && exportState.title) { - let data = Cast(dataDoc.data, RichTextField); - if (data) { - this.isGoogleDocsUpdate = true; - dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState.body)); - dataDoc.title = exportState.title; - } - } else { - delete dataDoc[googleDocId]; - } - } } diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index ec08293e9..ab58329f9 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -4,8 +4,8 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { Copy, ToScriptString } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; -export const ToGoogleDocText = Symbol("PlainText"); -export const FromGoogleDocText = Symbol("PlainText"); +export const ToPlainText = Symbol("PlainText"); +export const FromPlainText = Symbol("PlainText"); const delimiter = "\n"; const joiner = ""; @@ -28,7 +28,7 @@ export class RichTextField extends ObjectField { return `new RichTextField("${this.Data}")`; } - [ToGoogleDocText]() { + [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"); @@ -43,7 +43,7 @@ export class RichTextField extends ObjectField { return textParagraphs.join(joiner).trimEnd(delimiter); } - [FromGoogleDocText](plainText: string) { + [FromPlainText](plainText: string) { // Remap the text, creating blocks split on newlines let elements = plainText.split(delimiter); -- cgit v1.2.3-70-g09d2 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/client/views/nodes/DocumentView.tsx | 12 ++-- src/client/views/nodes/LinkMenuItem.tsx | 106 ++++++++++++++++++++------------ src/new_fields/Doc.ts | 67 ++++++++++++++++---- 3 files changed, 129 insertions(+), 56 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6f5235c4a..900cd266e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -747,18 +747,20 @@ export class DocumentView extends DocComponent(Docu } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" { private _drag = React.createRef(); @observable private _showMore: boolean = false; @action toggleShowMore() { this._showMore = !this._showMore; } + @observable shouldUnhighlight: boolean = false; - @undoBatch - onFollowLink = async (e: React.PointerEvent): Promise => { - e.stopPropagation(); - e.persist(); - let jumpToDoc = this.props.destinationDoc; - let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); - if (pdfDoc) { - jumpToDoc = pdfDoc; - } - let proto = Doc.GetProto(this.props.linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let sourceContext = await Cast(proto.sourceContext, Doc); - let self = this; + componentDidMount = () => { + // document.addEventListener("pointerdown", this.unhighlight); + } + unhighlight = () => { + // if (this.shouldUnhighlight) + // Doc.UnhighlightAll(); + Doc.UnHighlightDoc(this.props.destinationDoc); + } - let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; - if (e.ctrlKey) { - dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined); - } + @action + highlightDoc = () => { + // this.shouldUnhighlight = false; + document.removeEventListener("pointerdown", this.unhighlight); - if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!); - console.log("1") - } - else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!)); - console.log("2") - } - else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); - console.log("3") + Doc.HighlightDoc(this.props.destinationDoc); - } - else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc); - console.log("4") + window.setTimeout(() => { + // this.shouldUnhighlight = true; + document.addEventListener("pointerdown", this.unhighlight); - } + }, 3000); } // NOT DONE? @@ -92,17 +77,18 @@ export class LinkMenuItem extends React.Component { // this opens the linked doc in a right split, NOT in its collection @undoBatch openLinkRight = () => { + this.highlightDoc(); let alias = Doc.MakeAlias(this.props.destinationDoc); CollectionDockingView.Instance.AddRightSplit(alias, undefined); SelectionManager.DeselectAll(); - } - // NOT DONE + // DONE // this is the standard "follow link" (jump to document) // taken from follow link @undoBatch jumpToLink = async (shouldZoom: boolean = false) => { + this.highlightDoc(); let jumpToDoc = this.props.destinationDoc; let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); if (pdfDoc) { @@ -113,7 +99,6 @@ export class LinkMenuItem extends React.Component { let sourceContext = await Cast(proto.sourceContext, Doc); let self = this; - let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { @@ -136,6 +121,7 @@ export class LinkMenuItem extends React.Component { // this opens it full screen, do we need a separate full screen option? @undoBatch openLinkTab = () => { + this.highlightDoc(); let fullScreenAlias = Doc.MakeAlias(this.props.destinationDoc); this.props.addDocTab(fullScreenAlias, undefined, "inTab"); SelectionManager.DeselectAll(); @@ -146,12 +132,14 @@ export class LinkMenuItem extends React.Component { // target = the document to center on @undoBatch openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { - + this.highlightDoc(); } // this will open a link next to the source doc @undoBatch openLinkInPlace = () => { + this.highlightDoc(); + let alias = Doc.MakeAlias(this.props.destinationDoc); let y = this.props.sourceDoc.y; let x = this.props.sourceDoc.x; @@ -160,7 +148,7 @@ export class LinkMenuItem extends React.Component { } //set this to be the default link behavior, can be any of the above - private defaultLinkBehavior: any = this.openLinkInPlace; + private defaultLinkBehavior: any = this.openLinkRight; onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); @@ -237,4 +225,44 @@ export class LinkMenuItem extends React.Component {
); } -} \ No newline at end of file +} + + // @undoBatch + // onFollowLink = async (e: React.PointerEvent): Promise => { + // e.stopPropagation(); + // e.persist(); + // let jumpToDoc = this.props.destinationDoc; + // let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); + // if (pdfDoc) { + // jumpToDoc = pdfDoc; + // } + // let proto = Doc.GetProto(this.props.linkDoc); + // let targetContext = await Cast(proto.targetContext, Doc); + // let sourceContext = await Cast(proto.sourceContext, Doc); + // let self = this; + + + // let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + // if (e.ctrlKey) { + // dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined); + // } + + // if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!); + // console.log("1") + // } + // else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!)); + // console.log("2") + // } + // else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); + // console.log("3") + + // } + // else { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc); + // console.log("4") + + // } + // } \ No newline at end of file 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 b987fe6e18c4b37a1ec40abcb4d8360d57dc7d54 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Mon, 19 Aug 2019 23:14:00 -0400 Subject: switch to text needs help lol --- src/client/views/PreviewCursor.tsx | 18 +++++++++++++++++- src/client/views/nodes/WebBox.tsx | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 7bf0d8ace..40be470d6 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -34,8 +34,10 @@ export class PreviewCursor extends React.Component<{}> { let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); runInAction(() => { PreviewCursor.Visible = false; }); - // pasting in text/video from youtube + if (e.clipboardData.getData("text/plain") !== "") { + + // tests for youtube and makes video document if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) { const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/"); PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { @@ -45,6 +47,20 @@ export class PreviewCursor extends React.Component<{}> { }), false); return; } + + // tests for URL and makes web document + let re: any = /^https?:\/\/www\./g; + if (re.test(e.clipboardData.getData("text/plain"))) { + const url = e.clipboardData.getData("text/plain") + PreviewCursor._addDocument(Docs.Create.WebDocument(url, { + title: url, width: 300, height: 300, + // nativeWidth: 300, nativeHeight: 472.5, + x: newPoint[0], y: newPoint[1] + }), false); + return; + } + + // creates text document let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: newPoint[0], diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 91170e99a..6e23d6fb7 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -10,6 +10,7 @@ import { InkTool } from "../../../new_fields/InkField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faStickyNote } from '@fortawesome/free-solid-svg-icons'; import { observable, action, computed } from "mobx"; import { listSpec } from "../../../new_fields/Schema"; import { Field, FieldResult } from "../../../new_fields/Doc"; @@ -18,6 +19,11 @@ import { ObjectField } from "../../../new_fields/ObjectField"; import { updateSourceFile } from "typescript"; import { KeyValueBox } from "./KeyValueBox"; import { setReactionScheduler } from "mobx/lib/internal"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { Docs } from "../../documents/Documents"; +import { PreviewCursor } from "../PreviewCursor"; + +library.add(faStickyNote) @observer export class WebBox extends React.Component { @@ -69,6 +75,24 @@ export class WebBox extends React.Component { } } + switchToText() { + console.log("switchng to text") + if (this.props.removeDocument) this.props.removeDocument(this.props.Document); + // let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); + let newBox = Docs.Create.TextDocument({ + width: 200, height: 100, + // x: newPoint[0], + // y: newPoint[1], + x: NumCast(this.props.Document.x), + y: NumCast(this.props.Document.y), + title: "-pasted text-" + }); + + newBox.proto!.autoHeight = true; + PreviewCursor._addLiveTextDoc(newBox); + return; + } + urlEditor() { return (
@@ -91,9 +115,18 @@ export class WebBox extends React.Component { onChange={this.onURLChange} onKeyDown={this.onValueKeyDown} /> - + +
-- cgit v1.2.3-70-g09d2 From c92761206957aebdba1cc2e77f6b662ae42db847 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 20 Aug 2019 01:44:18 -0400 Subject: interactive ui --- src/client/views/DocumentDecorations.tsx | 84 +++++++++++++++++++++++------ src/client/views/nodes/FormattedTextBox.tsx | 63 +++++++++++++++------- 2 files changed, 112 insertions(+), 35 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 797b43add..1db452b45 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,5 +1,5 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp } from '@fortawesome/free-solid-svg-icons'; +import { library, IconProp } from '@fortawesome/fontawesome-svg-core'; +import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; @@ -18,7 +18,7 @@ import { CollectionView } from "./collections/CollectionView"; import './DocumentDecorations.scss'; import { DocumentView, PositionDocument } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; +import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; import { LinkMenu } from "./nodes/LinkMenu"; import { TemplateMenu } from "./TemplateMenu"; @@ -26,7 +26,6 @@ import { Template, Templates } from "./Templates"; import React = require("react"); import { RichTextField } from '../../new_fields/RichTextField'; import { LinkManager } from '../util/LinkManager'; -import { ObjectField } from '../../new_fields/ObjectField'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { ImageBox } from './nodes/ImageBox'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; @@ -39,6 +38,11 @@ library.add(faLink); library.add(faTag); library.add(faArrowAltCircleDown); library.add(faArrowAltCircleUp); +library.add(faStopCircle); +library.add(faCheckCircle); +library.add(faCloudUploadAlt); + +const cloud: IconProp = "cloud-upload-alt"; @observer export class DocumentDecorations extends React.Component<{}, { value: string }> { @@ -69,6 +73,52 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @observable private _removeIcon = false; @observable public Interacting = false; + @observable public pushIcon: IconProp = "arrow-alt-circle-up"; + @observable public pullIcon: IconProp = "arrow-alt-circle-down"; + @observable public pullColor: string = "white"; + public pullColorAnimating = false; + + private pullAnimating = false; + private pushAnimating = false; + + public startPullOutcome = action((success: boolean) => { + if (this.pullAnimating) { + return; + } + this.pullAnimating = true; + this.pullIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pullIcon = "arrow-alt-circle-down"; + this.pullAnimating = false; + }), 1000); + }); + + public startPushOutcome = action((success: boolean) => { + if (this.pushAnimating) { + return; + } + this.pushAnimating = true; + this.pushIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pushIcon = "arrow-alt-circle-up"; + this.pushAnimating = false; + }), 1000); + }); + + public setPullState = (unchanged: boolean) => { + if (this.pullColorAnimating) { + return; + } + this.pullColorAnimating = true; + this.pullColor = unchanged ? "lawngreen" : "red"; + setTimeout(() => { + runInAction(() => { + this.pullColor = "white"; + this.pullColorAnimating = false; + }); + }, 2000); + } + constructor(props: Readonly<{}>) { super(props); DocumentDecorations.Instance = this; @@ -621,33 +671,37 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> ); } + private get targetDoc() { + return SelectionManager.SelectedDocuments()[0].props.Document; + } + considerGoogleDocsPush = () => { - let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; - let canPush = thisDoc.data && thisDoc.data instanceof RichTextField; + let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; if (!canPush) return (null); + let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; + let icon: IconProp = published ? (this.pushIcon as any) : (cloud as any); return (
-
{ +
{ DocumentDecorations.hasPushedHack = false; - thisDoc[Pushes] = NumCast(thisDoc[Pushes]) + 1; + this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; }}> - +
); } considerGoogleDocsPull = () => { - let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; - let canPull = thisDoc.data && thisDoc.data instanceof RichTextField; - if (!canPull) return (null); + let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; + if (!canPull || !Doc.GetProto(this.targetDoc)[GoogleRef]) return (null); return (
-
{ +
{ DocumentDecorations.hasPulledHack = false; - thisDoc[Pulls] = NumCast(thisDoc[Pulls]) + 1; + this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; }}> - +
); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d2eb71350..33d222813 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -60,11 +60,13 @@ const richTextSchema = createSchema({ documentText: "string" }); -const googleDocId = "googleDocId"; +export const GoogleRef = "googleDocId"; type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); +type PullHandler = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => void; + @observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string = "data") { @@ -324,6 +326,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, { fireImmediately: true }); } + this.pullFromGoogleDoc(this.checkState); + this._reactionDisposer = reaction( () => { const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined; @@ -350,7 +354,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe () => { if (!DocumentDecorations.hasPulledHack) { DocumentDecorations.hasPulledHack = true; - this.pullFromGoogleDoc(); + this.pullFromGoogleDoc(this.updateState); } } ); @@ -394,44 +398,63 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, { fireImmediately: true }); } - pushToGoogleDoc = () => { + pushToGoogleDoc = async () => { let modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[googleDocId], "string"); + let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); if (!reference) { mode = modes.Insert; reference = { title: StrCast(this.dataDoc.title), - handler: id => this.dataDoc[googleDocId] = id + handler: id => this.dataDoc[GoogleRef] = id }; } if (this._editorView) { let data = Cast(this.dataDoc.data, RichTextField); let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent; - GoogleApiClientUtils.Docs.write({ reference, content, mode }); + let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + let pushSuccess = response !== undefined && !("errors" in response); + DocumentDecorations.Instance.startPushOutcome(pushSuccess); } } - pullFromGoogleDoc = async () => { + pullFromGoogleDoc = async (handler: PullHandler) => { let dataDoc = Doc.GetProto(this.props.Document); - let documentId = StrCast(dataDoc[googleDocId]); + let documentId = StrCast(dataDoc[GoogleRef]); if (documentId) { let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); - UndoManager.RunInBatch(() => { - if (exportState && exportState.body && exportState.title) { - let data = Cast(dataDoc.data, RichTextField); - if (data) { - this.isGoogleDocsUpdate = true; - dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); - dataDoc.title = exportState.title; - } - } else { - delete dataDoc[googleDocId]; - } - }, Pulls); + UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } } + updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + let pullSuccess = false; + if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { + let data = Cast(dataDoc.data, RichTextField); + if (data) { + pullSuccess = true; + this.isGoogleDocsUpdate = true; + dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); + dataDoc.title = exportState.title; + } + } else { + delete dataDoc[GoogleRef]; + } + DocumentDecorations.Instance.startPullOutcome(pullSuccess); + } + + checkState = (exportState: GoogleApiClientUtils.Docs.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; + DocumentDecorations.Instance.setPullState(storedPlainText === receivedPlainText); + } + } + } + + clipboardTextSerializer = (slice: Slice): string => { let text = "", separated = true; const from = 0, to = slice.content.size; -- cgit v1.2.3-70-g09d2 From aa0e6f5ffc30fdffc3be13a1948981b754544a01 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 20 Aug 2019 12:03:50 -0400 Subject: automation of pull push button visibility --- src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/nodes/FormattedTextBox.tsx | 61 +++++++++++++++++++---------- 2 files changed, 42 insertions(+), 22 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 1db452b45..05239073b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -694,7 +694,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> considerGoogleDocsPull = () => { let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; - if (!canPull || !Doc.GetProto(this.targetDoc)[GoogleRef]) return (null); + let dataDoc = Doc.GetProto(this.targetDoc); + if (!canPull || !dataDoc[GoogleRef] || dataDoc.unchanged) return (null); return (
{ diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 33d222813..8a355a425 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace, untracked } from "mobx"; +import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace, untracked, autorun } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -399,32 +399,46 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } pushToGoogleDoc = async () => { - let modes = GoogleApiClientUtils.Docs.WriteMode; - let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); - if (!reference) { - mode = modes.Insert; - reference = { - title: StrCast(this.dataDoc.title), - handler: id => this.dataDoc[GoogleRef] = id + this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + let modes = GoogleApiClientUtils.Docs.WriteMode; + let mode = modes.Replace; + let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); + if (!reference) { + mode = modes.Insert; + reference = { + title: StrCast(this.dataDoc.title), + handler: id => this.dataDoc[GoogleRef] = id + }; + } + let redo = async () => { + if (this._editorView && reference) { + let data = Cast(this.dataDoc.data, RichTextField); + let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent; + let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + let pushSuccess = response !== undefined && !("errors" in response); + dataDoc.unchanged = pushSuccess; + DocumentDecorations.Instance.startPushOutcome(pushSuccess); + } }; - } - if (this._editorView) { - let data = Cast(this.dataDoc.data, RichTextField); - let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent; - let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); - let pushSuccess = response !== undefined && !("errors" in response); - DocumentDecorations.Instance.startPushOutcome(pushSuccess); - } + let undo = () => { + let content = exportState.body; + if (reference && content) { + GoogleApiClientUtils.Docs.write({ reference, content, mode }); + } + }; + UndoManager.AddEvent({ undo, redo }); + redo(); + }); } pullFromGoogleDoc = async (handler: PullHandler) => { - let dataDoc = Doc.GetProto(this.props.Document); + let dataDoc = this.dataDoc; let documentId = StrCast(dataDoc[GoogleRef]); + let exportState: GoogleApiClientUtils.Docs.ReadResult = {}; if (documentId) { - let exportState = await GoogleApiClientUtils.Docs.read({ documentId }); - UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); + exportState = await GoogleApiClientUtils.Docs.read({ documentId }); } + UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { @@ -436,6 +450,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.isGoogleDocsUpdate = true; dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); dataDoc.title = exportState.title; + dataDoc.unchanged = true; } } else { delete dataDoc[GoogleRef]; @@ -449,7 +464,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (data) { let storedPlainText = data[ToPlainText]() + "\n"; let receivedPlainText = exportState.body; - DocumentDecorations.Instance.setPullState(storedPlainText === receivedPlainText); + let storedTitle = dataDoc.title; + let receivedTitle = exportState.title; + let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle; + dataDoc.unchanged = unchanged; + DocumentDecorations.Instance.setPullState(unchanged); } } } -- cgit v1.2.3-70-g09d2 From 9a39ac87972243787474d489856818f44b90d524 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Tue, 20 Aug 2019 12:12:58 -0400 Subject: send halp --- src/client/views/PreviewCursor.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 41 +++++++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 8 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 40be470d6..d8e161ab6 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -49,7 +49,7 @@ export class PreviewCursor extends React.Component<{}> { } // tests for URL and makes web document - let re: any = /^https?:\/\/www\./g; + let re: any = /^https?:\/\//g; if (re.test(e.clipboardData.getData("text/plain"))) { const url = e.clipboardData.getData("text/plain") PreviewCursor._addDocument(Docs.Create.WebDocument(url, { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 6e23d6fb7..c43b90f91 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -13,7 +13,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faStickyNote } from '@fortawesome/free-solid-svg-icons'; import { observable, action, computed } from "mobx"; import { listSpec } from "../../../new_fields/Schema"; -import { Field, FieldResult } from "../../../new_fields/Doc"; +import { Field, FieldResult, Doc, Opt } from "../../../new_fields/Doc"; import { RefField } from "../../../new_fields/RefField"; import { ObjectField } from "../../../new_fields/ObjectField"; import { updateSourceFile } from "typescript"; @@ -22,6 +22,10 @@ import { setReactionScheduler } from "mobx/lib/internal"; import { library } from "@fortawesome/fontawesome-svg-core"; import { Docs } from "../../documents/Documents"; import { PreviewCursor } from "../PreviewCursor"; +import { SelectionManager } from "../../util/SelectionManager"; +import { CollectionView } from "../collections/CollectionView"; +import { CollectionPDFView } from "../collections/CollectionPDFView"; +import { CollectionVideoView } from "../collections/CollectionVideoView"; library.add(faStickyNote) @@ -75,21 +79,44 @@ export class WebBox extends React.Component { } } - switchToText() { - console.log("switchng to text") - if (this.props.removeDocument) this.props.removeDocument(this.props.Document); - // let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); + + switchToText = () => { + let url: string = ""; + let field = Cast(this.props.Document[this.props.fieldKey], WebField); + if (field) url = field.url.href; + + let parent: Opt; + // let parentDoc: any; + SelectionManager.SelectedDocuments().map(dv => { + parent = dv.props.ContainingCollectionView; + // if(parent) parentDoc = parent.props.Document; + dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); + }); + + // // let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); let newBox = Docs.Create.TextDocument({ width: 200, height: 100, // x: newPoint[0], // y: newPoint[1], x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), - title: "-pasted text-" + title: url }); + console.log(newBox) + if (parent) { + let parentDoc: Doc = parent.props.Document; + if (parentDoc && parentDoc.props) { + parentDoc.props.addDocument(); + } + } + newBox.proto!.autoHeight = true; - PreviewCursor._addLiveTextDoc(newBox); + // PreviewCursor._addLiveTextDoc(newBox); + // if (parent && parent.props.addDocument) { + // console.log("adding doc") + // parent.props.addDocument(newBox); + // } return; } -- 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/client/views/nodes') 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 700dfc5add1ecd9c2b1ecafcdc593ff821b7a6a6 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 20 Aug 2019 17:20:27 -0400 Subject: UI enhancements and other backend fixes for templates. --- src/client/util/DragManager.ts | 3 +- src/client/views/MetadataEntryMenu.scss | 12 +++++ src/client/views/MetadataEntryMenu.tsx | 52 +++++++++++--------- .../views/collections/CollectionStackingView.tsx | 3 +- .../CollectionStackingViewFieldColumn.tsx | 9 ++-- src/client/views/collections/CollectionSubView.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 3 ++ .../views/collections/CollectionViewChromes.scss | 1 - .../views/collections/CollectionViewChromes.tsx | 57 +++++++++++----------- .../collectionFreeForm/CollectionFreeFormView.tsx | 30 ++++++------ src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 25 +++++++--- src/client/views/nodes/FormattedTextBox.tsx | 12 ++--- src/client/views/nodes/ImageBox.tsx | 7 +-- src/new_fields/Doc.ts | 11 +++-- 15 files changed, 131 insertions(+), 98 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0b6d9b5e5..894b366ef 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -9,8 +9,6 @@ import { DocumentManager } from "./DocumentManager"; import { LinkManager } from "./LinkManager"; import { SelectionManager } from "./SelectionManager"; import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { DocumentDecorations } from "../views/DocumentDecorations"; -import { NumberLiteralType } from "typescript"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag( @@ -211,6 +209,7 @@ export namespace DragManager { dropAction: dropActionType; userDropAction: dropActionType; moveDocument?: MoveFunction; + applyAsTemplate?: boolean; [id: string]: any; } diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss index bcfc9a82d..e28c4d0e0 100644 --- a/src/client/views/MetadataEntryMenu.scss +++ b/src/client/views/MetadataEntryMenu.scss @@ -1,8 +1,20 @@ .metadataEntry-outerDiv { display: flex; + flex-direction: column; width: 300px; } +.metadataEntry-keys { + max-height: 80; + overflow-y: auto; + display: flex; + flex-direction: column; +} +.metadataEntry-inputArea { + display:flex; + flex-direction: row; +} + .react-autosuggest__container { position: relative; } diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 36c240dd8..4a45eede9 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import "./MetadataEntryMenu.scss"; import { observer } from 'mobx-react'; -import { observable, action, runInAction, trace } from 'mobx'; +import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx'; import { KeyValueBox } from './nodes/KeyValueBox'; import { Doc, Field } from '../../new_fields/Doc'; import * as Autosuggest from 'react-autosuggest'; import { undoBatch } from '../util/UndoManager'; +import { emptyFunction } from '../../Utils'; export type DocLike = Doc | Doc[] | Promise | Promise; export interface MetadataEntryProps { @@ -18,7 +19,8 @@ export interface MetadataEntryProps { export class MetadataEntryMenu extends React.Component{ @observable private _currentKey: string = ""; @observable private _currentValue: string = ""; - @observable private suggestions: string[] = []; + @observable _allSuggestions: string[] = []; + _suggestionDispser: IReactionDisposer | undefined; private userModified = false; private autosuggestRef = React.createRef(); @@ -140,35 +142,39 @@ export class MetadataEntryMenu extends React.Component{ getSuggestionValue = (suggestion: string) => suggestion; renderSuggestion = (suggestion: string) => { - return

{suggestion}

; + return (null); } + componentDidMount() { - onSuggestionFetch = async ({ value }: { value: string }) => { - const sugg = await this.getKeySuggestions(value); - runInAction(() => { - this.suggestions = sugg; - }); + this._suggestionDispser = reaction(() => this._currentKey, + () => this.getKeySuggestions(this._currentKey).then(action((s: string[]) => this._allSuggestions = s)), + { fireImmediately: true }); } - - @action - onSuggestionClear = () => { - this.suggestions = []; + componentWillUnmount() { + this._suggestionDispser && this._suggestionDispser(); } render() { return (
- Key: - - Value: - +
+ Key: + + Value: + +
+
+
    + {this._allSuggestions.map(s =>
  • { this._currentKey = s; this.previewValue(); })} >{s}
  • )} +
+
); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 1f7ef0689..c74c60d8f 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -160,7 +160,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { previewScript={undefined}> ; } - getDocHeight(d: Doc) { + getDocHeight(d?: Doc) { + 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/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 74c7ef305..cc8476548 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -78,11 +78,14 @@ export class CollectionStackingViewFieldColumn extends React.Component { - let pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d); + const pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d); + if (!pair.layout || pair.data instanceof Promise) { + return (null); + } let width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns); - let height = () => parent.getDocHeight(pair.layout); + let height = () => parent.getDocHeight(pair!.layout); let dref = React.createRef(); - let dxf = () => this.getDocTransform(pair.layout, dref.current!); + let dxf = () => this.getDocTransform(pair!.layout, dref.current!); this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index cfe3fefb3..818e76619 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -113,7 +113,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { - if (de.data instanceof DragManager.DocumentDragData) { + 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) diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b6a14238e..ebd385743 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -443,6 +443,9 @@ class TreeView extends React.Component { let rowWidth = () => panelWidth() - 20; return docs.map((child, i) => { let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); + if (!pair.layout || pair.data instanceof Promise) { + return (null); + } let indent = i === 0 ? undefined : () => { if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) { diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 595f079d1..f39bd877a 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -7,7 +7,6 @@ z-index: 9001; transition: top .5s; background: lightgrey; - padding: 10px; .collectionViewChrome { display: grid; diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index b92bcc7cb..25b152d4e 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -1,30 +1,25 @@ -import * as React from "react"; -import { CollectionView } from "./CollectionView"; -import "./CollectionViewChromes.scss"; -import { CollectionViewType } from "./CollectionBaseView"; -import { undoBatch } from "../../util/UndoManager"; -import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, DocListCast, FieldResult } from "../../../new_fields/Doc"; -import { DocLike } from "../MetadataEntryMenu"; -import * as Autosuggest from 'react-autosuggest'; -import { EditableView } from "../EditableView"; -import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +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 { ScriptField } from "../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; -import KeyRestrictionRow from "./KeyRestrictionRow"; +import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { CollectionSchemaView } from "./CollectionSchemaView"; +import { undoBatch } from "../../util/UndoManager"; +import { EditableView } from "../EditableView"; import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss"; -import { listSpec } from "../../../new_fields/Schema"; -import { List } from "../../../new_fields/List"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { threadId } from "worker_threads"; -import { DragManager } from "../../util/DragManager"; +import { DocLike } from "../MetadataEntryMenu"; +import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionView } from "./CollectionView"; +import "./CollectionViewChromes.scss"; +import KeyRestrictionRow from "./KeyRestrictionRow"; const datepicker = require('js-datepicker'); -import * as $ from 'jquery'; -import { firebasedynamiclinks } from "googleapis/build/src/apis/firebasedynamiclinks"; interface CollectionViewChromeProps { CollectionView: CollectionView; @@ -51,7 +46,6 @@ export class CollectionViewBaseChrome extends React.Component { let re: any = /(!)?\(\(\(doc\.(\w+)\s+&&\s+\(doc\.\w+\s+as\s+\w+\)\.includes\(\"(\w+)\"\)/g; @@ -91,11 +85,6 @@ export class CollectionViewBaseChrome extends React.Component { - setTimeout(() => this._picker = datepicker("#" + this._datePickerElGuid, { - disabler: (date: Date) => date > new Date(), - onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), - dateSelected: new Date() - }), 1000); let fields: Filter[] = []; if (this.filterValue) { @@ -306,6 +295,15 @@ export class CollectionViewBaseChrome extends React.Component { + if (node) { + this._picker = datepicker("#" + node.id, { + disabler: (date: Date) => date > new Date(), + onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), + dateSelected: new Date() + }); + } + } render() { let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled"; return ( @@ -366,7 +364,8 @@ export class CollectionViewBaseChrome extends React.Component1 year of runInAction(() => this._dateValue = e.target.value)} onPointerDown={this.openDatePicker} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d69ece421..3be6aa3d3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -622,20 +622,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getScale = () => this.Document.scale ? this.Document.scale : 1; - getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps { - let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout); + getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { - DataDoc: pair.data, - Document: pair.layout, + DataDoc: childData, + Document: childLayout, addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, onClick: this.props.onClick, - ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform, + ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, - selectOnLoad: pair.layout[Id] === this._selectOnLoaded, - PanelWidth: pair.layout[WidthSym], - PanelHeight: pair.layout[HeightSym], + selectOnLoad: childLayout[Id] === this._selectOnLoaded, + PanelWidth: childLayout[WidthSym], + PanelHeight: childLayout[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, @@ -745,12 +744,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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; - prev.push({ - ele: , - bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined - }); + 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: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined + }); + } } } return prev; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index b901bdcfb..d77662355 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -68,7 +68,7 @@ export class DocumentContentsView extends React.Component(Docu private _lastTap: number = 0; private _doubleTap = false; private _hitExpander = false; + private _hitTemplateDrag = false; private _mainCont = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; _animateToIconDisposer?: IReactionDisposer; @@ -228,7 +229,7 @@ export class DocumentView extends DocComponent(Docu } get dataDoc() { - if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) { + 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. @@ -236,7 +237,7 @@ export class DocumentView extends DocComponent(Docu } return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; } - startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) { + startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean, 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) : [])]; @@ -247,6 +248,7 @@ export class DocumentView extends DocComponent(Docu dragData.xOffset = xoff; dragData.yOffset = yoff; dragData.moveDocument = this.props.moveDocument; + dragData.applyAsTemplate = applyAsTemplate; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { handlers: { dragComplete: action(emptyFunction) @@ -377,15 +379,19 @@ export class DocumentView extends DocComponent(Docu } } } + + 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; - // if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) { - // CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]); - // e.stopPropagation(); - // } else { + this._hitTemplateDrag = false; + for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) { + if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") { + this._hitTemplateDrag = true; + } + } if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); @@ -399,7 +405,7 @@ export class DocumentView extends DocComponent(Docu if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.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.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, 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 @@ -457,6 +463,10 @@ export class DocumentView extends DocComponent(Docu DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`); } + if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) { + Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc); + e.stopPropagation(); + } if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; @@ -679,7 +689,6 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" }); if (!ClientUtils.RELEASE) { let setWriteMode = (mode: DocServer.WriteMode) => { - console.log(DocServer.WriteMode[mode]); DocServer.AclsMode = mode; const mode1 = mode; const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index f7890e5a6..0e347ca67 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -167,7 +167,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false); if (state.selection.empty && FormattedTextBox._toolTipTextMenu) { const marks = tx.storedMarks; - console.log(marks); if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); } } this._applyingChange = true; @@ -296,10 +295,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2))) ); - this.props.isOverlay && (this._heightReactionDisposer = reaction( + this._heightReactionDisposer = reaction( () => this.props.Document[WidthSym](), () => this.tryUpdateHeight() - )); + ); this._textReactionDisposer = reaction( () => this.extensionDoc, @@ -448,8 +447,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this.props.selectOnLoad) { if (!this.props.isOverlay) this.props.select(false); else this._editorView!.focus(); - this.tryUpdateHeight(); } + this.tryUpdateHeight(); } componentWillUnmount() { @@ -610,9 +609,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { - if (this.props.Document.autoHeight && this.props.isOverlay) { + if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) { + console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent) let xf = this._ref.current!.getBoundingClientRect(); - let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height); + let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight); let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); let sh = scrBounds.height; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 708e00576..6fc94a140 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -89,10 +89,7 @@ export class ImageBox extends DocComponent(ImageD drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { de.data.droppedDocuments.forEach(action((drop: Doc) => { - if (de.mods === "CtrlKey") { - Doc.ApplyTemplateTo(drop, this.props.Document, this.props.DataDoc); - e.stopPropagation(); - } else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) { + 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") { @@ -321,7 +318,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 (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(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; layoutdoc.nativeHeight = realsize.height; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 31f1f7a12..ca05dfa45 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -446,20 +446,23 @@ export namespace Doc { if (expandedTemplateLayout instanceof Doc) { return expandedTemplateLayout; } + if (expandedTemplateLayout instanceof Promise) { + return undefined; + } let expandedLayoutFieldKey = "Layout[" + templateLayoutDoc[Id] + "]"; expandedTemplateLayout = dataDoc[expandedLayoutFieldKey]; if (expandedTemplateLayout instanceof Doc) { return expandedTemplateLayout; } if (expandedTemplateLayout === undefined) { - setTimeout(() => - dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"), 0); + setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined && + (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0); } - return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending. + return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending. } export function GetLayoutDataDocPair(doc: Doc, dataDoc: Doc | undefined, fieldKey: string, childDocLayout: Doc) { - let layoutDoc = childDocLayout; + let layoutDoc: Doc | undefined = childDocLayout; let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc && dataDoc ? Doc.GetDataDoc(dataDoc) : undefined; if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) { Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey); -- cgit v1.2.3-70-g09d2 From dc587b0fb493d869e6cd38fa94b81105da4fbaab Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 20 Aug 2019 20:03:08 -0400 Subject: finished UI --- src/client/views/DocumentDecorations.scss | 6 +- src/client/views/DocumentDecorations.tsx | 92 ++++++++++++++++++----------- src/client/views/nodes/FormattedTextBox.tsx | 3 +- src/new_fields/RichTextField.ts | 7 ++- 4 files changed, 67 insertions(+), 41 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 0b7411fca..ef7159370 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -255,4 +255,8 @@ $linkGap : 3px; input { margin-right: 10px; } -} \ 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); } } \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 05239073b..7645af054 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,5 +1,5 @@ import { library, IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; +import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; @@ -41,8 +41,10 @@ library.add(faArrowAltCircleUp); library.add(faStopCircle); library.add(faCheckCircle); library.add(faCloudUploadAlt); +library.add(faSyncAlt); const cloud: IconProp = "cloud-upload-alt"; +const fetch: IconProp = "sync-alt"; @observer export class DocumentDecorations extends React.Component<{}, { value: string }> { @@ -76,48 +78,47 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @observable public pushIcon: IconProp = "arrow-alt-circle-up"; @observable public pullIcon: IconProp = "arrow-alt-circle-down"; @observable public pullColor: string = "white"; + @observable public isAnimatingFetch = false; public pullColorAnimating = false; private pullAnimating = false; private pushAnimating = false; public startPullOutcome = action((success: boolean) => { - if (this.pullAnimating) { - return; + if (!this.pullAnimating) { + this.pullAnimating = true; + this.pullIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pullIcon = "arrow-alt-circle-down"; + this.pullAnimating = false; + }), 1000); } - this.pullAnimating = true; - this.pullIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pullIcon = "arrow-alt-circle-down"; - this.pullAnimating = false; - }), 1000); }); public startPushOutcome = action((success: boolean) => { - if (this.pushAnimating) { - return; + if (!this.pushAnimating) { + this.pushAnimating = true; + this.pushIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pushIcon = "arrow-alt-circle-up"; + this.pushAnimating = false; + }), 1000); } - this.pushAnimating = true; - this.pushIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pushIcon = "arrow-alt-circle-up"; - this.pushAnimating = false; - }), 1000); }); - public setPullState = (unchanged: boolean) => { - if (this.pullColorAnimating) { - return; + public setPullState = action((unchanged: boolean) => { + this.isAnimatingFetch = false; + if (!this.pullColorAnimating) { + this.pullColorAnimating = true; + this.pullColor = unchanged ? "lawngreen" : "red"; + setTimeout(this.clearPullColor, 1000); } - this.pullColorAnimating = true; - this.pullColor = unchanged ? "lawngreen" : "red"; - setTimeout(() => { - runInAction(() => { - this.pullColor = "white"; - this.pullColorAnimating = false; - }); - }, 2000); - } + }); + + private clearPullColor = action(() => { + this.pullColor = "white"; + this.pullColorAnimating = false; + }); constructor(props: Readonly<{}>) { super(props); @@ -679,7 +680,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; if (!canPush) return (null); let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; - let icon: IconProp = published ? (this.pushIcon as any) : (cloud as any); + let icon: IconProp = published ? (this.pushIcon as any) : cloud; return (
{ @@ -695,14 +696,33 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> considerGoogleDocsPull = () => { let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; let dataDoc = Doc.GetProto(this.targetDoc); - if (!canPull || !dataDoc[GoogleRef] || dataDoc.unchanged) return (null); + if (!canPull || !dataDoc[GoogleRef]) return (null); + let icon = !dataDoc.unchanged ? (this.pullIcon as any) : fetch; + let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; return (
-
{ - DocumentDecorations.hasPulledHack = false; - this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; - }}> - +
{ + this.clearPullColor(); + DocumentDecorations.hasPulledHack = false; + this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; + dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); + }}> +
); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8a355a425..e06be7079 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -354,7 +354,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe () => { if (!DocumentDecorations.hasPulledHack) { DocumentDecorations.hasPulledHack = true; - this.pullFromGoogleDoc(this.updateState); + let unchanged = this.dataDoc.unchanged; + this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); } } ); diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index ab58329f9..cae5623e6 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -38,9 +38,10 @@ export class RichTextField extends ObjectField { 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. Trim the last newline, an artifact. - let textParagraphs = paragraphs.map(concatenateParagraph); - return textParagraphs.join(joiner).trimEnd(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) { -- cgit v1.2.3-70-g09d2 From 820ad7343816ee9d5881eefdbcbe8783fe903257 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 20 Aug 2019 21:07:43 -0400 Subject: animation fix --- src/client/views/nodes/FormattedTextBox.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 2705fac14..d4cbb5116 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, Lambda, observable, reaction } from "mobx"; +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"; @@ -298,6 +298,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this.pullFromGoogleDoc(this.checkState); + runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true); this._reactionDisposer = reaction( () => { -- cgit v1.2.3-70-g09d2 From c0439996ec8b4858c48a7871120e19bb7c06c67d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 20 Aug 2019 21:37:32 -0400 Subject: drag and drop google docs tweaks --- package.json | 4 +--- src/client/DocServer.ts | 1 - src/client/views/collections/CollectionSubView.tsx | 8 ++++---- src/client/views/nodes/FormattedTextBox.tsx | 1 + 4 files changed, 6 insertions(+), 8 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package.json b/package.json index e6a0a54b9..de1f3f6e6 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "@types/cookie-parser": "^1.4.1", "@types/cookie-session": "^2.0.36", "@types/d3-format": "^1.3.1", - "@types/diff": "^4.0.2", "@types/dotenv": "^6.1.1", "@types/express": "^4.16.1", "@types/express-flash": "0.0.0", @@ -128,7 +127,6 @@ "cookie-session": "^2.0.0-beta.3", "crypto-browserify": "^3.11.0", "d3-format": "^1.3.2", - "diff": "^4.0.1", "dotenv": "^8.0.0", "express": "^4.16.4", "express-flash": "0.0.2", @@ -221,4 +219,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} +} \ No newline at end of file diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 56d9147ae..2cec1046b 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -5,7 +5,6 @@ import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../new_fields/RefField'; import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; -import { docs_v1 } from 'googleapis'; /** * This class encapsulates the transfer and cross-client synchronization of diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 3deec22b5..5b50c545c 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -17,7 +17,7 @@ import { DragManager } from "../../util/DragManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; -import { FormattedTextBox, Blank } from "../nodes/FormattedTextBox"; +import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox"; import { CollectionPDFView } from "./CollectionPDFView"; import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; @@ -209,11 +209,11 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { } let matches: RegExpExecArray | null; if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) { - let newBox = Docs.Create.TextDocument({ ...options, width: 600, height: 400, title: "Fetching title from Google Docs..." }); + let newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." }); let proto = newBox.proto!; proto.autoHeight = true; - proto.googleDocId = matches[2]; - proto.data = "Fetching contents from Google Docs..."; + proto[GoogleRef] = matches[2]; + proto.data = "Please select and then click on this document's pull button to load its contents from from Google Docs..."; proto.backgroundColor = "#eeeeff"; this.props.addDocument(newBox); return; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d4cbb5116..2b01fb70a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -434,6 +434,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe delete dataDoc[GoogleRef]; } DocumentDecorations.Instance.startPullOutcome(pullSuccess); + this.tryUpdateHeight(); } checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { -- cgit v1.2.3-70-g09d2 From 73bb0f572e261850583b698dd819d35a6fe768ec Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 20 Aug 2019 21:42:37 -0400 Subject: cleanup --- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e85374f6a..f28844009 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema'; import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; -import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString, PostToServer } from '../../Utils'; +import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils'; import { DocServer } from '../DocServer'; import { Docs } from '../documents/Documents'; import { ClientUtils } from '../util/ClientUtils'; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5b50c545c..99e5ab7b3 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -213,7 +213,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { let proto = newBox.proto!; proto.autoHeight = true; proto[GoogleRef] = matches[2]; - proto.data = "Please select and then click on this document's pull button to load its contents from from Google Docs..."; + 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); return; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 2b01fb70a..606e8edb0 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -389,9 +389,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }; } let redo = async () => { - if (this._editorView && reference) { - let data = Cast(this.dataDoc.data, RichTextField); - let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent; + let data = Cast(this.dataDoc.data, RichTextField); + if (this._editorView && reference && data) { + let content = data[ToPlainText](); let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); let pushSuccess = response !== undefined && !("errors" in response); dataDoc.unchanged = pushSuccess; -- cgit v1.2.3-70-g09d2 From 8fa3c03f1fae853e3c626e748aef76f9b7bbc875 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Tue, 20 Aug 2019 22:42:25 -0400 Subject: initial commit --- package.json | 2 +- src/client/util/DictationManager.ts | 12 ++++++++++-- src/client/views/nodes/FormattedTextBox.tsx | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package.json b/package.json index de1f3f6e6..cd60b7b55 100644 --- a/package.json +++ b/package.json @@ -219,4 +219,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 488a146bf..758482eae 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -301,11 +301,16 @@ export namespace DictationManager { } }], - ["create bulleted note", { + ["new outline", { action: (target: DocumentView) => { let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" }); + newBox.autoHeight = true; let proto = newBox.proto!; - let proseMirrorState = '"{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":""}]}]}]}]},"selection":{"type":"text","anchor":1,"head":1}}"'; + proto.page = -1; + let prompt = "Press alt + r to start dictating here..."; + let head = 3; + let anchor = head + prompt.length; + let proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`; proto.data = new RichTextField(proseMirrorState); proto.backgroundColor = "#eeffff"; target.props.addDocTab(newBox, proto, "onRight"); @@ -323,6 +328,9 @@ export namespace DictationManager { let what = matches[2]; let dataDoc = Doc.GetProto(target.props.Document); let fieldKey = "data"; + if (isNaN(count)) { + return; + } for (let i = 0; i < count; i++) { let created: Doc | undefined; switch (what) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 606e8edb0..e460e3e50 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -35,6 +35,7 @@ import React = require("react"); import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocumentDecorations } from '../DocumentDecorations'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; +import { DictationManager } from '../../util/DictationManager'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -265,7 +266,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + setCurrentBulletContent = (value: string) => { + if (this._editorView) { + this._editorView.state; + } + } + + considerRecordingSession = (e: KeyboardEvent) => { + if (e.which === 82 && e.altKey) { + let continuous = { indefinite: true }; + DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous + }); + } + } + componentDidMount() { + document.removeEventListener("keypress", this.considerRecordingSession); + document.addEventListener("keypress", this.considerRecordingSession); const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -576,6 +595,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentWillUnmount() { + document.removeEventListener("keypress", this.considerRecordingSession); this._editorView && this._editorView.destroy(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); -- cgit v1.2.3-70-g09d2 From d260d9abc13ae60c3206c3110f2d7f23ceeb2449 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 05:29:07 -0400 Subject: prosemirror bulleted list dictation integration --- src/client/util/DictationManager.ts | 48 ++++++++++-------- src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/nodes/FormattedTextBox.tsx | 76 ++++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 34 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 758482eae..781e5e465 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -45,7 +45,7 @@ export namespace DictationManager { export namespace Controls { - const infringe = "unable to process: dictation manager still involved in previous session"; + export const Infringed = "unable to process: dictation manager still involved in previous session"; const intraSession = ". "; const interSession = " ... "; @@ -69,30 +69,32 @@ export namespace DictationManager { delimiters: DelimiterArgs; interimHandler: InterimResultHandler; tryExecute: boolean; + terminators: string[]; } export const listen = async (options?: Partial) => { let results: string | undefined; - let main = MainView.Instance; + // let main = MainView.Instance; - main.dictationOverlayVisible = true; - main.isListening = { interim: false }; + // main.dictationOverlayVisible = true; + // main.isListening = { interim: false }; try { results = await listenImpl(options); if (results) { Utils.CopyText(results); - main.isListening = false; - let execute = options && options.tryExecute; - main.dictatedPhrase = execute ? results.toLowerCase() : results; - main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + // main.isListening = false; + // let execute = options && options.tryExecute; + // main.dictatedPhrase = execute ? results.toLowerCase() : results; + // main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + options && options.tryExecute && await DictationManager.Commands.execute(results); } } catch (e) { - main.isListening = false; - main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; - main.dictationSuccess = false; + // main.isListening = false; + // main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + // main.dictationSuccess = false; } finally { - main.initiateDictationFade(); + // main.initiateDictationFade(); } return results; @@ -100,7 +102,7 @@ export namespace DictationManager { const listenImpl = (options?: Partial) => { if (isListening) { - return infringe; + return Infringed; } isListening = true; @@ -128,6 +130,12 @@ export namespace DictationManager { recognizer.onresult = (e: SpeechRecognitionEvent) => { current = synthesize(e, intra); + let matchedTerminator: string | undefined; + if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) { + current = matchedTerminator; + recognizer.abort(); + return complete(); + } handler && handler(current); isManuallyStopped && complete(); }; @@ -163,13 +171,13 @@ export namespace DictationManager { } isManuallyStopped = true; salvageSession ? recognizer.stop() : recognizer.abort(); - let main = MainView.Instance; - if (main.dictationOverlayVisible) { - main.cancelDictationFade(); - main.dictationOverlayVisible = false; - main.dictationSuccess = undefined; - setTimeout(() => main.dictatedPhrase = placeholder, 500); - } + // let main = MainView.Instance; + // if (main.dictationOverlayVisible) { + // main.cancelDictationFade(); + // main.dictationOverlayVisible = false; + // main.dictationSuccess = undefined; + // setTimeout(() => main.dictatedPhrase = placeholder, 500); + // } }; const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 891fd7847..cc8e57af9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -730,7 +730,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onPointerEnter={e => e.ctrlKey && runInAction(() => this.openHover = true)} onPointerLeave={() => runInAction(() => this.openHover = false)} onClick={e => { - if (e.ctrlKey) { + if (e.altKey) { + e.preventDefault(); window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); } else { this.clearPullColor(); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index e460e3e50..222ebed8f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -6,7 +6,7 @@ import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model"; -import { EditorState, Plugin, Transaction } from "prosemirror-state"; +import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; @@ -36,6 +36,7 @@ import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/Goog import { DocumentDecorations } from '../DocumentDecorations'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; import { DictationManager } from '../../util/DictationManager'; +import { ReplaceStep } from 'prosemirror-transform'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -181,6 +182,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe const marks = tx.storedMarks; if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); } } + this._applyingChange = true; const fieldkey = "preview"; if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"); @@ -268,23 +270,70 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe setCurrentBulletContent = (value: string) => { if (this._editorView) { - this._editorView.state; + // let next = value.endsWith("bullet"); + // if (next) { + // value = value.split("bullet")[0]; + // } + let state = this._editorView.state; + let from = state.selection.from; + let to = state.selection.to; + this._editorView.dispatch(state.tr.insertText(value, from, to)); + state = this._editorView.state; + let updated = TextSelection.create(state.doc, from, from + value.length); + this._editorView.dispatch(state.tr.setSelection(updated)); + // if (next) { + // this.nextBullet(this._editorView.state.selection.to); + // } } } - considerRecordingSession = (e: KeyboardEvent) => { - if (e.which === 82 && e.altKey) { - let continuous = { indefinite: true }; - DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous - }); + nextBullet = (pos: number) => { + if (this._editorView) { + // DictationManager.Controls.stop(false); + let frag = Fragment.fromArray(this.newListItems(2)); + let slice = new Slice(frag, 2, 2); + let state = this._editorView.state; + this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); + pos += 4; + state = this._editorView.state; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); + // this.recordSession(); + } + } + + private newListItems = (count: number) => { + let listItems: any[] = []; + for (let i = 0; i < count; i++) { + listItems.push(schema.nodes.list_item.create(null, schema.nodes.paragraph.create(null))); } + return listItems; + } + + recordSession = async () => { + let completedCue = "end session"; + let results = await DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + terminators: [completedCue, "bullet", "next"] + }); + if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { + DictationManager.Controls.stop(); + return; + } + this.nextBullet(this._editorView!.state.selection.to); + setTimeout(this.recordSession, 2000); + } + + recordKeyHandler = (e: KeyboardEvent) => { + if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) { + return; + } + e.stopPropagation(); + e.preventDefault(); + e.which === 174 && e.altKey && this.recordSession(); } componentDidMount() { - document.removeEventListener("keypress", this.considerRecordingSession); - document.addEventListener("keypress", this.considerRecordingSession); const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -335,7 +384,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe updatedState.selection = { type: "text", anchor: end, head: end }; this._editorView.updateState(EditorState.fromJSON(config, updatedState)); } - this.tryUpdateHeight(); } } ); @@ -595,7 +643,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentWillUnmount() { - document.removeEventListener("keypress", this.considerRecordingSession); this._editorView && this._editorView.destroy(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); @@ -678,6 +725,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action onFocused = (e: React.FocusEvent): void => { + document.removeEventListener("keypress", this.recordKeyHandler); + document.addEventListener("keypress", this.recordKeyHandler); if (!this.props.isOverlay) { FormattedTextBox.InputBoxOverlay = this; } else { @@ -727,6 +776,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } onBlur = (e: any) => { + document.removeEventListener("keypress", this.recordKeyHandler); if (this._undoTyping) { this._undoTyping.end(); this._undoTyping = undefined; -- cgit v1.2.3-70-g09d2 From 654db73e0615e77f7206128b6e5524553d653c66 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 05:30:39 -0400 Subject: cleanup --- src/client/views/nodes/FormattedTextBox.tsx | 57 ++++++++++++----------------- 1 file changed, 24 insertions(+), 33 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 222ebed8f..a2ea12ac5 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -268,12 +268,32 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + recordKeyHandler = (e: KeyboardEvent) => { + if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) { + return; + } + e.stopPropagation(); + e.preventDefault(); + e.which === 174 && e.altKey && this.recordBullet(); + } + + recordBullet = async () => { + let completedCue = "end session"; + let results = await DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + terminators: [completedCue, "bullet", "next"] + }); + if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { + DictationManager.Controls.stop(); + return; + } + this.nextBullet(this._editorView!.state.selection.to); + setTimeout(this.recordBullet, 2000); + } + setCurrentBulletContent = (value: string) => { if (this._editorView) { - // let next = value.endsWith("bullet"); - // if (next) { - // value = value.split("bullet")[0]; - // } let state = this._editorView.state; let from = state.selection.from; let to = state.selection.to; @@ -281,15 +301,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe state = this._editorView.state; let updated = TextSelection.create(state.doc, from, from + value.length); this._editorView.dispatch(state.tr.setSelection(updated)); - // if (next) { - // this.nextBullet(this._editorView.state.selection.to); - // } } } nextBullet = (pos: number) => { if (this._editorView) { - // DictationManager.Controls.stop(false); let frag = Fragment.fromArray(this.newListItems(2)); let slice = new Slice(frag, 2, 2); let state = this._editorView.state; @@ -297,7 +313,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe pos += 4; state = this._editorView.state; this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); - // this.recordSession(); } } @@ -309,30 +324,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return listItems; } - recordSession = async () => { - let completedCue = "end session"; - let results = await DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous: { indefinite: false }, - terminators: [completedCue, "bullet", "next"] - }); - if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { - DictationManager.Controls.stop(); - return; - } - this.nextBullet(this._editorView!.state.selection.to); - setTimeout(this.recordSession, 2000); - } - - recordKeyHandler = (e: KeyboardEvent) => { - if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) { - return; - } - e.stopPropagation(); - e.preventDefault(); - e.which === 174 && e.altKey && this.recordSession(); - } - componentDidMount() { const config = { schema, -- cgit v1.2.3-70-g09d2 From 2423533d1044ed14b5b356709234bbaa27fd2561 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 10:21:02 -0400 Subject: yeet --- src/client/views/nodes/LinkMenuItem.tsx | 34 ++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx index 12eb2c2f7..1e7ff07c8 100644 --- a/src/client/views/nodes/LinkMenuItem.tsx +++ b/src/client/views/nodes/LinkMenuItem.tsx @@ -48,7 +48,7 @@ export class LinkMenuItem extends React.Component { }, 10000); } - // NOT DONE? + // NOT TESTED // col = collection the doc is in // target = the document to center on @undoBatch @@ -77,7 +77,9 @@ export class LinkMenuItem extends React.Component { // this is the standard "follow link" (jump to document) // taken from follow link @undoBatch - jumpToLink = async (shouldZoom: boolean = false) => { + jumpToLink = async (shouldZoom: boolean) => { + //there is an issue right now so this will be false automatically + shouldZoom = false; this.highlightDoc(); let jumpToDoc = this.props.destinationDoc; let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); @@ -117,12 +119,23 @@ export class LinkMenuItem extends React.Component { SelectionManager.DeselectAll(); } - //opens link in new tab in collection + // NOT TESTED + // opens link in new tab in collection // col = collection the doc is in // target = the document to center on @undoBatch openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { this.highlightDoc(); + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + // CollectionDockingView.Instance.AddRightSplit(col, undefined); + this.props.addDocTab(col, undefined, "inTab"); + SelectionManager.DeselectAll(); } // this will open a link next to the source doc @@ -133,12 +146,23 @@ export class LinkMenuItem extends React.Component { let alias = Doc.MakeAlias(this.props.destinationDoc); let y = this.props.sourceDoc.y; let x = this.props.sourceDoc.x; + let parentView: any = undefined; + let parentDoc: Doc = this.props.sourceDoc; - console.log(x, y); + SelectionManager.SelectedDocuments().map(dv => { + if (dv.props.Document === this.props.sourceDoc) { + parentView = dv.props.ContainingCollectionView; + } + }); + + if (parentView) { + // console.log(parentDoc) + console.log(parentView.props.addDocument) + } } //set this to be the default link behavior, can be any of the above - private defaultLinkBehavior: any = this.openLinkRight; + private defaultLinkBehavior: any = this.openLinkInPlace; onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From 0759b23448de29158367f344342e939dfa6eaf48 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 11:00:08 -0400 Subject: moved links to own folder --- package.json | 2 +- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/PreviewCursor.tsx | 2 +- src/client/views/linking/LinkEditor.scss | 145 ++++++++ src/client/views/linking/LinkEditor.tsx | 400 +++++++++++++++++++++ src/client/views/linking/LinkFollowBox.tsx | 9 + src/client/views/linking/LinkMenu.scss | 137 +++++++ src/client/views/linking/LinkMenu.tsx | 76 ++++ src/client/views/linking/LinkMenuGroup.tsx | 111 ++++++ src/client/views/linking/LinkMenuItem.tsx | 282 +++++++++++++++ src/client/views/nodes/LinkEditor.scss | 145 -------- src/client/views/nodes/LinkEditor.tsx | 400 --------------------- src/client/views/nodes/LinkMenu.scss | 137 ------- src/client/views/nodes/LinkMenu.tsx | 76 ---- src/client/views/nodes/LinkMenuGroup.tsx | 111 ------ src/client/views/nodes/LinkMenuItem.tsx | 282 --------------- src/client/views/nodes/WebBox.tsx | 3 +- .../authentication/models/current_user_utils.ts | 2 +- 18 files changed, 1165 insertions(+), 1157 deletions(-) create mode 100644 src/client/views/linking/LinkEditor.scss create mode 100644 src/client/views/linking/LinkEditor.tsx create mode 100644 src/client/views/linking/LinkFollowBox.tsx create mode 100644 src/client/views/linking/LinkMenu.scss create mode 100644 src/client/views/linking/LinkMenu.tsx create mode 100644 src/client/views/linking/LinkMenuGroup.tsx create mode 100644 src/client/views/linking/LinkMenuItem.tsx delete mode 100644 src/client/views/nodes/LinkEditor.scss delete mode 100644 src/client/views/nodes/LinkEditor.tsx delete mode 100644 src/client/views/nodes/LinkMenu.scss delete mode 100644 src/client/views/nodes/LinkMenu.tsx delete mode 100644 src/client/views/nodes/LinkMenuGroup.tsx delete mode 100644 src/client/views/nodes/LinkMenuItem.tsx (limited to 'src/client/views/nodes') diff --git a/package.json b/package.json index de1f3f6e6..cd60b7b55 100644 --- a/package.json +++ b/package.json @@ -219,4 +219,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b18a3c192..2d92aaba7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -20,7 +20,7 @@ import { DocumentView, PositionDocument } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; -import { LinkMenu } from "./nodes/LinkMenu"; +import { LinkMenu } from "./linking/LinkMenu"; import { TemplateMenu } from "./TemplateMenu"; import { Template, Templates } from "./Templates"; import React = require("react"); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 40be470d6..9ec31d67d 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -51,7 +51,7 @@ export class PreviewCursor extends React.Component<{}> { // tests for URL and makes web document let re: any = /^https?:\/\/www\./g; if (re.test(e.clipboardData.getData("text/plain"))) { - const url = e.clipboardData.getData("text/plain") + const url = e.clipboardData.getData("text/plain"); PreviewCursor._addDocument(Docs.Create.WebDocument(url, { title: url, width: 300, height: 300, // nativeWidth: 300, nativeHeight: 472.5, diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss new file mode 100644 index 000000000..fc5f2410c --- /dev/null +++ b/src/client/views/linking/LinkEditor.scss @@ -0,0 +1,145 @@ +@import "../globalCssVariables"; + +.linkEditor { + width: 100%; + height: auto; + font-size: 12px; // TODO +} + +.linkEditor-back { + margin-bottom: 6px; +} + +.linkEditor-info { + border-bottom: 0.5px solid $light-color-secondary; + padding-bottom: 6px; + margin-bottom: 6px; + display: flex; + justify-content: space-between; + + .linkEditor-linkedTo { + width: calc(100% - 26px); + } +} + +.linkEditor-button { + width: 20px; + height: 20px; + margin-left: 6px; + padding: 0; + // font-size: 12px; + border-radius: 10px; + + &:disabled { + background-color: gray; + } +} + +.linkEditor-groupsLabel { + display: flex; + justify-content: space-between; +} + +.linkEditor-group { + background-color: $light-color-secondary; + padding: 6px; + margin: 3px 0; + border-radius: 3px; + + .linkEditor-group-row { + display: flex; + margin-bottom: 3px; + + .linkEditor-group-row-label { + margin-right: 6px; + } + } + + .linkEditor-metadata-row { + display: flex; + justify-content: space-between; + margin-bottom: 6px; + + .linkEditor-error { + border-color: red; + } + + input { + width: calc(50% - 16px); + height: 20px; + } + + button { + width: 20px; + height: 20px; + margin-left: 3px; + padding: 0; + font-size: 10px; + } + } +} + + +.linkEditor-dropdown { + width: 100%; + position: relative; + z-index: 999; + + input { + width: 100%; + } + + .linkEditor-options-wrapper { + width: 100%; + position: absolute; + top: 19px; + left: 0; + display: flex; + flex-direction: column; + } + + .linkEditor-option { + background-color: $light-color-secondary; + border: 1px solid $intermediate-color; + border-top: 0; + padding: 3px; + cursor: pointer; + + &:hover { + background-color: lightgray; + } + + &.onDown { + background-color: gray; + } + } +} + +.linkEditor-typeButton { + background-color: transparent; + color: $dark-color; + width: 100%; + height: 20px; + padding: 0 3px; + padding-bottom: 2px; + text-align: left; + text-transform: none; + letter-spacing: normal; + font-size: 12px; + font-weight: bold; + + &:hover { + background-color: $light-color; + } +} + +.linkEditor-group-buttons { + height: 20px; + display: flex; + justify-content: flex-end; + margin-top: 5px; + + .linkEditor-button { + margin-left: 6px; + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx new file mode 100644 index 000000000..ecb3e9db4 --- /dev/null +++ b/src/client/views/linking/LinkEditor.tsx @@ -0,0 +1,400 @@ +import { observable, computed, action, trace } from "mobx"; +import React = require("react"); +import { observer } from "mobx-react"; +import './LinkEditor.scss'; +import { StrCast, Cast, FieldValue } from "../../../new_fields/Types"; +import { Doc } from "../../../new_fields/Doc"; +import { LinkManager } from "../../util/LinkManager"; +import { Docs } from "../../documents/Documents"; +import { Utils } from "../../../Utils"; +import { faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus } from '@fortawesome/free-solid-svg-icons'; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { SetupDrag } from "../../util/DragManager"; +import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; + +library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus); + + +interface GroupTypesDropdownProps { + groupType: string; + setGroupType: (group: string) => void; +} +// this dropdown could be generalized +@observer +class GroupTypesDropdown extends React.Component { + @observable private _searchTerm: string = this.props.groupType; + @observable private _groupType: string = this.props.groupType; + @observable private _isEditing: boolean = false; + + @action + createGroup = (groupType: string): void => { + this.props.setGroupType(groupType); + LinkManager.Instance.addGroupType(groupType); + } + + @action + onChange = (val: string): void => { + this._searchTerm = val; + this._groupType = val; + this._isEditing = true; + } + + @action + onKeyDown = (e: React.KeyboardEvent): void => { + if (e.key === "Enter") { + let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes()); + let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()); + + if (exactFound > -1) { + let groupType = groupOptions[exactFound]; + this.props.setGroupType(groupType); + this._groupType = groupType; + } else { + this.createGroup(this._searchTerm); + this._groupType = this._searchTerm; + } + + this._searchTerm = this._groupType; + this._isEditing = false; + } + } + + @action + onOptionClick = (value: string, createNew: boolean): void => { + if (createNew) { + this.createGroup(this._searchTerm); + this._groupType = this._searchTerm; + + } else { + this.props.setGroupType(value); + this._groupType = value; + + } + this._searchTerm = this._groupType; + this._isEditing = false; + } + + @action + onButtonPointerDown = (): void => { + this._isEditing = true; + } + + renderOptions = (): JSX.Element[] | JSX.Element => { + if (this._searchTerm === "") return <>; + + let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes()); + let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1; + + let options = groupOptions.map(groupType => { + let ref = React.createRef(); + return
this.onOptionClick(groupType, false)}>{groupType}
; + }); + + // if search term does not already exist as a group type, give option to create new group type + if (!exactFound && this._searchTerm !== "") { + let ref = React.createRef(); + options.push(
this.onOptionClick(this._searchTerm, true)}>Define new "{this._searchTerm}" relationship
); + } + + return options; + } + + render() { + if (this._isEditing || this._groupType === "") { + return ( +
+ this.onChange(e.target.value)} onKeyDown={this.onKeyDown} autoFocus> +
+ {this.renderOptions()} +
+
+ ); + } else { + return ; + } + } +} + + +interface LinkMetadataEditorProps { + id: string; + groupType: string; + mdDoc: Doc; + mdKey: string; + mdValue: string; + changeMdIdKey: (id: string, newKey: string) => void; +} +@observer +class LinkMetadataEditor extends React.Component { + @observable private _key: string = this.props.mdKey; + @observable private _value: string = this.props.mdValue; + @observable private _keyError: boolean = false; + + @action + setMetadataKey = (value: string): void => { + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType); + + // don't allow user to create existing key + let newIndex = groupMdKeys.findIndex(key => key.toUpperCase() === value.toUpperCase()); + if (newIndex > -1) { + this._keyError = true; + this._key = value; + return; + } else { + this._keyError = false; + } + + // set new value for key + let currIndex = groupMdKeys.findIndex(key => { + return StrCast(key).toUpperCase() === this._key.toUpperCase(); + }); + if (currIndex === -1) console.error("LinkMetadataEditor: key was not found"); + groupMdKeys[currIndex] = value; + + this.props.changeMdIdKey(this.props.id, value); + this._key = value; + LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, [...groupMdKeys]); + } + + @action + setMetadataValue = (value: string): void => { + if (!this._keyError) { + this._value = value; + this.props.mdDoc[this._key] = value; + } + } + + @action + removeMetadata = (): void => { + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType); + + let index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase()); + if (index === -1) console.error("LinkMetadataEditor: key was not found"); + groupMdKeys.splice(index, 1); + + LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys); + this._key = ""; + } + + render() { + return ( +
+ this.setMetadataKey(e.target.value)}>: + this.setMetadataValue(e.target.value)}> + +
+ ); + } +} + +interface LinkGroupEditorProps { + sourceDoc: Doc; + linkDoc: Doc; + groupDoc: Doc; +} +@observer +export class LinkGroupEditor extends React.Component { + + private _metadataIds: Map = new Map(); + + constructor(props: LinkGroupEditorProps) { + super(props); + + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.type)); + groupMdKeys.forEach(key => { + this._metadataIds.set(key, Utils.GenerateGuid()); + }); + } + + @action + setGroupType = (groupType: string): void => { + this.props.groupDoc.type = groupType; + } + + removeGroupFromLink = (groupType: string): void => { + LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType); + } + + deleteGroup = (groupType: string): void => { + LinkManager.Instance.deleteGroupType(groupType); + } + + copyGroup = async (groupType: string): Promise => { + let sourceGroupDoc = this.props.groupDoc; + const sourceMdDoc = await Cast(sourceGroupDoc.metadata, Doc); + if (!sourceMdDoc) return; + + let destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + // let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc); + let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + + // create new metadata doc with copied kvp + let destMdDoc = new Doc(); + destMdDoc.anchor1 = StrCast(sourceMdDoc.anchor2); + destMdDoc.anchor2 = StrCast(sourceMdDoc.anchor1); + keys.forEach(key => { + let val = sourceMdDoc[key] === undefined ? "" : StrCast(sourceMdDoc[key]); + destMdDoc[key] = val; + }); + + // create new group doc with new metadata doc + let destGroupDoc = new Doc(); + destGroupDoc.type = groupType; + destGroupDoc.metadata = destMdDoc; + + if (destDoc) { + LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true); + } + } + + @action + addMetadata = (groupType: string): void => { + this._metadataIds.set("new key", Utils.GenerateGuid()); + let mdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + // only add "new key" if there is no other key with value "new key"; prevents spamming + if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key"); + LinkManager.Instance.setMetadataKeysForGroup(groupType, mdKeys); + } + + // for key rendering purposes + changeMdIdKey = (id: string, newKey: string) => { + this._metadataIds.set(newKey, id); + } + + renderMetadata = (): JSX.Element[] => { + let metadata: Array = []; + let groupDoc = this.props.groupDoc; + const mdDoc = FieldValue(Cast(groupDoc.metadata, Doc)); + if (!mdDoc) { + return []; + } + let groupType = StrCast(groupDoc.type); + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + + groupMdKeys.forEach((key) => { + let val = StrCast(mdDoc[key]); + metadata.push( + + ); + }); + return metadata; + } + + viewGroupAsTable = (groupType: string): JSX.Element => { + let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + let index = keys.indexOf(""); + if (index > -1) keys.splice(index, 1); + let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb")); + let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType); + let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" })); + let ref = React.createRef(); + return
; + } + + render() { + let groupType = StrCast(this.props.groupDoc.type); + // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") { + let buttons; + if (groupType === "") { + buttons = ( + <> + + + + + + + ); + } else { + buttons = ( + <> + + + + + {this.viewGroupAsTable(groupType)} + + ); + } + return ( +
+
+

type:

+ +
+ {this.renderMetadata().length > 0 ?

metadata:

: <>} + {this.renderMetadata()} +
+ {buttons} +
+
+ ); + } +} + + +interface LinkEditorProps { + sourceDoc: Doc; + linkDoc: Doc; + showLinks: () => void; +} +@observer +export class LinkEditor extends React.Component { + + @action + deleteLink = (): void => { + LinkManager.Instance.deleteLink(this.props.linkDoc); + this.props.showLinks(); + } + + @action + addGroup = (): void => { + // create new metadata document for group + let mdDoc = new Doc(); + mdDoc.anchor1 = this.props.sourceDoc.title; + let opp = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + if (opp) { + mdDoc.anchor2 = opp.title; + } + + // create new group document + let groupDoc = new Doc(); + groupDoc.type = ""; + groupDoc.metadata = mdDoc; + + LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, this.props.sourceDoc, groupDoc); + } + + render() { + let destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + + let groupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc); + let groups = groupList.map(groupDoc => { + return ; + }); + + if (destination) { + return ( +
+ +
+

editing link to: {destination.proto!.title}

+ +
+
+ Relationships: + +
+ {groups.length > 0 ? groups :
There are currently no relationships associated with this link.
} +
+ + ); + } + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx new file mode 100644 index 000000000..487281d50 --- /dev/null +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -0,0 +1,9 @@ +import { observable, computed, action, trace } from "mobx"; +import React = require("react"); +import { observer } from "mobx-react"; +import { FieldViewProps } from "../nodes/FieldView"; + +@observer +export class LinkFollowBox extends React.Component { + +} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss new file mode 100644 index 000000000..a4018bd2d --- /dev/null +++ b/src/client/views/linking/LinkMenu.scss @@ -0,0 +1,137 @@ +@import "../globalCssVariables"; + +.linkMenu { + width: 100%; + height: auto; +} + +.linkMenu-list { + max-height: 200px; + overflow-y: scroll; +} + +.linkMenu-group { + border-bottom: 0.5px solid lightgray; + padding: 5px 0; + + + &:last-child { + border-bottom: none; + } + + .linkMenu-group-name { + display: flex; + + &:hover { + p { + background-color: lightgray; + } + p.expand-one { + width: calc(100% - 26px); + } + .linkEditor-tableButton { + display: block; + } + } + + p { + width: 100%; + padding: 4px 6px; + line-height: 12px; + border-radius: 5px; + font-weight: bold; + } + + .linkEditor-tableButton { + display: none; + } + } +} + +.linkMenu-item { + // border-top: 0.5px solid $main-accent; + position: relative; + display: flex; + font-size: 12px; + + + .link-name { + position: relative; + + p { + padding: 4px 6px; + line-height: 12px; + border-radius: 5px; + overflow-wrap: break-word; + } + } + + .linkMenu-item-content { + width: 100%; + } + + .link-metadata { + padding: 0 10px 0 16px; + margin-bottom: 4px; + color: $main-accent; + font-style: italic; + font-size: 10.5px; + } + + &:hover { + .linkMenu-item-buttons { + display: flex; + } + .linkMenu-item-content { + &.expand-two p { + width: calc(100% - 52px); + background-color: lightgray; + } + &.expand-three p { + width: calc(100% - 84px); + background-color: lightgray; + } + } + } +} + +.linkMenu-item-buttons { + display: none; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + + .button { + width: 20px; + height: 20px; + margin: 0; + margin-right: 6px; + border-radius: 50%; + cursor: pointer; + pointer-events: auto; + background-color: $dark-color; + color: $light-color; + font-size: 65%; + transition: transform 0.2s; + text-align: center; + position: relative; + + .fa-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &:last-child { + margin-right: 0; + } + &:hover { + background: $main-accent; + } + } +} + + + diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx new file mode 100644 index 000000000..842ce45b1 --- /dev/null +++ b/src/client/views/linking/LinkMenu.tsx @@ -0,0 +1,76 @@ +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { DocumentView } from "../nodes/DocumentView"; +import { LinkEditor } from "./LinkEditor"; +import './LinkMenu.scss'; +import React = require("react"); +import { Doc } from "../../../new_fields/Doc"; +import { LinkManager } from "../../util/LinkManager"; +import { LinkMenuGroup } from "./LinkMenuGroup"; +import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +library.add(faTrash); + +interface Props { + docView: DocumentView; + changeFlyout: () => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +} + +@observer +export class LinkMenu extends React.Component { + + @observable private _editingLink?: Doc; + + @action + componentWillReceiveProps() { + this._editingLink = undefined; + } + + clearAllLinks = () => { + LinkManager.Instance.deleteAllLinksOnAnchor(this.props.docView.props.Document); + } + + renderAllGroups = (groups: Map>): Array => { + let linkItems: Array = []; + groups.forEach((group, groupType) => { + linkItems.push( + this._editingLink = linkDoc)} + addDocTab={this.props.addDocTab} /> + ); + }); + + // if source doc has no links push message + if (linkItems.length === 0) linkItems.push(

No links have been created yet. Drag the linking button onto another document to create a link.

); + + return linkItems; + } + + render() { + let sourceDoc = this.props.docView.props.Document; + let groups: Map = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc); + if (this._editingLink === undefined) { + return ( +
+ + {/* */} +
+ {this.renderAllGroups(groups)} +
+
+ ); + } else { + return ( + this._editingLink = undefined)}> + ); + } + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx new file mode 100644 index 000000000..b6a24b0c8 --- /dev/null +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -0,0 +1,111 @@ +import { action, observable } 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 { Id } from "../../../new_fields/FieldSymbols"; +import { LinkManager } from "../../util/LinkManager"; +import { DragLinksAsDocuments, DragManager, SetupDrag } from "../../util/DragManager"; +import { emptyFunction } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { UndoManager } from "../../util/UndoManager"; +import { StrCast } from "../../../new_fields/Types"; +import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; + +interface LinkMenuGroupProps { + sourceDoc: Doc; + group: Doc[]; + groupType: string; + showEditor: (linkDoc: Doc) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + docView: DocumentView; + +} + +@observer +export class LinkMenuGroup extends React.Component { + + private _drag = React.createRef(); + private _table = React.createRef(); + + onLinkButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.addEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + document.addEventListener("pointerup", this.onLinkButtonUp); + } + + onLinkButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + e.stopPropagation(); + } + + + onLinkButtonMoved = async (e: PointerEvent) => { + UndoManager.RunInBatch(() => { + if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + + let draggedDocs = this.props.group.map(linkDoc => { + 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)); + + DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + }, "drag links"); + e.stopPropagation(); + } + + viewGroupAsTable = (groupType: string): JSX.Element => { + let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + let index = keys.indexOf(""); + if (index > -1) keys.splice(index, 1); + let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb")); + let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType); + let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" })); + let ref = React.createRef(); + return
; + } + + render() { + let groupItems = this.props.group.map(linkDoc => { + let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); + if (destination && this.props.sourceDoc) { + return ; + } + }); + + return ( +
+
+

{this.props.groupType}:

+ {this.props.groupType === "*" || this.props.groupType === "" ? <> : this.viewGroupAsTable(this.props.groupType)} +
+
+ {groupItems} +
+
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx new file mode 100644 index 000000000..7dc0a9fcd --- /dev/null +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -0,0 +1,282 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { observer } from "mobx-react"; +import { DocumentManager } from "../../util/DocumentManager"; +import { undoBatch } from "../../util/UndoManager"; +import './LinkMenu.scss'; +import React = require("react"); +import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; +import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types'; +import { observable, action } from 'mobx'; +import { LinkManager } from '../../util/LinkManager'; +import { DragLinkAsDocument } from '../../util/DragManager'; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { SelectionManager } from '../../util/SelectionManager'; +import { CollectionViewType } from '../collections/CollectionBaseView'; +import { DocumentView } from '../nodes/DocumentView'; +library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); + + +interface LinkMenuItemProps { + groupType: string; + linkDoc: Doc; + sourceDoc: Doc; + destinationDoc: Doc; + showEditor: (linkDoc: Doc) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +} + +@observer +export class LinkMenuItem extends React.Component { + private _drag = React.createRef(); + @observable private _showMore: boolean = false; + @action toggleShowMore() { this._showMore = !this._showMore; } + + + unhighlight = () => { + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", this.unhighlight); + } + + @action + highlightDoc = () => { + document.removeEventListener("pointerdown", this.unhighlight); + Doc.HighlightDoc(this.props.destinationDoc); + window.setTimeout(() => { + document.addEventListener("pointerdown", this.unhighlight); + }, 10000); + } + + // NOT TESTED + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColRight = ({ col, target }: { col: Doc, target: Doc }) => { + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + CollectionDockingView.Instance.AddRightSplit(col, undefined); + } + + // DONE + // this opens the linked doc in a right split, NOT in its collection + @undoBatch + openLinkRight = () => { + this.highlightDoc(); + let alias = Doc.MakeAlias(this.props.destinationDoc); + CollectionDockingView.Instance.AddRightSplit(alias, undefined); + SelectionManager.DeselectAll(); + } + + // DONE + // this is the standard "follow link" (jump to document) + // taken from follow link + @undoBatch + jumpToLink = async (shouldZoom: boolean) => { + //there is an issue right now so this will be false automatically + shouldZoom = false; + this.highlightDoc(); + let jumpToDoc = this.props.destinationDoc; + let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); + if (pdfDoc) { + jumpToDoc = pdfDoc; + } + let proto = Doc.GetProto(this.props.linkDoc); + let targetContext = await Cast(proto.targetContext, Doc); + let sourceContext = await Cast(proto.sourceContext, Doc); + let self = this; + + let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + + if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); + } + else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + } + else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); + + } + else { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); + } + } + + // DONE + // opens link in new tab (not in a collection) + // this opens it full screen, do we need a separate full screen option? + @undoBatch + openLinkTab = () => { + this.highlightDoc(); + let fullScreenAlias = Doc.MakeAlias(this.props.destinationDoc); + this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // NOT TESTED + // opens link in new tab in collection + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { + this.highlightDoc(); + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + // CollectionDockingView.Instance.AddRightSplit(col, undefined); + this.props.addDocTab(col, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // this will open a link next to the source doc + @undoBatch + openLinkInPlace = () => { + this.highlightDoc(); + + let alias = Doc.MakeAlias(this.props.destinationDoc); + let y = this.props.sourceDoc.y; + let x = this.props.sourceDoc.x; + let parentView: any = undefined; + let parentDoc: Doc = this.props.sourceDoc; + + SelectionManager.SelectedDocuments().map(dv => { + if (dv.props.Document === this.props.sourceDoc) { + parentView = dv.props.ContainingCollectionView; + } + }); + + if (parentView) { + // console.log(parentDoc) + console.log(parentView.props.addDocument); + } + } + + //set this to be the default link behavior, can be any of the above + private defaultLinkBehavior: any = this.openLinkInPlace; + + onEdit = (e: React.PointerEvent): void => { + e.stopPropagation(); + this.props.showEditor(this.props.linkDoc); + SelectionManager.DeselectAll(); + } + + renderMetadata = (): JSX.Element => { + let groups = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc); + let index = groups.findIndex(groupDoc => StrCast(groupDoc.type).toUpperCase() === this.props.groupType.toUpperCase()); + let groupDoc = index > -1 ? groups[index] : undefined; + + let mdRows: Array = []; + if (groupDoc) { + let mdDoc = Cast(groupDoc.metadata, Doc, null); + if (mdDoc) { + let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType); + mdRows = keys.map(key => { + return (
{key}: {StrCast(mdDoc[key])}
); + }); + } + } + + return (
{mdRows}
); + } + + onLinkButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.addEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + document.addEventListener("pointerup", this.onLinkButtonUp); + } + + onLinkButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + e.stopPropagation(); + } + + onLinkButtonMoved = async (e: PointerEvent) => { + if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + + DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc); + } + e.stopPropagation(); + } + + render() { + + let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType); + let canExpand = keys ? keys.length > 0 : false; + + return ( +
+
+
+

{StrCast(this.props.destinationDoc.title)}

+
+ {canExpand ?
this.toggleShowMore()}> +
: <>} +
+ {/* Original */} + {/*
*/} + {/* New */} +
+
+
+ {this._showMore ? this.renderMetadata() : <>} +
+ +
+ ); + } +} + + // @undoBatch + // onFollowLink = async (e: React.PointerEvent): Promise => { + // e.stopPropagation(); + // e.persist(); + // let jumpToDoc = this.props.destinationDoc; + // let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); + // if (pdfDoc) { + // jumpToDoc = pdfDoc; + // } + // let proto = Doc.GetProto(this.props.linkDoc); + // let targetContext = await Cast(proto.targetContext, Doc); + // let sourceContext = await Cast(proto.sourceContext, Doc); + // let self = this; + + + // let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + // if (e.ctrlKey) { + // dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined); + // } + + // if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!); + // console.log("1") + // } + // else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!)); + // console.log("2") + // } + // else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); + // console.log("3") + + // } + // else { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc); + // console.log("4") + + // } + // } \ No newline at end of file diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss deleted file mode 100644 index fc5f2410c..000000000 --- a/src/client/views/nodes/LinkEditor.scss +++ /dev/null @@ -1,145 +0,0 @@ -@import "../globalCssVariables"; - -.linkEditor { - width: 100%; - height: auto; - font-size: 12px; // TODO -} - -.linkEditor-back { - margin-bottom: 6px; -} - -.linkEditor-info { - border-bottom: 0.5px solid $light-color-secondary; - padding-bottom: 6px; - margin-bottom: 6px; - display: flex; - justify-content: space-between; - - .linkEditor-linkedTo { - width: calc(100% - 26px); - } -} - -.linkEditor-button { - width: 20px; - height: 20px; - margin-left: 6px; - padding: 0; - // font-size: 12px; - border-radius: 10px; - - &:disabled { - background-color: gray; - } -} - -.linkEditor-groupsLabel { - display: flex; - justify-content: space-between; -} - -.linkEditor-group { - background-color: $light-color-secondary; - padding: 6px; - margin: 3px 0; - border-radius: 3px; - - .linkEditor-group-row { - display: flex; - margin-bottom: 3px; - - .linkEditor-group-row-label { - margin-right: 6px; - } - } - - .linkEditor-metadata-row { - display: flex; - justify-content: space-between; - margin-bottom: 6px; - - .linkEditor-error { - border-color: red; - } - - input { - width: calc(50% - 16px); - height: 20px; - } - - button { - width: 20px; - height: 20px; - margin-left: 3px; - padding: 0; - font-size: 10px; - } - } -} - - -.linkEditor-dropdown { - width: 100%; - position: relative; - z-index: 999; - - input { - width: 100%; - } - - .linkEditor-options-wrapper { - width: 100%; - position: absolute; - top: 19px; - left: 0; - display: flex; - flex-direction: column; - } - - .linkEditor-option { - background-color: $light-color-secondary; - border: 1px solid $intermediate-color; - border-top: 0; - padding: 3px; - cursor: pointer; - - &:hover { - background-color: lightgray; - } - - &.onDown { - background-color: gray; - } - } -} - -.linkEditor-typeButton { - background-color: transparent; - color: $dark-color; - width: 100%; - height: 20px; - padding: 0 3px; - padding-bottom: 2px; - text-align: left; - text-transform: none; - letter-spacing: normal; - font-size: 12px; - font-weight: bold; - - &:hover { - background-color: $light-color; - } -} - -.linkEditor-group-buttons { - height: 20px; - display: flex; - justify-content: flex-end; - margin-top: 5px; - - .linkEditor-button { - margin-left: 6px; - } -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx deleted file mode 100644 index ecb3e9db4..000000000 --- a/src/client/views/nodes/LinkEditor.tsx +++ /dev/null @@ -1,400 +0,0 @@ -import { observable, computed, action, trace } from "mobx"; -import React = require("react"); -import { observer } from "mobx-react"; -import './LinkEditor.scss'; -import { StrCast, Cast, FieldValue } from "../../../new_fields/Types"; -import { Doc } from "../../../new_fields/Doc"; -import { LinkManager } from "../../util/LinkManager"; -import { Docs } from "../../documents/Documents"; -import { Utils } from "../../../Utils"; -import { faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus } from '@fortawesome/free-solid-svg-icons'; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { SetupDrag } from "../../util/DragManager"; -import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; - -library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus); - - -interface GroupTypesDropdownProps { - groupType: string; - setGroupType: (group: string) => void; -} -// this dropdown could be generalized -@observer -class GroupTypesDropdown extends React.Component { - @observable private _searchTerm: string = this.props.groupType; - @observable private _groupType: string = this.props.groupType; - @observable private _isEditing: boolean = false; - - @action - createGroup = (groupType: string): void => { - this.props.setGroupType(groupType); - LinkManager.Instance.addGroupType(groupType); - } - - @action - onChange = (val: string): void => { - this._searchTerm = val; - this._groupType = val; - this._isEditing = true; - } - - @action - onKeyDown = (e: React.KeyboardEvent): void => { - if (e.key === "Enter") { - let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes()); - let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); - let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()); - - if (exactFound > -1) { - let groupType = groupOptions[exactFound]; - this.props.setGroupType(groupType); - this._groupType = groupType; - } else { - this.createGroup(this._searchTerm); - this._groupType = this._searchTerm; - } - - this._searchTerm = this._groupType; - this._isEditing = false; - } - } - - @action - onOptionClick = (value: string, createNew: boolean): void => { - if (createNew) { - this.createGroup(this._searchTerm); - this._groupType = this._searchTerm; - - } else { - this.props.setGroupType(value); - this._groupType = value; - - } - this._searchTerm = this._groupType; - this._isEditing = false; - } - - @action - onButtonPointerDown = (): void => { - this._isEditing = true; - } - - renderOptions = (): JSX.Element[] | JSX.Element => { - if (this._searchTerm === "") return <>; - - let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes()); - let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); - let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1; - - let options = groupOptions.map(groupType => { - let ref = React.createRef(); - return
this.onOptionClick(groupType, false)}>{groupType}
; - }); - - // if search term does not already exist as a group type, give option to create new group type - if (!exactFound && this._searchTerm !== "") { - let ref = React.createRef(); - options.push(
this.onOptionClick(this._searchTerm, true)}>Define new "{this._searchTerm}" relationship
); - } - - return options; - } - - render() { - if (this._isEditing || this._groupType === "") { - return ( -
- this.onChange(e.target.value)} onKeyDown={this.onKeyDown} autoFocus> -
- {this.renderOptions()} -
-
- ); - } else { - return ; - } - } -} - - -interface LinkMetadataEditorProps { - id: string; - groupType: string; - mdDoc: Doc; - mdKey: string; - mdValue: string; - changeMdIdKey: (id: string, newKey: string) => void; -} -@observer -class LinkMetadataEditor extends React.Component { - @observable private _key: string = this.props.mdKey; - @observable private _value: string = this.props.mdValue; - @observable private _keyError: boolean = false; - - @action - setMetadataKey = (value: string): void => { - let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType); - - // don't allow user to create existing key - let newIndex = groupMdKeys.findIndex(key => key.toUpperCase() === value.toUpperCase()); - if (newIndex > -1) { - this._keyError = true; - this._key = value; - return; - } else { - this._keyError = false; - } - - // set new value for key - let currIndex = groupMdKeys.findIndex(key => { - return StrCast(key).toUpperCase() === this._key.toUpperCase(); - }); - if (currIndex === -1) console.error("LinkMetadataEditor: key was not found"); - groupMdKeys[currIndex] = value; - - this.props.changeMdIdKey(this.props.id, value); - this._key = value; - LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, [...groupMdKeys]); - } - - @action - setMetadataValue = (value: string): void => { - if (!this._keyError) { - this._value = value; - this.props.mdDoc[this._key] = value; - } - } - - @action - removeMetadata = (): void => { - let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType); - - let index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase()); - if (index === -1) console.error("LinkMetadataEditor: key was not found"); - groupMdKeys.splice(index, 1); - - LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys); - this._key = ""; - } - - render() { - return ( -
- this.setMetadataKey(e.target.value)}>: - this.setMetadataValue(e.target.value)}> - -
- ); - } -} - -interface LinkGroupEditorProps { - sourceDoc: Doc; - linkDoc: Doc; - groupDoc: Doc; -} -@observer -export class LinkGroupEditor extends React.Component { - - private _metadataIds: Map = new Map(); - - constructor(props: LinkGroupEditorProps) { - super(props); - - let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.type)); - groupMdKeys.forEach(key => { - this._metadataIds.set(key, Utils.GenerateGuid()); - }); - } - - @action - setGroupType = (groupType: string): void => { - this.props.groupDoc.type = groupType; - } - - removeGroupFromLink = (groupType: string): void => { - LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType); - } - - deleteGroup = (groupType: string): void => { - LinkManager.Instance.deleteGroupType(groupType); - } - - copyGroup = async (groupType: string): Promise => { - let sourceGroupDoc = this.props.groupDoc; - const sourceMdDoc = await Cast(sourceGroupDoc.metadata, Doc); - if (!sourceMdDoc) return; - - let destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - // let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc); - let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); - - // create new metadata doc with copied kvp - let destMdDoc = new Doc(); - destMdDoc.anchor1 = StrCast(sourceMdDoc.anchor2); - destMdDoc.anchor2 = StrCast(sourceMdDoc.anchor1); - keys.forEach(key => { - let val = sourceMdDoc[key] === undefined ? "" : StrCast(sourceMdDoc[key]); - destMdDoc[key] = val; - }); - - // create new group doc with new metadata doc - let destGroupDoc = new Doc(); - destGroupDoc.type = groupType; - destGroupDoc.metadata = destMdDoc; - - if (destDoc) { - LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true); - } - } - - @action - addMetadata = (groupType: string): void => { - this._metadataIds.set("new key", Utils.GenerateGuid()); - let mdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType); - // only add "new key" if there is no other key with value "new key"; prevents spamming - if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key"); - LinkManager.Instance.setMetadataKeysForGroup(groupType, mdKeys); - } - - // for key rendering purposes - changeMdIdKey = (id: string, newKey: string) => { - this._metadataIds.set(newKey, id); - } - - renderMetadata = (): JSX.Element[] => { - let metadata: Array = []; - let groupDoc = this.props.groupDoc; - const mdDoc = FieldValue(Cast(groupDoc.metadata, Doc)); - if (!mdDoc) { - return []; - } - let groupType = StrCast(groupDoc.type); - let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType); - - groupMdKeys.forEach((key) => { - let val = StrCast(mdDoc[key]); - metadata.push( - - ); - }); - return metadata; - } - - viewGroupAsTable = (groupType: string): JSX.Element => { - let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); - let index = keys.indexOf(""); - if (index > -1) keys.splice(index, 1); - let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb")); - let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType); - let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" })); - let ref = React.createRef(); - return
; - } - - render() { - let groupType = StrCast(this.props.groupDoc.type); - // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") { - let buttons; - if (groupType === "") { - buttons = ( - <> - - - - - - - ); - } else { - buttons = ( - <> - - - - - {this.viewGroupAsTable(groupType)} - - ); - } - return ( -
-
-

type:

- -
- {this.renderMetadata().length > 0 ?

metadata:

: <>} - {this.renderMetadata()} -
- {buttons} -
-
- ); - } -} - - -interface LinkEditorProps { - sourceDoc: Doc; - linkDoc: Doc; - showLinks: () => void; -} -@observer -export class LinkEditor extends React.Component { - - @action - deleteLink = (): void => { - LinkManager.Instance.deleteLink(this.props.linkDoc); - this.props.showLinks(); - } - - @action - addGroup = (): void => { - // create new metadata document for group - let mdDoc = new Doc(); - mdDoc.anchor1 = this.props.sourceDoc.title; - let opp = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - if (opp) { - mdDoc.anchor2 = opp.title; - } - - // create new group document - let groupDoc = new Doc(); - groupDoc.type = ""; - groupDoc.metadata = mdDoc; - - LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, this.props.sourceDoc, groupDoc); - } - - render() { - let destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - - let groupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc); - let groups = groupList.map(groupDoc => { - return ; - }); - - if (destination) { - return ( -
- -
-

editing link to: {destination.proto!.title}

- -
-
- Relationships: - -
- {groups.length > 0 ? groups :
There are currently no relationships associated with this link.
} -
- - ); - } - } -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss deleted file mode 100644 index a4018bd2d..000000000 --- a/src/client/views/nodes/LinkMenu.scss +++ /dev/null @@ -1,137 +0,0 @@ -@import "../globalCssVariables"; - -.linkMenu { - width: 100%; - height: auto; -} - -.linkMenu-list { - max-height: 200px; - overflow-y: scroll; -} - -.linkMenu-group { - border-bottom: 0.5px solid lightgray; - padding: 5px 0; - - - &:last-child { - border-bottom: none; - } - - .linkMenu-group-name { - display: flex; - - &:hover { - p { - background-color: lightgray; - } - p.expand-one { - width: calc(100% - 26px); - } - .linkEditor-tableButton { - display: block; - } - } - - p { - width: 100%; - padding: 4px 6px; - line-height: 12px; - border-radius: 5px; - font-weight: bold; - } - - .linkEditor-tableButton { - display: none; - } - } -} - -.linkMenu-item { - // border-top: 0.5px solid $main-accent; - position: relative; - display: flex; - font-size: 12px; - - - .link-name { - position: relative; - - p { - padding: 4px 6px; - line-height: 12px; - border-radius: 5px; - overflow-wrap: break-word; - } - } - - .linkMenu-item-content { - width: 100%; - } - - .link-metadata { - padding: 0 10px 0 16px; - margin-bottom: 4px; - color: $main-accent; - font-style: italic; - font-size: 10.5px; - } - - &:hover { - .linkMenu-item-buttons { - display: flex; - } - .linkMenu-item-content { - &.expand-two p { - width: calc(100% - 52px); - background-color: lightgray; - } - &.expand-three p { - width: calc(100% - 84px); - background-color: lightgray; - } - } - } -} - -.linkMenu-item-buttons { - display: none; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - - .button { - width: 20px; - height: 20px; - margin: 0; - margin-right: 6px; - border-radius: 50%; - cursor: pointer; - pointer-events: auto; - background-color: $dark-color; - color: $light-color; - font-size: 65%; - transition: transform 0.2s; - text-align: center; - position: relative; - - .fa-icon { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - - &:last-child { - margin-right: 0; - } - &:hover { - background: $main-accent; - } - } -} - - - diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx deleted file mode 100644 index 16e318f7a..000000000 --- a/src/client/views/nodes/LinkMenu.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import { DocumentView } from "./DocumentView"; -import { LinkEditor } from "./LinkEditor"; -import './LinkMenu.scss'; -import React = require("react"); -import { Doc } from "../../../new_fields/Doc"; -import { LinkManager } from "../../util/LinkManager"; -import { LinkMenuGroup } from "./LinkMenuGroup"; -import { faTrash } from '@fortawesome/free-solid-svg-icons'; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -library.add(faTrash); - -interface Props { - docView: DocumentView; - changeFlyout: () => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; -} - -@observer -export class LinkMenu extends React.Component { - - @observable private _editingLink?: Doc; - - @action - componentWillReceiveProps() { - this._editingLink = undefined; - } - - clearAllLinks = () => { - LinkManager.Instance.deleteAllLinksOnAnchor(this.props.docView.props.Document); - } - - renderAllGroups = (groups: Map>): Array => { - let linkItems: Array = []; - groups.forEach((group, groupType) => { - linkItems.push( - this._editingLink = linkDoc)} - addDocTab={this.props.addDocTab} /> - ); - }); - - // if source doc has no links push message - if (linkItems.length === 0) linkItems.push(

No links have been created yet. Drag the linking button onto another document to create a link.

); - - return linkItems; - } - - render() { - let sourceDoc = this.props.docView.props.Document; - let groups: Map = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc); - if (this._editingLink === undefined) { - return ( -
- - {/* */} -
- {this.renderAllGroups(groups)} -
-
- ); - } else { - return ( - this._editingLink = undefined)}> - ); - } - } -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/nodes/LinkMenuGroup.tsx deleted file mode 100644 index aa23d80a1..000000000 --- a/src/client/views/nodes/LinkMenuGroup.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import { DocumentView } from "./DocumentView"; -import { LinkMenuItem } from "./LinkMenuItem"; -import { LinkEditor } from "./LinkEditor"; -import './LinkMenu.scss'; -import React = require("react"); -import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { LinkManager } from "../../util/LinkManager"; -import { DragLinksAsDocuments, DragManager, SetupDrag } from "../../util/DragManager"; -import { emptyFunction } from "../../../Utils"; -import { Docs } from "../../documents/Documents"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { UndoManager } from "../../util/UndoManager"; -import { StrCast } from "../../../new_fields/Types"; -import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; - -interface LinkMenuGroupProps { - sourceDoc: Doc; - group: Doc[]; - groupType: string; - showEditor: (linkDoc: Doc) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; - docView: DocumentView; - -} - -@observer -export class LinkMenuGroup extends React.Component { - - private _drag = React.createRef(); - private _table = React.createRef(); - - onLinkButtonDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.addEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - document.addEventListener("pointerup", this.onLinkButtonUp); - } - - onLinkButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - e.stopPropagation(); - } - - - onLinkButtonMoved = async (e: PointerEvent) => { - UndoManager.RunInBatch(() => { - if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - - let draggedDocs = this.props.group.map(linkDoc => { - 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)); - - DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } - }, "drag links"); - e.stopPropagation(); - } - - viewGroupAsTable = (groupType: string): JSX.Element => { - let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); - let index = keys.indexOf(""); - if (index > -1) keys.splice(index, 1); - let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb")); - let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType); - let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" })); - let ref = React.createRef(); - return
; - } - - render() { - let groupItems = this.props.group.map(linkDoc => { - let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); - if (destination && this.props.sourceDoc) { - return ; - } - }); - - return ( -
-
-

{this.props.groupType}:

- {this.props.groupType === "*" || this.props.groupType === "" ? <> : this.viewGroupAsTable(this.props.groupType)} -
-
- {groupItems} -
-
- ); - } -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx deleted file mode 100644 index 9349dbbac..000000000 --- a/src/client/views/nodes/LinkMenuItem.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { observer } from "mobx-react"; -import { DocumentManager } from "../../util/DocumentManager"; -import { undoBatch } from "../../util/UndoManager"; -import './LinkMenu.scss'; -import React = require("react"); -import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; -import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types'; -import { observable, action } from 'mobx'; -import { LinkManager } from '../../util/LinkManager'; -import { DragLinkAsDocument } from '../../util/DragManager'; -import { CollectionDockingView } from '../collections/CollectionDockingView'; -import { SelectionManager } from '../../util/SelectionManager'; -import { CollectionViewType } from '../collections/CollectionBaseView'; -import { DocumentView } from './DocumentView'; -library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); - - -interface LinkMenuItemProps { - groupType: string; - linkDoc: Doc; - sourceDoc: Doc; - destinationDoc: Doc; - showEditor: (linkDoc: Doc) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; -} - -@observer -export class LinkMenuItem extends React.Component { - private _drag = React.createRef(); - @observable private _showMore: boolean = false; - @action toggleShowMore() { this._showMore = !this._showMore; } - - - unhighlight = () => { - Doc.UnhighlightAll(); - document.removeEventListener("pointerdown", this.unhighlight); - } - - @action - highlightDoc = () => { - document.removeEventListener("pointerdown", this.unhighlight); - Doc.HighlightDoc(this.props.destinationDoc); - window.setTimeout(() => { - document.addEventListener("pointerdown", this.unhighlight); - }, 10000); - } - - // NOT TESTED - // col = collection the doc is in - // target = the document to center on - @undoBatch - openLinkColRight = ({ col, target }: { col: Doc, target: Doc }) => { - col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; - col.panX = newPanX; - col.panY = newPanY; - } - CollectionDockingView.Instance.AddRightSplit(col, undefined); - } - - // DONE - // this opens the linked doc in a right split, NOT in its collection - @undoBatch - openLinkRight = () => { - this.highlightDoc(); - let alias = Doc.MakeAlias(this.props.destinationDoc); - CollectionDockingView.Instance.AddRightSplit(alias, undefined); - SelectionManager.DeselectAll(); - } - - // DONE - // this is the standard "follow link" (jump to document) - // taken from follow link - @undoBatch - jumpToLink = async (shouldZoom: boolean) => { - //there is an issue right now so this will be false automatically - shouldZoom = false; - this.highlightDoc(); - let jumpToDoc = this.props.destinationDoc; - let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); - if (pdfDoc) { - jumpToDoc = pdfDoc; - } - let proto = Doc.GetProto(this.props.linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let sourceContext = await Cast(proto.sourceContext, Doc); - let self = this; - - let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; - - if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); - } - else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); - } - else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); - - } - else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); - } - } - - // DONE - // opens link in new tab (not in a collection) - // this opens it full screen, do we need a separate full screen option? - @undoBatch - openLinkTab = () => { - this.highlightDoc(); - let fullScreenAlias = Doc.MakeAlias(this.props.destinationDoc); - this.props.addDocTab(fullScreenAlias, undefined, "inTab"); - SelectionManager.DeselectAll(); - } - - // NOT TESTED - // opens link in new tab in collection - // col = collection the doc is in - // target = the document to center on - @undoBatch - openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { - this.highlightDoc(); - col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; - col.panX = newPanX; - col.panY = newPanY; - } - // CollectionDockingView.Instance.AddRightSplit(col, undefined); - this.props.addDocTab(col, undefined, "inTab"); - SelectionManager.DeselectAll(); - } - - // this will open a link next to the source doc - @undoBatch - openLinkInPlace = () => { - this.highlightDoc(); - - let alias = Doc.MakeAlias(this.props.destinationDoc); - let y = this.props.sourceDoc.y; - let x = this.props.sourceDoc.x; - let parentView: any = undefined; - let parentDoc: Doc = this.props.sourceDoc; - - SelectionManager.SelectedDocuments().map(dv => { - if (dv.props.Document === this.props.sourceDoc) { - parentView = dv.props.ContainingCollectionView; - } - }); - - if (parentView) { - // console.log(parentDoc) - console.log(parentView.props.addDocument) - } - } - - //set this to be the default link behavior, can be any of the above - private defaultLinkBehavior: any = this.openLinkInPlace; - - onEdit = (e: React.PointerEvent): void => { - e.stopPropagation(); - this.props.showEditor(this.props.linkDoc); - SelectionManager.DeselectAll(); - } - - renderMetadata = (): JSX.Element => { - let groups = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc); - let index = groups.findIndex(groupDoc => StrCast(groupDoc.type).toUpperCase() === this.props.groupType.toUpperCase()); - let groupDoc = index > -1 ? groups[index] : undefined; - - let mdRows: Array = []; - if (groupDoc) { - let mdDoc = Cast(groupDoc.metadata, Doc, null); - if (mdDoc) { - let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType); - mdRows = keys.map(key => { - return (
{key}: {StrCast(mdDoc[key])}
); - }); - } - } - - return (
{mdRows}
); - } - - onLinkButtonDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.addEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - document.addEventListener("pointerup", this.onLinkButtonUp); - } - - onLinkButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - e.stopPropagation(); - } - - onLinkButtonMoved = async (e: PointerEvent) => { - if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); - - DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc); - } - e.stopPropagation(); - } - - render() { - - let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType); - let canExpand = keys ? keys.length > 0 : false; - - return ( -
-
-
-

{StrCast(this.props.destinationDoc.title)}

-
- {canExpand ?
this.toggleShowMore()}> -
: <>} -
- {/* Original */} - {/*
*/} - {/* New */} -
-
-
- {this._showMore ? this.renderMetadata() : <>} -
- -
- ); - } -} - - // @undoBatch - // onFollowLink = async (e: React.PointerEvent): Promise => { - // e.stopPropagation(); - // e.persist(); - // let jumpToDoc = this.props.destinationDoc; - // let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); - // if (pdfDoc) { - // jumpToDoc = pdfDoc; - // } - // let proto = Doc.GetProto(this.props.linkDoc); - // let targetContext = await Cast(proto.targetContext, Doc); - // let sourceContext = await Cast(proto.sourceContext, Doc); - // let self = this; - - - // let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; - // if (e.ctrlKey) { - // dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined); - // } - - // if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { - // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!); - // console.log("1") - // } - // else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { - // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!)); - // console.log("2") - // } - // else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); - // console.log("3") - - // } - // else { - // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc); - // console.log("4") - - // } - // } \ No newline at end of file diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 173de64de..1b54472e5 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -23,7 +23,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { Docs } from "../../documents/Documents"; import { PreviewCursor } from "../PreviewCursor"; -library.add(faStickyNote) +library.add(faStickyNote); @observer export class WebBox extends React.Component { @@ -76,7 +76,6 @@ export class WebBox extends React.Component { } switchToText() { - console.log("switchng to text") if (this.props.removeDocument) this.props.removeDocument(this.props.Document); // let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); let newBox = Docs.Create.TextDocument({ diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 508655605..de45aad02 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -29,7 +29,7 @@ export class CurrentUserUtils { doc.viewType = CollectionViewType.Tree; doc.dropAction = "alias"; doc.layout = CollectionView.LayoutString(); - doc.title = Doc.CurrentUserEmail + doc.title = Doc.CurrentUserEmail; this.updateUserDocument(doc); doc.data = new List(); doc.gridGap = 5; -- cgit v1.2.3-70-g09d2 From 186cf33322b6c96386c6e9fdb823025d09c5caeb Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 11:35:18 -0400 Subject: extended treeview functionality - better indenting, state management, drag dropping. --- src/client/views/EditableView.tsx | 28 +++++--- .../views/collections/CollectionTreeView.tsx | 83 +++++++++++++--------- src/client/views/nodes/FormattedTextBox.tsx | 1 - .../authentication/models/current_user_utils.ts | 5 +- 4 files changed, 75 insertions(+), 42 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index c3612fee9..dd5395802 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -3,6 +3,7 @@ import { observer } from 'mobx-react'; import { observable, action, trace } from 'mobx'; import "./EditableView.scss"; import * as Autosuggest from 'react-autosuggest'; +import { undoBatch } from '../util/UndoManager'; export interface EditableProps { /** @@ -70,14 +71,12 @@ export class EditableView extends React.Component { onKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Tab") { e.stopPropagation(); + this.finalizeEdit(e.currentTarget.value, e.shiftKey); this.props.OnTab && this.props.OnTab(); } else if (e.key === "Enter") { e.stopPropagation(); if (!e.ctrlKey) { - if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) { - this._editing = false; - this.props.isEditingCallback && this.props.isEditingCallback(false); - } + this.finalizeEdit(e.currentTarget.value, e.shiftKey); } else if (this.props.OnFillDown) { this.props.OnFillDown(e.currentTarget.value); this._editing = false; @@ -100,6 +99,14 @@ export class EditableView extends React.Component { e.stopPropagation(); } + @action + private finalizeEdit(value: string, shiftDown: boolean) { + if (this.props.SetValue(value, shiftDown)) { + this._editing = false; + this.props.isEditingCallback && this.props.isEditingCallback(false); + } + } + stopPropagation(e: React.SyntheticEvent) { e.stopPropagation(); } @@ -118,7 +125,7 @@ export class EditableView extends React.Component { className: "editableView-input", onKeyDown: this.onKeyDown, autoFocus: true, - onBlur: action(() => this._editing = false), + onBlur: e => this.finalizeEdit(e.currentTarget.value, false), onPointerDown: this.stopPropagation, onClick: this.stopPropagation, onPointerUp: this.stopPropagation, @@ -126,9 +133,14 @@ export class EditableView extends React.Component { onChange: this.props.autosuggestProps.onChange }} /> - : { this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false); })} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} - style={{ display: this.props.display, fontSize: this.props.fontSize }} />; + : this.finalizeEdit(e.currentTarget.value, false)} + onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} + style={{ display: this.props.display, fontSize: this.props.fontSize }} + />; } else { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); return ( diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ebd385743..e1eb9e1a5 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -49,6 +49,8 @@ export interface TreeViewProps { treeViewId: string; parentKey: string; active: () => boolean; + showHeaderFields: () => boolean; + preventTreeViewOpen: boolean; } library.add(faTrashAlt); @@ -65,7 +67,12 @@ library.add(faArrowsAltH); library.add(faPlus, faMinus); @observer /** - * Component that takes in a document prop and a boolean whether it's collapsed or not. + * Renders a treeView of a collection of documents + * + * special fields: + * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden + * preventTreeViewOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) + * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ class TreeView extends React.Component { static loadId = ""; @@ -73,7 +80,9 @@ class TreeView extends React.Component { private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; } - @observable _collapsed: boolean = true; + @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state + @computed get treeViewOpen() { return (BoolCast(this.props.document.treeViewOpen) && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; } + set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = c; } @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; } @@ -146,7 +155,7 @@ class TreeView extends React.Component { let rect = this._header!.current!.getBoundingClientRect(); let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); let before = x[1] < bounds[1]; - let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed); + let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen); this._header!.current!.className = "treeViewItem-header"; if (inside) this._header!.current!.className += " treeViewItem-header-inside"; else if (before) this._header!.current!.className += " treeViewItem-header-above"; @@ -163,15 +172,15 @@ class TreeView extends React.Component { fontStyle={style} fontSize={12} GetValue={() => StrCast(this.props.document[key])} - SetValue={(value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true} - OnFillDown={(value: string) => { + 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; 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); - }} - OnTab={() => this.props.indentDocument && this.props.indentDocument()} + })} + OnTab={() => { TreeView.loadId = ""; this.props.indentDocument && this.props.indentDocument(); }} />) onWorkspaceContextMenu = (e: React.MouseEvent): void => { @@ -200,7 +209,7 @@ class TreeView extends React.Component { let rect = this._header!.current!.getBoundingClientRect(); let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); let before = x[1] < bounds[1]; - let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed); + let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen); if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.document; @@ -256,14 +265,14 @@ class TreeView extends React.Component { let rows: JSX.Element[] = []; for (let key of Object.keys(ids).slice().sort()) { let contents = doc[key]; - let contentElement: JSX.Element[] | JSX.Element = []; + let contentElement: (JSX.Element | null)[] | JSX.Element = []; if (contents instanceof Doc || Cast(contents, listSpec(Doc))) { let remDoc = (doc: Doc) => this.remove(doc, key); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true)); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen); } else { contentElement = { const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; if (expandKey !== undefined) { let remDoc = (doc: Doc) => this.remove(doc, expandKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true)); let docs = expandKey === "links" ? this.childLinks : this.childDocs; return
    {!docs ? (null) : TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, - this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)} + this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen)}
; } else if (this.treeViewExpandedView === "fields") { return
    @@ -329,8 +338,8 @@ class TreeView extends React.Component { @computed get renderBullet() { - return
    this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> - {} + return
    this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> + {}
    ; } /** @@ -344,13 +353,13 @@ class TreeView extends React.Component { let headerElements = ( { - if (!this._collapsed) { + if (this.treeViewOpen) { this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : this.treeViewExpandedView === "layout" && this.props.document.links ? "links" : this.childDocs ? this.fieldKey : "fields"; } - this._collapsed = false; + this.treeViewOpen = true; })}> {this.treeViewExpandedView} ); @@ -368,7 +377,7 @@ class TreeView extends React.Component { }} > {this.editableView("title")}
    - {headerElements} + {this.props.showHeaderFields() ? headerElements : (null)} {openRight} ; } @@ -381,13 +390,13 @@ class TreeView extends React.Component { {this.renderTitle}
    - {this._collapsed ? (null) : this.renderContent} + {!this.treeViewOpen ? (null) : this.renderContent}
; } public static GetChildElements( - docs: Doc[], + docList: Doc[], treeViewId: string, containingCollection: Doc, dataDoc: Doc | undefined, @@ -402,8 +411,11 @@ class TreeView extends React.Component { outerXf: () => { translateX: number, translateY: number }, active: () => boolean, panelWidth: () => number, - renderDepth: number + renderDepth: number, + showHeaderFields: () => boolean, + preventTreeViewOpen: boolean ) { + let docs = docList.filter(child => !child.excludeFromLibrary); let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); if (viewSpecScript) { let script = viewSpecScript.script; @@ -418,7 +430,7 @@ class TreeView extends React.Component { }); } - let descending = BoolCast(containingCollection.stackingHeadersSortDescending); + let descending = BoolCast(containingCollection.stackingHeadersSortDescending, true); docs.slice().sort(function (a, b): 1 | -1 { let descA = descending ? b : a; let descB = descending ? a : b; @@ -448,11 +460,14 @@ class TreeView extends React.Component { } let indent = i === 0 ? undefined : () => { - if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) { + if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) { let fieldKeysub = StrCast(docs[i - 1].layout).split("fieldKey")[1]; let fieldKey = fieldKeysub.split("\"")[1]; - Doc.AddDocToList(docs[i - 1], fieldKey, child); - remove(child); + if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) { + Doc.AddDocToList(docs[i - 1], fieldKey, child); + docs[i - 1].treeViewOpen = true; + remove(child); + } } }; let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => { @@ -481,7 +496,9 @@ class TreeView extends React.Component { ScreenToLocalTransform={screenToLocalXf} outerXf={outerXf} parentKey={key} - active={active} />; + active={active} + showHeaderFields={showHeaderFields} + preventTreeViewOpen={preventTreeViewOpen} />; }); } } @@ -561,7 +578,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { render() { Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); let dropAction = StrCast(this.props.Document.dropAction) as dropActionType; - let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, !BoolCast(this.props.Document.stackingHeadersSortDescending)); + let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true)); let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); return !this.childDocs ? (null) : (
StrCast(this.resolvedDataDoc.title)} - SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true} - OnFillDown={(value: string) => { + 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; 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, !BoolCast(this.props.Document.stackingHeadersSortDescending)); - }} /> + Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true)); + })} /> {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)} {this.props.Document.allowClear ? this.renderClearButton : (null)}
    { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, - moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth) + moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, + this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth, () => this.props.Document.chromeStatus !== "disabled", + BoolCast(this.props.Document.preventTreeViewOpen)) }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 606e8edb0..3f3360eff 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -736,7 +736,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) { - console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent); let xf = this._ref.current!.getBoundingClientRect(); let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight); let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 508655605..aa3da0034 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -10,7 +10,7 @@ import { CollectionView } from "../../../client/views/collections/CollectionView import { Doc } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, StrCast } from "../../../new_fields/Types"; +import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { RouteStore } from "../../RouteStore"; @@ -49,12 +49,14 @@ export class CurrentUserUtils { workspaces.boxShadow = "0 0"; doc.workspaces = workspaces; } + PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true)); 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)); if (doc.curPresentation === undefined) { const curPresentation = Docs.Create.PresDocument(new List(), { title: "Presentation" }); curPresentation.excludeFromLibrary = true; @@ -73,6 +75,7 @@ export class CurrentUserUtils { } StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library"); doc.width = 100; + doc.preventTreeViewOpen = true; } public static loadCurrentUser() { -- cgit v1.2.3-70-g09d2 From 542d272921666ad6ffbef8a028d8204e5c357791 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 11:51:55 -0400 Subject: added pinning tree items to presentation. fixed float doc opacity when = 0 --- src/client/views/MetadataEntryMenu.tsx | 2 +- src/client/views/collections/CollectionTreeView.tsx | 3 ++- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 4a45eede9..4d696e37a 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -172,7 +172,7 @@ export class MetadataEntryMenu extends React.Component{
    - {this._allSuggestions.map(s =>
  • { this._currentKey = s; this.previewValue(); })} >{s}
  • )} + {this._allSuggestions.sort().map(s =>
  • { this._currentKey = s; this.previewValue(); })} >{s}
  • )}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index e1eb9e1a5..472e2f256 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -186,6 +186,7 @@ class TreeView extends React.Component { onWorkspaceContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { + ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" }); ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" }); ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" }); if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) { @@ -415,7 +416,7 @@ class TreeView extends React.Component { showHeaderFields: () => boolean, preventTreeViewOpen: boolean ) { - let docs = docList.filter(child => !child.excludeFromLibrary); + let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0); let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); if (viewSpecScript) { let script = viewSpecScript.script; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index ee596c841..7631ecc6c 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -83,7 +83,7 @@ export class CollectionFreeFormDocumentView extends DocComponent Date: Wed, 21 Aug 2019 12:58:34 -0400 Subject: tweaks --- src/client/views/nodes/FormattedTextBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index a2ea12ac5..f2ca0715a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -274,7 +274,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } e.stopPropagation(); e.preventDefault(); - e.which === 174 && e.altKey && this.recordBullet(); + e.key === "R" && this.recordBullet(); } recordBullet = async () => { @@ -282,7 +282,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let results = await DictationManager.Controls.listen({ interimHandler: this.setCurrentBulletContent, continuous: { indefinite: false }, - terminators: [completedCue, "bullet", "next"] + // terminators: [completedCue, "bullet", "next"] }); if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { DictationManager.Controls.stop(); -- cgit v1.2.3-70-g09d2 From 3535bd703cfdb4872ff6ff0c41d17e39ad1c8e45 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 13:17:50 -0400 Subject: fixed updating size of textboxes --- src/client/views/nodes/FormattedTextBox.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0c39a57ff..505257e9c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -272,9 +272,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) { return; } - e.stopPropagation(); - e.preventDefault(); - e.key === "R" && this.recordBullet(); + if (e.key === "R" && e.altKey) { + e.stopPropagation(); + e.preventDefault(); + this.recordBullet(); + } } recordBullet = async () => { @@ -480,8 +482,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { let pullSuccess = false; if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { - let data = Cast(dataDoc.data, RichTextField); - if (data) { + const data = Cast(dataDoc.data, RichTextField); + if (data instanceof RichTextField) { pullSuccess = true; this.isGoogleDocsUpdate = true; dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); @@ -492,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe delete dataDoc[GoogleRef]; } DocumentDecorations.Instance.startPullOutcome(pullSuccess); - this.tryUpdateHeight(); + setTimeout(() => this.tryUpdateHeight(), 0); } checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { @@ -630,7 +632,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!this.props.isOverlay) this.props.select(false); else this._editorView!.focus(); } - this.tryUpdateHeight(); } componentWillUnmount() { -- cgit v1.2.3-70-g09d2 From 11d2743b553da47da88e77de1ac758e16e09b3e0 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 13:55:46 -0400 Subject: adding docs works --- src/client/views/linking/LinkFollowBox.tsx | 3 ++- src/client/views/linking/LinkMenuItem.tsx | 26 ++++++++++++++------------ src/client/views/nodes/FormattedTextBox.tsx | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 487281d50..061a4fa93 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -1,9 +1,10 @@ import { observable, computed, action, trace } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; -import { FieldViewProps } from "../nodes/FieldView"; +import { FieldViewProps, FieldView } from "../nodes/FieldView"; @observer export class LinkFollowBox extends React.Component { + public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } } \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 7dc0a9fcd..65516b374 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -1,12 +1,12 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; +import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp, faGlobeAsia } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observer } from "mobx-react"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; import './LinkMenu.scss'; import React = require("react"); -import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; +import { Doc, DocListCastAsync, WidthSym } from '../../../new_fields/Doc'; import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types'; import { observable, action } from 'mobx'; import { LinkManager } from '../../util/LinkManager'; @@ -138,27 +138,29 @@ export class LinkMenuItem extends React.Component { SelectionManager.DeselectAll(); } + // DONE // this will open a link next to the source doc @undoBatch openLinkInPlace = () => { this.highlightDoc(); let alias = Doc.MakeAlias(this.props.destinationDoc); - let y = this.props.sourceDoc.y; - let x = this.props.sourceDoc.x; - let parentView: any = undefined; - let parentDoc: Doc = this.props.sourceDoc; + let y = NumCast(this.props.sourceDoc.y); + let x = NumCast(this.props.sourceDoc.x); + + let width = NumCast(this.props.sourceDoc.width); + let height = NumCast(this.props.sourceDoc.height); + + alias.x = x + width + 30; + alias.y = y; + alias.width = width; + alias.height = height; SelectionManager.SelectedDocuments().map(dv => { if (dv.props.Document === this.props.sourceDoc) { - parentView = dv.props.ContainingCollectionView; + dv.props.addDocument && dv.props.addDocument(alias, false); } }); - - if (parentView) { - // console.log(parentDoc) - console.log(parentView.props.addDocument); - } } //set this to be the default link behavior, can be any of the above diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9ae092bad..9652a3a78 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -739,7 +739,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) { - console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent); + // console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent); let xf = this._ref.current!.getBoundingClientRect(); let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight); let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); -- cgit v1.2.3-70-g09d2 From 76a693012866178a3fbe037ab06cfd4482f37917 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 14:22:30 -0400 Subject: djlzdkfj --- src/client/views/nodes/FormattedTextBox.tsx | 7 +++++++ src/client/views/nodes/WebBox.tsx | 26 ++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 25f611f19..d299bbf72 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -162,6 +162,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + public setText = (text: string) => { + const tx = this._editorView!.state.tr.insertText(text); + const state = this._editorView!.state; + this._editorView!.dispatch(tx); + return new RichTextField(JSON.stringify(state.toJSON())); + } + dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index c43b90f91..e7bed3bed 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -26,6 +26,8 @@ import { SelectionManager } from "../../util/SelectionManager"; import { CollectionView } from "../collections/CollectionView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; +import { DocumentView } from "./DocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; library.add(faStickyNote) @@ -85,14 +87,15 @@ export class WebBox extends React.Component { let field = Cast(this.props.Document[this.props.fieldKey], WebField); if (field) url = field.url.href; - let parent: Opt; + let docView: DocumentView; // let parentDoc: any; SelectionManager.SelectedDocuments().map(dv => { - parent = dv.props.ContainingCollectionView; - // if(parent) parentDoc = parent.props.Document; + // docView = dv; dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); }); + console.log("happening") + // // let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); let newBox = Docs.Create.TextDocument({ width: 200, height: 100, @@ -103,13 +106,16 @@ export class WebBox extends React.Component { title: url }); - console.log(newBox) - if (parent) { - let parentDoc: Doc = parent.props.Document; - if (parentDoc && parentDoc.props) { - parentDoc.props.addDocument(); - } - } + console.log(typeof newBox) + + // const script = KeyValueBox.CompileKVPScript(`new RichTextField("{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"${url}"}]}]},"selection":{"type":"text","anchor":1,"head":1}}")`); + // const script = KeyValueBox.CompileKVPScript(newBox.setText(url)) + // console.log(script) + // if (!script) return; + // KeyValueBox.ApplyKVPScript(this.props.Document, "data", script); + + console.log(newBox); + newBox.proto!.autoHeight = true; // PreviewCursor._addLiveTextDoc(newBox); -- cgit v1.2.3-70-g09d2 From e50dc35f922efdeadfde70c9cf552b9ed5722810 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 14:35:35 -0400 Subject: uncommented terminators --- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 505257e9c..1e0975b4b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -284,7 +284,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let results = await DictationManager.Controls.listen({ interimHandler: this.setCurrentBulletContent, continuous: { indefinite: false }, - // terminators: [completedCue, "bullet", "next"] + terminators: [completedCue, "bullet", "next"] }); if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { DictationManager.Controls.stop(); -- cgit v1.2.3-70-g09d2 From 5c9f40006aa157c58ec40828ebd4845c16daa8af Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 15:08:39 -0400 Subject: start of making link follow --- src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 4 + src/client/views/MainView.tsx | 11 ++ src/client/views/linking/LinkFollowBox.scss | 6 + src/client/views/linking/LinkFollowBox.tsx | 148 ++++++++++++++++++++++++ src/client/views/linking/LinkMenuItem.tsx | 12 +- src/client/views/nodes/DocumentContentsView.tsx | 4 +- 7 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 src/client/views/linking/LinkFollowBox.scss (limited to 'src/client/views/nodes') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 1578e49fe..381981e1b 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -19,4 +19,5 @@ export enum DocumentType { YOUTUBE = "youtube", DRAGBOX = "dragbox", PRES = "presentation", + LINKFOLLOW = "linkfollow", } \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 47df17329..cd612aaa9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -45,6 +45,7 @@ import { PresBox } from "../views/nodes/PresBox"; 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'); @@ -169,6 +170,9 @@ export namespace Docs { [DocumentType.DRAGBOX, { layout: { view: DragBox }, options: { width: 40, height: 40 }, + }], + [DocumentType.LINKFOLLOW, { + layout: { view: LinkFollowBox } }] ]); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f28844009..f3c8a176c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,6 +40,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; +import { LinkFollowBox } from './linking/LinkFollowBox'; @observer export class MainView extends React.Component { @@ -55,6 +56,8 @@ export class MainView extends React.Component { public overlayTimeout: NodeJS.Timeout | undefined; + @observable private _linkFollowBox = false; + public initiateDictationFade = () => { let duration = DictationManager.Commands.dictationFadeDuration; this.overlayTimeout = setTimeout(() => { @@ -498,6 +501,12 @@ export class MainView extends React.Component { this._colorPickerDisplay = close ? false : !this._colorPickerDisplay; } + @action + toggleLinkFollowBox = () => { + console.log("toggling link editor") + this._linkFollowBox = !this._linkFollowBox; + } + /* @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() { @@ -506,6 +515,7 @@ export class MainView extends React.Component { return [ this.isSearchVisible ?
: null,
+
]; @@ -564,6 +574,7 @@ export class MainView extends React.Component { + {/* */}
); } diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss new file mode 100644 index 000000000..c764b002f --- /dev/null +++ b/src/client/views/linking/LinkFollowBox.scss @@ -0,0 +1,6 @@ +@import "../globalCssVariables"; + +.linkFollowBox-main { + position: absolute; + background: $main-accent; +} \ No newline at end of file diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 061a4fa93..7fc4449d3 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -2,9 +2,157 @@ import { observable, computed, action, trace } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; +import { Doc } from "../../../new_fields/Doc"; +import { undoBatch } from "../../util/UndoManager"; +import { NumCast, FieldValue, Cast } from "../../../new_fields/Types"; +import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { SelectionManager } from "../../util/SelectionManager"; +import { DocumentManager } from "../../util/DocumentManager"; @observer export class LinkFollowBox extends React.Component { public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } + public static Instance: LinkFollowBox; + //set this to be the default link behavior, can be any of the above + + unhighlight = () => { + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", this.unhighlight); + } + + @action + highlightDoc = (destinationDoc: Doc) => { + document.removeEventListener("pointerdown", this.unhighlight); + Doc.HighlightDoc(destinationDoc); + window.setTimeout(() => { + document.addEventListener("pointerdown", this.unhighlight); + }, 10000); + } + + // NOT TESTED + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColRight = (destinationDoc: Doc, col: Doc) => { + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + CollectionDockingView.Instance.AddRightSplit(col, undefined); + } + + // DONE + // this opens the linked doc in a right split, NOT in its collection + @undoBatch + openLinkRight = (destinationDoc: Doc) => { + this.highlightDoc(destinationDoc); + let alias = Doc.MakeAlias(destinationDoc); + CollectionDockingView.Instance.AddRightSplit(alias, undefined); + SelectionManager.DeselectAll(); + } + + // DONE + // this is the standard "follow link" (jump to document) + // taken from follow link + @undoBatch + jumpToLink = async (destinationDoc: Doc, shouldZoom: boolean, linkDoc: Doc) => { + //there is an issue right now so this will be false automatically + shouldZoom = false; + this.highlightDoc(destinationDoc); + let jumpToDoc = destinationDoc; + let pdfDoc = FieldValue(Cast(destinationDoc, Doc)); + if (pdfDoc) { + jumpToDoc = pdfDoc; + } + let proto = Doc.GetProto(linkDoc); + let targetContext = await Cast(proto.targetContext, Doc); + let sourceContext = await Cast(proto.sourceContext, Doc); + + let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + + if (destinationDoc === linkDoc.anchor2 && targetContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); + } + else if (destinationDoc === linkDoc.anchor1 && sourceContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + } + else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((destinationDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + + } + else { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); + } + } + + // DONE + // opens link in new tab (not in a collection) + // this opens it full screen, do we need a separate full screen option? + @undoBatch + openLinkTab = (destinationDoc: Doc) => { + this.highlightDoc(destinationDoc); + let fullScreenAlias = Doc.MakeAlias(destinationDoc); + this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // NOT TESTED + // opens link in new tab in collection + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColTab = (destinationDoc: Doc, col: Doc) => { + this.highlightDoc(destinationDoc); + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + // CollectionDockingView.Instance.AddRightSplit(col, undefined); + this.props.addDocTab(col, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // DONE + // this will open a link next to the source doc + @undoBatch + openLinkInPlace = (destinationDoc: Doc, sourceDoc: Doc) => { + this.highlightDoc(destinationDoc); + + let alias = Doc.MakeAlias(destinationDoc); + let y = NumCast(sourceDoc.y); + let x = NumCast(sourceDoc.x); + + let width = NumCast(sourceDoc.width); + let height = NumCast(sourceDoc.height); + + alias.x = x + width + 30; + alias.y = y; + alias.width = width; + alias.height = height; + + SelectionManager.SelectedDocuments().map(dv => { + if (dv.props.Document === sourceDoc) { + dv.props.addDocument && dv.props.addDocument(alias, false); + } + }); + } + + private defaultLinkBehavior: any = this.openLinkInPlace; + private currentLinkBehavior: any = this.defaultLinkBehavior; + + render() { + return ( +
+ +
+ ); + } } \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 65516b374..41723030d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -52,11 +52,11 @@ export class LinkMenuItem extends React.Component { // col = collection the doc is in // target = the document to center on @undoBatch - openLinkColRight = ({ col, target }: { col: Doc, target: Doc }) => { + openLinkColRight = (col: Doc) => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; col.panX = newPanX; col.panY = newPanY; } @@ -124,12 +124,12 @@ export class LinkMenuItem extends React.Component { // col = collection the doc is in // target = the document to center on @undoBatch - openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { + openLinkColTab = (col: Doc) => { this.highlightDoc(); col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; col.panX = newPanX; col.panY = newPanY; } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d77662355..d0e117fe4 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -14,6 +14,7 @@ import { ImageBox } from "./ImageBox"; import { DragBox } from "./DragBox"; import { ButtonBox } from "./ButtonBox"; import { PresBox } from "./PresBox"; +import { LinkFollowBox } from "../linking/LinkFollowBox"; import { IconBox } from "./IconBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; @@ -30,6 +31,7 @@ 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; @@ -108,7 +110,7 @@ export class DocumentContentsView extends React.Component Date: Wed, 21 Aug 2019 15:46:23 -0400 Subject: restructed presentationview --- src/client/views/nodes/PresBox.tsx | 279 ++-------- .../views/presentationview/PresentationElement.tsx | 611 ++------------------- .../views/presentationview/PresentationList.tsx | 47 +- .../views/presentationview/PresentationView.scss | 10 +- src/new_fields/Doc.ts | 3 + 5 files changed, 128 insertions(+), 822 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 112d39c32..cf222601f 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -12,7 +12,7 @@ import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_field import { Utils } from "../../../Utils"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; -import PresentationElement, { buttonIndex } from "../presentationview/PresentationElement"; +import PresentationElement from "../presentationview/PresentationElement"; import PresentationViewList from "../presentationview/PresentationList"; import "../presentationview/PresentationView.scss"; import { FieldView, FieldViewProps } from './FieldView'; @@ -45,17 +45,12 @@ export class PresBox extends React.Component { //FieldViewProps? //Keeping track of the doc for the current presentation -- bcz: keeping a list of current presentations shouldn't be needed. Let users create them, store them, as they see fit. @computed get curPresentation() { return this.props.Document; } - //Mapping from presentation ids to a list of doc that represent a group - @observable groupMappings: Map = new Map(); //mapping from docs to their rendered component @observable presElementsMappings: Map = new Map(); //variable that holds all the docs in the presentation @observable childrenDocs: Doc[] = []; //variable to hold if presentation is started @observable presStatus: boolean = false; - //back-up so that presentation stays the way it's when refreshed - @observable presGroupBackUp: Doc = new Doc(); - @observable presButtonBackUp: Doc = new Doc(); //Mapping of guids to presentations. @observable presentationsMapping: Map = new Map(); //Mapping of presentations to guid, so that select option values can be given. @@ -102,87 +97,11 @@ export class PresBox extends React.Component { //FieldViewProps? * otherwise initializes. */ setPresentationBackUps = async () => { - //getting both backUp documents - - let castedGroupBackUp = Cast(this.curPresentation.presGroupBackUp, Doc); - let castedButtonBackUp = Cast(this.curPresentation.presButtonBackUp, Doc); - //if instantiated before - if (castedGroupBackUp instanceof Promise) { - castedGroupBackUp.then(doc => { - let toAssign = doc ? doc : new Doc(); - this.curPresentation.presGroupBackUp = toAssign; - runInAction(() => this.presGroupBackUp = toAssign); - if (doc) { - if (toAssign[Id] === doc[Id]) { - this.retrieveGroupMappings(); - } - } - }); - - //if never instantiated a store doc yet - } else if (castedGroupBackUp instanceof Doc) { - let castedDoc: Doc = await castedGroupBackUp; - runInAction(() => this.presGroupBackUp = castedDoc); - this.retrieveGroupMappings(); - } else { - runInAction(() => { - let toAssign = new Doc(); - this.presGroupBackUp = toAssign; - this.curPresentation.presGroupBackUp = toAssign; - - }); - - } - //if instantiated before - if (castedButtonBackUp instanceof Promise) { - castedButtonBackUp.then(doc => { - let toAssign = doc ? doc : new Doc(); - this.curPresentation.presButtonBackUp = toAssign; - runInAction(() => this.presButtonBackUp = toAssign); - }); - - //if never instantiated a store doc yet - } else if (castedButtonBackUp instanceof Doc) { - let castedDoc: Doc = await castedButtonBackUp; - runInAction(() => this.presButtonBackUp = castedDoc); - - } else { - runInAction(() => { - let toAssign = new Doc(); - this.presButtonBackUp = toAssign; - this.curPresentation.presButtonBackUp = toAssign; - }); - - } - - //storing the presentation status,ie. whether it was stopped or playing let presStatusBackUp = BoolCast(this.curPresentation.presStatus); runInAction(() => this.presStatus = presStatusBackUp); } - /** - * This is the function that is called to retrieve the groups that have been stored and - * push them to the groupMappings. - */ - retrieveGroupMappings = async () => { - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs !== undefined) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - let castedKey = StrCast(groupDoc.presentIdStore, null); - if (castedGrouping) { - castedGrouping.forEach((doc: Doc) => { - doc.presentId = castedKey; - }); - } - if (castedGrouping !== undefined && castedKey !== undefined) { - this.groupMappings.set(castedKey, castedGrouping); - } - }); - } - } - //observable means render is re-called every time variable is changed @observable collapsed: boolean = false; @@ -193,17 +112,13 @@ export class PresBox extends React.Component { //FieldViewProps? if (docAtCurrentNext === undefined) { return; } - //asking for it's presentation id - let curNextPresId = StrCast(docAtCurrentNext.presentId); let nextSelected = current + 1; - //if curDoc is in a group, selection slides until last one, if not it's next one - if (this.groupMappings.has(curNextPresId)) { - let currentsArray = this.groupMappings.get(StrCast(docAtCurrentNext.presentId))!; - nextSelected = current + currentsArray.length - currentsArray.indexOf(docAtCurrentNext); + let presDocs = DocListCast(this.curPresentation.data); + for (; nextSelected < presDocs.length - 1; nextSelected++) { + if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) + break; - //end of grup so go beyond - if (nextSelected === current) nextSelected = current + 1; } this.gotoDocument(nextSelected, current); @@ -219,31 +134,31 @@ export class PresBox extends React.Component { //FieldViewProps? //asking for its presentation id. let curPresId = StrCast(docAtCurrent.presentId); - let prevSelected = current - 1; + 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 - if (this.groupMappings.has(curPresId)) { - let currentsArray = this.groupMappings.get(StrCast(docAtCurrent.presentId))!; - prevSelected = current - currentsArray.length + (currentsArray.length - currentsArray.indexOf(docAtCurrent)) - 1; - //end of grup so go beyond - if (prevSelected === current) prevSelected = current - 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)!.selected[buttonIndex.Show]) { - zoomOut = true; - return; - } - }); - + let presDocs = DocListCast(this.curPresentation.data); + let currentsArray: Doc[] = []; + for (; 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) { + zoomOut = true; + return; + } + }); + // 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)!.selected[buttonIndex.Show]) { + if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.showButton) { let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null); let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]); if (prevScale !== undefined) { @@ -264,19 +179,18 @@ export class PresBox extends React.Component { //FieldViewProps? */ showAfterPresented = (index: number) => { this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { - let selectedButtons: boolean[] = presElem.selected; //the order of cases is aligned based on priority - if (selectedButtons[buttonIndex.HideTillPressed]) { + if (presElem.props.document.hideTillShownButton) { if (this.childrenDocs.indexOf(key) <= index) { key.opacity = 1; } } - if (selectedButtons[buttonIndex.HideAfter]) { + if (presElem.props.document.hideAfterButton) { if (this.childrenDocs.indexOf(key) < index) { key.opacity = 0; } } - if (selectedButtons[buttonIndex.FadeAfter]) { + if (presElem.props.document.fadeButton) { if (this.childrenDocs.indexOf(key) < index) { key.opacity = 0.5; } @@ -291,21 +205,19 @@ export class PresBox extends React.Component { //FieldViewProps? */ hideIfNotPresented = (index: number) => { this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { - let selectedButtons: boolean[] = presElem.selected; - //the order of cases is aligned based on priority - if (selectedButtons[buttonIndex.HideAfter]) { + if (presElem.props.document.hideAfterButton) { if (this.childrenDocs.indexOf(key) >= index) { key.opacity = 1; } } - if (selectedButtons[buttonIndex.FadeAfter]) { + if (presElem.props.document.fadeButton) { if (this.childrenDocs.indexOf(key) >= index) { key.opacity = 1; } } - if (selectedButtons[buttonIndex.HideTillPressed]) { + if (presElem.props.document.hideTillShownButton) { if (this.childrenDocs.indexOf(key) > index) { key.opacity = 0; } @@ -320,34 +232,35 @@ export class PresBox extends React.Component { //FieldViewProps? */ navigateToElement = async (curDoc: Doc, fromDoc: number) => { let docToJump: Doc = curDoc; - let curDocPresId = StrCast(curDoc.presentId, null); let willZoom: boolean = false; - //checking if in group - if (curDocPresId !== undefined) { - if (this.groupMappings.has(curDocPresId)) { - let currentDocGroup = this.groupMappings.get(curDocPresId)!; - currentDocGroup.forEach((doc: Doc, index: number) => { - let selectedButtons: boolean[] = this.presElementsMappings.get(doc)!.selected; - if (selectedButtons[buttonIndex.Navigate]) { - docToJump = doc; - willZoom = false; - } - if (selectedButtons[buttonIndex.Show]) { - docToJump = doc; - willZoom = true; - } - }); - } + let presDocs = DocListCast(this.curPresentation.data); + let nextSelected = presDocs.indexOf(curDoc); + let currentDocGroups: Doc[] = []; + for (; nextSelected < presDocs.length - 1; nextSelected++) { + if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) + break; + currentDocGroups.push(presDocs[nextSelected]); } + + currentDocGroups.forEach((doc: Doc, index: number) => { + if (this.presElementsMappings.get(doc)!.navButton) { + docToJump = doc; + willZoom = false; + } + if (this.presElementsMappings.get(doc)!.showButton) { + docToJump = doc; + willZoom = true; + } + }); + //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 curDocButtons = this.presElementsMappings.get(curDoc)!.selected; - if (curDocButtons[buttonIndex.Navigate]) { + if (this.presElementsMappings.get(curDoc)!.navButton) { DocumentManager.Instance.jumpToDocument(curDoc, false); - } else if (curDocButtons[buttonIndex.Show]) { + } else if (this.presElementsMappings.get(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); @@ -406,69 +319,6 @@ export class PresBox extends React.Component { //FieldViewProps? //removing the Presentation Element stored for it this.presElementsMappings.delete(removedDoc); - let removedDocPresentId = StrCast(removedDoc.presentId); - - //Removing it from local mapping of the groups - if (this.groupMappings.has(removedDocPresentId)) { - let removedDocsGroup = this.groupMappings.get(removedDocPresentId); - if (removedDocsGroup) { - removedDocsGroup.splice(removedDocsGroup.indexOf(removedDoc), 1); - if (removedDocsGroup.length === 0) { - this.groupMappings.delete(removedDocPresentId); - } - } - } - - //removing it from the backUp of selected Buttons - // let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - // if (castedList) { - // castedList.forEach(async (doc, indexOfDoc) => { - // let curDoc = await doc; - // let curDocId = StrCast(curDoc.docId); - // if (curDocId === removedDoc[Id]) { - // if (castedList) { - // castedList.splice(indexOfDoc, 1); - // return; - // } - // } - // }); - - // } - //removing it from the backUp of selected Buttons - - let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - if (castedList) { - for (let doc of castedList) { - let curDoc = await doc; - let curDocId = StrCast(curDoc.docId); - if (curDocId === removedDoc[Id]) { - castedList.splice(castedList.indexOf(curDoc), 1); - break; - - } - } - } - - //removing it from the backup of groups - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedKey = StrCast(groupDoc.presentIdStore, null); - if (castedKey === removedDocPresentId) { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - if (castedGrouping) { - castedGrouping.splice(castedGrouping.indexOf(removedDoc), 1); - if (castedGrouping.length === 0) { - castedGroupDocs!.splice(castedGroupDocs!.indexOf(groupDoc), 1); - } - } - } - - }); - - } - - } } @@ -489,6 +339,7 @@ export class PresBox extends React.Component { //FieldViewProps? //it'll also execute the necessary actions if presentation is playing. @action public gotoDocument = async (index: number, fromDoc: number) => { + Doc.UnBrushAllDocs(); const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); if (!list) { return; @@ -509,26 +360,7 @@ export class PresBox extends React.Component { //FieldViewProps? this.hideIfNotPresented(index); this.showAfterPresented(index); } - } - - //Function that is called to resetGroupIds, so that documents get new groupIds at - //first load, when presentation is changed. - resetGroupIds = async () => { - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs !== undefined) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - if (castedGrouping) { - castedGrouping.forEach((doc: Doc) => { - doc.presentId = Utils.GenerateGuid(); - }); - } - }); - } - runInAction(() => this.groupMappings = new Map()); - } - //Function that sets the store of the children docs. @action setChildrenDocs = (docList: Doc[]) => { @@ -580,21 +412,19 @@ export class PresBox extends React.Component { //FieldViewProps? //The function that starts the presentation, also checking if actions should be applied //directly at start. startPresentation = (startIndex: number) => { - let selectedButtons: boolean[]; this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => { - selectedButtons = component.selected; - if (selectedButtons[buttonIndex.HideTillPressed]) { + if (component.props.document.hideTillShownButton) { if (this.childrenDocs.indexOf(doc) > startIndex) { doc.opacity = 0; } } - if (selectedButtons[buttonIndex.HideAfter]) { + if (component.props.document.hideAfterButton) { if (this.childrenDocs.indexOf(doc) < startIndex) { doc.opacity = 0; } } - if (selectedButtons[buttonIndex.FadeAfter]) { + if (component.props.document.fadeButton) { if (this.childrenDocs.indexOf(doc) < startIndex) { doc.opacity = 0.5; } @@ -684,12 +514,9 @@ export class PresBox extends React.Component { //FieldViewProps? mainDocument={this.curPresentation} deleteDocument={this.RemoveDoc} gotoDocument={this.gotoDocument} - groupMappings={this.groupMappings} PresElementsMappings={this.presElementsMappings} setChildrenDocs={this.setChildrenDocs} presStatus={this.presStatus} - presButtonBackUp={this.presButtonBackUp} - presGroupBackUp={this.presGroupBackUp} removeDocByRef={this.removeDocByRef} clearElemMap={() => this.presElementsMappings.clear()} /> diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 912970a50..68c5a5b8d 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -1,21 +1,19 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons'; -import { faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch, faArrowRight } from '@fortawesome/free-solid-svg-icons'; +import { faArrowRight, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; +import { action, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc } 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, StrCast } from "../../../new_fields/Types"; -import { Utils, returnFalse, emptyFunction, returnOne, returnEmptyString } from "../../../Utils"; +import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne } from "../../../Utils"; +import { DocumentType } from "../../documents/DocumentTypes"; import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; -import { ContextMenu } from "../ContextMenu"; import { Transform } from "../../util/Transform"; +import { ContextMenu } from "../ContextMenu"; import { DocumentView } from "../nodes/DocumentView"; -import { DocumentType } from "../../documents/DocumentTypes"; import React = require("react"); @@ -33,26 +31,9 @@ interface PresentationElementProps { deleteDocument(index: number): void; gotoDocument(index: number, fromDoc: number): Promise; allListElements: Doc[]; - groupMappings: Map; presStatus: boolean; - presButtonBackUp: Doc; - presGroupBackUp: Doc; removeDocByRef(doc: Doc): boolean; PresElementsMappings: Map; - - -} - -//enum for the all kinds of buttons a doc in presentation can have -export enum buttonIndex { - Show = 0, - Navigate = 1, - HideTillPressed = 2, - FadeAfter = 3, - HideAfter = 4, - Group = 5, - OpenRight = 6 - } /** @@ -62,37 +43,33 @@ export enum buttonIndex { @observer export default class PresentationElement extends React.Component { - @observable private selectedButtons: boolean[]; private header?: HTMLDivElement | undefined; private listdropDisposer?: DragManager.DragDropDisposer; - private presElRef: React.RefObject; - private backUpDoc: Doc | undefined; - - - constructor(props: PresentationElementProps) { - super(props); - this.selectedButtons = new Array(7); - - this.presElRef = React.createRef(); - } - + private presElRef: React.RefObject = React.createRef(); componentWillUnmount() { this.listdropDisposer && this.listdropDisposer(); } + @computed get currentIndex() { return NumCast(this.props.mainDocument.selectedDoc); } - /** - * Getter to get the status of the buttons. - */ - @computed - get selected() { - return this.selectedButtons; - } + @computed get showButton() { return BoolCast(this.props.document.showButton); } + @computed get navButton() { return BoolCast(this.props.document.navButton); } + @computed get hideTillShownButton() { return BoolCast(this.props.document.hideTillShownButton); } + @computed get fadeButton() { return BoolCast(this.props.document.fadeButton); } + @computed get hideAfterButton() { return BoolCast(this.props.document.hideAfterButton); } + @computed get groupButton() { return BoolCast(this.props.document.groupButton); } + @computed get openRightButton() { return BoolCast(this.props.document.openRightButton); } + set showButton(val: boolean) { this.props.document.showButton = val; } + set navButton(val: boolean) { this.props.document.navButton = val; } + set hideTillShownButton(val: boolean) { this.props.document.hideTillShownButton = val; } + set fadeButton(val: boolean) { this.props.document.fadeButton = val; } + set hideAfterButton(val: boolean) { this.props.document.hideAfterButton = val; } + set groupButton(val: boolean) { this.props.document.groupButton = val; } + set openRightButton(val: boolean) { this.props.document.openRightButton = val; } //Lifecycle function that makes sure that button BackUp is received when mounted. async componentDidMount() { - this.receiveButtonBackUp(); if (this.presElRef.current) { this.header = this.presElRef.current; this.createListDropTarget(this.presElRef.current); @@ -107,156 +84,9 @@ export default class PresentationElement extends React.Component { - - //get the list that stores docs that keep track of buttons - let castedList = Cast(this.props.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - if (!castedList) { - this.props.presButtonBackUp.selectedButtonDocs = castedList = new List(); - } - - let foundDoc: boolean = false; - - //if this is the first time this doc mounts, push a doc for it to store - - for (let doc of castedList) { - let curDoc = await doc; - let curDocId = StrCast(curDoc.docId); - if (curDocId === this.props.document[Id]) { - let selectedButtonOfDoc = Cast(curDoc.selectedButtons, listSpec("boolean"), null); - if (selectedButtonOfDoc !== undefined) { - runInAction(() => this.selectedButtons = selectedButtonOfDoc); - foundDoc = true; - this.backUpDoc = curDoc; - break; - } - } - } - - if (!foundDoc) { - let newDoc = new Doc(); - let defaultBooleanArray: boolean[] = new Array(7); - newDoc.selectedButtons = new List(defaultBooleanArray); - newDoc.docId = this.props.document[Id]; - castedList.push(newDoc); - this.backUpDoc = newDoc; - } - - } - - /** - * The function that is called to group docs together. It tries to group a doc - * that turned grouping option with the above document. If that doc is grouped with - * other documents. Those other documents will be grouped with doc's above document as well. - */ @action - onGroupClick = (document: Doc, index: number, buttonStatus: boolean) => { - let p = this.props; - if (index >= 1) { - //checking if options was turned true - if (buttonStatus) { - //getting the id of the above-doc and the doc - let aboveGuid = StrCast(p.allListElements[index - 1].presentId, null); - let docGuid = StrCast(document.presentId, null); - //the case where above-doc is already in group - if (p.groupMappings.has(aboveGuid)) { - let aboveArray = p.groupMappings.get(aboveGuid)!; - //case where doc is already in group - if (p.groupMappings.has(docGuid)) { - let docsArray = p.groupMappings.get(docGuid)!; - docsArray.forEach((doc: Doc) => { - if (!aboveArray.includes(doc)) { - aboveArray.push(doc); - } - doc.presentId = aboveGuid; - }); - p.groupMappings.delete(docGuid); - //the case where doc was not in group - } else { - if (!aboveArray.includes(document)) { - aboveArray.push(document); - - } - - } - //the case where above-doc was not in group - } else { - let newAboveArray: Doc[] = []; - newAboveArray.push(p.allListElements[index - 1]); - - //the case where doc is in group - if (p.groupMappings.has(docGuid)) { - let docsArray = p.groupMappings.get(docGuid)!; - docsArray.forEach((doc: Doc) => { - newAboveArray.push(doc); - doc.presentId = aboveGuid; - }); - p.groupMappings.delete(docGuid); - - //the case where doc is not in a group - } else { - newAboveArray.push(document); - - } - p.groupMappings.set(aboveGuid, newAboveArray); - - } - document.presentId = aboveGuid; - - //when grouping is turned off - } else { - let curArray = p.groupMappings.get(StrCast(document.presentId, Utils.GenerateGuid()))!; - let targetIndex = curArray.indexOf(document); - let firstPart = curArray.slice(0, targetIndex); - let firstPartNewGuid = Utils.GenerateGuid(); - firstPart.forEach((doc: Doc) => doc.presentId = firstPartNewGuid); - let secondPart = curArray.slice(targetIndex); - p.groupMappings.set(StrCast(p.allListElements[index - 1].presentId, Utils.GenerateGuid()), firstPart); - p.groupMappings.set(StrCast(document.presentId, Utils.GenerateGuid()), secondPart); - - - } - - } - this.autoSaveGroupChanges(); - - } - - - /** - * This function is called at the end of each group update to update the group updates. - */ - @action - autoSaveGroupChanges = () => { - let castedList: List = new List(); - this.props.presGroupBackUp.groupDocs = castedList; - this.props.groupMappings.forEach((docArray: Doc[], id: String) => { - //create a new doc for each group - let newGroupDoc = new Doc(); - castedList.push(newGroupDoc); - //store the id of the group in the doc - newGroupDoc.presentIdStore = id.toString(); - //store the doc array which represents the group in the doc - newGroupDoc.grouping = new List(docArray); - }); - - } - - /** - * Function that is called on click to change the group status of a docus, by turning the option on/off. - */ - @action - changeGroupStatus = () => { - if (this.selectedButtons[buttonIndex.Group]) { - this.selectedButtons[buttonIndex.Group] = false; - } else { - this.selectedButtons[buttonIndex.Group] = true; - } - this.autoSaveButtonChange(buttonIndex.Group); - + onGroupClick = (e: React.MouseEvent) => { + this.groupButton = !this.groupButton; } /** @@ -266,31 +96,18 @@ export default class PresentationElement extends React.Component { e.stopPropagation(); - const current = NumCast(this.props.mainDocument.selectedDoc); - if (this.selectedButtons[buttonIndex.HideTillPressed]) { - this.selectedButtons[buttonIndex.HideTillPressed] = false; - if (this.props.index >= current) { + this.hideTillShownButton = !this.hideTillShownButton; + if (!this.hideTillShownButton) { + if (this.props.index >= this.currentIndex) { this.props.document.opacity = 1; } } else { - this.selectedButtons[buttonIndex.HideTillPressed] = true; if (this.props.presStatus) { - if (this.props.index > current) { + if (this.props.index > this.currentIndex) { this.props.document.opacity = 0; } } } - this.autoSaveButtonChange(buttonIndex.HideTillPressed); - } - - /** - * This function is called to get the updates for the changed buttons. - */ - @action - autoSaveButtonChange = async (index: buttonIndex) => { - if (this.backUpDoc) { - this.backUpDoc.selectedButtons = new List(this.selectedButtons); - } } /** @@ -301,25 +118,19 @@ export default class PresentationElement extends React.Component { e.stopPropagation(); - const current = NumCast(this.props.mainDocument.selectedDoc); - if (this.selectedButtons[buttonIndex.HideAfter]) { - this.selectedButtons[buttonIndex.HideAfter] = false; - if (this.props.index <= current) { + this.hideAfterButton = !this.hideAfterButton; + if (!this.hideAfterButton) { + if (this.props.index <= this.currentIndex) { this.props.document.opacity = 1; } } else { - if (this.selectedButtons[buttonIndex.FadeAfter]) { - this.selectedButtons[buttonIndex.FadeAfter] = false; - } - this.selectedButtons[buttonIndex.HideAfter] = true; + if (this.fadeButton) this.fadeButton = false; if (this.props.presStatus) { - if (this.props.index < current) { + if (this.props.index < this.currentIndex) { this.props.document.opacity = 0; } } } - this.autoSaveButtonChange(buttonIndex.HideAfter); - } /** @@ -330,25 +141,19 @@ export default class PresentationElement extends React.Component { e.stopPropagation(); - const current = NumCast(this.props.mainDocument.selectedDoc); - if (this.selectedButtons[buttonIndex.FadeAfter]) { - this.selectedButtons[buttonIndex.FadeAfter] = false; - if (this.props.index <= current) { + this.fadeButton = !this.fadeButton; + if (!this.fadeButton) { + if (this.props.index <= this.currentIndex) { this.props.document.opacity = 1; } } else { - if (this.selectedButtons[buttonIndex.HideAfter]) { - this.selectedButtons[buttonIndex.HideAfter] = false; - } - this.selectedButtons[buttonIndex.FadeAfter] = true; + this.hideAfterButton = false; if (this.props.presStatus) { - if (this.props.index < current) { + if (this.props.index < this.currentIndex) { this.props.document.opacity = 0.5; } } } - this.autoSaveButtonChange(buttonIndex.FadeAfter); - } /** @@ -357,22 +162,13 @@ export default class PresentationElement extends React.Component { e.stopPropagation(); - if (this.selectedButtons[buttonIndex.Navigate]) { - this.selectedButtons[buttonIndex.Navigate] = false; - - } else { - if (this.selectedButtons[buttonIndex.Show]) { - this.selectedButtons[buttonIndex.Show] = false; - } - this.selectedButtons[buttonIndex.Navigate] = true; - const current = NumCast(this.props.mainDocument.selectedDoc); - if (current === this.props.index) { + this.navButton = !this.navButton; + if (this.navButton) { + this.showButton = false; + if (this.currentIndex === this.props.index) { this.props.gotoDocument(this.props.index, this.props.index); } } - - this.autoSaveButtonChange(buttonIndex.Navigate); - } /** @@ -381,23 +177,16 @@ export default class PresentationElement extends React.Component { e.stopPropagation(); - if (this.selectedButtons[buttonIndex.Show]) { - this.selectedButtons[buttonIndex.Show] = false; - this.props.document.viewScale = 1; + this.showButton = !this.showButton; + if (!this.showButton) { + this.props.document.viewScale = 1; } else { - if (this.selectedButtons[buttonIndex.Navigate]) { - this.selectedButtons[buttonIndex.Navigate] = false; - } - this.selectedButtons[buttonIndex.Show] = true; - const current = NumCast(this.props.mainDocument.selectedDoc); - if (current === this.props.index) { + this.navButton = false; + if (this.currentIndex === this.props.index) { this.props.gotoDocument(this.props.index, this.props.index); } } - - this.autoSaveButtonChange(buttonIndex.Show); - } /** @@ -407,13 +196,8 @@ export default class PresentationElement extends React.Component { e.stopPropagation(); - if (this.selectedButtons[buttonIndex.OpenRight]) { - this.selectedButtons[buttonIndex.OpenRight] = false; - // action maybe - } else { - this.selectedButtons[buttonIndex.OpenRight] = true; - } - this.autoSaveButtonChange(buttonIndex.OpenRight); + + this.openRightButton = !this.openRightButton; } /** @@ -449,8 +233,6 @@ export default class PresentationElement extends React.Component Doc.AddDocToList(this.props.mainDocument, "data", d, this.props.document, before) || added, false) @@ -463,221 +245,13 @@ export default class PresentationElement extends React.Component { - - let x = this.ScreenToLocalListTransform(de.x, de.y); - let rect = this.header!.getBoundingClientRect(); - let bounds = this.ScreenToLocalListTransform(rect.left, rect.top + rect.height / 2); - let before = x[1] < bounds[1]; - - let droppedDocIndex = this.props.allListElements.indexOf(droppedDoc); - - let dropIndexDiff = droppedDocIndex - this.props.index; - - //checking if the position it's dropped corresponds to current location with 3 cases. - if (droppedDocIndex === this.props.index) { - return; - } - - if (dropIndexDiff === 1 && !before) { - return; - } - if (dropIndexDiff === -1 && before) { - return; - } - - let p = this.props; - let droppedDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(droppedDoc); - let curDocGuid = StrCast(droppedDoc.presentId, null); - - //Splicing the doc from its current group, since it's moved - if (p.groupMappings.has(curDocGuid)) { - let groupArray = this.props.groupMappings.get(curDocGuid)!; - - if (droppedDocSelectedButtons[buttonIndex.Group]) { - let groupIndexOfDrop = groupArray.indexOf(droppedDoc); - let firstPart = groupArray.splice(0, groupIndexOfDrop); - - if (firstPart.length > 1) { - let newGroupGuid = Utils.GenerateGuid(); - firstPart.forEach((doc: Doc) => doc.presentId = newGroupGuid); - this.props.groupMappings.set(newGroupGuid, firstPart); - } - } - - groupArray.splice(groupArray.indexOf(droppedDoc), 1); - if (groupArray.length === 0) { - this.props.groupMappings.delete(curDocGuid); - } - droppedDoc.presentId = Utils.GenerateGuid(); - - //making sure to correct to groups after splicing, in case the dragged element - //had the grouping on. - let indexOfBelow = droppedDocIndex + 1; - if (indexOfBelow < this.props.allListElements.length && indexOfBelow > 1) { - let selectedButtonsOrigBelow: boolean[] = await this.getSelectedButtonsOfDoc(this.props.allListElements[indexOfBelow]); - let aboveBelowDoc: Doc = this.props.allListElements[droppedDocIndex - 1]; - let aboveBelowDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(aboveBelowDoc); - let belowDoc: Doc = this.props.allListElements[indexOfBelow]; - let belowDocPresId = StrCast(belowDoc.presentId); - - if (selectedButtonsOrigBelow[buttonIndex.Group]) { - let belowDocGroup: Doc[] = this.props.groupMappings.get(belowDocPresId)!; - if (aboveBelowDocSelectedButtons[buttonIndex.Group]) { - let aboveBelowDocPresId = StrCast(aboveBelowDoc.presentId); - if (this.props.groupMappings.has(aboveBelowDocPresId)) { - let aboveBelowDocGroup: Doc[] = this.props.groupMappings.get(aboveBelowDocPresId)!; - aboveBelowDocGroup.push(...belowDocGroup); - this.props.groupMappings.delete(belowDocPresId); - belowDocGroup.forEach((doc: Doc) => doc.presentId = aboveBelowDocPresId); - - } - } else { - belowDocGroup.unshift(aboveBelowDoc); - aboveBelowDoc.presentId = belowDocPresId; - } - - - } - } - - } - - //Case, when the dropped doc had the group button clicked. - if (droppedDocSelectedButtons[buttonIndex.Group]) { - if (before) { - if (this.props.index > 0) { - let aboveDoc = this.props.allListElements[this.props.index - 1]; - let aboveDocGuid = StrCast(aboveDoc.presentId); - if (this.props.groupMappings.has(aboveDocGuid)) { - this.protectOrderAndPush(aboveDocGuid, aboveDoc, droppedDoc); - } else { - this.createNewGroup(aboveDoc, droppedDoc, aboveDocGuid); - } - } else { - let propsPresId = StrCast(this.props.document.presentId); - if (this.selectedButtons[buttonIndex.Group]) { - let propsArray = this.props.groupMappings.get(propsPresId)!; - propsArray.unshift(droppedDoc); - droppedDoc.presentId = propsPresId; - } - } - } else { - let propsDocGuid = StrCast(this.props.document.presentId); - if (this.props.groupMappings.has(propsDocGuid)) { - this.protectOrderAndPush(propsDocGuid, this.props.document, droppedDoc); - - } else { - this.createNewGroup(this.props.document, droppedDoc, propsDocGuid); - } - } - - - //if the group button of the element was not clicked. - } else { - if (before) { - if (this.props.index > 0) { - - let aboveDoc = this.props.allListElements[this.props.index - 1]; - let aboveDocGuid = StrCast(aboveDoc.presentId); - let aboveDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(aboveDoc); - - - if (this.selectedButtons[buttonIndex.Group]) { - if (aboveDocSelectedButtons[buttonIndex.Group]) { - let aboveGroupArray = this.props.groupMappings.get(aboveDocGuid)!; - let propsDocPresId = StrCast(this.props.document.presentId); - - this.halveGroupArray(aboveDoc, aboveGroupArray, droppedDoc, propsDocPresId); - - } else { - let belowPresentId = StrCast(this.props.document.presentId); - let belowGroup = this.props.groupMappings.get(belowPresentId)!; - belowGroup.splice(belowGroup.indexOf(aboveDoc), 1); - belowGroup.unshift(droppedDoc); - droppedDoc.presentId = belowPresentId; - aboveDoc.presentId = Utils.GenerateGuid(); - } - - - } - } else { - let propsPresId = StrCast(this.props.document.presentId); - if (this.selectedButtons[buttonIndex.Group]) { - let propsArray = this.props.groupMappings.get(propsPresId)!; - propsArray.unshift(droppedDoc); - droppedDoc.presentId = propsPresId; - } - } - } else { - if (this.props.index < this.props.allListElements.length - 1) { - let belowDoc = this.props.allListElements[this.props.index + 1]; - let belowDocGuid = StrCast(belowDoc.presentId); - let belowDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(belowDoc); - - let propsDocGuid = StrCast(this.props.document.presentId); - - if (belowDocSelectedButtons[buttonIndex.Group]) { - let belowGroupArray = this.props.groupMappings.get(belowDocGuid)!; - if (this.selectedButtons[buttonIndex.Group]) { - - let propsGroupArray = this.props.groupMappings.get(propsDocGuid)!; - - this.halveGroupArray(this.props.document, propsGroupArray, droppedDoc, belowDocGuid); - - } else { - belowGroupArray.splice(belowGroupArray.indexOf(this.props.document), 1); - this.props.document.presentId = Utils.GenerateGuid(); - belowGroupArray.unshift(droppedDoc); - droppedDoc.presentId = belowDocGuid; - } - } - - } - } - } - this.autoSaveGroupChanges(); - - } - - /** - * This method returns the selectedButtons boolean array of the passed in doc, - * retrieving it from the back-up. - */ - getSelectedButtonsOfDoc = async (paramDoc: Doc) => { - let castedList = Cast(this.props.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - let foundSelectedButtons: boolean[] = new Array(7); - - //if this is the first time this doc mounts, push a doc for it to store - for (let doc of castedList!) { - let curDoc = await doc; - let curDocId = StrCast(curDoc.docId); - if (curDocId === paramDoc[Id]) { - let selectedButtonOfDoc = Cast(curDoc.selectedButtons, listSpec("boolean"), null); - if (selectedButtonOfDoc !== undefined) { - return selectedButtonOfDoc; - } - } - } - - return foundSelectedButtons; - - } - //This is used to add dragging as an event. onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SelectionManager.GetIsDragging()) { - let selected = NumCast(this.props.mainDocument.selectedDoc, 0); this.header!.className = "presentationView-item"; - - if (selected === this.props.index) { + if (this.currentIndex === this.props.index) { //this doc is selected this.header!.className = "presentationView-item presentationView-selected"; } @@ -688,12 +262,10 @@ export default class PresentationElement extends React.Component { //to get currently selected presentation doc - let selected = NumCast(this.props.mainDocument.selectedDoc, 0); this.header!.className = "presentationView-item"; - - if (selected === this.props.index) { + if (this.currentIndex === this.props.index) { //this doc is selected this.header!.className = "presentationView-item presentationView-selected"; @@ -729,62 +301,6 @@ export default class PresentationElement extends React.Component { return this.props.document !== target && this.props.removeDocByRef(doc) && addDoc(doc); } - - /** - * Helper method that gets called to divide a group array into two different groups - * including the targetDoc in first part. - * @param targetDoc document that is targeted as slicing point - * @param propsGroupArray the array that gets divided into 2 - * @param droppedDoc the dropped document - * @param belowDocGuid presentId of the belowGroup - */ - private halveGroupArray(targetDoc: Doc, propsGroupArray: Doc[], droppedDoc: Doc, belowDocGuid: string) { - let targetIndex = propsGroupArray.indexOf(targetDoc); - let firstPart = propsGroupArray.slice(0, targetIndex + 1); - let firstPartNewGuid = Utils.GenerateGuid(); - firstPart.forEach((doc: Doc) => doc.presentId = firstPartNewGuid); - let secondPart = propsGroupArray.slice(targetIndex + 1); - secondPart.unshift(droppedDoc); - droppedDoc.presentId = belowDocGuid; - this.props.groupMappings.set(firstPartNewGuid, firstPart); - this.props.groupMappings.set(belowDocGuid, secondPart); - } - - /** - * Helper method that creates a new group, pushing above document first, - * and dropped document second. - * @param aboveDoc the document above dropped document - * @param droppedDoc the dropped document itself - * @param aboveDocGuid above document's presentId - */ - private createNewGroup(aboveDoc: Doc, droppedDoc: Doc, aboveDocGuid: string) { - let newGroup: Doc[] = []; - newGroup.push(aboveDoc); - newGroup.push(droppedDoc); - droppedDoc.presentId = aboveDocGuid; - this.props.groupMappings.set(aboveDocGuid, newGroup); - } - - /** - * Helper method that finds the above document's group, and pushes the - * dropped document into that group, protecting the visual order of the - * presentation elements. - * @param aboveDoc the document above dropped document - * @param droppedDoc the dropped document itself - * @param aboveDocGuid above document's presentId - */ - private protectOrderAndPush(aboveDocGuid: string, aboveDoc: Doc, droppedDoc: Doc) { - let groupArray = this.props.groupMappings.get(aboveDocGuid)!; - let tempStack: Doc[] = []; - while (groupArray[groupArray.length - 1] !== aboveDoc) { - tempStack.push(groupArray.pop()!); - } - groupArray.push(droppedDoc); - droppedDoc.presentId = aboveDocGuid; - while (tempStack.length !== 0) { - groupArray.push(tempStack.pop()!); - } - } /** * This function is a getter to get if a document is in previewMode. */ @@ -872,11 +388,8 @@ export default class PresentationElement extends React.Component { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}> + onClick={e => { p.gotoDocument(p.index, this.currentIndex); e.stopPropagation(); }}> {`${p.index + 1}. ${title}`}

- - - - - - - + + + + + + +
{this.renderEmbeddedInline()} diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx index 288ade042..930ce202e 100644 --- a/src/client/views/presentationview/PresentationList.tsx +++ b/src/client/views/presentationview/PresentationList.tsx @@ -1,24 +1,20 @@ -import { observer } from "mobx-react"; -import React = require("react"); import { action } from "mobx"; -import "./PresentationView.scss"; -import { Utils } from "../../../Utils"; -import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { NumCast, StrCast } from "../../../new_fields/Types"; +import { observer } from "mobx-react"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; +import { NumCast } from "../../../new_fields/Types"; import PresentationElement from "./PresentationElement"; +import "./PresentationView.scss"; +import React = require("react"); interface PresListProps { mainDocument: Doc; deleteDocument(index: number): void; gotoDocument(index: number, fromDoc: number): Promise; - groupMappings: Map; PresElementsMappings: Map; setChildrenDocs: (docList: Doc[]) => void; presStatus: boolean; - presButtonBackUp: Doc; - presGroupBackUp: Doc; removeDocByRef(doc: Doc): boolean; clearElemMap(): void; @@ -31,35 +27,6 @@ interface PresListProps { */ export default class PresentationViewList extends React.Component { - /** - * Method that initializes presentation ids for the - * docs that is in the presentation, when presentation list - * gets re-rendered. It makes sure to not assign ids to the - * docs that are in the group, so that mapping won't be disrupted. - */ - - @action - initializeGroupIds = async (docList: Doc[]) => { - docList.forEach(async (doc: Doc, index: number) => { - let docGuid = StrCast(doc.presentId, null); - //checking if part of group - let storedGuids: string[] = []; - let castedGroupDocs = await DocListCastAsync(this.props.presGroupBackUp.groupDocs); - //making sure the docs that were in groups, which were stored, to not get new guids. - if (castedGroupDocs !== undefined) { - castedGroupDocs.forEach((doc: Doc) => { - let storedGuid = StrCast(doc.presentIdStore, null); - if (storedGuid) { - storedGuids.push(storedGuid); - } - - }); - } - if (!this.props.groupMappings.has(docGuid) && !storedGuids.includes(docGuid)) { - doc.presentId = Utils.GenerateGuid(); - } - }); - } /** * Initially every document starts with a viewScale 1, which means @@ -77,7 +44,6 @@ export default class PresentationViewList extends React.Component render() { const children = DocListCast(this.props.mainDocument.data); - this.initializeGroupIds(children); this.initializeScaleViews(children); this.props.setChildrenDocs(children); this.props.clearElemMap(); @@ -96,11 +62,8 @@ export default class PresentationViewList extends React.Component index={index} deleteDocument={this.props.deleteDocument} gotoDocument={this.props.gotoDocument} - groupMappings={this.props.groupMappings} allListElements={children} presStatus={this.props.presStatus} - presButtonBackUp={this.props.presButtonBackUp} - presGroupBackUp={this.props.presGroupBackUp} removeDocByRef={this.props.removeDocByRef} PresElementsMappings={this.props.PresElementsMappings} /> diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index b0968132b..bc1899811 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -1,6 +1,5 @@ .presentationView-cont { position: absolute; - background: white; z-index: 2; box-shadow: #AAAAAA .2vw .2vw .4vw; right: 0; @@ -49,12 +48,15 @@ .presentationView-item:hover { transition: all .1s; - background: #AAAAAA + background: #AAAAAA; + border-radius: 12px; } .presentationView-selected { background: gray; color: black; + border-radius: 12px; + box-shadow: black 2px 2px 5px; } .presentationView-heading { @@ -79,11 +81,12 @@ } .presentation-interaction { + color: gray; float: left; } .presentation-interaction-selected { - background: #505050; + color: white; float: left; } @@ -96,6 +99,7 @@ margin-right: 2.5%; margin-left: 2.5%; width: 20%; + border-radius: 5px; } .presentation-buttons { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index ca05dfa45..e5b609966 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -621,6 +621,9 @@ export namespace Doc { manager.BrushedDoc.delete(doc); manager.BrushedDoc.delete(Doc.GetDataDoc(doc)); } + export function UnBrushAllDocs() { + manager.BrushedDoc.clear(); + } } 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); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 88454c8163115b1396a34f4836b5f6f04817a3f0 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 16:11:20 -0400 Subject: tryupdateheight fixes --- src/client/views/DocumentDecorations.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cc8e57af9..a28088032 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -714,7 +714,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; let dataDoc = Doc.GetProto(this.targetDoc); if (!canPull || !dataDoc[GoogleRef]) return (null); - let icon = !dataDoc.unchanged ? (this.pullIcon as any) : fetch; + let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; icon = this.openHover ? "share" : icon; let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`; @@ -727,7 +727,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> backgroundColor: this.pullColor, transition: "0.2s ease all" }} - onPointerEnter={e => e.ctrlKey && runInAction(() => this.openHover = true)} + onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)} onPointerLeave={() => runInAction(() => this.openHover = false)} onClick={e => { if (e.altKey) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 1e0975b4b..c8722c6e3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, autorun } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -87,7 +87,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private pushReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - private isGoogleDocsUpdate = false; @observable _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; @@ -321,7 +320,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private newListItems = (count: number) => { let listItems: any[] = []; for (let i = 0; i < count; i++) { - listItems.push(schema.nodes.list_item.create(null, schema.nodes.paragraph.create(null))); + listItems.push(schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); } return listItems; } @@ -359,7 +358,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this.pullFromGoogleDoc(this.checkState); - runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true); + this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true); this._reactionDisposer = reaction( () => { @@ -370,13 +369,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._editorView && !this._applyingChange) { let updatedState = JSON.parse(incomingValue); this._editorView.updateState(EditorState.fromJSON(config, updatedState)); - // manually sets cursor selection at the end of the text on focus - if (this.isGoogleDocsUpdate) { - this.isGoogleDocsUpdate = false; - let end = this._editorView.state.doc.content.size - 1; - updatedState.selection = { type: "text", anchor: end, head: end }; - this._editorView.updateState(EditorState.fromJSON(config, updatedState)); - } + this.tryUpdateHeight(); } } ); @@ -485,16 +478,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe const data = Cast(dataDoc.data, RichTextField); if (data instanceof RichTextField) { pullSuccess = true; - this.isGoogleDocsUpdate = true; dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); + 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); - setTimeout(() => this.tryUpdateHeight(), 0); } checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { @@ -719,6 +718,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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 { -- cgit v1.2.3-70-g09d2 From 43d731ef2a6b2bd3fcdb7dd26fb6a8beac8e1306 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 16:23:38 -0400 Subject: from last with presentations and small tweaks. --- src/client/views/MetadataEntryMenu.tsx | 2 +- src/client/views/collections/CollectionStackingView.tsx | 4 ++-- src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 4 ++-- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/PresBox.tsx | 7 ++++--- src/client/views/presentationview/PresentationElement.tsx | 2 -- src/client/views/presentationview/PresentationView.scss | 4 ---- src/server/authentication/models/current_user_utils.ts | 2 +- 8 files changed, 11 insertions(+), 16 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 4d696e37a..ec628c5a3 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -172,7 +172,7 @@ export class MetadataEntryMenu extends React.Component{
    - {this._allSuggestions.sort().map(s =>
  • { this._currentKey = s; this.previewValue(); })} >{s}
  • )} + {this._allSuggestions.slice().sort().map(s =>
  • { this._currentKey = s; this.previewValue(); })} >{s}
  • )}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index c74c60d8f..be6ee1756 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -291,10 +291,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc); let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); let height = () => this.getDocHeight(layoutDoc); - let dxf = () => this.getDocTransform(layoutDoc, dref.current!); + let dxf = () => this.getDocTransform(layoutDoc!, dref.current!); let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); this._docXfs.push({ dxf: dxf, width: width, height: height }); - return
+ return !layoutDoc ? (null) :
{this.getDisplayDoc(layoutDoc, d, dxf, width)}
; }); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index cc8476548..2536eff00 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -83,9 +83,9 @@ export class CollectionStackingViewFieldColumn extends React.Component Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns); - let height = () => parent.getDocHeight(pair!.layout); + let height = () => parent.getDocHeight(pair.layout); let dref = React.createRef(); - let dxf = () => this.getDocTransform(pair!.layout, dref.current!); + let dxf = () => this.getDocTransform(pair.layout!, dref.current!); this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 0d4b377dd..653c5c27f 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -128,7 +128,7 @@ export class KeyValueBox extends React.Component { let rows: JSX.Element[] = []; let i = 0; const self = this; - for (let key of Object.keys(ids).sort()) { + for (let key of Object.keys(ids).slice().sort()) { rows.push( { diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index cf222601f..e376fbddb 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -116,9 +116,9 @@ export class PresBox extends React.Component { //FieldViewProps? let presDocs = DocListCast(this.curPresentation.data); for (; nextSelected < presDocs.length - 1; nextSelected++) { - if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) + if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) { break; - + } } this.gotoDocument(nextSelected, current); @@ -239,8 +239,9 @@ export class PresBox extends React.Component { //FieldViewProps? 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 (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) { break; + } currentDocGroups.push(presDocs[nextSelected]); } diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 68c5a5b8d..83413814f 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -261,8 +261,6 @@ export default class PresentationElement extends React.Component { - //to get currently selected presentation doc - this.header!.className = "presentationView-item"; if (this.currentIndex === this.props.index) { diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index bc1899811..5c40a8808 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -23,14 +23,11 @@ user-select: none; transition: all .1s; - - .documentView-node { position: absolute; z-index: 1; } - } .presentationView-item-above { @@ -73,7 +70,6 @@ display: inline-block; width: calc(100% - 200px); letter-spacing: 2px; - } .presentation-icon { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index aa3da0034..f7ce24967 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -29,7 +29,7 @@ export class CurrentUserUtils { doc.viewType = CollectionViewType.Tree; doc.dropAction = "alias"; doc.layout = CollectionView.LayoutString(); - doc.title = Doc.CurrentUserEmail + doc.title = Doc.CurrentUserEmail; this.updateUserDocument(doc); doc.data = new List(); doc.gridGap = 5; -- cgit v1.2.3-70-g09d2 From 6604c40d02b4dd7c6a6c663f301bcaedfee6998f Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 17:18:02 -0400 Subject: send help oh god --- src/client/util/RichTextSchema.tsx | 81 ------------------------------------- src/client/util/TooltipTextMenu.tsx | 29 ------------- src/client/views/nodes/WebBox.scss | 10 +++++ src/client/views/nodes/WebBox.tsx | 51 +++++++---------------- 4 files changed, 25 insertions(+), 146 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 733c50d20..1f6c2badb 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -111,19 +111,6 @@ export const nodes: { [index: string]: NodeSpec } = { // } // }] }, - - checkbox: { - inline: true, - attrs: { - visibility: { default: false } - }, - group: "inline", - toDOM(node) { - const attrs = { style: `width: 40px` }; - return ["span", { ...node.attrs, ...attrs }]; - }, - }, - // :: NodeSpec An inline image (``) node. Supports `src`, // `alt`, and `href` attributes. The latter two default to the empty // string. @@ -203,19 +190,6 @@ export const nodes: { [index: string]: NodeSpec } = { // toDOM() { return ulDOM } }, - checkbox_list: { - content: 'checklist_item+', - marks: '_', - group: 'block', - // inline: true, - parseDOM: [ - { tag: "ul" } - ], - toDOM() { - return ["ul", { style: 'list-style: none' }, 0]; - }, - }, - //bullet_list: { // content: 'list_item+', // group: 'block', @@ -228,18 +202,6 @@ export const nodes: { [index: string]: NodeSpec } = { ...listItem, content: 'paragraph block*' }, - - checklist_item: { - content: 'paragraph block*', - parseDOM: [{ tag: "li" }], - // toDOM() { - // return ["li", { style: 'content: checkbox' }, 0]; - // }, - toDOM() { - return ["li", 0]; - }, - defining: true - } }; const emDOM: DOMOutputSpecArray = ["em", 0]; @@ -562,49 +524,6 @@ export class ImageResizeView { } } -export class CheckboxView { - _view: any; - _collapsed: HTMLElement; - - constructor(node: any, view: any, getPos: any) { - this._collapsed = document.createElement("span"); - this._collapsed.textContent = node.attrs.visibility ? "⬛" : "⬜"; - this._collapsed.style.position = "relative"; - // this._collapsed.style.width = "80px"; - this._collapsed.style.height = "20px"; - let self = this; - this._view = view; - const js = node.toJSON; - node.toJSON = function () { - - return js.apply(this, arguments); - }; - this._collapsed.onpointerdown = function (e: any) { - console.log(node.attrs.visibility) - if (node.attrs.visibility) { - let y = getPos(); - const attrs = { ...node.attrs }; - attrs.visibility = !attrs.visibility; - view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs)); - self._collapsed.textContent = "⬜"; - } else { - let y = getPos(); - const attrs = { ...node.attrs }; - attrs.visibility = !attrs.visibility; - console.log(attrs.visibility) - view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs)); - self._collapsed.textContent = "⬛"; - } - e.preventDefault(); - e.stopPropagation(); - console.log(node.attrs.visibility) - - }; - (this as any).dom = this._collapsed; - } - -} - export class SummarizedView { // TODO: highlight text that is summarized. to find end of region, walk along mark _collapsed: HTMLElement; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 46961e416..450931b76 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -189,7 +189,6 @@ export class TooltipTextMenu { this.tooltip.appendChild(this._brushdom); this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); - this.tooltip.appendChild(this.createCheckbox().render(this.view).dom); this.updateListItemDropdown(":", this.listTypeBtnDom); @@ -441,14 +440,6 @@ export class TooltipTextMenu { return true; } - public static insertCheckbox(state: EditorState, dispatch: any) { - let newNode = schema.nodes.checkbox.create({ visibility: false }); - if (dispatch) { - dispatch(state.tr.replaceSelectionWith(newNode)); - } - return true; - } - //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown updateListItemDropdown(label: string, listTypeBtn: any) { //remove old btn @@ -461,7 +452,6 @@ export class TooltipTextMenu { }); //option to remove the list formatting toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType)); - toAdd.push(this.dropdownNodeBtn("⬜", "color:black; width:40px;", schema.nodes.checkbox_list, this.view, this.listTypes, this.changeToNodeType)) listTypeBtn = (new Dropdown(toAdd, { label: label, @@ -525,11 +515,6 @@ export class TooltipTextMenu { liftListItem(schema.nodes.list_item)(view.state, view.dispatch); if (nodeType) { //add new wrapInList(nodeType)(view.state, view.dispatch); - // console.log(nodeType === schema.nodes.checkbox_list) - // if (nodeType === schema.nodes.checkbox_list) { - // TooltipTextMenu.insertCheckbox(view.state, view.dispatch) - // } - } } @@ -564,20 +549,6 @@ export class TooltipTextMenu { }); } - createCheckbox() { - return new MenuItem({ - title: "Checkbox", - label: "Checkbox", - icon: icons.code, - css: "color:white", - class: "checkbox", - execEvent: "", - run: (state, dispatch) => { - TooltipTextMenu.insertCheckbox(state, dispatch); - } - }) - } - deleteLinkItem() { const icon = { height: 16, width: 16, diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index c37f08eca..6272d3d47 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -1,3 +1,5 @@ +@import "../globalCssVariables.scss"; + .webBox-cont, .webBox-cont-interactive { padding: 0vw; @@ -79,6 +81,14 @@ // margin-top: 10px; } + .switchToText { + color: $main-accent; + } + + .switchToText:hover { + color: $dark-color; + } + .collectionViewBaseChrome-viewSpecs { margin-left: 10px; display: grid; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index e7bed3bed..ef8f8c664 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -87,43 +87,21 @@ export class WebBox extends React.Component { let field = Cast(this.props.Document[this.props.fieldKey], WebField); if (field) url = field.url.href; - let docView: DocumentView; - // let parentDoc: any; - SelectionManager.SelectedDocuments().map(dv => { - // docView = dv; - dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); - }); - - console.log("happening") - - // // let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); let newBox = Docs.Create.TextDocument({ - width: 200, height: 100, - // x: newPoint[0], - // y: newPoint[1], x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), - title: url + title: url, + width: 200, + height: 70, + documentText: "@@@" + url }); - console.log(typeof newBox) - - // const script = KeyValueBox.CompileKVPScript(`new RichTextField("{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"${url}"}]}]},"selection":{"type":"text","anchor":1,"head":1}}")`); - // const script = KeyValueBox.CompileKVPScript(newBox.setText(url)) - // console.log(script) - // if (!script) return; - // KeyValueBox.ApplyKVPScript(this.props.Document, "data", script); - - console.log(newBox); - + SelectionManager.SelectedDocuments().map(dv => { + dv.props.addDocument && dv.props.addDocument(newBox, false); + dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); + }); - newBox.proto!.autoHeight = true; - // PreviewCursor._addLiveTextDoc(newBox); - // if (parent && parent.props.addDocument) { - // console.log("adding doc") - // parent.props.addDocument(newBox); - // } - return; + Doc.BrushDoc(newBox); } urlEditor() { @@ -151,14 +129,15 @@ export class WebBox extends React.Component {
- +
+ +
-- cgit v1.2.3-70-g09d2 From 5bd664e113434ec08fd56a34bb8f6a3e2ce136e8 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 17:25:13 -0400 Subject: fixed upload of docs zip --- .../collectionFreeForm/CollectionFreeFormView.tsx | 46 ++++++++++++++++++---- src/client/views/nodes/DocumentView.tsx | 30 -------------- src/server/index.ts | 22 ++++++----- 3 files changed, 51 insertions(+), 47 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3be6aa3d3..cbc123c61 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,17 +1,18 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; -import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable, IReactionDisposer, reaction } from "mobx"; +import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; +import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast, FieldResult, Field, Opt } from "../../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; 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 } from "../../../../new_fields/Types"; -import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { DocServer } from "../../../DocServer"; +import { Docs } from "../../../documents/Documents"; +import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; @@ -29,15 +30,14 @@ import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; import { pageSchema } from "../../nodes/ImageBox"; import { OverlayElementOptions, OverlayView } from "../../OverlayView"; import PDFMenu from "../../pdf/PDFMenu"; -import { CollectionSubView } from "../CollectionSubView"; import { ScriptBox } from "../../ScriptBox"; +import { CollectionSubView } from "../CollectionSubView"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { Docs } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; +import { DocServer } from "../../../DocServer"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); @@ -841,6 +841,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; analyzers.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); + ContextMenu.Instance.addItem({ + description: "Import document", icon: "upload", event: () => { + 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; + } + 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(); + } + }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fd53262d7..8dadf2bef 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,7 +40,6 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { PresBox } from './PresBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -629,35 +628,6 @@ export class DocumentView extends DocComponent(Docu } }); - cm.addItem({ - description: "Import document", icon: "upload", event: () => { - 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; - } - 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(); - } - }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); type User = { email: string, userDocumentId: string }; let usersMenu: ContextMenuProps[] = []; diff --git a/src/server/index.ts b/src/server/index.ts index ef1829f30..51c7d8a38 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -38,7 +38,7 @@ import flash = require('connect-flash'); import { Search } from './Search'; import _ = require('lodash'); import * as Archiver from 'archiver'; -import AdmZip from 'adm-zip'; +var AdmZip = require('adm-zip'); import * as YoutubeApi from "./apis/youtube/youtubeApiSample"; import { Response } from 'express-serve-static-core'; import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils"; @@ -358,7 +358,7 @@ app.post("/uploadDoc", (req, res) => { for (const name in files) { const path_2 = files[name].path; const zip = new AdmZip(path_2); - zip.getEntries().forEach(entry => { + zip.getEntries().forEach((entry: any) => { if (!entry.entryName.startsWith("files/")) return; let dirname = path.dirname(entry.entryName) + "/"; let extname = path.extname(entry.entryName); @@ -367,13 +367,17 @@ app.post("/uploadDoc", (req, res) => { // zip.extractEntryTo(dirname + basename + "_s" + extname, __dirname + RouteStore.public, true, false); // zip.extractEntryTo(dirname + basename + "_m" + extname, __dirname + RouteStore.public, true, false); // zip.extractEntryTo(dirname + basename + "_l" + extname, __dirname + RouteStore.public, true, false); - zip.extractEntryTo(entry.entryName, __dirname + RouteStore.public, true, false); - dirname = "/" + dirname; - - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_o" + extname)); - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_s" + extname)); - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_m" + extname)); - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_l" + extname)); + try { + zip.extractEntryTo(entry.entryName, __dirname + RouteStore.public, true, false); + dirname = "/" + dirname; + + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_o" + extname)); + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_s" + extname)); + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_m" + extname)); + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_l" + extname)); + } catch (e) { + console.log(e); + } }); const json = zip.getEntry("doc.json"); let docs: any; -- cgit v1.2.3-70-g09d2 From f4888ec4b2862cdf890ac1b0f6670b41398266df Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 20:01:35 -0400 Subject: can create google slides presentations --- .../apis/google_docs/GoogleApiClientUtils.ts | 99 +++++++++++++--------- src/client/views/MainView.tsx | 4 + src/client/views/nodes/FormattedTextBox.tsx | 20 ++--- src/server/apis/google/GoogleApiServerUtils.ts | 65 +++++++++----- src/server/index.ts | 19 ++--- 5 files changed, 123 insertions(+), 84 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 821c52270..52452cd95 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,4 +1,4 @@ -import { docs_v1 } from "googleapis"; +import { docs_v1, slides_v1 } from "googleapis"; import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { Opt } from "../../../new_fields/Doc"; @@ -9,47 +9,47 @@ export const Pushes = "googleDocsPushCount"; export namespace GoogleApiClientUtils { - export namespace Docs { + export enum Actions { + Create = "create", + Retrieve = "retrieve", + Update = "update" + } - export enum Actions { - Create = "create", - Retrieve = "retrieve", - Update = "update" - } + export enum WriteMode { + Insert, + Replace + } - export enum WriteMode { - Insert, - Replace - } + export type DocumentId = string; + export type Reference = DocumentId | CreateOptions; + export type TextContent = string | string[]; + 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 }; - export type DocumentId = string; - export type Reference = DocumentId | CreateOptions; - export type TextContent = string | string[]; - export type IdHandler = (id: DocumentId) => any; + export interface CreateOptions { + title?: string; // if excluded, will use a default title annotated with the current date + } - export type CreationResult = Opt; - export type RetrievalResult = Opt; - export type UpdateResult = Opt; - export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; - export type ReadResult = { title?: string, body?: string }; + export interface RetrieveOptions { + documentId: DocumentId; + } - export interface CreateOptions { - handler: IdHandler; // callback to process the documentId of the newly created Google Doc - title?: string; // if excluded, will use a default title annotated with the current date - } + export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; - export interface RetrieveOptions { - documentId: DocumentId; - } + export interface WriteOptions { + mode: WriteMode; + content: TextContent; + reference: Reference; + index?: number; // if excluded, will compute the last index of the document and append the content there + } - export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; - export interface WriteOptions { - mode: WriteMode; - content: TextContent; - reference: Reference; - index?: number; // if excluded, will compute the last index of the document and append the content there - } + export namespace Docs { + + export type RetrievalResult = Opt; + export type UpdateResult = Opt; export interface UpdateOptions { documentId: DocumentId; @@ -106,7 +106,7 @@ export namespace GoogleApiClientUtils { * @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 + Actions.Create; + const path = RouteStore.googleDocs + "Documents/" + Actions.Create; const parameters = { requestBody: { title: options.title || `Dash Export (${new Date().toDateString()})` @@ -115,17 +115,14 @@ export namespace GoogleApiClientUtils { try { const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); const generatedId = schema.documentId; - if (generatedId) { - options.handler(generatedId); - return generatedId; - } + return generatedId; } catch { return undefined; } }; export const retrieve = async (options: RetrieveOptions): Promise => { - const path = RouteStore.googleDocs + Actions.Retrieve; + const path = RouteStore.googleDocs + "Documents/" + Actions.Retrieve; try { const schema: RetrievalResult = await PostToServer(path, options); return schema; @@ -135,7 +132,7 @@ export namespace GoogleApiClientUtils { }; export const update = async (options: UpdateOptions): Promise => { - const path = RouteStore.googleDocs + Actions.Update; + const path = RouteStore.googleDocs + "Documents/" + Actions.Update; const parameters = { documentId: options.documentId, requestBody: { @@ -221,4 +218,24 @@ export namespace GoogleApiClientUtils { } + export namespace Slides { + + export const create = async (options: CreateOptions): Promise => { + const path = RouteStore.googleDocs + "Slides/" + Actions.Create; + const parameters = { + requestBody: { + title: options.title || `Dash Export (${new Date().toDateString()})` + } + }; + try { + const schema: slides_v1.Schema$Presentation = await PostToServer(path, parameters); + const generatedId = schema.presentationId; + return generatedId; + } catch { + return undefined; + } + }; + + } + } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f28844009..3490a0f68 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,6 +40,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; +import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils'; @observer export class MainView extends React.Component { @@ -120,6 +121,9 @@ export class MainView extends React.Component { componentWillMount() { var tag = document.createElement('script'); + let title = "THIS IS MY FIRST DASH PRESENTATION"; + GoogleApiClientUtils.Slides.create({ title }).then(id => console.log("We received this! ", id)); + 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 c8722c6e3..408a6948e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -64,7 +64,7 @@ export const GoogleRef = "googleDocId"; type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); -type PullHandler = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => void; +type PullHandler = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => void; @observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { @@ -430,22 +430,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } pushToGoogleDoc = async () => { - this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { - let modes = GoogleApiClientUtils.Docs.WriteMode; + this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { + let modes = GoogleApiClientUtils.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 = { - title: StrCast(this.dataDoc.title), - handler: id => this.dataDoc[GoogleRef] = id - }; + reference = { title: StrCast(this.dataDoc.title) }; } let redo = async () => { let data = Cast(this.dataDoc.data, RichTextField); if (this._editorView && reference && data) { let content = data[ToPlainText](); let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + response && (this.dataDoc[GoogleRef] = response.documentId); let pushSuccess = response !== undefined && !("errors" in response); dataDoc.unchanged = pushSuccess; DocumentDecorations.Instance.startPushOutcome(pushSuccess); @@ -465,14 +463,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe pullFromGoogleDoc = async (handler: PullHandler) => { let dataDoc = this.dataDoc; let documentId = StrCast(dataDoc[GoogleRef]); - let exportState: GoogleApiClientUtils.Docs.ReadResult = {}; + let exportState: GoogleApiClientUtils.ReadResult = {}; if (documentId) { exportState = await GoogleApiClientUtils.Docs.read({ documentId }); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } - updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + updateState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { let pullSuccess = false; if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { const data = Cast(dataDoc.data, RichTextField); @@ -496,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe DocumentDecorations.Instance.startPullOutcome(pullSuccess); } - checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { let data = Cast(dataDoc.data, RichTextField); if (data) { diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 817b2b696..ff027c501 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -1,7 +1,10 @@ -import { google, docs_v1 } from "googleapis"; +import { google, docs_v1, slides_v1 } from "googleapis"; import { createInterface } from "readline"; import { readFile, writeFile } from "fs"; import { OAuth2Client } from "google-auth-library"; +import { Opt } from "../../../new_fields/Doc"; +import { GlobalOptions } from "googleapis-common"; +import { GaxiosResponse } from "gaxios"; /** * Server side authentication for Google Api queries. @@ -13,38 +16,57 @@ export namespace GoogleApiServerUtils { const SCOPES = [ 'documents.readonly', 'documents', + 'presentations', + 'presentations.readonly', 'drive', 'drive.file', ]; - // The file token.json stores the user's access and refresh tokens, and is - // created automatically when the authorization flow completes for the first - // time. + export const parseBuffer = (data: Buffer) => JSON.parse(data.toString()); - export namespace Docs { + export enum Sector { + Documents = "Documents", + Slides = "Slides" + } + - export interface CredentialPaths { - credentials: string; - token: string; - } + export interface CredentialPaths { + credentials: string; + token: string; + } - export type Endpoint = docs_v1.Docs; + export type ApiResponse = Promise; + export type ApiRouter = (endpoint: Endpoint, paramters: any) => ApiResponse; + export type ApiHandler = (parameters: any) => ApiResponse; + export type Action = "create" | "retrieve" | "update"; - export const GetEndpoint = async (paths: CredentialPaths) => { - return new Promise((resolve, reject) => { - readFile(paths.credentials, (err, credentials) => { - if (err) { - reject(err); - return console.log('Error loading client secret file:', err); + export type Endpoint = { get: ApiHandler, create: ApiHandler, batchUpdate: ApiHandler }; + export type EndpointParameters = GlobalOptions & { version: "v1" }; + + export const GetEndpoint = async (sector: string, paths: CredentialPaths) => { + return new Promise>((resolve, reject) => { + readFile(paths.credentials, (err, credentials) => { + if (err) { + reject(err); + return console.log('Error loading client secret file:', err); + } + return authorize(parseBuffer(credentials), paths.token).then(auth => { + let routed: Opt; + let parameters: EndpointParameters = { auth, version: "v1" }; + switch (sector) { + case Sector.Documents: + routed = google.docs(parameters).documents; + break; + case Sector.Slides: + routed = google.slides(parameters).presentations; + break; } - return authorize(parseBuffer(credentials), paths.token).then(auth => { - resolve(google.docs({ version: "v1", auth })); - }); + resolve(routed); }); }); - }; + }); + }; - } /** * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client @@ -105,5 +127,4 @@ export namespace GoogleApiServerUtils { }); }); } - } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index ef1829f30..6aecb875a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -45,6 +45,7 @@ import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils"; import { GaxiosResponse } from 'gaxios'; import { Opt } from '../new_fields/Doc'; import { docs_v1 } from 'googleapis'; +import { Endpoint } from 'googleapis-common'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -799,21 +800,19 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json"); const token = path.join(__dirname, "./credentials/google_docs_token.json"); -type ApiResponse = Promise; -type ApiHandler = (endpoint: docs_v1.Resource$Documents, parameters: any) => ApiResponse; -type Action = "create" | "retrieve" | "update"; - -const EndpointHandlerMap = new Map([ +const EndpointHandlerMap = new Map([ ["create", (api, params) => api.create(params)], ["retrieve", (api, params) => api.get(params)], ["update", (api, params) => api.batchUpdate(params)], ]); -app.post(RouteStore.googleDocs + ":action", (req, res) => { - GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { - let handler = EndpointHandlerMap.get(req.params.action); - if (handler) { - let execute = handler(endpoint.documents, req.body).then( +app.post(RouteStore.googleDocs + ":sector/:action", (req, res) => { + let sector = req.params.sector; + let action = req.params.action; + GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Sector[sector], { credentials, token }).then(endpoint => { + let handler = EndpointHandlerMap.get(action); + if (endpoint && handler) { + let execute = handler(endpoint, req.body).then( response => res.send(response.data), rejection => res.send(rejection) ); -- cgit v1.2.3-70-g09d2 From 30d55c5b91329d32a79b7135fff3566b2cd0e822 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 20:33:42 -0400 Subject: create is now shared between slides and docs --- .../apis/google_docs/GoogleApiClientUtils.ts | 79 +++++++++------------- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/server/RouteStore.ts | 2 +- src/server/apis/google/GoogleApiServerUtils.ts | 6 +- src/server/index.ts | 4 +- 6 files changed, 40 insertions(+), 55 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 52452cd95..ea020ed00 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -9,6 +9,11 @@ export const Pushes = "googleDocsPushCount"; export namespace GoogleApiClientUtils { + export enum Service { + Documents = "Documents", + Slides = "Slides" + } + export enum Actions { Create = "create", Retrieve = "retrieve", @@ -29,6 +34,7 @@ export namespace GoogleApiClientUtils { export type ReadResult = { title?: string, body?: string }; export interface CreateOptions { + service: Service; title?: string; // if excluded, will use a default title annotated with the current date } @@ -45,6 +51,30 @@ export namespace GoogleApiClientUtils { 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}/${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 namespace Docs { @@ -96,33 +126,8 @@ export namespace GoogleApiClientUtils { } - /** - * 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); - const generatedId = schema.documentId; - return generatedId; - } catch { - return undefined; - } - }; - export const retrieve = async (options: RetrieveOptions): Promise => { - const path = RouteStore.googleDocs + "Documents/" + Actions.Retrieve; + const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Retrieve}`; try { const schema: RetrievalResult = await PostToServer(path, options); return schema; @@ -132,7 +137,7 @@ export namespace GoogleApiClientUtils { }; export const update = async (options: UpdateOptions): Promise => { - const path = RouteStore.googleDocs + "Documents/" + Actions.Update; + const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Update}`; const parameters = { documentId: options.documentId, requestBody: { @@ -218,24 +223,4 @@ export namespace GoogleApiClientUtils { } - export namespace Slides { - - export const create = async (options: CreateOptions): Promise => { - const path = RouteStore.googleDocs + "Slides/" + Actions.Create; - const parameters = { - requestBody: { - title: options.title || `Dash Export (${new Date().toDateString()})` - } - }; - try { - const schema: slides_v1.Schema$Presentation = await PostToServer(path, parameters); - const generatedId = schema.presentationId; - return generatedId; - } catch { - return undefined; - } - }; - - } - } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 3490a0f68..2a219fdd1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -122,7 +122,7 @@ export class MainView extends React.Component { var tag = document.createElement('script'); let title = "THIS IS MY FIRST DASH PRESENTATION"; - GoogleApiClientUtils.Slides.create({ title }).then(id => console.log("We received this! ", id)); + GoogleApiClientUtils.create({ service: GoogleApiClientUtils.Service.Slides, title }).then(id => console.log("We received this! ", id)); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 408a6948e..bcec6f7c3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -436,7 +436,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); if (!reference) { mode = modes.Insert; - reference = { title: StrCast(this.dataDoc.title) }; + reference = { service: GoogleApiClientUtils.Service.Documents, title: StrCast(this.dataDoc.title) }; } let redo = async () => { let data = Cast(this.dataDoc.data, RichTextField); diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts index 5d977006a..014906054 100644 --- a/src/server/RouteStore.ts +++ b/src/server/RouteStore.ts @@ -31,6 +31,6 @@ export enum RouteStore { // APIS cognitiveServices = "/cognitiveservices", - googleDocs = "/googleDocs/" + googleDocs = "/googleDocs" } \ No newline at end of file diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index ff027c501..8785cd974 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -24,7 +24,7 @@ export namespace GoogleApiServerUtils { export const parseBuffer = (data: Buffer) => JSON.parse(data.toString()); - export enum Sector { + export enum Service { Documents = "Documents", Slides = "Slides" } @@ -54,10 +54,10 @@ export namespace GoogleApiServerUtils { let routed: Opt; let parameters: EndpointParameters = { auth, version: "v1" }; switch (sector) { - case Sector.Documents: + case Service.Documents: routed = google.docs(parameters).documents; break; - case Sector.Slides: + case Service.Slides: routed = google.slides(parameters).presentations; break; } diff --git a/src/server/index.ts b/src/server/index.ts index 6aecb875a..0476bf3df 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -806,10 +806,10 @@ const EndpointHandlerMap = new Map api.batchUpdate(params)], ]); -app.post(RouteStore.googleDocs + ":sector/:action", (req, res) => { +app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => { let sector = req.params.sector; let action = req.params.action; - GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Sector[sector], { credentials, token }).then(endpoint => { + GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { let execute = handler(endpoint, req.body).then( -- cgit v1.2.3-70-g09d2 From e68b1cc60b2a8ffe55eea3e1d5266d6897dcdcb6 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 21:31:17 -0400 Subject: integrated slides --- .../apis/google_docs/GoogleApiClientUtils.ts | 47 +- src/client/views/MainView.tsx | 3 - src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/server/slides.json | 10820 +++++++++++++++++++ 4 files changed, 10851 insertions(+), 21 deletions(-) create mode 100644 src/server/slides.json (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index ea020ed00..3e90adc4d 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -25,11 +25,11 @@ export namespace GoogleApiClientUtils { Replace } - export type DocumentId = string; - export type Reference = DocumentId | CreateOptions; + export type Identifier = string; + export type Reference = Identifier | CreateOptions; export type TextContent = string | string[]; - export type IdHandler = (id: DocumentId) => any; - export type CreationResult = Opt; + 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 }; @@ -39,10 +39,14 @@ export namespace GoogleApiClientUtils { } export interface RetrieveOptions { - documentId: DocumentId; + service: Service; + identifier: Identifier; } - export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; + export interface ReadOptions { + identifier: Identifier; + removeNewlines?: boolean; + } export interface WriteOptions { mode: WriteMode; @@ -78,11 +82,11 @@ export namespace GoogleApiClientUtils { export namespace Docs { - export type RetrievalResult = Opt; + export type RetrievalResult = Opt; export type UpdateResult = Opt; export interface UpdateOptions { - documentId: DocumentId; + documentId: Identifier; requests: docs_v1.Schema$Request[]; } @@ -126,11 +130,20 @@ export namespace GoogleApiClientUtils { } + const KeyMapping = new Map([ + [Service.Documents, "documentId"], + [Service.Slides, "presentationId"] + ]); + export const retrieve = async (options: RetrieveOptions): Promise => { - const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Retrieve}`; + const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Retrieve}`; try { - const schema: RetrievalResult = await PostToServer(path, options); - return schema; + 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; + } } catch { return undefined; } @@ -153,7 +166,7 @@ export namespace GoogleApiClientUtils { }; export const read = async (options: ReadOptions): Promise => { - return retrieve(options).then(document => { + return retrieve({ ...options, service: Service.Documents }).then(document => { let result: ReadResult = {}; if (document) { let title = document.title; @@ -165,7 +178,7 @@ export namespace GoogleApiClientUtils { }; export const readLines = async (options: ReadOptions): Promise => { - return retrieve(options).then(document => { + return retrieve({ ...options, service: Service.Documents }).then(document => { let result: ReadLinesResult = {}; if (document) { let title = document.title; @@ -179,14 +192,14 @@ export namespace GoogleApiClientUtils { export const write = async (options: WriteOptions): Promise => { const requests: docs_v1.Schema$Request[] = []; - const documentId = await Utils.initialize(options.reference); - if (!documentId) { + const identifier = await Utils.initialize(options.reference); + if (!identifier) { return undefined; } let index = options.index; const mode = options.mode; if (!(index && mode === WriteMode.Insert)) { - let schema = await retrieve({ documentId }); + let schema = await retrieve({ identifier, service: Service.Documents }); if (!schema || !(index = Utils.endOf(schema))) { return undefined; } @@ -212,7 +225,7 @@ export namespace GoogleApiClientUtils { if (!requests.length) { return undefined; } - let replies: any = await update({ documentId, requests }); + let replies: any = await update({ documentId: identifier, requests }); let errors = "errors"; if (errors in replies) { console.log("Write operation failed:"); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2a219fdd1..0286a201b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -121,9 +121,6 @@ export class MainView extends React.Component { componentWillMount() { var tag = document.createElement('script'); - let title = "THIS IS MY FIRST DASH PRESENTATION"; - GoogleApiClientUtils.create({ service: GoogleApiClientUtils.Service.Slides, title }).then(id => console.log("We received this! ", id)); - 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 bcec6f7c3..a603676bb 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -465,7 +465,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let documentId = StrCast(dataDoc[GoogleRef]); let exportState: GoogleApiClientUtils.ReadResult = {}; if (documentId) { - exportState = await GoogleApiClientUtils.Docs.read({ documentId }); + exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId }); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } diff --git a/src/server/slides.json b/src/server/slides.json new file mode 100644 index 000000000..323cac3a6 --- /dev/null +++ b/src/server/slides.json @@ -0,0 +1,10820 @@ +{ + "presentationId": "1gHxyT6bBhsPVeuWNnWDzI33yEviMVo8n60JtZiVy3tY", + "pageSize": { + "width": { + "magnitude": 9144000, + "unit": "EMU" + }, + "height": { + "magnitude": 5143500, + "unit": "EMU" + } + }, + "slides": [ + { + "objectId": "p", + "pageElements": [ + { + "objectId": "i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.6842, + "translateX": 311708.35000000003, + "translateY": 744575, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 20, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 20, + "textRun": { + "content": "Importing into Dash\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "CENTERED_TITLE", + "parentObjectId": "p2_i0" + } + } + }, + { + "objectId": "i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.2642, + "translateX": 311700, + "translateY": 2834125, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 15, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 15, + "textRun": { + "content": "By Sam Wilkins\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SUBTITLE", + "parentObjectId": "p2_i1" + } + } + } + ], + "slideProperties": { + "layoutObjectId": "p2", + "masterObjectId": "simple-light-2", + "notesPage": { + "objectId": "p:notes", + "pageType": "NOTES", + "pageElements": [ + { + "objectId": "i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.032025, + "scaleY": 1.143, + "translateX": 381300, + "translateY": 685800, + "unit": "EMU" + }, + "shape": { + "shapeProperties": { + "outline": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_IMAGE", + "parentObjectId": "n:slide" + } + } + }, + { + "objectId": "i3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.8288, + "scaleY": 1.3716, + "translateX": 685800, + "translateY": 4343400, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "index": 1, + "parentObjectId": "n:text" + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + }, + "notesProperties": { + "speakerNotesObjectId": "i3" + } + } + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "g5f40953d50_0_0", + "pageElements": [ + { + "objectId": "g5f40953d50_0_1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 10, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 10, + "textRun": { + "content": "Dr. Seuss\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p4_i0" + } + } + }, + { + "objectId": "g5f40953d50_0_2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 25, + "paragraphMarker": { + "style": { + "indentStart": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 25, + "textRun": { + "content": "Here is a bulleted list!\n", + "style": {} + } + }, + { + "startIndex": 25, + "endIndex": 34, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 25, + "endIndex": 34, + "textRun": { + "content": "One fish\n", + "style": {} + } + }, + { + "startIndex": 34, + "endIndex": 43, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 34, + "endIndex": 43, + "textRun": { + "content": "Two fish\n", + "style": {} + } + }, + { + "startIndex": 43, + "endIndex": 52, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 43, + "endIndex": 52, + "textRun": { + "content": "Red fish\n", + "style": {} + } + }, + { + "startIndex": 52, + "endIndex": 62, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 52, + "endIndex": 62, + "textRun": { + "content": "Blue fish\n", + "style": {} + } + } + ], + "lists": { + "kix.wifbmqnyqu4p": { + "listId": "kix.wifbmqnyqu4p", + "nestingLevel": { + "0": { + "bulletStyle": { + "underline": false + } + }, + "1": { + "bulletStyle": { + "underline": false + } + }, + "2": { + "bulletStyle": { + "underline": false + } + }, + "3": { + "bulletStyle": { + "underline": false + } + }, + "4": { + "bulletStyle": { + "underline": false + } + }, + "5": { + "bulletStyle": { + "underline": false + } + }, + "6": { + "bulletStyle": { + "underline": false + } + }, + "7": { + "bulletStyle": { + "underline": false + } + }, + "8": { + "bulletStyle": { + "underline": false + } + } + } + }, + "kix.yuy8atv38lqp": { + "listId": "kix.yuy8atv38lqp", + "nestingLevel": { + "0": { + "bulletStyle": { + "underline": false + } + }, + "1": { + "bulletStyle": { + "underline": false + } + }, + "2": { + "bulletStyle": { + "underline": false + } + }, + "3": { + "bulletStyle": { + "underline": false + } + }, + "4": { + "bulletStyle": { + "underline": false + } + }, + "5": { + "bulletStyle": { + "underline": false + } + }, + "6": { + "bulletStyle": { + "underline": false + } + }, + "7": { + "bulletStyle": { + "underline": false + } + }, + "8": { + "bulletStyle": { + "underline": false + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p4_i1" + } + } + } + ], + "slideProperties": { + "layoutObjectId": "p4", + "masterObjectId": "simple-light-2", + "notesPage": { + "objectId": "g5f40953d50_0_0:notes", + "pageType": "NOTES", + "pageElements": [ + { + "objectId": "g5f40953d50_0_3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.032, + "scaleY": 1.143, + "translateX": 381300, + "translateY": 685800, + "unit": "EMU" + }, + "shape": { + "shapeProperties": { + "outline": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_IMAGE", + "parentObjectId": "n:slide" + } + } + }, + { + "objectId": "g5f40953d50_0_4", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.8288, + "scaleY": 1.3716, + "translateX": 685800, + "translateY": 4343400, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "index": 1, + "parentObjectId": "n:text" + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + }, + "notesProperties": { + "speakerNotesObjectId": "g5f40953d50_0_4" + } + } + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + } + ], + "title": "THIS IS MY FIRST DASH PRESENTATION", + "masters": [ + { + "objectId": "simple-light-2", + "pageType": "MASTER", + "pageElements": [ + { + "objectId": "p1_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "1": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "2": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "3": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "4": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "5": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "6": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "7": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "8": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "TOP" + }, + "placeholder": { + "type": "TITLE" + } + } + }, + { + "objectId": "p1_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 18, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 18, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "1": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "2": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "3": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "4": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "5": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "6": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "7": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "8": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "TOP" + }, + "placeholder": { + "type": "BODY" + } + } + }, + { + "objectId": "p1_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "END", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 10, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 10, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "SLIDE_NUMBER" + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "solidFill": { + "color": { + "themeColor": "LIGHT1" + }, + "alpha": 1 + } + }, + "colorScheme": { + "colors": [ + { + "type": "DARK1", + "color": {} + }, + { + "type": "LIGHT1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "DARK2", + "color": { + "red": 0.34901962, + "green": 0.34901962, + "blue": 0.34901962 + } + }, + { + "type": "LIGHT2", + "color": { + "red": 0.93333334, + "green": 0.93333334, + "blue": 0.93333334 + } + }, + { + "type": "ACCENT1", + "color": { + "red": 1, + "green": 0.67058825, + "blue": 0.2509804 + } + }, + { + "type": "ACCENT2", + "color": { + "red": 0.12941177, + "green": 0.12941177, + "blue": 0.12941177 + } + }, + { + "type": "ACCENT3", + "color": { + "red": 0.47058824, + "green": 0.5647059, + "blue": 0.6117647 + } + }, + { + "type": "ACCENT4", + "color": { + "red": 1, + "green": 0.67058825, + "blue": 0.2509804 + } + }, + { + "type": "ACCENT5", + "color": { + "green": 0.5921569, + "blue": 0.654902 + } + }, + { + "type": "ACCENT6", + "color": { + "red": 0.93333334, + "green": 1, + "blue": 0.25490198 + } + }, + { + "type": "HYPERLINK", + "color": { + "green": 0.5921569, + "blue": 0.654902 + } + }, + { + "type": "FOLLOWED_HYPERLINK", + "color": { + "green": 0.5921569, + "blue": 0.654902 + } + }, + { + "type": "TEXT1", + "color": {} + }, + { + "type": "BACKGROUND1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "TEXT2", + "color": { + "red": 0.93333334, + "green": 0.93333334, + "blue": 0.93333334 + } + }, + { + "type": "BACKGROUND2", + "color": { + "red": 0.34901962, + "green": 0.34901962, + "blue": 0.34901962 + } + } + ] + } + }, + "masterProperties": { + "displayName": "Simple Light" + } + } + ], + "layouts": [ + { + "objectId": "p2", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p2_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.6842, + "translateX": 311708.35000000003, + "translateY": 744575, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "CENTERED_TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p2_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.2642, + "translateX": 311700, + "translateY": 2834125, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SUBTITLE", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p2_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE", + "displayName": "Title slide" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p3", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p3_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.2806, + "translateX": 311700, + "translateY": 2150850, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p3_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "SECTION_HEADER", + "displayName": "Section header" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p4", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p4_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p4_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p4_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE_AND_BODY", + "displayName": "Title and body" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p5", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p5_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p5_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3333, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p5_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3333, + "scaleY": 1.1388, + "translateX": 4832400, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "index": 1, + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p5_i3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE_AND_TWO_COLUMNS", + "displayName": "Title and two columns" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p6", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p6_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p6_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE_ONLY", + "displayName": "Title only" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p7", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p7_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.936, + "scaleY": 0.2519, + "translateX": 311700, + "translateY": 555600, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p7_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.936, + "scaleY": 1.0598, + "translateX": 311700, + "translateY": 1389600, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p7_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "ONE_COLUMN_TEXT", + "displayName": "One column text" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p8", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p8_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.1226, + "scaleY": 1.3636, + "translateX": 490250, + "translateY": 450150, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p8_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "MAIN_POINT", + "displayName": "Main point" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p9", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p9_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.524, + "scaleY": 1.7145, + "translateX": 4572000, + "translateY": -125, + "unit": "EMU" + }, + "shape": { + "shapeType": "RECTANGLE", + "shapeProperties": { + "shapeBackgroundFill": { + "solidFill": { + "color": { + "themeColor": "LIGHT2" + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "MIDDLE" + } + } + }, + { + "objectId": "p9_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3484, + "scaleY": 0.4941, + "translateX": 265500, + "translateY": 1233175, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p9_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3484, + "scaleY": 0.4117, + "translateX": 265500, + "translateY": 2803075, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SUBTITLE", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p9_i3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.279, + "scaleY": 1.2317, + "translateX": 4939500, + "translateY": 724075, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p9_i4", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "SECTION_TITLE_AND_DESCRIPTION", + "displayName": "Section title and description" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p10", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p10_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.9996, + "scaleY": 0.2017, + "translateX": 311700, + "translateY": 4230575, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p10_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "CAPTION_ONLY", + "displayName": "Caption" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p11", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p11_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.6545, + "translateX": 311700, + "translateY": 1106125, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p11_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.4336, + "translateX": 311700, + "translateY": 3152225, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p11_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "BIG_NUMBER", + "displayName": "Big number" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p12", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p12_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "BLANK", + "displayName": "Blank" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + } + ], + "locale": "en", + "revisionId": "kaHql7SEgvqFcw", + "notesMaster": { + "objectId": "n", + "pageType": "NOTES_MASTER", + "pageElements": [ + { + "objectId": "n:slide", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.032025, + "scaleY": 1.143, + "translateX": 381300, + "translateY": 685800, + "unit": "EMU" + }, + "shape": { + "shapeProperties": { + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "SLIDE_IMAGE" + } + } + }, + { + "objectId": "n:text", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.8288, + "scaleY": 1.3716, + "translateX": 685800, + "translateY": 4343400, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "1": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "2": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "3": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "4": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "5": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "6": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "7": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "8": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "TOP" + }, + "placeholder": { + "type": "BODY", + "index": 1 + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "colorScheme": { + "colors": [ + { + "type": "DARK1", + "color": {} + }, + { + "type": "LIGHT1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "DARK2", + "color": { + "red": 0.08235294, + "green": 0.5058824, + "blue": 0.34509805 + } + }, + { + "type": "LIGHT2", + "color": { + "red": 0.9529412, + "green": 0.9529412, + "blue": 0.9529412 + } + }, + { + "type": "ACCENT1", + "color": { + "red": 0.019607844, + "green": 0.5529412, + "blue": 0.78039217 + } + }, + { + "type": "ACCENT2", + "color": { + "red": 0.3137255, + "green": 0.7058824, + "blue": 0.19607843 + } + }, + { + "type": "ACCENT3", + "color": { + "red": 0.92941177, + "green": 0.3372549, + "blue": 0.105882354 + } + }, + { + "type": "ACCENT4", + "color": { + "red": 0.92941177, + "green": 0.9372549 + } + }, + { + "type": "ACCENT5", + "color": { + "red": 0.14117648, + "green": 0.79607844, + "blue": 0.8980392 + } + }, + { + "type": "ACCENT6", + "color": { + "red": 0.39215687, + "green": 0.8980392, + "blue": 0.44705883 + } + }, + { + "type": "HYPERLINK", + "color": { + "red": 0.13333334, + "blue": 0.8 + } + }, + { + "type": "FOLLOWED_HYPERLINK", + "color": { + "red": 0.33333334, + "green": 0.101960786, + "blue": 0.54509807 + } + }, + { + "type": "TEXT1", + "color": {} + }, + { + "type": "BACKGROUND1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "TEXT2", + "color": { + "red": 0.9529412, + "green": 0.9529412, + "blue": 0.9529412 + } + }, + { + "type": "BACKGROUND2", + "color": { + "red": 0.08235294, + "green": 0.5058824, + "blue": 0.34509805 + } + } + ] + } + } + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 6927418364d58c2aaba1b25f1d11537dd510535c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 22 Aug 2019 08:29:43 -0400 Subject: fixed autoheight for text --- src/client/views/nodes/FormattedTextBox.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 3f3360eff..849f942b6 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -735,12 +735,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { - if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) { - let xf = this._ref.current!.getBoundingClientRect(); - let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight); + let sh = this._ref.current ? this._ref.current.scrollHeight : 0; + if (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); - let sh = scrBounds.height; const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight; this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? sh : undefined; -- cgit v1.2.3-70-g09d2 From 8cffe031fe364e19897cfb638cffa9e4056c4343 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 22 Aug 2019 11:07:11 -0400 Subject: fixed textheight with titles. fixed marquee with floating docs. other tweaks. --- src/client/util/DocumentManager.ts | 4 +++- src/client/views/MainOverlayTextBox.tsx | 1 + .../views/collections/CollectionStackingView.tsx | 13 ++++++++----- .../views/collections/CollectionViewChromes.tsx | 14 +++++++++----- .../collections/collectionFreeForm/MarqueeView.tsx | 20 ++++++++++++++++++-- src/client/views/nodes/DocumentView.tsx | 10 ++++++++-- src/client/views/nodes/FormattedTextBox.tsx | 6 ++++-- 7 files changed, 51 insertions(+), 17 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 7f526b247..124faf266 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -9,6 +9,7 @@ import { CollectionView } from '../views/collections/CollectionView'; import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { undoBatch, UndoManager } from './UndoManager'; +import { Scripting } from './Scripting'; export class DocumentManager { @@ -202,4 +203,5 @@ export class DocumentManager { return 1; } } -} \ No newline at end of file +} +Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)) }) \ No newline at end of file diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 9fe435bc5..0839e1114 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -144,6 +144,7 @@ export class MainOverlayTextBox extends React.Component Document={FormattedTextBox.InputBoxOverlay.props.Document} DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} onClick={undefined} + ChromeHeight={this.ChromeHeight} isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index be6ee1756..4ab656744 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -287,15 +287,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { masonryChildren(docs: Doc[]) { this._docXfs.length = 0; return docs.map((d, i) => { + const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d); + if (!pair.layout || pair.data instanceof Promise) { + return (null); + } let dref = React.createRef(); - let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc); let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); - let height = () => this.getDocHeight(layoutDoc); - let dxf = () => this.getDocTransform(layoutDoc!, dref.current!); + let height = () => this.getDocHeight(pair.layout); + let dxf = () => this.getDocTransform(pair.layout!, dref.current!); let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); this._docXfs.push({ dxf: dxf, width: width, height: height }); - return !layoutDoc ? (null) :
- {this.getDisplayDoc(layoutDoc, d, dxf, width)} + return !pair.layout ? (null) :
+ {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
; }); } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 25b152d4e..74e57611d 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -297,11 +297,15 @@ export class CollectionViewBaseChrome extends React.Component { if (node) { - this._picker = datepicker("#" + node.id, { - disabler: (date: Date) => date > new Date(), - onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), - dateSelected: new Date() - }); + try { + this._picker = datepicker("#" + node.id, { + disabler: (date: Date) => date > new Date(), + onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), + dateSelected: new Date() + }); + } catch (e) { + console.log("date picker exception:" + e); + } } } render() { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index aad26efa0..221237365 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -373,7 +373,7 @@ export class MarqueeView extends React.Component marqueeSelect(selectBackgrounds: boolean = true) { let selRect = this.Bounds; let selection: Doc[] = []; - this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => { + this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { var x = NumCast(doc.x); var y = NumCast(doc.y); var w = NumCast(doc.width); @@ -383,7 +383,7 @@ export class MarqueeView extends React.Component } }); if (!selection.length && selectBackgrounds) { - this.props.activeDocuments().map(doc => { + this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { var x = NumCast(doc.x); var y = NumCast(doc.y); var w = NumCast(doc.width); @@ -393,6 +393,22 @@ export class MarqueeView extends React.Component } }); } + if (!selection.length) { + let left = this._downX < this._lastX ? this._downX : this._lastX; + let top = this._downY < this._lastY ? this._downY : this._lastY; + let topLeft = this.props.getContainerTransform().transformPoint(left, top); + let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; + this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { + var x = NumCast(doc.x); + var y = NumCast(doc.y); + var w = NumCast(doc.width); + var h = NumCast(doc.height); + if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { + selection.push(doc); + } + }); + } return selection; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8dadf2bef..0c577af86 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -715,7 +715,13 @@ 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); - return showTitle ? 25 : 0; + 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?? } get layoutDoc() { @@ -769,7 +775,7 @@ export class DocumentView extends DocComponent(Docu {!showTitle && !showCaption ? this.contents :
-
+
{this.contents}
{!showTitle ? (null) : diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 849f942b6..94d9f2f66 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -374,6 +374,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); + this.tryUpdateHeight(); } pushToGoogleDoc = async () => { @@ -735,11 +736,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { + const ChromeHeight = this.props.ChromeHeight; let sh = this._ref.current ? this._ref.current.scrollHeight : 0; + console.log(this.props.Document.title + " " + sh + " " + (ChromeHeight && ChromeHeight())); if (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); - const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight; this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? sh : undefined; } @@ -775,7 +777,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
Date: Thu, 22 Aug 2019 11:18:19 -0400 Subject: from last --- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 94d9f2f66..1cc3e821f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -374,7 +374,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); - this.tryUpdateHeight(); + setTimeout(() => this.tryUpdateHeight(), 0); } pushToGoogleDoc = async () => { -- cgit v1.2.3-70-g09d2 From 0ee435f6bd686c667a067fa750b4589cedfb0070 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 22 Aug 2019 11:18:41 -0400 Subject: again --- src/client/views/nodes/FormattedTextBox.tsx | 1 - 1 file changed, 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 1cc3e821f..44a481953 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -738,7 +738,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe tryUpdateHeight() { const ChromeHeight = this.props.ChromeHeight; let sh = this._ref.current ? this._ref.current.scrollHeight : 0; - console.log(this.props.Document.title + " " + sh + " " + (ChromeHeight && ChromeHeight())); if (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); -- cgit v1.2.3-70-g09d2 From a32a1b4f32ee309c306ab9c9fdc90573cddeed11 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 22 Aug 2019 11:45:23 -0400 Subject: video box fix and slides parsing beginning --- .../apis/google_docs/GoogleApiClientUtils.ts | 32 ++++++++++++++++++++++ src/client/views/nodes/VideoBox.tsx | 6 ++-- 2 files changed, 35 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 3e90adc4d..798886def 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -236,4 +236,36 @@ 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/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 704030d85..3f4ee8960 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -34,7 +34,7 @@ library.add(faVideo); export class VideoBox extends DocComponent(VideoDocument) { private _reactionDisposer?: IReactionDisposer; private _youtubeReactionDisposer?: IReactionDisposer; - private _youtubePlayer: any = undefined; + private _youtubePlayer: YT.Player | undefined = undefined; private _videoRef: HTMLVideoElement | null = null; private _youtubeIframeId: number = -1; private _youtubeContentCreated = false; @@ -78,7 +78,7 @@ export class VideoBox extends DocComponent(VideoD @action public Pause = (update: boolean = true) => { this.Playing = false; update && this.player && this.player.pause(); - update && this._youtubePlayer && this._youtubePlayer.pauseVideo(); + update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo(); this._youtubePlayer && this._playTimer && clearInterval(this._playTimer); this._playTimer = undefined; this.updateTimecode(); @@ -244,7 +244,7 @@ export class VideoBox extends DocComponent(VideoD let onYoutubePlayerStateChange = (event: any) => runInAction(() => { if (started && event.data === YT.PlayerState.PLAYING) { started = false; - this._youtubePlayer.unMute(); + this._youtubePlayer && this._youtubePlayer.unMute(); this.Pause(); return; } -- cgit v1.2.3-70-g09d2 From 65aec7b22e63de15e0d911971fa4e5a32d09d9b5 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 22 Aug 2019 12:00:47 -0400 Subject: cleanup --- src/client/util/DictationManager.ts | 30 ++++++++++++++++++----------- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/MainView.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/new_fields/RichTextField.ts | 6 ++++++ 5 files changed, 27 insertions(+), 14 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 781e5e465..fb3c15cea 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -64,6 +64,7 @@ export namespace DictationManager { export type ListeningUIStatus = { interim: boolean } | false; export interface ListeningOptions { + useOverlay: boolean; language: string; continuous: ContinuityArgs; delimiters: DelimiterArgs; @@ -74,27 +75,34 @@ export namespace DictationManager { export const listen = async (options?: Partial) => { let results: string | undefined; - // let main = MainView.Instance; + let main = MainView.Instance; - // main.dictationOverlayVisible = true; - // main.isListening = { interim: false }; + let overlay = options !== undefined && options.useOverlay; + if (overlay) { + main.dictationOverlayVisible = true; + main.isListening = { interim: false }; + } try { results = await listenImpl(options); if (results) { Utils.CopyText(results); - // main.isListening = false; - // let execute = options && options.tryExecute; - // main.dictatedPhrase = execute ? results.toLowerCase() : results; - // main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + if (overlay) { + main.isListening = false; + let execute = options && options.tryExecute; + main.dictatedPhrase = execute ? results.toLowerCase() : results; + main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + } options && options.tryExecute && await DictationManager.Commands.execute(results); } } catch (e) { - // main.isListening = false; - // main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; - // main.dictationSuccess = false; + if (overlay) { + main.isListening = false; + main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + main.dictationSuccess = false; + } } finally { - // main.initiateDictationFade(); + overlay && main.initiateDictationFade(); } return results; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 8a7295c65..d0464bd5f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -105,7 +105,7 @@ export default class KeyManager { switch (keyname) { case " ": - DictationManager.Controls.listen({ tryExecute: true }); + DictationManager.Controls.listen({ useOverlay: true, tryExecute: true }); stopPropagation = true; preventDefault = true; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0286a201b..f28844009 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,7 +40,6 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; -import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils'; @observer export class MainView extends React.Component { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d9043c739..5492a457d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, autorun } from "mobx"; +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"; diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index cae5623e6..1b52e6f82 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -28,6 +28,12 @@ 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; -- cgit v1.2.3-70-g09d2 From 135da417387b5fef28df8f07266d404be8211320 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Thu, 22 Aug 2019 12:04:40 -0400 Subject: done? --- src/client/views/nodes/WebBox.scss | 51 -------------------------------------- 1 file changed, 51 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index b28f950c2..43220df71 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -62,7 +62,6 @@ position: absolute; width: 40px; transform-origin: top left; - // margin-top: 10px; } .switchToText { @@ -72,56 +71,6 @@ .switchToText:hover { color: $dark-color; } - - .collectionViewBaseChrome-viewSpecs { - margin-left: 10px; - display: grid; - - - - .collectionViewBaseChrome-viewSpecsMenu { - overflow: hidden; - transition: height .5s, display .5s; - position: absolute; - top: 60px; - z-index: 100; - display: flex; - flex-direction: column; - background: rgb(238, 238, 238); - box-shadow: grey 2px 2px 4px; - - .qs-datepicker { - left: unset; - right: 0; - } - - .collectionViewBaseChrome-viewSpecsMenu-row { - display: grid; - grid-template-columns: 150px 200px 150px; - margin-top: 10px; - margin-right: 10px; - - .collectionViewBaseChrome-viewSpecsMenu-rowLeft, - .collectionViewBaseChrome-viewSpecsMenu-rowMiddle, - .collectionViewBaseChrome-viewSpecsMenu-rowRight { - font-size: 75%; - letter-spacing: 2px; - color: grey; - margin-left: 10px; - padding: 5px; - border: none; - outline-color: black; - } - } - - .collectionViewBaseChrome-viewSpecsMenu-lastRow { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-gap: 10px; - margin: 10px; - } - } - } } button:hover { -- cgit v1.2.3-70-g09d2 From f0f0cc36654183921076db5a341fe7cac2bfdd3c Mon Sep 17 00:00:00 2001 From: monikahedman Date: Thu, 22 Aug 2019 12:08:12 -0400 Subject: cleaned up --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e70d526c5..27eafd769 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -203,7 +203,6 @@ export class MarqueeView extends React.Component onClick = (e: React.MouseEvent): void => { if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { - //this is probably the wrong transform PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument); // let the DocumentView stopPropagation of this event when it selects this document } else { // why do we get a click event when the cursor have moved a big distance? diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0c0ab4d87..1db66d4a0 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -145,7 +145,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe paste = (e: ClipboardEvent) => { - //this is throwing a ton of erros so i had to comment it out + //this is throwing a ton of errors so commented it out if (e.clipboardData && this._editorView) { // let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`; // for (let i = 0; i < e.clipboardData.items.length; i++) { @@ -173,13 +173,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - public setText = (text: string) => { - const tx = this._editorView!.state.tr.insertText(text); - const state = this._editorView!.state; - this._editorView!.dispatch(tx); - return new RichTextField(JSON.stringify(state.toJSON())); - } - dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); -- cgit v1.2.3-70-g09d2 From 224d58da7c8bf8a7eb27cb616100473afcad4f7b Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 22 Aug 2019 12:22:54 -0400 Subject: cleaned up more context menu stuff. fixed buttons to follow links by default. --- src/client/util/SelectionManager.ts | 1 - .../views/collections/CollectionTreeView.tsx | 5 +++ .../collectionFreeForm/CollectionFreeFormView.tsx | 47 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 24 +++++++---- 4 files changed, 46 insertions(+), 31 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index ee623d082..9efef888d 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,7 +4,6 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast, StrCast } from "../../new_fields/Types"; import { InkingControl } from "../views/InkingControl"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; export namespace SelectionManager { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b61894b29..7e1aacd5d 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -28,6 +28,7 @@ import "./CollectionTreeView.scss"; import React = require("react"); import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; import { KeyValueBox } from '../nodes/KeyValueBox'; +import { ContextMenuProps } from '../ContextMenuItem'; export interface TreeViewProps { @@ -541,6 +542,10 @@ export class CollectionTreeView extends CollectionSubView(Document) { e.stopPropagation(); e.preventDefault(); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + } else { + let layoutItems: ContextMenuProps[] = []; + layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" }); + ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" }); } } outerXf = () => Utils.GetScreenTransform(this._mainEle!); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cbc123c61..224e8047d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -345,9 +345,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, -1); if (cluster !== -1) { let eles = this.childDocs.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(); this.selectDocuments(eles); let clusterDocs = SelectionManager.SelectedDocuments(); SelectionManager.DeselectAll(); + prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); + let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); @@ -818,30 +823,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; - 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: "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.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; - }, - icon: !this.props.Document.useClusters ? "braille" : "braille" - }); 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: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); - ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); - - let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); - let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; - analyzers.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); - ContextMenu.Instance.addItem({ description: "Import document", icon: "upload", event: () => { const input = document.createElement("input"); input.type = "file"; @@ -871,6 +853,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { input.click(); } }); + 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: "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.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; + }, + icon: !this.props.Document.useClusters ? "braille" : "braille" + }); + 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: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); + layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); + ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0c577af86..6c944c6b2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,6 +40,7 @@ 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); @@ -294,8 +295,8 @@ 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. - e.stopPropagation(); 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 }); e.preventDefault(); return; @@ -303,6 +304,7 @@ export class DocumentView extends DocComponent(Docu 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); @@ -314,10 +316,13 @@ export class DocumentView extends DocComponent(Docu 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; + } + e.stopPropagation(); SelectionManager.SelectDoc(this, e.ctrlKey); let isExpander = (e.target as any).id === "isExpander"; - if (BoolCast(this.props.Document.isButton) || isExpander) { - SelectionManager.DeselectAll(); + 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); @@ -328,6 +333,7 @@ export class DocumentView extends DocComponent(Docu 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) { @@ -356,6 +362,7 @@ 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)); let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; @@ -393,13 +400,15 @@ export class DocumentView extends DocComponent(Docu } if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); - // } } onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble && this.active) { + if (e.cancelBubble && this.active) { + 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)) { document.removeEventListener("pointermove", this.onPointerMove); @@ -582,7 +591,7 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); 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: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); + 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({ @@ -592,6 +601,7 @@ export class DocumentView extends DocComponent(Docu this.makeBtnClicked(); }, 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" }) !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 : []; -- cgit v1.2.3-70-g09d2 From 20f7d2dca1c115c84f6ac89981ef1e3c7c9a2757 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 22 Aug 2019 23:18:42 -0400 Subject: added start of collection script building UI --- src/client/util/DragManager.ts | 4 +- .../views/collections/CollectionViewChromes.scss | 73 +++++++++++++++++- .../views/collections/CollectionViewChromes.tsx | 87 ++++++++++++++++++++-- src/client/views/nodes/ButtonBox.tsx | 27 ++++++- src/new_fields/Doc.ts | 3 +- 5 files changed, 184 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 894b366ef..24c093213 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -140,6 +140,8 @@ export namespace DragManager { withoutShiftDrag?: boolean; + finishDrag?: (dropData: { [id: string]: any }) => void; + offsetX?: number; offsetY?: number; @@ -234,7 +236,7 @@ export namespace DragManager { export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { runInAction(() => StartDragFunctions.map(func => func())); - StartDrag(eles, dragData, downX, downY, options, + StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : (dropData: { [id: string]: any }) => { (dropData.droppedDocuments = dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) : diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index f39bd877a..64411b5fe 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -64,7 +64,7 @@ font-size: 75%; background: rgb(238, 238, 238); height: 100%; - width: 150px; + width: 75px; } .collectionViewBaseChrome-viewSpecsMenu { @@ -234,4 +234,75 @@ margin-left: 50px; } } +} + + +.commandEntry-outerDiv { + display: flex; + flex-direction: column; + width: 165px; + height: 40px; +} +.commandEntry-inputArea { + display:flex; + flex-direction: row; + width: 150px; + margin: auto 0 auto auto; +} + +.react-autosuggest__container { + position: relative; + width: 100%; + margin-left: 5px; + margin-right: 5px; +} + +.react-autosuggest__input { + border: 1px solid #aaa; + border-radius: 4px; + width: 100%; +} + +.react-autosuggest__input--focused { + outline: none; +} + +.react-autosuggest__input--open { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.react-autosuggest__suggestions-container { + display: none; +} + +.react-autosuggest__suggestions-container--open { + display: block; + position: fixed; + overflow-y: auto; + max-height: 400px; + width: 180px; + border: 1px solid #aaa; + background-color: #fff; + font-family: Helvetica, sans-serif; + font-weight: 300; + font-size: 16px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + z-index: 2; +} + +.react-autosuggest__suggestions-list { + margin: 0; + padding: 0; + list-style-type: none; +} + +.react-autosuggest__suggestion { + cursor: pointer; + padding: 10px 20px; +} + +.react-autosuggest__suggestion--highlighted { + background-color: #ddd; } \ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 74e57611d..352bcd4a6 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -8,7 +8,7 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils } from "../../../Utils"; +import { Utils, emptyFunction } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; import { undoBatch } from "../../util/UndoManager"; @@ -18,7 +18,9 @@ import { DocLike } from "../MetadataEntryMenu"; import { CollectionViewType } from "./CollectionBaseView"; import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; +import * as Autosuggest from 'react-autosuggest'; import KeyRestrictionRow from "./KeyRestrictionRow"; +import { Docs } from "../../documents/Documents"; const datepicker = require('js-datepicker'); interface CollectionViewChromeProps { @@ -43,6 +45,8 @@ export class CollectionViewBaseChrome extends React.Component(); @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); } private _picker: any; @@ -287,7 +291,12 @@ export class CollectionViewBaseChrome extends React.Component(de.data.draggedDocuments); + } e.stopPropagation(); return true; } @@ -308,6 +317,63 @@ export class CollectionViewBaseChrome extends React.Component(); + + renderSuggestion = (suggestion: string) => { + return

{suggestion}

; + } + getSuggestionValue = (suggestion: string) => suggestion; + + @action + onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { + this._currentKey = newValue; + } + onSuggestionFetch = async ({ value }: { value: string }) => { + const sugg = await this.getKeySuggestions(value); + runInAction(() => this.suggestions = sugg); + } + @action + onSuggestionClear = () => { + this.suggestions = []; + } + getKeySuggestions = async (value: string): Promise => { + return this._allCommands.filter(c => c.indexOf(value) !== -1); + } + + autoSuggestDown = (e: React.PointerEvent) => { + e.stopPropagation(); + } + dragCommandDown = (e: React.PointerEvent) => { + let de = new DragManager.DocumentDragData([this.props.CollectionView.props.Document], [undefined]); + DragManager.StartDocumentDrag([this._commandRef.current!], de, e.clientX, e.clientY, { + finishDrag: (dropData: { [id: string]: any }) => { + let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: this._currentKey }); + let script = `getProto(target).data = copyField(this.source);`; + let compiled = CompileScript(script, { + params: { doc: Doc.name }, + capturedVariables: { target: this.props.CollectionView.props.Document }, + typecheck: false, + editable: true + }); + if (compiled.compiled) { + let scriptField = new ScriptField(compiled); + bd.onClick = scriptField; + } + dropData.droppedDocuments = [bd]; + }, + handlers: { + dragComplete: action(() => { + }), + }, + hideSource: false + }); + e.preventDefault(); + e.stopPropagation(); + } + render() { let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled"; return ( @@ -337,7 +403,7 @@ export class CollectionViewBaseChrome extends React.Component
{ }} onPointerDown={this.openViewSpecs} @@ -388,8 +454,19 @@ export class CollectionViewBaseChrome extends React.Component
-
- TEMPLATE +
+
+
+ +
+
{this.subChrome()} diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 8b6f11aac..ca5f0acc2 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -15,7 +15,11 @@ import { Doc } from '../../../new_fields/Doc'; import './ButtonBox.scss'; import { observer } from 'mobx-react'; import { DocumentIconContainer } from './DocumentIcon'; -import { StrCast } from '../../../new_fields/Types'; +import { StrCast, BoolCast } from '../../../new_fields/Types'; +import { DragManager } from '../../util/DragManager'; +import { undoBatch } from '../../util/UndoManager'; +import { action, computed } from 'mobx'; +import { List } from '../../../new_fields/List'; library.add(faEdit as any); @@ -30,10 +34,29 @@ const ButtonDocument = makeInterface(ButtonSchema); @observer export class ButtonBox extends DocComponent(ButtonDocument) { public static LayoutString() { return FieldView.LayoutString(ButtonBox); } + private dropDisposer?: DragManager.DragDropDisposer; + @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); } + + + protected createDropTarget = (ele: HTMLDivElement) => { + if (this.dropDisposer) { + this.dropDisposer(); + } + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + @undoBatch + @action + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.DocumentDragData) { + Doc.GetProto(this.dataDoc).source = new List(de.data.droppedDocuments); + } + } render() { return ( -
+
{this.Document.text || this.Document.title}
); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e5b609966..f6114d476 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -626,4 +626,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); }); \ No newline at end of file +Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); +Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From e680f97f9d50ce5759d4533bae38af341956ddce Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 23 Aug 2019 11:26:43 -0400 Subject: cleanup --- src/client/views/nodes/FormattedTextBox.tsx | 5 ----- src/server/index.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 191a24664..0e04bacf7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -34,16 +34,12 @@ import "./FormattedTextBox.scss"; import React = require("react"); import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocumentDecorations } from '../DocumentDecorations'; -import { MainOverlayTextBox } from '../MainOverlayTextBox'; import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); -// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document -// - export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; export interface FormattedTextBoxProps { @@ -146,7 +142,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe paste = (e: ClipboardEvent) => { - //this is throwing a ton of errors so commented it out if (e.clipboardData && this._editorView) { // let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`; // for (let i = 0; i < e.clipboardData.items.length; i++) { diff --git a/src/server/index.ts b/src/server/index.ts index eeadef239..34a0a19f1 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport(1 as any); + let viewport = page.getViewport(1); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From f4afa471e03618169137a6533db8623aae93d6c5 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 23 Aug 2019 12:42:38 -0400 Subject: got drag drop buttons working better. --- src/client/util/DragManager.ts | 30 ++++++++- .../views/collections/CollectionViewChromes.tsx | 71 +++++++--------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 11 +++- src/client/views/nodes/ButtonBox.scss | 19 +++++- src/client/views/nodes/ButtonBox.tsx | 42 +++++++------ src/new_fields/Doc.ts | 14 ++++- 6 files changed, 111 insertions(+), 76 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 24c093213..748fb9d13 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,5 +1,5 @@ import { action, runInAction } from "mobx"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, Field } from "../../new_fields/Doc"; import { Cast, StrCast } from "../../new_fields/Types"; import { URLField } from "../../new_fields/URLField"; import { emptyFunction } from "../../Utils"; @@ -9,6 +9,11 @@ import { DocumentManager } from "./DocumentManager"; import { LinkManager } from "./LinkManager"; import { SelectionManager } from "./SelectionManager"; import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; +import { Docs } from "../documents/Documents"; +import { CompileScript } from "./Scripting"; +import { ScriptField } from "../../new_fields/ScriptField"; +import { List } from "../../new_fields/List"; +import { PrefetchProxy } from "../../new_fields/Proxy"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag( @@ -247,6 +252,27 @@ export namespace DragManager { }); } + export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], downX: number, downY: number, options?: DragOptions) { + let dragData = new DragManager.DocumentDragData([], [undefined]); + runInAction(() => StartDragFunctions.map(func => func())); + StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : + (dropData: { [id: string]: any }) => { + let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title }); + let compiled = CompileScript(script, { + params: { doc: Doc.name }, + typecheck: false, + editable: true + }); + if (compiled.compiled) { + let scriptField = new ScriptField(compiled); + bd.onClick = scriptField; + } + params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); + bd.buttonParams = new List(params); + dropData.droppedDocuments = [bd]; + }); + } + export function StartLinkedDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { dragData.moveDocument = moveLinkedDocument; @@ -482,7 +508,7 @@ export namespace DragManager { // if (parent && dragEle) parent.appendChild(dragEle); }); if (target) { - if (finishDrag) finishDrag(dragData); + finishDrag && finishDrag(dragData); target.dispatchEvent( new CustomEvent("dashOnDrop", { diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 352bcd4a6..15bef39b3 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -286,20 +286,22 @@ export class CollectionViewBaseChrome extends React.Component await p));", params: ["target", "source"], // bcz: doesn't look like we can do async stuff in scripting... + title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"], + immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List(draggedDocs) + }, + { + title: "set template", script: "this.target.childLayout = this.source ? this.source[0] : undefined", params: ["target", "source"], + immediate: (draggedDocs: Doc[]) => this.props.CollectionView.props.Document.childLayout = draggedDocs.length ? draggedDocs[0] : undefined + }]; @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { - if (de.data instanceof DragManager.DocumentDragData) { - if (de.data.draggedDocuments.length) { - if (this._currentKey === "Set Template") { - this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0]; - } - if (this._currentKey === "Set Content") { - Doc.GetProto(this.props.CollectionView.props.Document).data = new List(de.data.draggedDocuments); - } - e.stopPropagation(); - return true; - } + if (de.data instanceof DragManager.DocumentDragData && de.data.draggedDocuments.length) { + this.commands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.data.draggedDocuments)); + e.stopPropagation(); } return true; } @@ -317,9 +319,7 @@ export class CollectionViewBaseChrome extends React.Component(); renderSuggestion = (suggestion: string) => { @@ -340,38 +340,19 @@ export class CollectionViewBaseChrome extends React.Component => { - return this._allCommands.filter(c => c.indexOf(value) !== -1); + return this.commands.filter(c => c.title.indexOf(value) !== -1).map(c => c.title); } autoSuggestDown = (e: React.PointerEvent) => { e.stopPropagation(); } + dragCommandDown = (e: React.PointerEvent) => { - let de = new DragManager.DocumentDragData([this.props.CollectionView.props.Document], [undefined]); - DragManager.StartDocumentDrag([this._commandRef.current!], de, e.clientX, e.clientY, { - finishDrag: (dropData: { [id: string]: any }) => { - let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: this._currentKey }); - let script = `getProto(target).data = copyField(this.source);`; - let compiled = CompileScript(script, { - params: { doc: Doc.name }, - capturedVariables: { target: this.props.CollectionView.props.Document }, - typecheck: false, - editable: true - }); - if (compiled.compiled) { - let scriptField = new ScriptField(compiled); - bd.onClick = scriptField; - } - dropData.droppedDocuments = [bd]; - }, - handlers: { - dragComplete: action(() => { - }), - }, - hideSource: false - }); - e.preventDefault(); + this.commands.filter(c => c.title === this._currentKey).map(c => + DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, + { target: this.props.CollectionView.props.Document }, c.params, e.clientX, e.clientY)); e.stopPropagation(); + e.preventDefault(); } render() { @@ -419,7 +400,7 @@ export class CollectionViewBaseChrome extends React.Component
CREATED WITHIN: -
+
+ Open Self +
+ {[...this._docs, ...this._otherDocs].map(doc => { + if (doc && doc.target) { + return

; + } + })} +
+ ); + } + + @computed + get availableOptions() { return (
@@ -299,13 +417,13 @@ export class LinkFollowBox extends React.Component {
Context
- + {this.selectedMode !== "" ? this.availableContexts : "Please select a mode to view contexts"}
Options
- + {this.selectedContextString !== "" ? this.availableOptions : "Please select a context to view options"}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 095b0094e..29eef27a0 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -24,7 +24,6 @@ import { SelectionManager } from "../../util/SelectionManager"; import { Docs } from "../../documents/Documents"; library.add(faStickyNote); -library.add(faStickyNote) @observer export class WebBox extends React.Component { diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 41fc49c2e..567bc6c97 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -63,6 +63,7 @@ export class SelectorContextMenu extends React.Component { runInAction(() => { this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); + }); } -- cgit v1.2.3-70-g09d2 From d237c67b236ce66e9bc711931e4c283a8c1e95f2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 23 Aug 2019 18:46:35 -0400 Subject: linter --- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 876d4c428..d6ba1700a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -259,7 +259,7 @@ 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) != "") { + if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") { this.props.Document.layout = draggedDoc; draggedDoc.isTemplate = true; e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From 094bbd52f2b55f501357a9f6b057042ad7684f27 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 26 Aug 2019 16:58:24 -0400 Subject: playing more with bulleted lists. --- src/client/util/ProsemirrorExampleTransfer.ts | 70 +++++++++++++++++++++------ src/client/util/RichTextRules.ts | 3 +- src/client/util/RichTextSchema.tsx | 10 +++- src/client/util/TooltipTextMenu.tsx | 10 +++- src/client/views/nodes/FormattedTextBox.tsx | 12 ++--- 5 files changed, 78 insertions(+), 27 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index b928532d6..3cdfba59a 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -2,8 +2,8 @@ import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setB import { redo, undo } from "prosemirror-history"; import { undoInputRule } from "prosemirror-inputrules"; import { Schema } from "prosemirror-model"; -import { liftListItem, splitListItem, wrapInList } from "prosemirror-schema-list"; -import { EditorState, Transaction } from "prosemirror-state"; +import { liftListItem, splitListItem, wrapInList, sinkListItem } from "prosemirror-schema-list"; +import { EditorState, Transaction, TextSelection, NodeSelection } from "prosemirror-state"; import { TooltipTextMenu } from "./TooltipTextMenu"; const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; @@ -79,32 +79,72 @@ export default function buildKeymap>(schema: S, mapKeys?: bind("Mod-s", TooltipTextMenu.insertStar); - bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - marks && tx2.ensureMarks(marks); - marks && tx2.setStoredMarks(marks); - dispatch(tx2); - }); - }); + let levelMark = (depth: number) => { + let p10 = schema.marks.pFontSize.create({ fontSize: 10 }); + let p14 = schema.marks.pFontSize.create({ fontSize: 14 }); + let p18 = schema.marks.pFontSize.create({ fontSize: 18 }); + let p24 = schema.marks.pFontSize.create({ fontSize: 24 }); + return depth == 0 ? p24 : depth == 2 ? p18 : depth == 4 ? p14 : p10; + } let bulletFunc = (state: EditorState, dispatch: (tx: Transaction) => void) => { var ref = state.selection; var range = ref.$from.blockRange(ref.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); let depth = range && range.depth ? range.depth : 0; let nodeType = depth == 2 ? schema.nodes.cap_alphabet_list : depth == 4 ? schema.nodes.roman_list : depth == 6 ? schema.nodes.alphabet_list : schema.nodes.ordered_list; + let nodeTypeMark = depth == 2 ? schema.marks.mcap_alphabet_list : depth == 4 ? schema.marks.mroman_list : depth == 6 ? schema.marks.malphabet_list : schema.marks.mordered_list; + let created = levelMark(depth); + if (!sinkListItem(nodeType /*schema.nodes.list_item */)(state, (tx2: Transaction) => { + const resolvedPos = tx2.doc.resolve(range!.start); + + let ns = new NodeSelection(resolvedPos); + let tx3 = tx2.addMark(ns.from - 1, ns.to, created).addMark(ns.from - 1, ns.to, nodeTypeMark as any).setSelection(TextSelection.create(tx2.doc, ns.to - (depth == 0 ? 3 : 1))); + marks && tx3.ensureMarks([...marks, created]); + marks && tx3.setStoredMarks([...marks, created]); - if (!wrapInList(nodeType)(state, (tx2: Transaction) => { - marks && tx2.ensureMarks(marks); - marks && tx2.setStoredMarks(marks); - dispatch(tx2); + dispatch(tx3); })) { - console.log("bullet fail"); + let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)); + let newstate = state.applyTransaction(sxf); + if (!wrapInList(nodeType)(newstate.state, (tx2: Transaction) => { + const resolvedPos = tx2.doc.resolve(range!.start); + let ns = new TextSelection(resolvedPos, tx2.doc.resolve(range!.end + 1)); // new NodeSelection(resolvedPos); + let tx3 = tx2.setSelection(ns).removeMark(ns.from, ns.to, created).addMark(ns.from, ns.to, created).setSelection(TextSelection.create(tx2.doc, ns.to)); + let tx4 = depth > 0 ? tx3.insertText(" ").setSelection(TextSelection.create(tx2.doc, ns.to - 2, ns.to + 2)).deleteSelection() : tx3; + marks && tx4.ensureMarks([...marks, created]); + marks && tx4.setStoredMarks([...marks, created]); + + dispatch(tx4); + })) { + console.log("bullet fail"); + } } } bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => bulletFunc(state, dispatch)); + bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + var ref = state.selection; + var range = ref.$from.blockRange(ref.$to); + var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + let created = levelMark(range && range.depth ? range.depth - 4 : 0); + liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { + try { + const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2)); + let nodeIndex = resolvedPos.pos - (resolvedPos.nodeBefore && resolvedPos.nodeBefore.type.name === "text" ? resolvedPos.nodeBefore!.nodeSize : 0); + let ns = new NodeSelection(tx2.doc.resolve(nodeIndex)); + if (resolvedPos.nodeAfter && resolvedPos.nodeAfter.type.name === "list_item") + ns = new NodeSelection(tx2.doc.resolve(nodeIndex + 1)); + let tx3 = tx2.setSelection(ns).removeMark(ns.from, ns.to, created).addMark(ns.from, ns.to, created).setSelection(TextSelection.create(tx2.doc, ns.to)); + marks && tx3.ensureMarks([...marks, created]); + marks && tx3.setStoredMarks([...marks, created]); + dispatch(tx3); + } catch (e) { + dispatch(tx2); + } + }); + }); + 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) => { diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 89933650b..8c4c76027 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -26,7 +26,7 @@ export const inpRules = { match => ({ order: +match[1] }), (match, node) => node.childCount + node.attrs.order === +match[1] ), - // 1. ordered list + // a. alphabbetical list wrappingInputRule( /^([a-z]+)\.\s$/, schema.nodes.alphabet_list, @@ -34,7 +34,6 @@ export const inpRules = { (match, node) => node.childCount + node.attrs.order === +match[1] ), - // * bullet list wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list), diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a8ce4731c..f128162c2 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -319,6 +319,15 @@ export const marks: { [index: string]: MarkSpec } = { toDOM: () => ['sup'] }, + malphabet_list: { + }, + mcap_alphabet_list: { + }, + mroman_list: { + }, + mo_list: { + }, + highlight: { parseDOM: [{ style: 'color: blue' }], toDOM() { @@ -412,7 +421,6 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { fontSize: { default: 10 } }, - inclusive: false, parseDOM: [{ style: 'font-size: 10px;' }], toDOM: (node) => ['span', { style: `font-size: ${node.attrs.fontSize}px;` diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 7a0c6f8c5..77396c829 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -165,7 +165,15 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p48, 48); this.fontSizeToNum.set(schema.marks.p72, 72); this.fontSizeToNum.set(schema.marks.pFontSize, 10); - this.fontSizeToNum.set(schema.marks.pFontSize, 10); + // this.fontSizeToNum.set(schema.marks.pFontSize, 12); + // this.fontSizeToNum.set(schema.marks.pFontSize, 14); + // this.fontSizeToNum.set(schema.marks.pFontSize, 16); + // this.fontSizeToNum.set(schema.marks.pFontSize, 18); + // this.fontSizeToNum.set(schema.marks.pFontSize, 20); + // this.fontSizeToNum.set(schema.marks.pFontSize, 24); + // this.fontSizeToNum.set(schema.marks.pFontSize, 32); + // this.fontSizeToNum.set(schema.marks.pFontSize, 48); + // this.fontSizeToNum.set(schema.marks.pFontSize, 72); this.fontSizes = Array.from(this.fontSizeToNum.keys()); //list types diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d6ba1700a..acfd2a3b8 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -115,7 +115,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch public setFontColor(color: string) { - let self = this; 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; @@ -176,15 +175,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true); this._editorView.updateState(state); FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false); - if (state.selection.empty && FormattedTextBox._toolTipTextMenu) { - const marks = tx.storedMarks; - if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); } + if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { + FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } this._applyingChange = true; - const fieldkey = "preview"; - if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"); - if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now())); + this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); + this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); this._applyingChange = false; let title = StrCast(this.dataDoc.title); @@ -198,7 +195,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe public highlightSearchTerms = (terms: String[]) => { if (this._editorView && (this._editorView as any).docView) { - const fieldkey = "preview"; const doc = this._editorView.state.doc; const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => { -- cgit v1.2.3-70-g09d2 From 32c0388d9334ce1f0be04962e00ba3d389c50303 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 26 Aug 2019 21:36:29 -0400 Subject: testing things --- src/client/util/RichTextSchema.tsx | 13 ++++++++++++- src/client/views/nodes/FormattedTextBox.scss | 9 +++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 6e3d9ab77..b5d81a359 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -243,7 +243,18 @@ export const nodes: { [index: string]: NodeSpec } = { // }, list_item: { ...listItem, - content: 'paragraph block*' + content: 'paragraph block*', + toDOM(node: any) { + let first = node.firstChild; + while (first) { + if (first.marks.find((m: any) => m.type === schema.marks.mbulletType)) { + let x = first.marks.find((m: any) => m.type === schema.marks.mbulletType); + return ["li", { class: "XXX" }, 0]; + } + first = first.firstChild; + } + return ["li", 0]; + } }, }; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 1b537cc52..9d5dc76d3 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -65,4 +65,13 @@ .em { font-style: italic; +} + +ol { counter-reset: item } +.XXX:before { + content: counters(item, ".") " "; + counter-increment: item ; +} +p { + display:inline; } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 674cbf8d796351e607edd93ef520d662893c13b0 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 26 Aug 2019 22:41:41 -0400 Subject: working better --- src/client/util/ProsemirrorExampleTransfer.ts | 23 +++++++++++++---------- src/client/views/nodes/FormattedTextBox.scss | 9 --------- 2 files changed, 13 insertions(+), 19 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 78b992ac8..8bec2015e 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -99,10 +99,9 @@ export default function buildKeymap>(schema: S, mapKeys?: const resolvedPos = tx2.doc.resolve(range!.start); let ns = new NodeSelection(resolvedPos); - let tx3 = tx2.removeMark(ns.from - 1, ns.to, created).removeMark(ns.from - 1, ns.to, nodeTypeMark as any).addMark(ns.from - 1, ns.to, created).addMark(ns.from - 1, ns.to, nodeTypeMark as any).setSelection(TextSelection.create(tx2.doc, ns.to - (depth == 0 ? 3 : 1))); - marks && tx3.ensureMarks([...marks, created]); - marks && tx3.setStoredMarks([...marks, created]); - + let tx3 = tx2.removeMark(ns.from - 1, ns.to, created).removeMark(ns.from - 1, ns.to, nodeTypeMark as any).addMark(ns.from - 1, ns.to, created).addMark(ns.from - 1, ns.to, nodeTypeMark).setSelection(TextSelection.create(tx2.doc, ns.to - (depth == 0 ? 3 : 1))); + marks && tx3.ensureMarks([...marks.filter(m => m.type !== schema.marks.mbulletType && m.type !== schema.marks.pFontSize), created, nodeTypeMark]); + marks && tx3.setStoredMarks([...marks.filter(m => m.type !== schema.marks.mbulletType && m.type !== schema.marks.pFontSize), created, nodeTypeMark]); dispatch(tx3); })) { let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)); @@ -110,10 +109,10 @@ export default function buildKeymap>(schema: S, mapKeys?: if (!wrapInList(nodeType)(newstate.state, (tx2: Transaction) => { const resolvedPos = tx2.doc.resolve(range!.start); let ns = new TextSelection(resolvedPos, tx2.doc.resolve(range!.end + 1)); // new NodeSelection(resolvedPos); - let tx3 = tx2.setSelection(ns).removeMark(ns.from, ns.to, created).removeMark(ns.from, ns.to, nodeTypeMark as any).addMark(ns.from - 1, ns.to, nodeTypeMark as any).addMark(ns.from, ns.to, created).setSelection(TextSelection.create(tx2.doc, ns.to)); + let tx3 = tx2.setSelection(ns).removeMark(ns.from, ns.to, created).removeMark(ns.from, ns.to, nodeTypeMark as any).addMark(ns.from, ns.to, nodeTypeMark as any).addMark(ns.from, ns.to, created).setSelection(TextSelection.create(tx2.doc, ns.to)); let tx4 = depth > 0 ? tx3.insertText(" ").setSelection(TextSelection.create(tx2.doc, ns.to - 2, ns.to + 2)).deleteSelection() : tx3; - marks && tx4.ensureMarks([...marks, created]); - marks && tx4.setStoredMarks([...marks, created]); + marks && tx4.ensureMarks([...marks.filter(m => m.type !== schema.marks.mbulletType && m.type !== schema.marks.pFontSize), created, nodeTypeMark]); + marks && tx4.setStoredMarks([...marks.filter(m => m.type !== schema.marks.mbulletType && m.type !== schema.marks.pFontSize), created, nodeTypeMark]); dispatch(tx4); })) { @@ -127,6 +126,8 @@ export default function buildKeymap>(schema: S, mapKeys?: var ref = state.selection; var range = ref.$from.blockRange(ref.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + let depth = range && range.depth > 3 ? range.depth - 4 : 0; + let nodeTypeMark = schema.marks.mbulletType.create({ bulletType: depth == 2 ? "upper-alpha" : depth == 4 ? "lower-roman" : depth == 6 ? "lower-alpha" : "decimal" }); let created = levelMark(range && range.depth ? range.depth - 4 : 0); liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { try { @@ -135,9 +136,11 @@ export default function buildKeymap>(schema: S, mapKeys?: let ns = new NodeSelection(tx2.doc.resolve(nodeIndex)); if (resolvedPos.nodeAfter && resolvedPos.nodeAfter.type.name === "list_item") ns = new NodeSelection(tx2.doc.resolve(nodeIndex + 1)); - let tx3 = tx2.setSelection(ns).removeMark(ns.from, ns.to, created).addMark(ns.from, ns.to, created).setSelection(TextSelection.create(tx2.doc, ns.to)); - marks && tx3.ensureMarks([...marks, created]); - marks && tx3.setStoredMarks([...marks, created]); + let tx3 = tx2.setSelection(ns).removeMark(ns.from - 1, ns.to, created).addMark(ns.from - 1, ns.to, created) + .removeMark(ns.from - 1, ns.to, nodeTypeMark).addMark(ns.from - 1, ns.to, nodeTypeMark).setSelection(TextSelection.create(tx2.doc, ns.to)); + + marks && tx3.ensureMarks([...marks.filter(m => m.type !== schema.marks.mbulletType && m.type !== schema.marks.pFontSize), created, nodeTypeMark]); + marks && tx3.setStoredMarks([...marks.filter(m => m.type !== schema.marks.mbulletType && m.type !== schema.marks.pFontSize), created, nodeTypeMark]); dispatch(tx3); } catch (e) { dispatch(tx2); diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 9d5dc76d3..1b537cc52 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -65,13 +65,4 @@ .em { font-style: italic; -} - -ol { counter-reset: item } -.XXX:before { - content: counters(item, ".") " "; - counter-increment: item ; -} -p { - display:inline; } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 074961d68788d2d19b6bfbcc9b95712a7b3940eb Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 27 Aug 2019 09:13:07 -0400 Subject: not quite working... --- src/client/util/RichTextSchema.tsx | 9 +++++++-- src/client/views/nodes/FormattedTextBox.scss | 13 ++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 255f4a60d..655edb68a 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -178,8 +178,10 @@ export const nodes: { [index: string]: NodeSpec } = { bulletStyle: { default: "decimal" }, }, toDOM(node: Node) { + (node.content as any).content.map((x: any) => x.type.attrs.className = node.attrs.bulletStyle); let fsize = node.attrs.bulletStyle === "decimal" ? 24 : node.attrs.bulletStyle === "upper-alpha" ? 18 : node.attrs.bulletStyle === "lower-roman" ? 14 : 10; - return ['ol', { style: `list-style: ${node.attrs.bulletStyle}; font-size: ${fsize}` }, 0] + return ['ol', { class: `${node.attrs.bulletStyle}-ol`, style: `list-style: none; font-size: ${fsize}` }, 0] + //return ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: ${fsize}` }, 0] } }, //this doesn't currently work for some reason @@ -202,6 +204,9 @@ export const nodes: { [index: string]: NodeSpec } = { //select: state => true, // }, list_item: { + attrs: { + className: { default: "" } + }, ...listItem, content: 'paragraph block*', toDOM(node: any) { @@ -213,7 +218,7 @@ export const nodes: { [index: string]: NodeSpec } = { } first = first.firstChild; } - return ["li", 0]; + return ["li", { class: node.type.attrs.className }, 0]; } }, }; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 1b537cc52..1e429e4be 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -65,4 +65,15 @@ .em { font-style: italic; -} \ No newline at end of file +} + + +ol { counter-reset: deci 0;} +.decimal-ol {counter-reset: deci 0;} +.upper-alpha-ol {counter-reset: ualph; } +.lower-roman-ol {counter-reset: lroman; } +.lower-alpha-ol {counter-reset: lalpha; } +.decimal:before { content: counter(deci) " "; counter-increment: deci } +.upper-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) " "; counter-increment: ualph } +.lower-roman:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) " "; counter-increment: lroman } +.lower-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha)" "; counter-increment: lalpha } -- cgit v1.2.3-70-g09d2 From d31999dd3fce11a886bd402c27f34c35c7c85935 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 27 Aug 2019 10:16:21 -0400 Subject: mostly working. some glitches though. --- src/client/util/ProsemirrorExampleTransfer.ts | 9 +++++---- src/client/util/RichTextSchema.tsx | 17 ++++------------- src/client/views/nodes/FormattedTextBox.scss | 23 ++++++++++++++--------- 3 files changed, 23 insertions(+), 26 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index d602ce4a1..4ca19eff1 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -79,19 +79,21 @@ export default function buildKeymap>(schema: S, mapKeys?: bind("Mod-s", TooltipTextMenu.insertStar); + // let nodeTypeMark = depth == 2 ? "upper-alpha" : depth == 4 ? "lower-roman" : depth == 6 ? "lower-alpha" : "decimal"; + let nodeTypeMark = (depth: number) => { return depth == 2 ? "decimal2" : depth == 4 ? "decimal3" : depth == 6 ? "decimal4" : "decimal" } + let bulletFunc = (state: EditorState, dispatch: (tx: Transaction) => void) => { var ref = state.selection; var range = ref.$from.blockRange(ref.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); let depth = range && range.depth ? range.depth : 0; - let nodeTypeMark = depth == 2 ? "upper-alpha" : depth == 4 ? "lower-roman" : depth == 6 ? "lower-alpha" : "decimal"; if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { const resolvedPos = tx2.doc.resolve(range!.start); let path = (resolvedPos as any).path as any; for (let i = path.length - 1; i > 0; i--) { if (path[i].type === schema.nodes.ordered_list) { - path[i].attrs.bulletStyle = nodeTypeMark; + path[i].attrs.bulletStyle = nodeTypeMark(depth); break; } } @@ -118,7 +120,6 @@ export default function buildKeymap>(schema: S, mapKeys?: var range = ref.$from.blockRange(ref.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); let depth = range && range.depth > 3 ? range.depth - 4 : 0; - let nodeTypeMark = depth == 2 ? "upper-alpha" : depth == 4 ? "lower-roman" : depth == 6 ? "lower-alpha" : "decimal"; liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { try { const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2)); @@ -126,7 +127,7 @@ export default function buildKeymap>(schema: S, mapKeys?: let path = (resolvedPos as any).path as any; for (let i = path.length - 1; i > 0; i--) { if (path[i].type === schema.nodes.ordered_list) { - path[i].attrs.bulletStyle = nodeTypeMark; + path[i].attrs.bulletStyle = nodeTypeMark(depth); break; } } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 655edb68a..2df49d8a4 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -178,10 +178,9 @@ export const nodes: { [index: string]: NodeSpec } = { bulletStyle: { default: "decimal" }, }, toDOM(node: Node) { - (node.content as any).content.map((x: any) => x.type.attrs.className = node.attrs.bulletStyle); - let fsize = node.attrs.bulletStyle === "decimal" ? 24 : node.attrs.bulletStyle === "upper-alpha" ? 18 : node.attrs.bulletStyle === "lower-roman" ? 14 : 10; - return ['ol', { class: `${node.attrs.bulletStyle}-ol`, style: `list-style: none; font-size: ${fsize}` }, 0] - //return ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: ${fsize}` }, 0] + for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = node.attrs.bulletStyle; + return ['ol', { class: `${node.attrs.bulletStyle}-ol`, style: `list-style: none;` }, 0] + //return ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle};`, 0] } }, //this doesn't currently work for some reason @@ -210,15 +209,7 @@ export const nodes: { [index: string]: NodeSpec } = { ...listItem, content: 'paragraph block*', toDOM(node: any) { - let first = node.firstChild; - while (first) { - if (first.marks.find((m: any) => m.type === schema.marks.mbulletType)) { - let x = first.marks.find((m: any) => m.type === schema.marks.mbulletType); - return ["li", { class: "XXX" }, 0]; - } - first = first.firstChild; - } - return ["li", { class: node.type.attrs.className }, 0]; + return ["li", { class: node.attrs.className }, 0]; } }, }; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 1e429e4be..e93ceda21 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -67,13 +67,18 @@ font-style: italic; } - ol { counter-reset: deci 0;} -.decimal-ol {counter-reset: deci 0;} -.upper-alpha-ol {counter-reset: ualph; } -.lower-roman-ol {counter-reset: lroman; } -.lower-alpha-ol {counter-reset: lalpha; } -.decimal:before { content: counter(deci) " "; counter-increment: deci } -.upper-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) " "; counter-increment: ualph } -.lower-roman:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) " "; counter-increment: lroman } -.lower-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha)" "; counter-increment: lalpha } +.decimal-ol { counter-reset: deci 0; p { display: inline }; font-size: 24 } +.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 } +.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 } +.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 } +.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 } +.lower-roman-ol {counter-reset: lroman; p { display: inline }; font-size: 14; } +.lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10;} +.decimal:before { content: counter(deci) " "; counter-increment: deci; display:inline-block; width: 30} +.decimal2:before { content: counter(deci) "." counter(deci2) " "; counter-increment: deci2; display:inline-block; width: 35} +.decimal3:before { content: counter(deci) "." counter(deci2) "." counter(deci3) " "; counter-increment: deci3; display:inline-block; width: 35} +.decimal4:before { content: counter(deci) "." counter(deci2) "." counter(deci3) "." counter(deci4) " "; counter-increment: deci4; display:inline-block; width: 40} +.upper-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) " "; counter-increment: ualph; display:inline-block; width: 35 } +.lower-roman:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) " "; counter-increment: lroman;display:inline-block; width: 50 } +.lower-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha)" "; counter-increment: lalpha; display:inline-block; width: 35} -- cgit v1.2.3-70-g09d2 From 34e0b1cf34474ec6765e212b6a35defefbfb49c9 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 27 Aug 2019 15:46:03 -0400 Subject: removed selectOnLoad prop. fixed textboxes from deselecting on carriage return. --- src/client/views/MainOverlayTextBox.tsx | 2 +- src/client/views/MainView.tsx | 2 -- src/client/views/OverlayView.tsx | 1 - src/client/views/collections/CollectionDockingView.tsx | 1 - src/client/views/collections/CollectionSchemaCells.tsx | 1 - src/client/views/collections/CollectionSchemaView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 14 +++----------- src/client/views/nodes/DocumentView.tsx | 4 +--- src/client/views/nodes/FieldView.tsx | 2 -- src/client/views/nodes/FormattedTextBox.tsx | 9 ++++++--- src/client/views/nodes/KeyValuePair.tsx | 1 - src/client/views/presentationview/PresentationElement.tsx | 1 - src/client/views/search/SearchItem.tsx | 1 - 13 files changed, 11 insertions(+), 29 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 65a291d99..f15b60347 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -156,7 +156,7 @@ export class MainOverlayTextBox extends React.Component DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} onClick={undefined} ChromeHeight={this.ChromeHeight} - isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true} + isSelected={returnTrue} select={emptyFunction} renderDepth={0} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} /> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a02214deb..6b856443b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -326,7 +326,6 @@ export class MainView extends React.Component { PanelHeight={this.getPHeight} renderDepth={0} backgroundColor={returnEmptyString} - selectOnLoad={false} focus={emptyFunction} parentActive={returnTrue} whenActiveChanged={emptyFunction} @@ -389,7 +388,6 @@ export class MainView extends React.Component { PanelWidth={this.flyoutWidthFunc} PanelHeight={this.getPHeight} renderDepth={0} - selectOnLoad={false} focus={emptyFunction} backgroundColor={returnEmptyString} parentActive={returnTrue} diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index a60dc591c..538614089 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -153,7 +153,6 @@ export class OverlayView extends React.Component { PanelHeight={returnOne} ScreenToLocalTransform={Transform.Identity} renderDepth={1} - selectOnLoad={false} parentActive={returnTrue} whenActiveChanged={emptyFunction} focus={emptyFunction} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index dc0cbda0b..95f94875c 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -636,7 +636,6 @@ export class DockedFrameRenderer extends React.Component { PanelHeight={this.panelHeight} ScreenToLocalTransform={this.ScreenToLocalTransform} renderDepth={0} - selectOnLoad={false} parentActive={returnTrue} whenActiveChanged={emptyFunction} focus={emptyFunction} diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 551b485e7..9c26a08f0 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -153,7 +153,6 @@ export class CollectionSchemaCell extends React.Component { isSelected: returnFalse, select: emptyFunction, renderDepth: this.props.renderDepth + 1, - selectOnLoad: false, ScreenToLocalTransform: Transform.Identity, focus: emptyFunction, active: returnFalse, diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 4008dea51..9d83aa6c1 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -1006,7 +1006,6 @@ export class CollectionSchemaPreview extends React.Component this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { - this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed + 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 this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { @@ -642,7 +640,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { onClick: this.props.onClick, ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, - selectOnLoad: childLayout[Id] === this._selectOnLoaded, PanelWidth: childLayout[WidthSym], PanelHeight: childLayout[HeightSym], ContentScaling: returnOne, @@ -668,7 +665,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { onClick: this.props.onClick, ScreenToLocalTransform: this.getTransform, renderDepth: this.props.renderDepth, - selectOnLoad: layoutDoc[Id] === this._selectOnLoaded, PanelWidth: layoutDoc[WidthSym], PanelHeight: layoutDoc[HeightSym], ContentScaling: returnOne, @@ -768,13 +764,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return prev; }, elements); - this.resetSelectOnLoaded(); - return docviews; } - resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way .... - @computed.struct get views() { let source = this.elements; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3cf86b6f9..f7ebfb75a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -92,7 +92,6 @@ export interface DocumentViewProps { PanelWidth: () => number; PanelHeight: () => number; focus: (doc: Doc, willZoom: boolean, scale?: number) => void; - selectOnLoad: boolean; parentActive: () => boolean; whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; @@ -717,7 +716,6 @@ export class DocumentView extends DocComponent(Docu isSelected={this.isSelected} select={this.select} onClick={this.onClickHandler} - selectOnLoad={this.props.selectOnLoad} layoutKey={"layout"} fitToBox={BoolCast(this.props.Document.fitToBox) ? true : this.props.fitToBox} DataDoc={this.dataDoc} />); @@ -808,7 +806,7 @@ export class DocumentView extends DocComponent(Docu } {!showCaption ? (null) :
- +
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index f0f1b3b73..d9774303b 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -36,7 +36,6 @@ export interface FieldViewProps { isSelected: () => boolean; select: (isCtrlPressed: boolean) => void; renderDepth: number; - selectOnLoad: boolean; addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; pinToPres: (document: Doc) => void; @@ -108,7 +107,6 @@ export class FieldView extends React.Component { // PanelWidth={returnHundred} // PanelHeight={returnHundred} // renderDepth={0} //TODO Why is renderDepth reset? - // selectOnLoad={false} // focus={emptyFunction} // isSelected={this.props.isSelected} // select={returnFalse} diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index acfd2a3b8..c28bf1821 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -622,12 +622,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (this.props.selectOnLoad) { - if (!this.props.isOverlay) this.props.select(false); - else this._editorView!.focus(); + if (this.props.Document[Id] == FormattedTextBox.SelectOnLoad) { + FormattedTextBox.SelectOnLoad = ""; + this.props.select(false); } + else if (this.props.isOverlay) this._editorView!.focus(); } + public static SelectOnLoad = ""; + componentWillUnmount() { this._editorView && this._editorView.destroy(); this._reactionDisposer && this._reactionDisposer(); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 8001b24a7..5afd4d834 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -60,7 +60,6 @@ export class KeyValuePair extends React.Component { isSelected: returnFalse, select: emptyFunction, renderDepth: 1, - selectOnLoad: false, active: returnFalse, whenActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 83413814f..80aa25f48 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -359,7 +359,6 @@ export default class PresentationElement extends React.Component 90} focus={emptyFunction} backgroundColor={returnEmptyString} - selectOnLoad={false} parentActive={returnFalse} whenActiveChanged={returnFalse} bringToFront={emptyFunction} diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 41fc49c2e..672892fdf 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -209,7 +209,6 @@ export class SearchItem extends React.Component { PanelHeight={returnYDimension} focus={emptyFunction} backgroundColor={returnEmptyString} - selectOnLoad={false} parentActive={returnFalse} whenActiveChanged={returnFalse} bringToFront={emptyFunction} -- cgit v1.2.3-70-g09d2 From 656a08a130124870c9f652f7b1529b2b496cdac7 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 27 Aug 2019 15:52:25 -0400 Subject: lint fixes. --- src/client/views/PreviewCursor.tsx | 2 +- src/client/views/collections/CollectionViewChromes.tsx | 2 ++ src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index d8e161ab6..1329dc02c 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -51,7 +51,7 @@ export class PreviewCursor extends React.Component<{}> { // tests for URL and makes web document let re: any = /^https?:\/\//g; if (re.test(e.clipboardData.getData("text/plain"))) { - const url = e.clipboardData.getData("text/plain") + const url = e.clipboardData.getData("text/plain"); PreviewCursor._addDocument(Docs.Create.WebDocument(url, { title: url, width: 300, height: 300, // nativeWidth: 300, nativeHeight: 472.5, diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index b2df0e747..4b3f7c87e 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -42,11 +42,13 @@ export class CollectionViewBaseChrome extends React.Component this.props.CollectionView.props.Document.childLayout = draggedDocs.length ? draggedDocs[0] : undefined }; _contentCommand = { // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"], // bcz: doesn't look like we can do async stuff in scripting... title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"], + initialize: emptyFunction, immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List(draggedDocs.map((d: any) => Doc.MakeAlias(d))) }; _viewCommand = { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f7ebfb75a..4d5307c88 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -600,7 +600,7 @@ export class DocumentView extends DocComponent(Docu this.makeBtnClicked(); }, 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: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.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 : []; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c28bf1821..49189c5f3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -622,7 +622,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (this.props.Document[Id] == FormattedTextBox.SelectOnLoad) { + if (this.props.Document[Id] === FormattedTextBox.SelectOnLoad) { FormattedTextBox.SelectOnLoad = ""; this.props.select(false); } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f0140d04b..642f58daf 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -18,7 +18,7 @@ import { Docs } from "../../documents/Documents"; import { faStickyNote } from "@fortawesome/free-solid-svg-icons"; import { library } from "@fortawesome/fontawesome-svg-core"; -library.add(faStickyNote) +library.add(faStickyNote); @observer export class WebBox extends React.Component { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index e3bfea237..e5917fefc 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -163,7 +163,7 @@ export class PDFViewer extends React.Component {
"PAGE IS LOADING... " -
) +
); this.getPlaceholderPage(i); })))); this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages); -- cgit v1.2.3-70-g09d2 From 5da0459ad76c614e455ea99798c940d4e93707bb Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 27 Aug 2019 17:29:58 -0400 Subject: multi-user marks. --- src/client/util/RichTextSchema.tsx | 13 +++++++++++++ src/client/views/nodes/FormattedTextBox.tsx | 11 ++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index bbced3b77..76c45e6c1 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,6 +1,7 @@ import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; import { TextSelection } from "prosemirror-state"; +import { Doc } from "../../new_fields/Doc"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -324,6 +325,18 @@ export const marks: { [index: string]: MarkSpec } = { } }, + // the id of the user who entered the text + user_mark: { + attrs: { + userid: { default: "" } + }, + toDOM(node: any) { + return ['span', { + style: `background: ${node.attrs.userid.indexOf(Doc.CurrentUserEmail) === -1 ? "rgba(255, 255, 0, 0.267)" : undefined};` + }]; + } + }, + // :: MarkSpec Code font mark. Represented as a `` element. code: { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 49189c5f3..667bac3be 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; -import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model"; +import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark } from "prosemirror-model"; import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; @@ -37,6 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations'; import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -171,6 +172,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe dispatchTransaction = (tx: Transaction) => { if (this._editorView) { + var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks()); + let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })]; + tx.ensureMarks(newMarks); + tx.setStoredMarks(newMarks); const state = this._editorView.state.apply(tx); FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true); this._editorView.updateState(state); @@ -267,6 +272,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) { return; } + 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; + if (e.key === "R" && e.altKey) { e.stopPropagation(); e.preventDefault(); -- cgit v1.2.3-70-g09d2 From a7515d1e80e32fcc19096c73335f624042b85d51 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 27 Aug 2019 21:20:52 -0400 Subject: added auto-reformatting after shift-tab. --- src/client/views/MainOverlayTextBox.tsx | 10 +- src/client/views/nodes/FormattedTextBox.tsx | 144 +++++++++++++--------------- 2 files changed, 67 insertions(+), 87 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index f15b60347..755e5de14 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -50,14 +50,8 @@ export class MainOverlayTextBox extends React.Component (box?: FormattedTextBox) => { const tb = this._textBox; const container = tb && tb.props.ContainingCollectionView; - if (tb && container) { // this hacky section is needed to force the edited text box to completely recreate itself since things can get out synch -- specifically, the bullet label state which is computed when the dom elements are created - var dl = DocListCast(container.props.Document[container.props.fieldKey]); - let dli = dl.indexOf(tb.props.Document); - if (dli !== -1) { - let prev = dli > 0 ? dl[dli - 1] : undefined; - tb.props.removeDocument && tb.props.removeDocument(tb.props.Document); - setTimeout(() => Doc.AddDocToList(container.props.Document, container.props.fieldKey, tb.props.Document, prev, false, dli === 0), 0); - } + if (tb && container) { + tb.rebuildEditor();// this forces the edited text box to completely recreate itself to avoid get out of sync -- e.g., bullet labels are only computed when the dom elements are created } this._textBox = box; if (box) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 667bac3be..5cbbc3b55 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 } from '../../../Utils'; +import { Utils, numberRange } from '../../../Utils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; @@ -37,7 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations'; import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { number } from 'prop-types'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -69,25 +69,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } - public static Instance: FormattedTextBox; - private _ref: React.RefObject; + private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + private _ref: React.RefObject = React.createRef(); private _proseRef?: HTMLDivElement; private _editorView: Opt; - private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined; private _applyingChange: boolean = false; private _linkClicked = ""; + private _undoTyping?: UndoManager.Batch; private _reactionDisposer: Opt; private _searchReactionDisposer?: Lambda; private _textReactionDisposer: Opt; private _heightReactionDisposer: Opt; private _proxyReactionDisposer: Opt; - private pullReactionDisposer: Opt; - private pushReactionDisposer: Opt; + private _pullReactionDisposer: Opt; + private _pushReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; - public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - @observable _entered = false; + @observable private _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; + public static SelectOnLoad = ""; public static InputBoxOverlayScroll: number = 0; public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; @@ -128,15 +128,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe constructor(props: FieldViewProps) { super(props); - FormattedTextBox.Instance = this; - this._ref = React.createRef(); if (this.props.isOverlay) { DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } - - document.addEventListener("paste", this.paste); } + 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); } @@ -172,10 +170,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks()); - let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })]; - tx.ensureMarks(newMarks); - tx.setStoredMarks(newMarks); + // var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks()); + // let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })]; + // if (!this._down) { // if the pointer is down, we're likely doing a drag selection. If setStoreMarks is called during + // tx.ensureMarks(newMarks); // this operation, then it is likely (but not guaranteed) that nothing will be selected due to strange prosemirror behavior. + // tx.setStoredMarks(newMarks); + // } const state = this._editorView.state.apply(tx); FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true); this._editorView.updateState(state); @@ -240,12 +240,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe protected createDropTarget = (ele: HTMLDivElement) => { this._proseRef = ele; - if (this.dropDisposer) { - this.dropDisposer(); - } - if (ele) { - this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); - } + this.dropDisposer && this.dropDisposer(); + ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } })); } @undoBatch @@ -269,17 +265,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } recordKeyHandler = (e: KeyboardEvent) => { - if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) { - return; - } - 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; - - if (e.key === "R" && e.altKey) { - e.stopPropagation(); - e.preventDefault(); - this.recordBullet(); + if (this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { + if (e.key === "R" && e.altKey) { + e.stopPropagation(); + e.preventDefault(); + this.recordBullet(); + } } } @@ -323,15 +314,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } private newListItems = (count: number) => { - let listItems: any[] = []; - for (let i = 0; i < count; i++) { - listItems.push(schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); - } - return listItems; + return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); } - componentDidMount() { - const config = { + @computed get config() { + return { schema, inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ @@ -350,7 +337,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe keymap(buildKeymap(schema)), keymap(baseKeymap), ] - }; + } + }; + + @action + rebuildEditor() { + this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); + } + + componentDidMount() { + document.addEventListener("paste", this.paste); if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), @@ -373,13 +369,13 @@ 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.config, updatedState)); this.tryUpdateHeight(); } } ); - this.pullReactionDisposer = reaction( + this._pullReactionDisposer = reaction( () => this.props.Document[Pulls], () => { if (!DocumentDecorations.hasPulledHack) { @@ -390,7 +386,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } ); - this.pushReactionDisposer = reaction( + this._pushReactionDisposer = reaction( () => this.props.Document[Pushes], () => { if (!DocumentDecorations.hasPushedHack) { @@ -415,7 +411,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc.lastModified = undefined; } }, { fireImmediately: true }); - this.setupEditor(config, this.dataDoc, this.props.fieldKey); + + + this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); this._searchReactionDisposer = reaction(() => { return StrCast(this.props.Document.search_string); @@ -515,7 +513,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - clipboardTextSerializer = (slice: Slice): string => { let text = "", separated = true; const from = 0, to = slice.content.size; @@ -615,6 +612,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } if (this._proseRef) { + this._editorView && this._editorView.destroy(); this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction, @@ -638,20 +636,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe else if (this.props.isOverlay) this._editorView!.focus(); } - public static SelectOnLoad = ""; - componentWillUnmount() { - this._editorView && this._editorView.destroy(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); this._textReactionDisposer && this._textReactionDisposer(); - this.pushReactionDisposer && this.pushReactionDisposer(); - this.pullReactionDisposer && this.pullReactionDisposer(); + this._pushReactionDisposer && this._pushReactionDisposer(); + this._pullReactionDisposer && this._pullReactionDisposer(); this._heightReactionDisposer && this._heightReactionDisposer(); this._searchReactionDisposer && this._searchReactionDisposer(); + document.removeEventListener("paste", this.paste); } + _down = false; onPointerDown = (e: React.PointerEvent): void => { + this._down = true; if (this.props.onClick && e.button === 0) { e.preventDefault(); } @@ -712,10 +710,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.preventDefault(); } } + onPointerUp = (e: React.PointerEvent): void => { - if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) { - //this._toolTipTextMenu.tooltip.style.opacity = "1"; - } + this._down = false; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } @@ -763,7 +760,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops); } }); - //this.props.Document.tooltip = self._toolTipTextMenu; } tooltipLinkingMenuPlugin() { @@ -781,13 +777,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._undoTyping = undefined; } } - public _undoTyping?: UndoManager.Batch; onKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Escape") { SelectionManager.DeselectAll(); } e.stopPropagation(); - if (e.key === "Tab") e.preventDefault(); + if (e.key === "Tab") { + e.preventDefault(); + setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) + SelectionManager.DeselectAll(); + SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); + }, 0); + } + 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; + // 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; @@ -814,24 +819,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - @action - onPointerEnter = (e: React.PointerEvent) => { - this._entered = true; - } - @action - onPointerLeave = (e: React.PointerEvent) => { - this._entered = false; - } - - specificContextMenu = (e: React.MouseEvent): void => { - // let subitems: ContextMenuProps[] = []; - // subitems.push({ - // description: BoolCast(this.props.Document.autoHeight) ? "Manual Height" : "Auto Height", - // event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt" - // }); - // ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" }); - } - render() { let self = this; @@ -854,14 +841,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onKeyDown={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} - onContextMenu={this.specificContextMenu} onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onMouseDown={this.onMouseDown} onWheel={this.onPointerWheel} - onPointerEnter={this.onPointerEnter} - onPointerLeave={this.onPointerLeave} + onPointerEnter={action(() => this._entered = true)} + onPointerLeave={action(() => this._entered = false)} >
-- cgit v1.2.3-70-g09d2 From 41c79ceb4b52965f374db750d06a04a94ecc8212 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 27 Aug 2019 21:21:30 -0400 Subject: from last --- src/client/views/nodes/FormattedTextBox.tsx | 6 ------ 1 file changed, 6 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 5cbbc3b55..ba558a0b2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -170,12 +170,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - // var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks()); - // let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })]; - // if (!this._down) { // if the pointer is down, we're likely doing a drag selection. If setStoreMarks is called during - // tx.ensureMarks(newMarks); // this operation, then it is likely (but not guaranteed) that nothing will be selected due to strange prosemirror behavior. - // tx.setStoredMarks(newMarks); - // } const state = this._editorView.state.apply(tx); FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true); this._editorView.updateState(state); -- cgit v1.2.3-70-g09d2 From 1fbf7d7e10bb4dfa7e3a323ee0641d7bbf97b6a8 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 27 Aug 2019 23:24:18 -0400 Subject: fixed several lint errors, and minor issues with bullets --- src/client/util/DocumentManager.ts | 2 +- src/client/util/ProsemirrorExampleTransfer.ts | 17 ++++----- src/client/util/RichTextSchema.tsx | 13 ++++--- src/client/util/TooltipTextMenu.tsx | 31 ++++++++++++---- src/client/views/DocumentDecorations.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 42 +++++++++++----------- .../views/collections/CollectionViewChromes.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 6 ++-- 8 files changed, 71 insertions(+), 44 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 124faf266..ec731da84 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -204,4 +204,4 @@ export class DocumentManager { } } } -Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)) }) \ No newline at end of file +Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 8b6936748..12ad28199 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -79,8 +79,7 @@ export default function buildKeymap>(schema: S, mapKeys?: bind("Mod-s", TooltipTextMenu.insertStar); - // let nodeTypeMark = depth == 2 ? "upper-alpha" : depth == 4 ? "lower-roman" : depth == 6 ? "lower-alpha" : "decimal"; - let nodeTypeMark = (depth: number) => { return depth == 2 ? "decimal2" : depth == 4 ? "decimal3" : depth == 6 ? "decimal4" : "decimal" } + let nodeTypeMark = (depth: number) => depth === 2 ? "indent2" : depth === 4 ? "indent3" : depth === 6 ? "indent4" : "indent1"; let bulletFunc = (state: EditorState, dispatch: (tx: Transaction) => void) => { var ref = state.selection; @@ -90,7 +89,7 @@ export default function buildKeymap>(schema: S, mapKeys?: if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { const resolvedPos = tx2.doc.resolve(range!.start); - let path = (resolvedPos as any).path as any; + let path = (resolvedPos as any).path; for (let i = path.length - 1; i > 0; i--) { if (path[i].type === schema.nodes.ordered_list) { path[i].attrs.bulletStyle = nodeTypeMark(depth); @@ -105,7 +104,7 @@ export default function buildKeymap>(schema: S, mapKeys?: let newstate = state.applyTransaction(sxf); if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2)); - let path = (resolvedPos as any).path as any; + let path = (resolvedPos as any).path; for (let i = path.length - 1; i > 0; i--) { if (path[i].type === schema.nodes.ordered_list) { path[i].attrs.bulletStyle = nodeTypeMark(depth); @@ -120,8 +119,9 @@ export default function buildKeymap>(schema: S, mapKeys?: console.log("bullet fail"); } } - } - bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => bulletFunc(state, dispatch)); + }; + + bind("Tab", bulletFunc); bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { var ref = state.selection; @@ -132,7 +132,7 @@ export default function buildKeymap>(schema: S, mapKeys?: try { const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2)); - let path = (resolvedPos as any).path as any; + let path = (resolvedPos as any).path; for (let i = path.length - 1; i > 0; i--) { if (path[i].type === schema.nodes.ordered_list) { path[i].attrs.bulletStyle = nodeTypeMark(depth); @@ -159,7 +159,8 @@ export default function buildKeymap>(schema: S, mapKeys?: if (!splitBlockKeepMarks(state, (tx3: Transaction) => { marks && tx3.ensureMarks(marks); marks && tx3.setStoredMarks(marks); - if (!liftListItem(schema.nodes.list_item)(state, (tx4: Transaction) => dispatch(tx4))) { + if (!liftListItem(schema.nodes.list_item)(state, dispatch as ((tx: Transaction>) => void)) + ) { dispatch(tx3); } })) { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 76c45e6c1..4e18f410d 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -177,10 +177,15 @@ export const nodes: { [index: string]: NodeSpec } = { group: 'block', attrs: { bulletStyle: { default: "" }, + mapStyle: { default: "decimal" } }, toDOM(node: Node) { - for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = node.attrs.bulletStyle; - return ['ol', { class: `${node.attrs.bulletStyle}-ol`, style: `list-style: none;` }, 0] + const bs = node.attrs.bulletStyle; + const decMap = bs === "indent1" ? "decimal" : bs === "indent2" ? "decimal2" : bs === "indent3" ? "decimal3" : bs === "indent4" ? "decimal4" : ""; + const multiMap = bs === "indent1" ? "decimal" : bs === "indent2" ? "upper-alpha" : bs === "indent3" ? "lower-roman" : bs === "indent4" ? "lower-alpha" : ""; + let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; + for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = map; + return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0]; //return ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle};`, 0] } }, @@ -192,7 +197,7 @@ export const nodes: { [index: string]: NodeSpec } = { // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], toDOM(node: Node) { for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = ""; - return ['ul', 0] + return ['ul', 0]; } }, @@ -302,7 +307,7 @@ export const marks: { [index: string]: MarkSpec } = { }, toDOM(node: any) { return ['span', { - style: `background: ${node.attrs.bulletType == "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` + style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` }]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 7f6ba3aac..e979e6cde 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -28,11 +28,11 @@ export class TooltipTextMenu { private view: EditorView; private fontStyles: MarkType[]; private fontSizes: MarkType[]; - private listTypes: NodeType[]; + private listTypes: (NodeType | any)[]; private editorProps: FieldViewProps & FormattedTextBoxProps; private fontSizeToNum: Map; private fontStylesToName: Map; - private listTypeToIcon: Map; + private listTypeToIcon: Map; //private link: HTMLAnchorElement; private wrapper: HTMLDivElement; private extras: HTMLDivElement; @@ -179,7 +179,8 @@ export class TooltipTextMenu { //list types this.listTypeToIcon = new Map(); this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); - this.listTypeToIcon.set(schema.nodes.ordered_list, "1)"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); this.listTypes = Array.from(this.listTypeToIcon.keys()); @@ -512,10 +513,28 @@ export class TooltipTextMenu { //remove all node typeand apply the passed-in one to the selected text changeToNodeType(nodeType: NodeType | undefined, view: EditorView) { - //remove old - liftListItem(schema.nodes.list_item)(view.state, view.dispatch); - if (nodeType) { //add new + //remove oldif (nodeType) { //add new + if (nodeType === schema.nodes.bullet_list) { wrapInList(nodeType)(view.state, view.dispatch); + } else { + var ref = view.state.selection; + var range = ref.$from.blockRange(ref.$to); + var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); + wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { + const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2)); + let path = resolvedPos.path; + for (let i = path.length - 1; i > 0; i--) { + if (path[i].type === schema.nodes.ordered_list) { + path[i].attrs.bulletStyle = "indent1"; + path[i].attrs.mapStyle = (nodeType as any).attrs.mapStyle; + break; + } + } + marks && tx2.ensureMarks([...marks]); + marks && tx2.setStoredMarks([...marks]); + + view.dispatch(tx2); + }); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index e93893586..203227241 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -201,7 +201,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } @observable _forceUpdate = 0; - _lastBox = { x: 0, y: 0, r: 0, b: 0 } + _lastBox = { x: 0, y: 0, r: 0, b: 0 }; @computed get Bounds(): { x: number, y: number, b: number, r: number } { let x = this._forceUpdate; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 04133fb5b..50f03005c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -432,26 +432,28 @@ class TreeView extends React.Component { } let ascending = Cast(containingCollection.sortAscending, "boolean", null); - if (ascending !== undefined) docs.sort(function (a, b): 1 | -1 { - let descA = ascending ? b : a; - let descB = ascending ? a : b; - let first = descA.title; - let second = descB.title; - // TODO find better way to sort how to sort.................. - if (typeof first === 'number' && typeof second === 'number') { - return (first - second) > 0 ? 1 : -1; - } - if (typeof first === 'string' && typeof second === 'string') { - return first > second ? 1 : -1; - } - if (typeof first === 'boolean' && typeof second === 'boolean') { - // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load - // return Number(descA.x) > Number(descB.x) ? 1 : -1; - // } - return first > second ? 1 : -1; - } - return ascending ? 1 : -1; - }); + if (ascending !== undefined) { + docs.sort(function (a, b): 1 | -1 { + let descA = ascending ? b : a; + let descB = ascending ? a : b; + let first = descA.title; + let second = descB.title; + // TODO find better way to sort how to sort.................. + if (typeof first === 'number' && typeof second === 'number') { + return (first - second) > 0 ? 1 : -1; + } + if (typeof first === 'string' && typeof second === 'string') { + return first > second ? 1 : -1; + } + if (typeof first === 'boolean' && typeof second === 'boolean') { + // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load + // return Number(descA.x) > Number(descB.x) ? 1 : -1; + // } + return first > second ? 1 : -1; + } + return ascending ? 1 : -1; + }); + } let rowWidth = () => panelWidth() - 20; return docs.map((child, i) => { diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 4b3f7c87e..c897af17e 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -53,7 +53,7 @@ export class CollectionViewBaseChrome extends React.Component { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1 }, + immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1; }, initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document.panX; button.restoredPanY = this.props.CollectionView.props.Document.panY; button.restoredScale = this.props.CollectionView.props.Document.scale; } }; _freeform_commands = [this._contentCommand, this._templateCommand, this._viewCommand]; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ba558a0b2..36740fc66 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -331,8 +331,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe keymap(buildKeymap(schema)), keymap(baseKeymap), ] - } - }; + }; + } @action rebuildEditor() { @@ -776,7 +776,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe SelectionManager.DeselectAll(); } e.stopPropagation(); - if (e.key === "Tab") { + if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) SelectionManager.DeselectAll(); -- cgit v1.2.3-70-g09d2 From be517c301997b617ba642d7f0745a254a6510bf1 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 28 Aug 2019 09:34:52 -0400 Subject: fixed initial storedmarks --- src/client/views/nodes/FormattedTextBox.tsx | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 36740fc66..1c5224e12 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -628,6 +628,10 @@ 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; + } componentWillUnmount() { -- cgit v1.2.3-70-g09d2 From 4b7672c75fe5cdf6afe534e67213917b24980c3e Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 28 Aug 2019 15:02:20 -0400 Subject: added better support for usermarks and fledliging for accept changes. --- src/client/util/RichTextSchema.tsx | 14 ++++++--- src/client/views/nodes/FormattedTextBox.scss | 17 ++++++++++ src/client/views/nodes/FormattedTextBox.tsx | 47 +++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 6c06cec4d..f567d803e 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -333,12 +333,18 @@ export const marks: { [index: string]: MarkSpec } = { // the id of the user who entered the text user_mark: { attrs: { - userid: { default: "" } + userid: { default: "" }, + hide_users: { default: [] }, + opened: { default: false } }, + group: "inline", + inclusive: false, toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.userid.indexOf(Doc.CurrentUserEmail) === -1 ? "rgba(255, 255, 0, 0.267)" : undefined};` - }]; + let hideUsers = node.attrs.hide_users; + let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail); + return hidden ? + ['span', { class: node.attrs.opened ? "userMarkOpen" : "userMark" }, 0] : + ['span', 0]; } }, diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index e93ceda21..03e81bfca 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -67,6 +67,23 @@ font-style: italic; } +.userMarkOpen { + background: rgba(255, 255, 0, 0.267); + display: inline; +} +.userMark { + background: rgba(255, 255, 0, 0.267); + font-size: 2px; + display: inline-grid; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width:10px; + min-height:10px; + text-align:center; + align-content: center; +} + ol { counter-reset: deci 0;} .decimal-ol { counter-reset: deci 0; 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 1c5224e12..146281f2b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -5,8 +5,8 @@ import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; -import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark } from "prosemirror-model"; -import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state"; +import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } from "prosemirror-model"; +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"; @@ -645,9 +645,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe document.removeEventListener("paste", this.paste); } - _down = false; onPointerDown = (e: React.PointerEvent): void => { - this._down = true; if (this.props.onClick && e.button === 0) { e.preventDefault(); } @@ -709,8 +707,47 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + findUserMark(marks: Mark[]) { + return marks.find(m => m.attrs && m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); + } + findStartOfMark(rpos: ResolvedPos) { + let before = 0; + let nbef = rpos.nodeBefore; + while (nbef && this.findUserMark(nbef.marks)) { + before += nbef.nodeSize; + rpos = this._editorView!.state.doc.resolve(rpos.pos - nbef.nodeSize); + rpos && (nbef = rpos.nodeBefore); + } + return before; + } + findEndOfMark(rpos: ResolvedPos) { + let after = 0; + let naft = rpos.nodeAfter; + while (naft && this.findUserMark(naft.marks)) { + after += naft.nodeSize; + rpos = this._editorView!.state.doc.resolve(rpos.pos + naft.nodeSize); + rpos && (naft = rpos.nodeAfter); + } + return after; + } + onPointerUp = (e: React.PointerEvent): void => { - this._down = false; + let view = this._editorView!; + const pos = view.posAtCoords({ left: e.clientX, top: e.clientY }); + const rpos = pos && view.state.doc.resolve(pos.pos); + if (pos && rpos && view.state.selection.$from === view.state.selection.$to) { + let nbef = this.findStartOfMark(rpos); + let naft = this.findEndOfMark(rpos); + const spos = view.state.doc.resolve(pos.pos - nbef); + const epos = view.state.doc.resolve(pos.pos + naft); + let ts = new TextSelection(spos, epos); + let child = rpos.nodeBefore; + let mark = child && this.findUserMark(child.marks); + if (mark && child && nbef && naft) { + let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: e.button === 2 ? false : !mark.attrs.opened }); + view.dispatch(view.state.tr.setSelection(ts).removeMark(ts.from, ts.to, nmark).addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(epos, epos))); + } + } if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } -- cgit v1.2.3-70-g09d2 From 18cc66aaa90c5054268eac9520a963867c0c2f8e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 29 Aug 2019 01:14:18 -0400 Subject: fixed expand/collapse of text. added user tooltip --- src/client/util/RichTextSchema.tsx | 5 +- src/client/views/nodes/FormattedTextBox.tsx | 50 +++++--------- .../views/nodes/FormattedTextBoxComment.scss | 34 ++++++++++ src/client/views/nodes/FormattedTextBoxComment.tsx | 78 ++++++++++++++++++++++ 4 files changed, 132 insertions(+), 35 deletions(-) create mode 100644 src/client/views/nodes/FormattedTextBoxComment.scss create mode 100644 src/client/views/nodes/FormattedTextBoxComment.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index f567d803e..ee0c0870a 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -343,7 +343,10 @@ export const marks: { [index: string]: MarkSpec } = { let hideUsers = node.attrs.hide_users; let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail); return hidden ? - ['span', { class: node.attrs.opened ? "userMarkOpen" : "userMark" }, 0] : + (node.attrs.opened ? + ['span', { class: "userMarkOpen" }, 0] : + ['span', { class: "userMark" }, ['span', { style: "font-size:2" }, 0]] + ) : ['span', 0]; } }, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 146281f2b..2485760df 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -37,7 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations'; import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; -import { number } from 'prop-types'; +import { selectionSizePlugin, findStartOfMark, findUserMark, findEndOfMark, findOtherUserMark, SelectionSizeTooltip } from './FormattedTextBoxComment'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -325,7 +325,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe props: { attributes: { class: "ProseMirror-example-setup-style" } } - }) + }), + selectionSizePlugin ] : [ history(), keymap(buildKeymap(schema)), @@ -643,6 +644,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._heightReactionDisposer && this._heightReactionDisposer(); this._searchReactionDisposer && this._searchReactionDisposer(); document.removeEventListener("paste", this.paste); + this._editorView && this._editorView.destroy(); } onPointerDown = (e: React.PointerEvent): void => { @@ -707,45 +709,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - findUserMark(marks: Mark[]) { - return marks.find(m => m.attrs && m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); - } - findStartOfMark(rpos: ResolvedPos) { - let before = 0; - let nbef = rpos.nodeBefore; - while (nbef && this.findUserMark(nbef.marks)) { - before += nbef.nodeSize; - rpos = this._editorView!.state.doc.resolve(rpos.pos - nbef.nodeSize); - rpos && (nbef = rpos.nodeBefore); - } - return before; - } - findEndOfMark(rpos: ResolvedPos) { - let after = 0; - let naft = rpos.nodeAfter; - while (naft && this.findUserMark(naft.marks)) { - after += naft.nodeSize; - rpos = this._editorView!.state.doc.resolve(rpos.pos + naft.nodeSize); - rpos && (naft = rpos.nodeAfter); - } - return after; - } - onPointerUp = (e: React.PointerEvent): void => { let view = this._editorView!; const pos = view.posAtCoords({ left: e.clientX, top: e.clientY }); const rpos = pos && view.state.doc.resolve(pos.pos); - if (pos && rpos && view.state.selection.$from === view.state.selection.$to) { - let nbef = this.findStartOfMark(rpos); - let naft = this.findEndOfMark(rpos); + let noselection = view.state.selection.$from === view.state.selection.$to; + if (pos && rpos) { + let nbef = findStartOfMark(rpos, view, findOtherUserMark); + let naft = findEndOfMark(rpos, view, findOtherUserMark); const spos = view.state.doc.resolve(pos.pos - nbef); const epos = view.state.doc.resolve(pos.pos + naft); let ts = new TextSelection(spos, epos); - let child = rpos.nodeBefore; - let mark = child && this.findUserMark(child.marks); - if (mark && child && nbef && naft) { - let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: e.button === 2 ? false : !mark.attrs.opened }); - view.dispatch(view.state.tr.setSelection(ts).removeMark(ts.from, ts.to, nmark).addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(epos, epos))); + let child = rpos.nodeBefore || rpos.nodeAfter; + let mark = child && findOtherUserMark(child.marks); + if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { + let opened = e.button === 2 ? false : !mark.attrs.opened; + SelectionSizeTooltip.tooltip.style.display = opened ? "" : "none"; + let mid = opened ? epos : view.state.doc.resolve((spos.pos + epos.pos) / 2); + let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); + view.dispatch(view.state.tr.addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(mid, mid))); } } if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss new file mode 100644 index 000000000..792cee182 --- /dev/null +++ b/src/client/views/nodes/FormattedTextBoxComment.scss @@ -0,0 +1,34 @@ +.FormattedTextBox-tooltip { + position: absolute; + pointer-events: none; + z-index: 20; + background: white; + border: 1px solid silver; + border-radius: 2px; + padding: 2px 10px; + margin-bottom: 7px; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + } + .FormattedTextBox-tooltip:before { + content: ""; + height: 0; width: 0; + position: absolute; + left: 50%; + margin-left: -5px; + bottom: -6px; + border: 5px solid transparent; + border-bottom-width: 0; + border-top-color: silver; + } + .FormattedTextBox-tooltip:after { + content: ""; + height: 0; width: 0; + position: absolute; + left: 50%; + margin-left: -5px; + bottom: -4.5px; + border: 5px solid transparent; + border-bottom-width: 0; + border-top-color: white; + } \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx new file mode 100644 index 000000000..e88c85a86 --- /dev/null +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -0,0 +1,78 @@ +import { Plugin, EditorState, TextSelection } from "prosemirror-state" +import './FormattedTextBoxComment.scss' +import { DragManager } from "../../util/DragManager"; +import { ResolvedPos, Mark } from "prosemirror-model"; +import { EditorView } from "prosemirror-view"; +import { Doc } from "../../../new_fields/Doc"; + +export let selectionSizePlugin = new Plugin({ + view(editorView) { return new SelectionSizeTooltip(editorView); } +}) +export function findOtherUserMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); +} +export function findUserMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid); +} +export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { + let before = 0; + let nbef = rpos.nodeBefore; + while (nbef && finder(nbef.marks)) { + before += nbef.nodeSize; + rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); + rpos && (nbef = rpos.nodeBefore); + } + return before; +} +export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { + let after = 0; + let naft = rpos.nodeAfter; + while (naft && finder(naft.marks)) { + after += naft.nodeSize; + rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); + rpos && (naft = rpos.nodeAfter); + } + return after; +} + +export class SelectionSizeTooltip { + static tooltip: any; + constructor(view: any) { + if (!SelectionSizeTooltip.tooltip) { + SelectionSizeTooltip.tooltip = document.createElement("div"); + SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip"; + DragManager.Root().appendChild(SelectionSizeTooltip.tooltip); + } + + this.update(view, undefined); + } + + + update(view: EditorView, lastState?: EditorState) { + let state = view.state; + // Don't do anything if the document/selection didn't change + if (lastState && lastState.doc.eq(state.doc) && + lastState.selection.eq(state.selection)) return; + + if (state.selection.$from) { + let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); + let child = state.selection.$from.nodeBefore; + let mark = child && findOtherUserMark(child.marks); + if (mark && child && nbef && naft && mark.attrs.opened && SelectionSizeTooltip.tooltip.offsetParent) { + SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid; + // These are in screen coordinates + let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + // The box in which the tooltip is positioned, to use as base + let box = SelectionSizeTooltip.tooltip.offsetParent.getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + let left = Math.max((start.left + end.left) / 2, start.left + 3); + SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px"; + SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px"; + } + } + } + + destroy() { SelectionSizeTooltip.tooltip.style.display = "none" } +} -- cgit v1.2.3-70-g09d2 From 1079d90c5d6752a0a2c06a25d42c1192cb433ed3 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 29 Aug 2019 10:09:03 -0400 Subject: made text author tooltip interactive. --- src/client/util/RichTextSchema.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 21 ++++++----- src/client/views/nodes/FormattedTextBoxComment.tsx | 41 +++++++++++++++++----- 3 files changed, 47 insertions(+), 17 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index ee0c0870a..f8da98f17 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -335,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { userid: { default: "" }, hide_users: { default: [] }, - opened: { default: false } + opened: { default: true } }, group: "inline", inclusive: false, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 2485760df..c23b85e83 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -714,27 +714,32 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe const pos = view.posAtCoords({ left: e.clientX, top: e.clientY }); const rpos = pos && view.state.doc.resolve(pos.pos); let noselection = view.state.selection.$from === view.state.selection.$to; + let set = false; if (pos && rpos) { let nbef = findStartOfMark(rpos, view, findOtherUserMark); let naft = findEndOfMark(rpos, view, findOtherUserMark); - const spos = view.state.doc.resolve(pos.pos - nbef); - const epos = view.state.doc.resolve(pos.pos + naft); - let ts = new TextSelection(spos, epos); + const spos = pos.pos - nbef; + const epos = pos.pos + naft; let child = rpos.nodeBefore || rpos.nodeAfter; let mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - let opened = e.button === 2 ? false : !mark.attrs.opened; - SelectionSizeTooltip.tooltip.style.display = opened ? "" : "none"; - let mid = opened ? epos : view.state.doc.resolve((spos.pos + epos.pos) / 2); - let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); - view.dispatch(view.state.tr.addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(mid, mid))); + SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark); + set = true; } } + !set && SelectionSizeTooltip.Hide(); if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } } + 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); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index e88c85a86..31eb06427 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -1,6 +1,5 @@ -import { Plugin, EditorState, TextSelection } from "prosemirror-state" +import { Plugin, EditorState } from "prosemirror-state" import './FormattedTextBoxComment.scss' -import { DragManager } from "../../util/DragManager"; import { ResolvedPos, Mark } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../new_fields/Doc"; @@ -35,18 +34,43 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark return after; } + export class SelectionSizeTooltip { - static tooltip: any; + static tooltip: HTMLElement; + static start: number; + static end: number; + static mark: Mark; + static opened: boolean; + static textBox: any; constructor(view: any) { if (!SelectionSizeTooltip.tooltip) { + const root = document.getElementById("root"); SelectionSizeTooltip.tooltip = document.createElement("div"); SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip"; - DragManager.Root().appendChild(SelectionSizeTooltip.tooltip); + SelectionSizeTooltip.tooltip.style.pointerEvents = "all"; + SelectionSizeTooltip.tooltip.onpointerdown = (e: PointerEvent) => { + SelectionSizeTooltip.opened = !SelectionSizeTooltip.opened; + SelectionSizeTooltip.textBox.setAnnotation( + SelectionSizeTooltip.start, SelectionSizeTooltip.end, SelectionSizeTooltip.mark, + SelectionSizeTooltip.opened, e.button == 2); + }; + root && root.appendChild(SelectionSizeTooltip.tooltip); } - this.update(view, undefined); } + public static Hide() { + SelectionSizeTooltip.textBox = undefined; + SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "none"); + } + public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) { + SelectionSizeTooltip.textBox = textBox; + SelectionSizeTooltip.start = start; + SelectionSizeTooltip.end = end; + SelectionSizeTooltip.mark = mark; + SelectionSizeTooltip.opened = opened; + SelectionSizeTooltip.textBox && SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = ""); + } update(view: EditorView, lastState?: EditorState) { let state = view.state; @@ -59,12 +83,13 @@ export class SelectionSizeTooltip { let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); let child = state.selection.$from.nodeBefore; let mark = child && findOtherUserMark(child.marks); - if (mark && child && nbef && naft && mark.attrs.opened && SelectionSizeTooltip.tooltip.offsetParent) { + if (mark && child && nbef && naft) { SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid; // These are in screen coordinates - let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); // The box in which the tooltip is positioned, to use as base - let box = SelectionSizeTooltip.tooltip.offsetParent.getBoundingClientRect(); + let box = (document.getElementById("main-div") as any).getBoundingClientRect(); // Find a center-ish x position from the selection endpoints (when // crossing lines, end may be more to the left) let left = Math.max((start.left + end.left) / 2, start.left + 3); -- cgit v1.2.3-70-g09d2 From a46ae95ed6570342f03b7590ff70d6249e56f059 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 29 Aug 2019 10:34:08 -0400 Subject: added a timestamp for edit marks. --- src/client/util/RichTextSchema.tsx | 3 ++- src/client/views/nodes/FormattedTextBox.tsx | 18 +++++++++++++++++- src/client/views/nodes/FormattedTextBoxComment.tsx | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index f8da98f17..a642ee46c 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -335,7 +335,8 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { userid: { default: "" }, hide_users: { default: [] }, - opened: { default: true } + opened: { default: true }, + modified: { default: "when?" } }, group: "inline", inclusive: false, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c23b85e83..0d4376d8d 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 { selectionSizePlugin, findStartOfMark, findUserMark, findEndOfMark, findOtherUserMark, SelectionSizeTooltip } from './FormattedTextBoxComment'; +import { date } from 'serializr'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -811,8 +812,23 @@ 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; + if (s < 10) s = '0' + s; + return now.toLocaleDateString() + ' ' + h + ':' + m + ':' + s + ' ' + 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 })]; + 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. diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 31eb06427..4ec0d6064 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -84,7 +84,7 @@ export class SelectionSizeTooltip { let child = state.selection.$from.nodeBefore; let mark = child && findOtherUserMark(child.marks); if (mark && child && nbef && naft) { - SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid; + SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid + " " + mark.attrs.modified; // These are in screen coordinates // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); -- cgit v1.2.3-70-g09d2 From 22a5999626b11cf75cafbcd421601e668438f6ad Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 29 Aug 2019 10:46:42 -0400 Subject: added keep checkbox --- src/client/views/nodes/FormattedTextBoxComment.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 4ec0d6064..9123f8aed 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -37,6 +37,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark export class SelectionSizeTooltip { static tooltip: HTMLElement; + static tooltipText: HTMLElement; static start: number; static end: number; static mark: Mark; @@ -45,14 +46,20 @@ export class SelectionSizeTooltip { constructor(view: any) { if (!SelectionSizeTooltip.tooltip) { const root = document.getElementById("root"); + let input = document.createElement("input"); + input.type = "checkbox"; SelectionSizeTooltip.tooltip = document.createElement("div"); + SelectionSizeTooltip.tooltipText = document.createElement("div"); + SelectionSizeTooltip.tooltip.appendChild(SelectionSizeTooltip.tooltipText); SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip"; SelectionSizeTooltip.tooltip.style.pointerEvents = "all"; + SelectionSizeTooltip.tooltip.appendChild(input); SelectionSizeTooltip.tooltip.onpointerdown = (e: PointerEvent) => { - SelectionSizeTooltip.opened = !SelectionSizeTooltip.opened; + let keep = e.target && (e.target as any).type === "checkbox"; + SelectionSizeTooltip.opened = keep || !SelectionSizeTooltip.opened; SelectionSizeTooltip.textBox.setAnnotation( SelectionSizeTooltip.start, SelectionSizeTooltip.end, SelectionSizeTooltip.mark, - SelectionSizeTooltip.opened, e.button == 2); + SelectionSizeTooltip.opened, keep); }; root && root.appendChild(SelectionSizeTooltip.tooltip); } @@ -84,7 +91,7 @@ export class SelectionSizeTooltip { let child = state.selection.$from.nodeBefore; let mark = child && findOtherUserMark(child.marks); if (mark && child && nbef && naft) { - SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid + " " + mark.attrs.modified; + SelectionSizeTooltip.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified; // These are in screen coordinates // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); -- cgit v1.2.3-70-g09d2 From f6c15087d30f27c15b3b6304af58c93c65536131 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 29 Aug 2019 14:50:22 -0400 Subject: changed linkFollowBox creation approach. fixed target contexts to add link default. --- src/client/views/MainView.tsx | 8 +- src/client/views/OverlayView.tsx | 108 +++++++++++---------- src/client/views/linking/LinkFollowBox.tsx | 20 ++-- src/client/views/linking/LinkMenuItem.tsx | 18 ++-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 - src/client/views/nodes/FormattedTextBoxComment.tsx | 8 +- .../authentication/models/current_user_utils.ts | 3 + 7 files changed, 78 insertions(+), 88 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 1fc8d1397..b64986084 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -438,13 +438,7 @@ export class MainView extends React.Component { if (LinkFollowBox.Instance) { let dvs = DocumentManager.Instance.getDocumentViews(LinkFollowBox.Instance.props.Document); // if it already exisits, close it - if (dvs.length > 0 && shouldClose) LinkFollowBox.Instance.close(); - // open it if not - else Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", LinkFollowBox.Instance.props.Document); - } - else { - let doc = Docs.Create.LinkFollowBoxDocument({ x: this.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" }); - Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc); + LinkFollowBox.Instance.props.Document.isMinimized = (dvs.length > 0 && shouldClose); } } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index a1afe651c..54ab8696e 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { observer } from "mobx-react"; -import { observable, action } from "mobx"; +import { observable, action, trace, computed } from "mobx"; import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString, returnZero, returnFalse } from "../../Utils"; import './OverlayView.scss'; @@ -140,61 +140,65 @@ export class OverlayView extends React.Component { return remove; } + @computed get overlayDocs() { + return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => { + let offsetx = 0, offsety = 0; + let onPointerMove = action((e: PointerEvent) => { + if (e.buttons === 1) { + d.x = e.clientX + offsetx; + d.y = e.clientY + offsety; + e.stopPropagation(); + e.preventDefault(); + } + }); + let onPointerUp = action((e: PointerEvent) => { + document.removeEventListener("pointermove", onPointerMove); + document.removeEventListener("pointerup", onPointerUp); + e.stopPropagation(); + e.preventDefault(); + }); + + let onPointerDown = (e: React.PointerEvent) => { + offsetx = NumCast(d.x) - e.clientX; + offsety = NumCast(d.y) - e.clientY; + e.stopPropagation(); + e.preventDefault(); + document.addEventListener("pointermove", onPointerMove); + document.addEventListener("pointerup", onPointerUp); + }; + return
+ +
; + }) + } + render() { return (
{this._elements} - {CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => { - let offsetx = 0, offsety = 0; - let onPointerMove = action((e: PointerEvent) => { - if (e.buttons === 1) { - d.x = e.clientX + offsetx; - d.y = e.clientY + offsety; - e.stopPropagation(); - e.preventDefault(); - } - }); - let onPointerUp = action((e: PointerEvent) => { - document.removeEventListener("pointermove", onPointerMove); - document.removeEventListener("pointerup", onPointerUp); - e.stopPropagation(); - e.preventDefault(); - }); - - let onPointerDown = (e: React.PointerEvent) => { - offsetx = NumCast(d.x) - e.clientX; - offsety = NumCast(d.y) - e.clientY; - e.stopPropagation(); - e.preventDefault(); - document.addEventListener("pointermove", onPointerMove); - document.addEventListener("pointerup", onPointerUp); - } - return
- -
- })} + {this.overlayDocs}
); } diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 07f07f72a..74663f9af 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -1,4 +1,4 @@ -import { observable, computed, action, trace, ObservableMap, runInAction, reaction, IReactionDisposer } from "mobx"; +import { observable, computed, action, runInAction, reaction, IReactionDisposer } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; @@ -15,8 +15,6 @@ import { SearchUtil } from "../../util/SearchUtil"; import { Id } from "../../../new_fields/FieldSymbols"; import { listSpec } from "../../../new_fields/Schema"; import { DocServer } from "../../DocServer"; -import { RefField } from "../../../new_fields/RefField"; -import { Docs } from "../../documents/Documents"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; @@ -126,9 +124,11 @@ export class LinkFollowBox extends React.Component { const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); docs.forEach(doc => map.delete(doc)); - runInAction(() => { + 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; + runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest })); }); } } @@ -268,7 +268,7 @@ export class LinkFollowBox extends React.Component { let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc); // THIS IS EMPTY FUNCTION this.props.addDocTab(fullScreenAlias, undefined, "inTab"); - console.log(this.props.addDocTab) + console.log(this.props.addDocTab); this.highlightDoc(); SelectionManager.DeselectAll(); @@ -348,7 +348,7 @@ export class LinkFollowBox extends React.Component { } else if (this.selectedMode === FollowModes.OPENTAB) { if (notOpenInContext) this.openLinkTab(); - else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom }) + else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom }); } else if (this.selectedMode === FollowModes.PAN) { this.jumpToLink({ shouldZoom: shouldZoom }); @@ -555,19 +555,13 @@ export class LinkFollowBox extends React.Component { return null; } - async close() { - let res = await DocListCastAsync((CurrentUserUtils.UserDocument.overlays as Doc).data); - if (res) res.splice(res.indexOf(LinkFollowBox.Instance!.props.Document), 1); - LinkFollowBox.Instance = undefined; - } - render() { return (
{LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"} -
+
this.props.Document.isMinimized = true} className="closeDocument">
{LinkFollowBox.linkDoc ? LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title) diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 6c10d4fb6..780f161d3 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -97,24 +97,20 @@ export class LinkMenuItem extends React.Component { @action.bound async followDefault() { - if (LinkFollowBox.Instance === undefined) { - let doc = await Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" }); - await Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc); - } else { - LinkFollowBox.Instance!.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); - LinkFollowBox.Instance!.defaultLinkBehavior(); + if (LinkFollowBox.Instance !== undefined) { + LinkFollowBox.Instance.props.Document.isMinimized = false; + LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); + LinkFollowBox.Instance.defaultLinkBehavior(); } } @action.bound async openLinkFollower() { - if (LinkFollowBox.Instance === undefined) { - let doc = await Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" }); - await Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc); - } else { + if (LinkFollowBox.Instance !== undefined) { + LinkFollowBox.Instance.props.Document.isMinimized = false; MainView.Instance.toggleLinkFollowBox(false); + LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); } - LinkFollowBox.Instance!.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); } render() { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7631ecc6c..c9c394960 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -8,7 +8,6 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView" import "./DocumentView.scss"; import React = require("react"); import { Doc } from "../../../new_fields/Doc"; -import { returnEmptyString } from "../../../Utils"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { x?: number; diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 9123f8aed..0287d93a9 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -1,12 +1,12 @@ -import { Plugin, EditorState } from "prosemirror-state" -import './FormattedTextBoxComment.scss' +import { Plugin, EditorState } from "prosemirror-state"; +import './FormattedTextBoxComment.scss'; import { ResolvedPos, Mark } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../new_fields/Doc"; export let selectionSizePlugin = new Plugin({ view(editorView) { return new SelectionSizeTooltip(editorView); } -}) +}); export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); } @@ -106,5 +106,5 @@ export class SelectionSizeTooltip { } } - destroy() { SelectionSizeTooltip.tooltip.style.display = "none" } + destroy() { SelectionSizeTooltip.tooltip.style.display = "none"; } } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 83e45d3ce..9866e22eb 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -79,6 +79,9 @@ export class CurrentUserUtils { 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; -- cgit v1.2.3-70-g09d2 From 6aee8e71c50f363ec8c7eb2a2fc5b136295319d2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 30 Aug 2019 23:37:01 -0400 Subject: made list levels more regular. marks now aren't copied on splitting list items. --- src/client/util/ProsemirrorExampleTransfer.ts | 4 ++-- src/client/util/RichTextSchema.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.scss | 24 +++++++++++++++--------- 3 files changed, 19 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 4af2cf58f..bcb8b404b 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -152,8 +152,8 @@ export default function buildKeymap>(schema: S, mapKeys?: 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) => { - marks && tx3.ensureMarks(marks); - marks && tx3.setStoredMarks(marks); + // marks && tx3.ensureMarks(marks); + // marks && tx3.setStoredMarks(marks); dispatch(tx3); })) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0994f5f6b..c74881680 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -181,8 +181,8 @@ export const nodes: { [index: string]: NodeSpec } = { }, toDOM(node: Node) { const bs = node.attrs.bulletStyle; - const decMap = bs === 1 ? "decimal" : bs === 2 ? "decimal2" : bs === 3 ? "decimal3" : bs === 4 ? "decimal4" : ""; - const multiMap = bs === 1 ? "decimal" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; + const decMap = bs ? "decimal" + bs : ""; + 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; for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = map; return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0]; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 03e81bfca..dd07f5924 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -84,18 +84,24 @@ align-content: center; } -ol { counter-reset: deci 0;} -.decimal-ol { counter-reset: deci 0; p { display: inline }; font-size: 24 } +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 } .decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 } .decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 } +.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 } +.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 } +.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 } .upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 } .lower-roman-ol {counter-reset: lroman; p { display: inline }; font-size: 14; } .lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10;} -.decimal:before { content: counter(deci) " "; counter-increment: deci; display:inline-block; width: 30} -.decimal2:before { content: counter(deci) "." counter(deci2) " "; counter-increment: deci2; display:inline-block; width: 35} -.decimal3:before { content: counter(deci) "." counter(deci2) "." counter(deci3) " "; counter-increment: deci3; display:inline-block; width: 35} -.decimal4:before { content: counter(deci) "." counter(deci2) "." counter(deci3) "." counter(deci4) " "; counter-increment: deci4; display:inline-block; width: 40} -.upper-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) " "; counter-increment: ualph; display:inline-block; width: 35 } -.lower-roman:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) " "; counter-increment: lroman;display:inline-block; width: 50 } -.lower-alpha:before { content: counter(deci) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha)" "; counter-increment: lalpha; display:inline-block; width: 35} +.decimal1:before { content: counter(deci1) ")"; counter-increment: deci1; display:inline-block; width: 30} +.decimal2:before { content: counter(deci1) "." counter(deci2) ")"; counter-increment: deci2; display:inline-block; width: 35} +.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ")"; counter-increment: deci3; display:inline-block; width: 35} +.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ")"; counter-increment: deci4; display:inline-block; width: 40} +.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ")"; counter-increment: deci5; display:inline-block; width: 40} +.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ")"; counter-increment: deci6; display:inline-block; width: 45} +.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ")"; counter-increment: deci7; display:inline-block; width: 50} +.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ")"; counter-increment: ualph; display:inline-block; width: 35 } +.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ")"; counter-increment: lroman;display:inline-block; width: 50 } +.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ")"; counter-increment: lalpha; display:inline-block; width: 35} -- cgit v1.2.3-70-g09d2 From ea25f19b08fba66b1ec281afdadf719ae6a6ed54 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 31 Aug 2019 00:13:33 -0400 Subject: fixed some hacks so that undo/redo works for ordered lists --- src/client/util/RichTextSchema.tsx | 8 ++++++++ src/client/views/MainOverlayTextBox.tsx | 5 ----- src/client/views/nodes/FormattedTextBox.tsx | 8 +++----- 3 files changed, 11 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index c74881680..3161467b8 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -573,6 +573,14 @@ export class ImageResizeView { } } +export class OrderedListView { + constructor(node: any, view: any, getPos: any) { } + + update(node: any) { + return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update + } +} + export class SummarizedView { // TODO: highlight text that is summarized. to find end of region, walk along mark _collapsed: HTMLElement; diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 755e5de14..27e0d181f 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -48,11 +48,6 @@ export class MainOverlayTextBox extends React.Component MainOverlayTextBox.Instance = this; reaction(() => FormattedTextBox.InputBoxOverlay, (box?: FormattedTextBox) => { - const tb = this._textBox; - const container = tb && tb.props.ContainingCollectionView; - if (tb && container) { - tb.rebuildEditor();// this forces the edited text box to completely recreate itself to avoid get out of sync -- e.g., bullet labels are only computed when the dom elements are created - } this._textBox = box; if (box) { this.ChromeHeight = box.props.ChromeHeight; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0d4376d8d..0f349780e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -22,7 +22,7 @@ import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { ImageResizeView, schema, SummarizedView } from "../../util/RichTextSchema"; +import { ImageResizeView, schema, SummarizedView, OrderedListView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; @@ -615,6 +615,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe nodeViews: { 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); } + }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, @@ -807,10 +809,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); - setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) - SelectionManager.DeselectAll(); - SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); - }, 0); } function timenow() { var now = new Date(); -- cgit v1.2.3-70-g09d2 From dc7990e969bff201fefe050ac321b2d2d1d58059 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 31 Aug 2019 13:27:17 -0400 Subject: added text tooltip for links --- src/client/views/nodes/FormattedTextBox.tsx | 19 +-------- src/client/views/nodes/FormattedTextBoxComment.tsx | 46 +++++++++++++++++++++- 2 files changed, 45 insertions(+), 20 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0f349780e..93ac2f06f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -713,24 +713,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onPointerUp = (e: React.PointerEvent): void => { - let view = this._editorView!; - const pos = view.posAtCoords({ left: e.clientX, top: e.clientY }); - const rpos = pos && view.state.doc.resolve(pos.pos); - let noselection = view.state.selection.$from === view.state.selection.$to; - let set = false; - if (pos && rpos) { - let nbef = findStartOfMark(rpos, view, findOtherUserMark); - let naft = findEndOfMark(rpos, view, findOtherUserMark); - const spos = pos.pos - nbef; - const epos = pos.pos + naft; - let child = rpos.nodeBefore || rpos.nodeAfter; - let mark = child && findOtherUserMark(child.marks); - if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark); - set = true; - } - } - !set && SelectionSizeTooltip.Hide(); + SelectionSizeTooltip.textBox = this; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 0287d93a9..c368a3f34 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -3,6 +3,10 @@ import './FormattedTextBoxComment.scss'; import { ResolvedPos, Mark } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../new_fields/Doc"; +import { schema } from "../../util/RichTextSchema"; +import { DocServer } from "../../DocServer"; +import { Utils } from "../../../Utils"; +import { StrCast } from "../../../new_fields/Types"; export let selectionSizePlugin = new Plugin({ view(editorView) { return new SelectionSizeTooltip(editorView); } @@ -13,6 +17,9 @@ export function findOtherUserMark(marks: Mark[]): Mark | undefined { export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); } +export function findLinkMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.type === schema.marks.link); +} export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { let before = 0; let nbef = rpos.nodeBefore; @@ -57,7 +64,7 @@ export class SelectionSizeTooltip { SelectionSizeTooltip.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox"; SelectionSizeTooltip.opened = keep || !SelectionSizeTooltip.opened; - SelectionSizeTooltip.textBox.setAnnotation( + SelectionSizeTooltip.textBox && SelectionSizeTooltip.textBox.setAnnotation( SelectionSizeTooltip.start, SelectionSizeTooltip.end, SelectionSizeTooltip.mark, SelectionSizeTooltip.opened, keep); }; @@ -76,7 +83,7 @@ export class SelectionSizeTooltip { SelectionSizeTooltip.end = end; SelectionSizeTooltip.mark = mark; SelectionSizeTooltip.opened = opened; - SelectionSizeTooltip.textBox && SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = ""); + SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = ""); } update(view: EditorView, lastState?: EditorState) { @@ -85,11 +92,18 @@ export class SelectionSizeTooltip { if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; + let set = "none" if (state.selection.$from) { let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); + const spos = state.selection.$from.pos - nbef; + const epos = state.selection.$from.pos + naft; let child = state.selection.$from.nodeBefore; let mark = child && findOtherUserMark(child.marks); + let noselection = view.state.selection.$from === view.state.selection.$to; + if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { + SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark); + } if (mark && child && nbef && naft) { SelectionSizeTooltip.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified; // These are in screen coordinates @@ -102,8 +116,36 @@ export class SelectionSizeTooltip { let left = Math.max((start.left + end.left) / 2, start.left + 3); SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px"; SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px"; + set = ""; + } + } + if (set === "none" && state.selection.$from) { + SelectionSizeTooltip.textBox = undefined; + let nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + let naft = findEndOfMark(state.selection.$from, view, findLinkMark); + let child = state.selection.$from.nodeBefore; + let mark = child && findLinkMark(child.marks); + if (mark && child && nbef && naft) { + SelectionSizeTooltip.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href); + if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { + let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + docTarget && DocServer.GetRefField(docTarget).then(linkDoc => + (linkDoc as Doc) && (SelectionSizeTooltip.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title))); + } + // These are in screen coordinates + // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + let box = (document.getElementById("main-div") as any).getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + let left = Math.max((start.left + end.left) / 2, start.left + 3); + SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px"; + SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px"; + set = ""; } } + SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = set); } destroy() { SelectionSizeTooltip.tooltip.style.display = "none"; } -- cgit v1.2.3-70-g09d2 From 4a1d94325d5d1b5641cd280e89c442c114074e8d Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 3 Sep 2019 08:03:43 -0400 Subject: cleanup names for formattedTextBoxComment stuff. fixed brushing bug with user_mark. --- src/client/util/RichTextSchema.tsx | 2 +- src/client/util/TooltipTextMenu.tsx | 6 +- src/client/views/nodes/FormattedTextBox.tsx | 12 ++-- src/client/views/nodes/FormattedTextBoxComment.tsx | 70 +++++++++++----------- 4 files changed, 41 insertions(+), 49 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3161467b8..7911cf629 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -346,7 +346,7 @@ export const marks: { [index: string]: MarkSpec } = { return hidden ? (node.attrs.opened ? ['span', { class: "userMarkOpen" }, 0] : - ['span', { class: "userMark" }, ['span', { style: "font-size:2" }, 0]] + ['span', { class: "userMark" }, ['span', 0]] ) : ['span', 0]; } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 3b4b7f05a..ce7b04e31 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -60,8 +60,6 @@ export class TooltipTextMenu { @observable private _storedMarks: Mark[] | null | undefined; - public HackToFixTextSelectionGlitch: boolean = false; - constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) { this.view = view; @@ -629,7 +627,7 @@ export class TooltipTextMenu { if (!this.view.state.selection.empty && $from && $from.nodeAfter) { if (this._brushMarks && to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); - this._brushMarks.forEach((mark: Mark) => { + Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; this.changeToMarkInGroup(markType, this.view, []); @@ -876,8 +874,6 @@ export class TooltipTextMenu { this.updateFontSizeDropdown("Various"); } } - !this.HackToFixTextSelectionGlitch && - this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks)); // bcz: what's the purpose of this line? It messes up text selection without the Hack. this.update_mark_doms(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 93ac2f06f..6232dd3ab 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -37,8 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations'; import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; -import { selectionSizePlugin, findStartOfMark, findUserMark, findEndOfMark, findOtherUserMark, SelectionSizeTooltip } from './FormattedTextBoxComment'; -import { date } from 'serializr'; +import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -172,9 +171,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); - FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true); this._editorView.updateState(state); - FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false); if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } @@ -327,7 +324,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe attributes: { class: "ProseMirror-example-setup-style" } } }), - selectionSizePlugin + formattedTextBoxCommentPlugin ] : [ history(), keymap(buildKeymap(schema)), @@ -713,7 +710,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onPointerUp = (e: React.PointerEvent): void => { - SelectionSizeTooltip.textBox = this; + FormattedTextBoxComment.textBox = this; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } @@ -805,8 +802,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (m < 10) m = '0' + m; - if (s < 10) s = '0' + s; - return now.toLocaleDateString() + ' ' + h + ':' + m + ':' + s + ' ' + ampm; + 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() })]; diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index c368a3f34..255000936 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -8,8 +8,8 @@ import { DocServer } from "../../DocServer"; import { Utils } from "../../../Utils"; import { StrCast } from "../../../new_fields/Types"; -export let selectionSizePlugin = new Plugin({ - view(editorView) { return new SelectionSizeTooltip(editorView); } +export let formattedTextBoxCommentPlugin = new Plugin({ + view(editorView) { return new FormattedTextBoxComment(editorView); } }); export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); @@ -42,7 +42,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark } -export class SelectionSizeTooltip { +export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; static start: number; @@ -51,39 +51,39 @@ export class SelectionSizeTooltip { static opened: boolean; static textBox: any; constructor(view: any) { - if (!SelectionSizeTooltip.tooltip) { + if (!FormattedTextBoxComment.tooltip) { const root = document.getElementById("root"); let input = document.createElement("input"); input.type = "checkbox"; - SelectionSizeTooltip.tooltip = document.createElement("div"); - SelectionSizeTooltip.tooltipText = document.createElement("div"); - SelectionSizeTooltip.tooltip.appendChild(SelectionSizeTooltip.tooltipText); - SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip"; - SelectionSizeTooltip.tooltip.style.pointerEvents = "all"; - SelectionSizeTooltip.tooltip.appendChild(input); - SelectionSizeTooltip.tooltip.onpointerdown = (e: PointerEvent) => { + FormattedTextBoxComment.tooltip = document.createElement("div"); + FormattedTextBoxComment.tooltipText = document.createElement("div"); + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; + FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; + FormattedTextBoxComment.tooltip.appendChild(input); + FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox"; - SelectionSizeTooltip.opened = keep || !SelectionSizeTooltip.opened; - SelectionSizeTooltip.textBox && SelectionSizeTooltip.textBox.setAnnotation( - SelectionSizeTooltip.start, SelectionSizeTooltip.end, SelectionSizeTooltip.mark, - SelectionSizeTooltip.opened, keep); + FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; + FormattedTextBoxComment.textBox && FormattedTextBoxComment.textBox.setAnnotation( + FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark, + FormattedTextBoxComment.opened, keep); }; - root && root.appendChild(SelectionSizeTooltip.tooltip); + root && root.appendChild(FormattedTextBoxComment.tooltip); } this.update(view, undefined); } public static Hide() { - SelectionSizeTooltip.textBox = undefined; - SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "none"); + FormattedTextBoxComment.textBox = undefined; + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); } public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) { - SelectionSizeTooltip.textBox = textBox; - SelectionSizeTooltip.start = start; - SelectionSizeTooltip.end = end; - SelectionSizeTooltip.mark = mark; - SelectionSizeTooltip.opened = opened; - SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = ""); + FormattedTextBoxComment.textBox = textBox; + FormattedTextBoxComment.start = start; + FormattedTextBoxComment.end = end; + FormattedTextBoxComment.mark = mark; + FormattedTextBoxComment.opened = opened; + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); } update(view: EditorView, lastState?: EditorState) { @@ -102,10 +102,10 @@ export class SelectionSizeTooltip { let mark = child && findOtherUserMark(child.marks); let noselection = view.state.selection.$from === view.state.selection.$to; if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark); + FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark); } if (mark && child && nbef && naft) { - SelectionSizeTooltip.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified; + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified; // These are in screen coordinates // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); @@ -114,23 +114,23 @@ export class SelectionSizeTooltip { // Find a center-ish x position from the selection endpoints (when // crossing lines, end may be more to the left) let left = Math.max((start.left + end.left) / 2, start.left + 3); - SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px"; - SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px"; + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; set = ""; } } if (set === "none" && state.selection.$from) { - SelectionSizeTooltip.textBox = undefined; + FormattedTextBoxComment.textBox = undefined; let nbef = findStartOfMark(state.selection.$from, view, findLinkMark); let naft = findEndOfMark(state.selection.$from, view, findLinkMark); let child = state.selection.$from.nodeBefore; let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { - SelectionSizeTooltip.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href); + FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href); if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => - (linkDoc as Doc) && (SelectionSizeTooltip.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title))); + (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title))); } // These are in screen coordinates // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); @@ -140,13 +140,13 @@ export class SelectionSizeTooltip { // Find a center-ish x position from the selection endpoints (when // crossing lines, end may be more to the left) let left = Math.max((start.left + end.left) / 2, start.left + 3); - SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px"; - SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px"; + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; set = ""; } } - SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = set); + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } - destroy() { SelectionSizeTooltip.tooltip.style.display = "none"; } + destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; } } -- cgit v1.2.3-70-g09d2 From a4d36f835b5c43351d1761034b61513b000445ba Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 3 Sep 2019 16:19:20 -0400 Subject: added inline footnote. --- src/client/util/ProsemirrorExampleTransfer.ts | 13 +++ src/client/util/RichTextSchema.tsx | 159 +++++++++++++++++++++++++- src/client/views/nodes/FormattedTextBox.scss | 47 ++++++++ src/client/views/nodes/FormattedTextBox.tsx | 30 ++++- 4 files changed, 240 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index bcb8b404b..419311df8 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -51,6 +51,19 @@ export default function buildKeymap>(schema: S, mapKeys?: bind("Ctrl->", wrapIn(schema.nodes.blockquote)); + bind("^", (state: EditorState, dispatch: (tx: Transaction) => void) => { + let newNode = schema.nodes.footnote.create({}); + if (dispatch && state.selection.from === state.selection.to) { + let tr = state.tr; + tr.replaceSelectionWith(newNode); // replace insertion with a footnote. + dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display + tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)))); + return true; + } + return false; + }) + let cmd = chainCommands(exitCode, (state, dispatch) => { if (dispatch) { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 7911cf629..25d972857 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,7 +1,12 @@ import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { TextSelection } from "prosemirror-state"; +import { TextSelection, EditorState } from "prosemirror-state"; import { Doc } from "../../new_fields/Doc"; +import { StepMap } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; +import { keymap } from "prosemirror-keymap"; +import { undo, redo } from "prosemirror-history"; +import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -14,6 +19,20 @@ export const nodes: { [index: string]: NodeSpec } = { content: "block+" }, + footnote: { + group: "inline", + content: "inline*", + inline: true, + attrs: { + visibility: { default: false } + }, + // This makes the view treat the node as a leaf, even though it + // technically has content + atom: true, + toDOM: () => ["footnote", 0], + parseDOM: [{ tag: "footnote" }] + }, + // :: NodeSpec A plain paragraph textblock. Represented in the DOM // as a `

` element. paragraph: { @@ -177,7 +196,7 @@ export const nodes: { [index: string]: NodeSpec } = { group: 'block', attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" } + mapStyle: { default: "decimal" }, }, toDOM(node: Node) { const bs = node.attrs.bulletStyle; @@ -186,7 +205,8 @@ export const nodes: { [index: string]: NodeSpec } = { let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = map; return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0]; - //return ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle};`, 0] + //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"]; } }, //this doesn't currently work for some reason @@ -313,10 +333,10 @@ export const marks: { [index: string]: MarkSpec } = { }, highlight: { - parseDOM: [{ style: 'color: blue' }], + parseDOM: [{ style: 'text-decoration: underline' }], toDOM() { return ['span', { - style: 'color: blue' + style: 'text-decoration: underline; text-decoration-color: rgba(204, 206, 210, 0.92)' }]; } }, @@ -581,6 +601,133 @@ export class OrderedListView { } } +export class FootnoteView { + innerView: any; + outerView: any; + node: any; + dom: any; + getPos: any; + + constructor(node: any, view: any, getPos: any) { + // We'll need these later + this.node = node + this.outerView = view + this.getPos = getPos + + // The node's representation in the editor (empty, for now) + this.dom = document.createElement("footnote"); + this.dom.addEventListener("pointerup", this.toggle, true); + // These are used when the footnote is selected + this.innerView = null + } + selectNode() { + const attrs = { ...this.node.attrs }; + attrs.visibility = true; + this.dom.classList.add("ProseMirror-selectednode") + if (!this.innerView) this.open() + } + + deselectNode() { + const attrs = { ...this.node.attrs }; + attrs.visibility = false; + this.dom.classList.remove("ProseMirror-selectednode") + if (this.innerView) this.close() + } + open() { + if (!(this.outerView as any).isOverlay) return; + // Append a tooltip to the outer node + let tooltip = this.dom.appendChild(document.createElement("div")) + tooltip.className = "footnote-tooltip"; + // And put a sub-ProseMirror into that + this.innerView = new EditorView(tooltip, { + // You can use any node as an editor document + state: EditorState.create({ + doc: this.node, + plugins: [keymap(baseKeymap), + keymap({ + "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch), + "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch), + "Mod-b": toggleMark(schema.marks.strong) + })] + }), + // This is the magic part + dispatchTransaction: this.dispatchInner.bind(this), + handleDOMEvents: { + pointerdown: ((view: any, e: PointerEvent) => { + // Kludge to prevent issues due to the fact that the whole + // footnote is node-selected (and thus DOM-selected) when + // the parent editor is focused. + e.stopPropagation(); + document.addEventListener("pointerup", this.ignore, true); + if (this.outerView.hasFocus()) this.innerView.focus(); + }) as any + } + + }); + setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0); + } + + ignore = (e: PointerEvent) => { + e.stopPropagation(); + document.removeEventListener("pointerup", this.ignore, true); + } + + toggle = () => { + if (this.innerView) this.close(); + else { + this.open(); + + } + } + close() { + this.innerView && this.innerView.destroy() + this.innerView = null + this.dom.textContent = "" + } + dispatchInner(tr: any) { + let { state, transactions } = this.innerView.state.applyTransaction(tr) + this.innerView.updateState(state) + + if (!tr.getMeta("fromOutside")) { + 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++) + outerTr.step(steps[j].map(offsetMap)) + } + if (outerTr.docChanged) this.outerView.dispatch(outerTr) + } + } + update(node: any) { + if (!node.sameMarkup(this.node)) return false + this.node = node + if (this.innerView) { + let state = this.innerView.state + let start = node.content.findDiffStart(state.doc.content) + if (start != null) { + let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content) + let overlap = start - Math.min(endA, endB) + if (overlap > 0) { endA += overlap; endB += overlap } + this.innerView.dispatch( + state.tr + .replace(start, endB, node.slice(start, endA)) + .setMeta("fromOutside", true)) + } + } + return true + } + + destroy() { + if (this.innerView) this.close() + } + + stopEvent(event: any) { + return this.innerView && this.innerView.dom.contains(event.target) + } + + ignoreMutation() { return true } +} + export class SummarizedView { // TODO: highlight text that is summarized. to find end of region, walk along mark _collapsed: HTMLElement; @@ -648,7 +795,7 @@ export class SummarizedView { 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.includes(_mark)) { + if (node.marks.find((m: any) => m.type === _mark.type)) { visited.add(node); endPos = i + node.nodeSize - 1; } diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index dd07f5924..8f47402c4 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -84,6 +84,53 @@ align-content: center; } +footnote { + display: inline-block; + position: relative; + cursor: pointer; + div { + padding : 0 !important; + } +} + +footnote::after { + content: counter(prosemirror-footnote); + vertical-align: super; + font-size: 75%; + counter-increment: prosemirror-footnote; +} + +.ProseMirror { + counter-reset: prosemirror-footnote; + } + +.footnote-tooltip { + cursor: auto; + font-size: 75%; + position: absolute; + left: -30px; + top: calc(100% + 10px); + background: silver; + padding: 3px; + border-radius: 2px; + max-width: 100px; + min-width: 50px; + width: max-content; +} + +.footnote-tooltip::before { + border: 5px solid silver; + border-top-width: 0px; + border-left-color: transparent; + border-right-color: transparent; + position: absolute; + top: -5px; + left: 27px; + content: " "; + height: 0; + width: 0; +} + 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 6232dd3ab..bb44c5ac6 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -22,7 +22,7 @@ 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 } from "../../util/RichTextSchema"; +import { ImageResizeView, schema, SummarizedView, OrderedListView, FootnoteView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; @@ -168,10 +168,33 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + // 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); + if (desc != view.lastSelectedViewDesc) { + if (view.lastSelectedViewDesc) { + view.lastSelectedViewDesc.deselectNode(); + view.lastSelectedViewDesc = null; + } + if (desc) { desc.selectNode(); } + view.lastSelectedViewDesc = desc; + } + } else { + if (view.lastSelectedViewDesc) { + view.lastSelectedViewDesc.deselectNode(); + view.lastSelectedViewDesc = null; + } + } + } + dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); + this.syncNodeSelection(this._editorView, this._editorView.state.selection); // bcz: ugh -- shouldn't be needed but without this the overlay view's footnote popup doesn't get deselected if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } @@ -612,12 +635,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe nodeViews: { 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); } - + ordered_list(node, view, getPos) { return new OrderedListView(node, view, getPos); }, + footnote(node, view, getPos) { return new FootnoteView(node, view, getPos) } }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); + (this._editorView as any).isOverlay = this.props.isOverlay; if (startup) { Doc.GetProto(doc).documentText = undefined; this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); -- cgit v1.2.3-70-g09d2 From 2d3de245e31e0c45c1e64720816c3d6d219efcd3 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 3 Sep 2019 18:53:08 -0400 Subject: Update FormattedTextBox.tsx --- src/client/views/nodes/FormattedTextBox.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index bb44c5ac6..b46502c1d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -811,8 +811,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe SelectionManager.DeselectAll(); } e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. + if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); e.preventDefault(); + setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) + SelectionManager.DeselectAll(); + SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); + }, 0); } function timenow() { var now = new Date(); -- cgit v1.2.3-70-g09d2 From 4921ef0121818381e4139137c0d204d41cb64a20 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 3 Sep 2019 18:57:23 -0400 Subject: reverted back to semi-working version of outline mode --- src/client/views/nodes/FormattedTextBox.tsx | 9 +++++---- src/server/index.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index b46502c1d..4cb52ec78 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -813,10 +813,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); e.preventDefault(); - setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) - SelectionManager.DeselectAll(); - SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); - }, 0); + // bcz: if we use this, it fixes some problesm with bullet numbers but kills undo/redo + // setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) + // SelectionManager.DeselectAll(); + // SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); + // }, 0); } function timenow() { var now = new Date(); diff --git a/src/server/index.ts b/src/server/index.ts index fca90a585..17cd59ec7 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport({ scale: 1 }); + let viewport = page.getViewport(1); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From 2262d7562b57c37c238fc8a7c373304984a5fe3b Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 4 Sep 2019 17:49:19 -0400 Subject: prosemirror restructuring. --- src/client/util/ProsemirrorExampleTransfer.ts | 71 +++++----------------- src/client/util/RichTextSchema.tsx | 71 +++++++++++----------- src/client/util/TooltipTextMenu.tsx | 37 ++++++----- src/client/util/prosemirrorPatches.js | 30 +++++++++ src/client/views/OverlayView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 6 +- src/client/views/nodes/FormattedTextBoxComment.tsx | 4 +- src/client/views/pdf/Page.tsx | 2 +- 8 files changed, 107 insertions(+), 116 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 5016e72c6..9c4b2d3d1 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -2,8 +2,8 @@ import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setB import { redo, undo } from "prosemirror-history"; import { undoInputRule } from "prosemirror-inputrules"; import { Schema } from "prosemirror-model"; -import { liftListItem, } from "./prosemirrorPatches.js"; -import { splitListItem, wrapInList, sinkListItem } from "prosemirror-schema-list"; +import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; +import { splitListItem, wrapInList, } from "prosemirror-schema-list"; import { EditorState, Transaction, TextSelection, NodeSelection } from "prosemirror-state"; import { TooltipTextMenu } from "./TooltipTextMenu"; @@ -62,7 +62,7 @@ export default function buildKeymap>(schema: S, mapKeys?: return true; } return false; - }) + }); let cmd = chainCommands(exitCode, (state, dispatch) => { @@ -93,54 +93,16 @@ export default function buildKeymap>(schema: S, mapKeys?: bind("Mod-s", TooltipTextMenu.insertStar); - let updateOrderedList = (start: number, rangeStart: any, delta: number, tx2: Transaction, forward: boolean) => { - let bs = rangeStart.attrs.bulletStyle; - bs + delta > 0 && tx2.setNodeMarkup(start, rangeStart.type, { mapStyle: rangeStart.attrs.mapStyle, bulletStyle: rangeStart.attrs.bulletStyle + delta }, rangeStart.marks); - - let brk = false; - rangeStart.descendants((node: any, offset: any, index: any) => { + let updateBullets = (tx2: Transaction) => { + tx2.doc.descendants((node: any, offset: any, index: any) => { if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { - if (!brk && (bs !== node.attrs.bulletStyle || delta > 0 || (forward && bs > 1))) { - tx2.setNodeMarkup(start + offset + 1, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: node.attrs.bulletStyle + delta }, node.marks); - } else { - brk = true; - } + 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); + if (node.type === schema.nodes.ordered_list) depth++; + tx2.setNodeMarkup(offset, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks); } }); - } - - let updateBullets = (tx2: Transaction, refStart: number, delta: number) => { - let i = refStart; - for (let i = refStart; i >= 0; i--) { - let testPos = tx2.doc.nodeAt(i); - if (!testPos) { - for (let i = refStart + 1; i <= tx2.doc.nodeSize; i++) { - try { - let testPos = tx2.doc.nodeAt(i); - if (testPos && testPos.type === schema.nodes.ordered_list) { - updateOrderedList(i, testPos, delta, tx2, true); - break; - } - } catch (e) { - break; - } - } - break; - } - if ((testPos.type === schema.nodes.list_item || testPos.type === schema.nodes.ordered_list)) { - let start = i; - let preve = i > 0 && tx2.doc.nodeAt(start - 1); - if (preve && preve.type === schema.nodes.ordered_list) { - start = start - 1; - } - let rangeStart = tx2.doc.nodeAt(start); - if (rangeStart) { - updateOrderedList(start, rangeStart, delta, tx2, false); - } - break; - } - } - } + }; bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { @@ -148,8 +110,7 @@ export default function buildKeymap>(schema: S, mapKeys?: var range = ref.$from.blockRange(ref.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - var range = state.selection.$from.blockRange(state.selection.$to); - updateBullets(tx2, range!.start - 1, 1); + updateBullets(tx2); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); dispatch(tx2); @@ -157,7 +118,7 @@ export default function buildKeymap>(schema: S, mapKeys?: let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)); let newstate = state.applyTransaction(sxf); if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { - updateBullets(tx2, range!.start, 1); + updateBullets(tx2); // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); @@ -169,14 +130,10 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - var range = state.selection.$from.blockRange(state.selection.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - let tr = state.tr; - - if (!liftListItem(schema.nodes.list_item)(tr, (tx2: Transaction) => { - var range = tx2.selection.$from.blockRange(tx2.selection.$to); - updateBullets(tx2, range!.start, -1); + if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { + updateBullets(tx2); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); dispatch(tx2); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 75e982872..3174d668c 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -597,8 +597,6 @@ export class ImageResizeView { } export class OrderedListView { - constructor(node: any, view: any, getPos: any) { } - update(node: any) { return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update } @@ -613,33 +611,33 @@ export class FootnoteView { constructor(node: any, view: any, getPos: any) { // We'll need these later - this.node = node - this.outerView = view - this.getPos = getPos + this.node = node; + this.outerView = view; + this.getPos = getPos; // The node's representation in the editor (empty, for now) this.dom = document.createElement("footnote"); this.dom.addEventListener("pointerup", this.toggle, true); // These are used when the footnote is selected - this.innerView = null + this.innerView = null; } selectNode() { const attrs = { ...this.node.attrs }; attrs.visibility = true; - this.dom.classList.add("ProseMirror-selectednode") - if (!this.innerView) this.open() + this.dom.classList.add("ProseMirror-selectednode"); + if (!this.innerView) this.open(); } deselectNode() { const attrs = { ...this.node.attrs }; attrs.visibility = false; - this.dom.classList.remove("ProseMirror-selectednode") - if (this.innerView) this.close() + this.dom.classList.remove("ProseMirror-selectednode"); + if (this.innerView) this.close(); } open() { - if (!(this.outerView as any).isOverlay) return; + if (!this.outerView.isOverlay) return; // Append a tooltip to the outer node - let tooltip = this.dom.appendChild(document.createElement("div")) + let tooltip = this.dom.appendChild(document.createElement("div")); tooltip.className = "footnote-tooltip"; // And put a sub-ProseMirror into that this.innerView = new EditorView(tooltip, { @@ -679,56 +677,55 @@ export class FootnoteView { if (this.innerView) this.close(); else { this.open(); - } } close() { - this.innerView && this.innerView.destroy() - this.innerView = null - this.dom.textContent = "" + this.innerView && this.innerView.destroy(); + this.innerView = null; + this.dom.textContent = ""; } dispatchInner(tr: any) { - let { state, transactions } = this.innerView.state.applyTransaction(tr) - this.innerView.updateState(state) + let { state, transactions } = this.innerView.state.applyTransaction(tr); + this.innerView.updateState(state); if (!tr.getMeta("fromOutside")) { - 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++) - outerTr.step(steps[j].map(offsetMap)) + let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); + for (let steps of transactions) { + for (let step of steps) { + outerTr.step(step.map(offsetMap)); + } } - if (outerTr.docChanged) this.outerView.dispatch(outerTr) + if (outerTr.docChanged) this.outerView.dispatch(outerTr); } } update(node: any) { - if (!node.sameMarkup(this.node)) return false - this.node = node + if (!node.sameMarkup(this.node)) return false; + this.node = node; if (this.innerView) { - let state = this.innerView.state - let start = node.content.findDiffStart(state.doc.content) - if (start != null) { - let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content) - let overlap = start - Math.min(endA, endB) - if (overlap > 0) { endA += overlap; endB += overlap } + let state = this.innerView.state; + let start = node.content.findDiffStart(state.doc.content); + if (start !== null) { + let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); + let overlap = start - Math.min(endA, endB); + if (overlap > 0) { endA += overlap; endB += overlap; } this.innerView.dispatch( state.tr .replace(start, endB, node.slice(start, endA)) - .setMeta("fromOutside", true)) + .setMeta("fromOutside", true)); } } - return true + return true; } destroy() { - if (this.innerView) this.close() + if (this.innerView) this.close(); } stopEvent(event: any) { - return this.innerView && this.innerView.dom.contains(event.target) + return this.innerView && this.innerView.dom.contains(event.target); } - ignoreMutation() { return true } + ignoreMutation() { return true; } } export class SummarizedView { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index ce7b04e31..6d375fc1d 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -4,7 +4,7 @@ import { action, observable } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; import { wrapInList } from 'prosemirror-schema-list'; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; +import { EditorState, NodeSelection, TextSelection, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc, Field, Opt } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; @@ -509,30 +509,37 @@ export class TooltipTextMenu { } } + updateBullets = (tx2: Transaction, style: string) => { + 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); + 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) { + changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { //remove oldif (nodeType) { //add new if (nodeType === schema.nodes.bullet_list) { wrapInList(nodeType)(view.state, view.dispatch); } else { - var ref = view.state.selection; - var range = ref.$from.blockRange(ref.$to); var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); - wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2)); - let path = resolvedPos.path; - for (let i = path.length - 1; i > 0; i--) { - if (path[i].type === schema.nodes.ordered_list) { - path[i].attrs.bulletStyle = 1; - path[i].attrs.mapStyle = (nodeType as any).attrs.mapStyle; - break; - } - } + if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { + this.updateBullets(tx2, (nodeType as any).attrs.mapStyle); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); view.dispatch(tx2); - }); + })) { + let tx2 = view.state.tr; + this.updateBullets(tx2, (nodeType as any).attrs.mapStyle); + marks && tx2.ensureMarks([...marks]); + marks && tx2.setStoredMarks([...marks]); + + view.dispatch(tx2); + } } } diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js index c273c2323..6bf4395ad 100644 --- a/src/client/util/prosemirrorPatches.js +++ b/src/client/util/prosemirrorPatches.js @@ -6,6 +6,7 @@ var prosemirrorTransform = require('prosemirror-transform'); var prosemirrorModel = require('prosemirror-model'); exports.liftListItem = liftListItem; +exports.sinkListItem = sinkListItem; // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool // Create a command to lift the list item around the selection up into // a wrapping list. @@ -59,4 +60,33 @@ function liftOutOfList(tr, dispatch, range) { atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1)); dispatch(tr.scrollIntoView()); return true +} + +// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool +// Create a command to sink the list item around the selection down +// into an inner list. +function sinkListItem(itemType) { + return function (state, dispatch) { + var ref = state.selection; + var $from = ref.$from; + var $to = ref.$to; + var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); + if (!range) { return false } + var startIndex = range.startIndex; + if (startIndex == 0) { return false } + var parent = range.parent, nodeBefore = parent.child(startIndex - 1); + if (nodeBefore.type != itemType) { return false; } + + if (dispatch) { + var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type; + var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null); + let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create(parent.attrs, inner)))), + nestedBefore ? 3 : 1, 0); + var before = range.start, after = range.end; + dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, + before, after, slice, 1, true)) + .scrollIntoView()); + } + return true + } } \ No newline at end of file diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 54ab8696e..fe06e4440 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -191,7 +191,7 @@ export class OverlayView extends React.Component { zoomToScale={emptyFunction} getScale={returnOne} />

; - }) + }); } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 4cb52ec78..f9ad040a2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -174,7 +174,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; @@ -635,8 +635,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe nodeViews: { 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) } + ordered_list(node, view, getPos) { return new OrderedListView(); }, + footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 255000936..2d2fff06d 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -92,7 +92,7 @@ export class FormattedTextBoxComment { if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; - let set = "none" + let set = "none"; if (state.selection.$from) { let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); @@ -130,7 +130,7 @@ export class FormattedTextBoxComment { if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => - (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title))); + (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title))); } // These are in screen coordinates // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 8df2dce29..856e883e7 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -64,7 +64,7 @@ export default class Page extends React.Component { // lower scale = easier to read at small sizes, higher scale = easier to read at large sizes if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) { this._state = "rendering"; - let viewport = page.getViewport({ scale: scale }); + let viewport = page.getViewport(scale); this._canvas.current.width = this._width = viewport.width; this._canvas.current.height = this._height = viewport.height; this.props.pageLoaded(viewport); -- cgit v1.2.3-70-g09d2 From a017d20fb40ffb5b99b06b716ac5a6f8635c45e6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 4 Sep 2019 20:43:28 -0400 Subject: added collapsing bullets --- src/client/util/ProsemirrorExampleTransfer.ts | 2 +- src/client/util/RichTextSchema.tsx | 8 ++++++-- src/client/views/nodes/FormattedTextBox.tsx | 10 ++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 9c4b2d3d1..9f6da7ade 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -99,7 +99,7 @@ export default function buildKeymap>(schema: S, mapKeys?: 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); if (node.type === schema.nodes.ordered_list) depth++; - tx2.setNodeMarkup(offset, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks); + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks); } }); }; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3174d668c..2b689b7ef 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -197,13 +197,15 @@ export const nodes: { [index: string]: NodeSpec } = { attrs: { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" }, + visibility: { default: true } }, toDOM(node: Node) { const bs = node.attrs.bulletStyle; const decMap = bs ? "decimal" + bs : ""; 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 ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0]; + 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"]; } @@ -231,6 +233,7 @@ export const nodes: { [index: string]: NodeSpec } = { attrs: { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" }, + visibility: { default: true } }, ...listItem, content: 'paragraph block*', @@ -239,7 +242,8 @@ export const nodes: { [index: string]: NodeSpec } = { const decMap = bs ? "decimal" + bs : ""; 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 ["li", { class: `${map}` }, 0]; + return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; + //return ["li", { class: `${map}` }, 0]; } }, }; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index f9ad040a2..2dbfeeba7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -672,6 +672,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onPointerDown = (e: React.PointerEvent): void => { + let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + if (pos) { + if (e.nativeEvent.offsetX < 40) { + 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 (node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + } + } + } if (this.props.onClick && e.button === 0) { e.preventDefault(); } -- cgit v1.2.3-70-g09d2 From a936f08e3c18ad34bbe74133eaa768c7bda832dc Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 4 Sep 2019 21:00:30 -0400 Subject: cleanup --- src/client/views/nodes/FormattedTextBox.tsx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 2dbfeeba7..d811273e8 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -75,6 +75,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _editorView: Opt; private _applyingChange: boolean = false; private _linkClicked = ""; + private _nodeClicked: any; private _undoTyping?: UndoManager.Batch; private _reactionDisposer: Opt; private _searchReactionDisposer?: Lambda; @@ -671,17 +672,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._editorView && this._editorView.destroy(); } + onPointerDown = (e: React.PointerEvent): void => { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - if (pos) { - if (e.nativeEvent.offsetX < 40) { - 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 (node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); - } - } - } + pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { e.preventDefault(); } @@ -778,6 +772,16 @@ 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) { + 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)) { + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + } + } + } this._proseRef!.focus(); if (this._linkClicked) { this._linkClicked = ""; -- 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/client/views/nodes') 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/client/views/nodes') 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 95f773b56b8ed4fa95b1fd308c19baee1744275a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Sep 2019 09:18:11 -0400 Subject: added note types. fixed user_mark --- src/client/util/ProsemirrorExampleTransfer.ts | 5 +++++ src/client/util/RichTextSchema.tsx | 3 +-- src/client/views/ContextMenu.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 ++++++++ src/client/views/nodes/FormattedTextBox.tsx | 22 ++++++++++------------ 5 files changed, 25 insertions(+), 15 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 9f6da7ade..55e07cfb9 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -14,6 +14,7 @@ export type KeyMap = { [key: string]: any }; export default function buildKeymap>(schema: S, mapKeys?: KeyMap): KeyMap { let keys: { [key: string]: any } = {}, type; + keys["ACTIVE"] = false; function bind(key: string, cmd: any) { if (mapKeys) { let mapped = mapKeys[key]; @@ -143,6 +144,10 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { + if (!keys["ACTIVE"]) { + dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); + return true; + } var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => { // marks && tx3.ensureMarks(marks); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 31a65dd3a..675c1d387 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -381,7 +381,6 @@ export const marks: { [index: string]: MarkSpec } = { modified: { default: "when?" } }, group: "inline", - inclusive: false, toDOM(node: any) { let hideUsers = node.attrs.hide_users; let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail); @@ -759,7 +758,7 @@ export class SummarizedView { 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); + let textSelection = TextSelection.create(view.state.doc, getPos() + 1); if (!visible) { // update summarized text and save in attrs textSelection = this.updateSummarizedText(getPos() + 1); attrs.text = textSelection.content(); diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 760736501..e27318429 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -246,7 +246,7 @@ export class ContextMenu extends React.Component { this.selectedIndex--; } e.preventDefault(); - } else if (e.key === "Enter") { + } else if (e.key === "Enter" || e.key === "Tab") { const item = this.flatItems[this.selectedIndex]; item.event(); this.closeMenu(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a7acd9e91..aadb7f6e9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -879,9 +879,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); 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: "1: Note", event: () => this.createText("yellow"), icon: "eye" }); + layoutItems.push({ description: "2: Idea", event: () => this.createText("pink"), icon: "eye" }); + layoutItems.push({ description: "3: Topic", event: () => this.createText("lightBlue"), icon: "eye" }); + layoutItems.push({ description: "4: Person", event: () => this.createText("lightGreen"), icon: "eye" }); ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } + createText = (color: string) => { + let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); + this.addLiveTextBox(Docs.Create.TextDocument({ x: pt[0], y: pt[1], backgroundColor: color })) + } private childViews = () => [ , diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0d530c9a1..944cc3211 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -173,6 +173,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } + this._keymap["ACTIVE"] = true; this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); @@ -307,14 +308,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); } + _keymap: any = undefined; @computed get config() { + this._keymap = buildKeymap(schema); + this._keymap["ACTIVE"] = this.extensionDoc.text; return { schema, inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ this.tooltipTextMenuPlugin(), history(), - keymap(buildKeymap(schema)), + keymap(this._keymap), keymap(baseKeymap), // this.tooltipLinkingMenuPlugin(), new Plugin({ @@ -325,7 +329,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe formattedTextBoxCommentPlugin ] : [ history(), - keymap(buildKeymap(schema)), + keymap(this._keymap), keymap(baseKeymap), ] }; @@ -626,8 +630,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.props.select(false); } else if (this.props.isOverlay) this._editorView!.focus(); - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark). - addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }))); + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; } componentWillUnmount() { @@ -651,8 +654,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); } - 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; @@ -786,14 +787,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe SelectionManager.DeselectAll(); } e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); + if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); - // bcz: if we use this, it fixes some problesm with bullet numbers but kills undo/redo - // setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) - // SelectionManager.DeselectAll(); - // SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); - // }, 0); } + //this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; // 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; -- cgit v1.2.3-70-g09d2 From eb05b987d7a1b2ca2e50268a0c15f2de7d44c5bd Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 10:11:23 -0400 Subject: cleanup of prosemirror stuff. --- src/client/util/ProsemirrorExampleTransfer.ts | 11 ++----- src/client/util/RichTextSchema.tsx | 2 -- src/client/views/PreviewCursor.tsx | 6 ++-- src/client/views/nodes/FormattedTextBox.tsx | 47 +++++++++++---------------- 4 files changed, 24 insertions(+), 42 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 55e07cfb9..e7566e3a4 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -116,8 +116,7 @@ export default function buildKeymap>(schema: S, mapKeys?: marks && tx2.setStoredMarks([...marks]); dispatch(tx2); })) { // couldn't sink into an existing list, so wrap in a new one - let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)); - let newstate = state.applyTransaction(sxf); + let newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { updateBullets(tx2); // when promoting to a list, assume list will format things so don't copy the stored marks. @@ -144,16 +143,12 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (!keys["ACTIVE"]) { + if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); return true; } var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => { - // marks && tx3.ensureMarks(marks); - // marks && tx3.setStoredMarks(marks); - dispatch(tx3); - })) { + if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { marks && tx3.ensureMarks(marks); marks && tx3.setStoredMarks(marks); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 675c1d387..baa95acb0 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -103,7 +103,6 @@ export const nodes: { [index: string]: NodeSpec } = { textslice: { default: undefined }, }, group: "inline", - inclusive: false, toDOM(node) { const attrs = { style: `width: 40px` }; return ["span", { ...node.attrs, ...attrs }]; @@ -333,7 +332,6 @@ export const marks: { [index: string]: MarkSpec } = { }, ], inclusive: false, - priority: 100, toDOM() { return ['span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 1329dc02c..45a8556bf 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -93,9 +93,7 @@ export class PreviewCursor extends React.Component<{}> { @action onKeyPress = (e: KeyboardEvent) => { - // Mixing events between React and Native is finicky. In FormattedTextBox, we set the - // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore - // the keyPress here. 112- + // Mixing events between React and Native is finicky. //if not these keys, make a textbox if preview cursor is active! if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" && e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && @@ -103,7 +101,7 @@ export class PreviewCursor extends React.Component<{}> { e.key !== "NumLock" && (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys !e.key.startsWith("Arrow") && - !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { + !e.defaultPrevented) { if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); PreviewCursor.Visible = false; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 944cc3211..8d3286d71 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -173,19 +173,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } - this._keymap["ACTIVE"] = true; + this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); this._applyingChange = false; + this.updateTitle(); let title = StrCast(this.dataDoc.title); - if (title && title.startsWith("-") && this._editorView && !this.Document.customTitle) { - let str = this._editorView.state.doc.textContent; - let titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); - } + } + } + + updateTitle = () => { + if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { + let str = this._editorView.state.doc.textContent; + let titlestr = str.substr(0, Math.min(40, str.length)); + this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); } } @@ -311,7 +315,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe _keymap: any = undefined; @computed get config() { this._keymap = buildKeymap(schema); - this._keymap["ACTIVE"] = this.extensionDoc.text; + this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -335,11 +339,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }; } - @action - rebuildEditor() { - this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); - } - componentDidMount() { if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), @@ -583,11 +582,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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", title: title, docref: true }); - if (linkIndex !== -1) { - marks.splice(linkIndex, 1, link); - } else { - marks.push(link); - } + marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); return node.mark(marks); } } @@ -630,6 +625,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.props.select(false); } 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() })]; } @@ -734,6 +730,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } 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 }); if (pos && pos.pos > 0) { @@ -790,16 +787,11 @@ 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.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - // 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; - if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { - let str = this._editorView.state.doc.textContent; - let titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); - } + + this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + + this.updateTitle(); + if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } @@ -820,7 +812,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe render() { - let self = this; 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 || -- cgit v1.2.3-70-g09d2 From 2707e0898d535cc143272b7bf3b80f829368c097 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 11:37:59 -0400 Subject: added metadata ui for text --- src/client/util/ProsemirrorExampleTransfer.ts | 27 ++++++++++++++++++++-- src/client/util/RichTextSchema.tsx | 6 +++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 12 +++++----- src/client/views/nodes/FormattedTextBox.tsx | 11 ++++++++- 4 files changed, 47 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index e7566e3a4..da26da4f9 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -150,8 +150,8 @@ export default function buildKeymap>(schema: S, mapKeys?: var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - marks && tx3.ensureMarks(marks); - marks && tx3.setStoredMarks(marks); + marks && tx3.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + marks && tx3.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { dispatch(tx3); } @@ -161,6 +161,29 @@ export default function buildKeymap>(schema: S, mapKeys?: } return true; }); + bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { + var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + let tx = state.tr; + marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + dispatch(tx); + return false; + }); + bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { + let range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { + return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); + }); + let path = (state.doc.resolve(state.selection.from - 1) as any).path; + let spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; + let textsel = TextSelection.create(state.doc, range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator, range!.end); + let text = range ? state.doc.textBetween(textsel.from, textsel.to) : ""; + let whitespace = text.length - 1; + for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } + if (text.endsWith(":")) { + dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any)); + } + return false; + }); return keys; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index baa95acb0..5ee445590 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -315,6 +315,12 @@ export const marks: { [index: string]: MarkSpec } = { } }, + metadata: { + toDOM() { + return ['span', { style: 'border-radius:5px; background:rgba(100, 100, 100, 0.1); box-shadow: black 1px 1px 1px' }]; + } + }, + highlight: { parseDOM: [ { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aadb7f6e9..074bc1822 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -879,16 +879,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); 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: "1: Note", event: () => this.createText("yellow"), icon: "eye" }); - layoutItems.push({ description: "2: Idea", event: () => this.createText("pink"), icon: "eye" }); - layoutItems.push({ description: "3: Topic", event: () => this.createText("lightBlue"), icon: "eye" }); - layoutItems.push({ description: "4: Person", event: () => this.createText("lightGreen"), icon: "eye" }); + layoutItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); + layoutItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); + layoutItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); + layoutItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } - createText = (color: string) => { + createText = (noteStyle: string, color: string) => { let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ x: pt[0], y: pt[1], backgroundColor: color })) + this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], backgroundColor: color })) } private childViews = () => [ diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8d3286d71..c09e88592 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -173,6 +173,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } + + let metadata = this._editorView!.state.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); + if (metadata) { + let range = this._editorView!.state.selection.$from.blockRange(this._editorView!.state.selection.$to); + let text = range ? this._editorView!.state.doc.textBetween(range.start, range.end) : ""; + let key = text.split("::")[0]; + let value = text.split("::")[text.split("::").length - 1]; + this.dataDoc[key] = value; + } + this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu this._applyingChange = true; @@ -787,7 +797,6 @@ 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.updateTitle(); -- cgit v1.2.3-70-g09d2 From 173863d85ee590c276bf22b1cfe91e0d00986720 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 13:45:04 -0400 Subject: added named target docs from rich text. --- src/client/documents/Documents.ts | 4 +-- src/client/util/ProsemirrorExampleTransfer.ts | 16 +++++---- src/client/util/RichTextSchema.tsx | 12 ++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 5 ++- src/client/views/nodes/FormattedTextBox.tsx | 40 ++++++++++++++++------ 6 files changed, 54 insertions(+), 25 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ef8b68c2f..fbdfa8966 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -419,8 +419,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options }); } - export function FreeformDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }); + export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id); } export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array, options: DocumentOptions) { diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index da26da4f9..cc2ae7d38 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -142,6 +142,11 @@ export default function buildKeymap>(schema: S, mapKeys?: } }); + let splitMetadata = (marks: any, tx: Transaction) => { + 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) => { if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); @@ -150,8 +155,7 @@ export default function buildKeymap>(schema: S, mapKeys?: var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - marks && tx3.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); - marks && tx3.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + splitMetadata(marks, tx3); if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { dispatch(tx3); } @@ -163,10 +167,7 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - let tx = state.tr; - marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); - marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); - dispatch(tx); + dispatch(splitMetadata(marks, state.tr)); return false; }); bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { @@ -180,7 +181,8 @@ export default function buildKeymap>(schema: S, mapKeys?: let whitespace = text.length - 1; for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } if (text.endsWith(":")) { - dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any)); + dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any). + addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any)); } return false; }); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 5ee445590..6bae63174 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -317,7 +317,17 @@ export const marks: { [index: string]: MarkSpec } = { metadata: { toDOM() { - return ['span', { style: 'border-radius:5px; background:rgba(100, 100, 100, 0.1); box-shadow: black 1px 1px 1px' }]; + return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; + } + }, + metadataKey: { + toDOM() { + return ['span', { style: 'font-style:italic; ' }]; + } + }, + metadataVal: { + toDOM() { + return ['span', { style: 'background:rgba(100, 100, 100, 0.1);' }]; } }, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 074bc1822..fac4d4970 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -888,7 +888,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { createText = (noteStyle: string, color: string) => { let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], backgroundColor: color })) + this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color })) } private childViews = () => [ diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 27eafd769..5015ee39a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -93,9 +93,8 @@ export class MarqueeView extends React.Component } }); } else if (!e.ctrlKey) { - let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); - newBox.proto!.autoHeight = true; - this.props.addLiveTextDocument(newBox); + this.props.addLiveTextDocument( + Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" })); } e.stopPropagation(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c09e88592..1dd84a3db 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -167,6 +167,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe dispatchTransaction = (tx: Transaction) => { if (this._editorView) { + let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); + if (metadata) { + 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++) { } + text = text.substr(0, textEndSelection - range!.start); + text = text.split(" ")[text.split(" ").length - 1]; + let split = text.split("::"); + if (split.length > 1 && split[1]) { + let key = split[0]; + let value = split[split.length - 1]; + + DocServer.GetRefField(value).then(doc => this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value)); + const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${value}`, 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; + } + } const state = this._editorView.state.apply(tx); this._editorView.updateState(state); this.syncNodeSelection(this._editorView, this._editorView.state.selection); // bcz: ugh -- shouldn't be needed but without this the overlay view's footnote popup doesn't get deselected @@ -174,15 +195,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } - let metadata = this._editorView!.state.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); - if (metadata) { - let range = this._editorView!.state.selection.$from.blockRange(this._editorView!.state.selection.$to); - let text = range ? this._editorView!.state.doc.textBetween(range.start, range.end) : ""; - let key = text.split("::")[0]; - let value = text.split("::")[text.split("::").length - 1]; - this.dataDoc[key] = value; - } - this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu this._applyingChange = true; @@ -191,7 +203,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); this._applyingChange = false; this.updateTitle(); - let title = StrCast(this.dataDoc.title); } } @@ -670,6 +681,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href; } + let node = this._editorView!.state.doc.nodeAt(this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY })!.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 (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; @@ -690,7 +707,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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")); - + } else { + DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } } }); -- cgit v1.2.3-70-g09d2 From 0d9133bbd8417e68dfd62706369067c1e2e30c97 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 15:59:10 -0400 Subject: turned link lines back on... switched metadata links into real links --- src/client/documents/Documents.ts | 4 ++-- src/client/util/RichTextSchema.tsx | 4 ++-- .../collectionFreeForm/CollectionFreeFormLinksView.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 15 ++++++++++----- 4 files changed, 16 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fbdfa8966..ae65fde1e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -606,13 +606,13 @@ export namespace Docs { export namespace DocUtils { - export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc) { + 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); if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; if (target === CurrentUserUtils.UserDocument) return undefined; - let linkDocProto = new Doc(); + let linkDocProto = new Doc(id, true); UndoManager.RunInBatch(() => { linkDocProto.type = DocumentType.LINK; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 6bae63174..8851839a2 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -327,7 +327,7 @@ export const marks: { [index: string]: MarkSpec } = { }, metadataVal: { toDOM() { - return ['span', { style: 'background:rgba(100, 100, 100, 0.1);' }]; + return ['span']; } }, @@ -347,7 +347,7 @@ export const marks: { [index: string]: MarkSpec } = { } }, ], - inclusive: false, + inclusive: true, toDOM() { return ['span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 2d94f1b8e..a593128be 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -120,9 +120,9 @@ export class CollectionFreeFormLinksView extends React.Component - {/* + {this.uniqueConnections} - */} + {this.props.children}
); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 1dd84a3db..5f185d8ae 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -180,8 +180,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let key = split[0]; let value = split[split.length - 1]; - DocServer.GetRefField(value).then(doc => this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value)); - const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${value}`, location: "onRight", title: 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); tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); @@ -203,6 +210,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); this._applyingChange = false; this.updateTitle(); + this.tryUpdateHeight(); } } @@ -817,12 +825,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); - this.updateTitle(); - if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } - this.tryUpdateHeight(); } @action -- cgit v1.2.3-70-g09d2 From ee8f7e6ff149defe919a5ac219ed700a4688b46c Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 17:46:02 -0400 Subject: fixed image resizing within text --- src/client/util/RichTextSchema.tsx | 4 ++-- src/client/views/nodes/WebBox.scss | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 8851839a2..153aaa791 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -598,9 +598,9 @@ export class ImageResizeView { document.removeEventListener("pointermove", onpointermove); document.removeEventListener("pointerup", onpointerup); view.dispatch( - view.state.tr.setNodeMarkup(getPos(), null, + view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, { src: node.attrs.src, width: self._outer.style.width }) - .setSelection(view.state.selection)); + ); }; document.addEventListener("pointermove", onpointermove); diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 43220df71..fbe9bf063 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -30,6 +30,7 @@ width: 100%; height: 100%; position: absolute; + pointer-events: all; } .webBox-button { -- cgit v1.2.3-70-g09d2 From 0fea8592b5bd790334d0557c3ef30eb03973c601 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Sep 2019 23:19:21 -0400 Subject: changed sub-menu pop up location. added rotation jitter --- src/client/views/ContextMenuItem.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 14 ++++++++++---- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 90f7be33f..1a0839060 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/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4f6055260..2df2a3464 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -152,6 +152,7 @@ export namespace PivotView { y={pos.y} width={pos.width} height={pos.height} + jitterRotation={NumCast(target.props.Document.jitterRotation)} {...target.getChildDocumentViewProps(doc)} />, bounds: { @@ -797,6 +798,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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) } @@ -912,10 +914,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); 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: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); - layoutItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); - layoutItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); - layoutItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); + layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); + + let noteItems: ContextMenuProps[] = []; + noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); + noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); + noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); + noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); + layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" }) ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c9c394960..f07584b4f 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -8,12 +8,14 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView" import "./DocumentView.scss"; import React = require("react"); import { Doc } from "../../../new_fields/Doc"; +import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { x?: number; y?: number; width?: number; height?: number; + jitterRotation: number; } const schema = createSchema({ @@ -27,7 +29,7 @@ const FreeformDocument = makeInterface(schema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(FreeformDocument) { - @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}) `; } + @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg) scale(${this.zoom}) `; } @computed get X() { return this.props.x !== undefined ? this.props.x : this.Document.x || 0; } @computed get Y() { return this.props.y !== undefined ? this.props.y : this.Document.y || 0; } @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } -- cgit v1.2.3-70-g09d2 From 8b7bfb4f9ecc9f85e9a413f57decd74e88179a6e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 7 Sep 2019 01:37:13 -0400 Subject: fixed image dragging within textboxes --- src/client/util/RichTextSchema.tsx | 17 +++++++++-------- src/client/views/nodes/FormattedTextBox.tsx | 11 +++++++++-- 2 files changed, 18 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 153aaa791..6ded78b4d 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -9,6 +9,7 @@ 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"; +import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -124,9 +125,10 @@ export const nodes: { [index: string]: NodeSpec } = { inline: true, attrs: { src: {}, - width: { default: "100px" }, + width: { default: 100 }, alt: { default: null }, - title: { default: null } + title: { default: null }, + float: { default: "left" } }, group: "inline", draggable: true, @@ -140,11 +142,6 @@ export const nodes: { [index: string]: NodeSpec } = { }; } }], - // TODO if we don't define toDom, something weird happens: dragging the image will not move it but clone it. Why? - toDOM(node) { - const attrs = { style: `width: ${node.attrs.width}` }; - return ["img", { ...node.attrs, ...attrs }]; - } }, video: { @@ -571,6 +568,7 @@ export class ImageResizeView { this._outer.style.width = node.attrs.width; this._outer.style.display = "inline-block"; this._outer.style.overflow = "hidden"; + (this._outer.style as any).float = node.attrs.float; this._img.setAttribute("src", node.attrs.src); this._img.style.width = "100%"; @@ -592,6 +590,8 @@ export class ImageResizeView { const currentX = e.pageX; const diffInPx = currentX - startX; self._outer.style.width = `${startWidth + diffInPx}`; + //Array.from(FormattedTextBox.InputBoxOverlay!.CurrentDiv.getElementsByTagName("img")).map((img: any) => img.opacity = "0.1"); + FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "0"; }; const onpointerup = () => { @@ -600,7 +600,8 @@ export class ImageResizeView { view.dispatch( view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, { src: node.attrs.src, width: self._outer.style.width }) - ); + ); + FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1"; }; document.addEventListener("pointermove", onpointermove); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 5f185d8ae..794d3a573 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -261,7 +261,12 @@ 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))); + } protected createDropTarget = (ele: HTMLDivElement) => { this._proseRef = ele; this.dropDisposer && this.dropDisposer(); @@ -276,7 +281,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // 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; - this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url }))); + 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 }))); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; @@ -629,6 +635,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } if (this._proseRef) { + let self = this; this._editorView && this._editorView.destroy(); this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), -- cgit v1.2.3-70-g09d2 From af34c087bf3226c09a657959cc14fc4ae291feb0 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 7 Sep 2019 13:51:11 -0400 Subject: added linking to images in text boxes --- src/client/util/RichTextSchema.tsx | 51 ++++++++++++++++++++++------- src/client/views/MainOverlayTextBox.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 9 +++-- 3 files changed, 46 insertions(+), 15 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 6ded78b4d..05a37759f 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,15 +1,16 @@ -import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice, Fragment } from "prosemirror-model"; +import { baseKeymap, toggleMark } from "prosemirror-commands"; +import { redo, undo } from "prosemirror-history"; +import { keymap } from "prosemirror-keymap"; +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { TextSelection, EditorState } from "prosemirror-state"; -import { Doc } from "../../new_fields/Doc"; +import { EditorState, TextSelection } from "prosemirror-state"; import { StepMap } from "prosemirror-transform"; 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"; +import { Doc } from "../../new_fields/Doc"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { DocServer } from "../DocServer"; +import { Cast, NumCast } from "../../new_fields/Types"; +import { DocumentManager } from "./DocumentManager"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -128,7 +129,8 @@ export const nodes: { [index: string]: NodeSpec } = { width: { default: 100 }, alt: { default: null }, title: { default: null }, - float: { default: "left" } + float: { default: "left" }, + docid: { default: "" } }, group: "inline", draggable: true, @@ -560,7 +562,7 @@ export class ImageResizeView { _handle: HTMLElement; _img: HTMLElement; _outer: HTMLElement; - constructor(node: any, view: any, getPos: any) { + constructor(node: any, view: any, getPos: any, addDocTab: any) { this._handle = document.createElement("span"); this._img = document.createElement("img"); this._outer = document.createElement("span"); @@ -581,6 +583,33 @@ export class ImageResizeView { this._handle.style.bottom = "-10px"; this._handle.style.right = "-10px"; let self = this; + this._img.onpointerdown = function (e: any) { + if (!view.isOverlay || e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + DocServer.GetRefField(node.attrs.docid).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, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + return; + } + } + if (targetContext) { + DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); + } else if (jumpToDoc) { + 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(); @@ -599,7 +628,7 @@ export class ImageResizeView { document.removeEventListener("pointerup", onpointerup); view.dispatch( view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, - { src: node.attrs.src, width: self._outer.style.width }) + { ...node.attrs, width: self._outer.style.width }) ); FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1"; }; diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 27e0d181f..c3a2cb214 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -25,7 +25,6 @@ export class MainOverlayTextBox extends React.Component private _textTargetDiv: HTMLDivElement | undefined; private _textProxyDiv: React.RefObject; private _textBottom: boolean | undefined; - private _textAutoHeight: boolean | undefined; private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); }; private _outerdiv: HTMLElement | null = null; private _textBox: FormattedTextBox | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 794d3a573..ae1393fc4 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -278,11 +278,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe drop = async (e: Event, de: DragManager.DropEvent) => { // We're dealing with a link to a document if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) { + 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 pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url }))); + 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]); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; @@ -641,7 +643,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction, nodeViews: { - image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }, + image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(); }, footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } @@ -696,7 +698,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href; } - let node = this._editorView!.state.doc.nodeAt(this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY })!.pos); + let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + 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; -- cgit v1.2.3-70-g09d2 From 3865ccd688015d92c8c551bf78ee2a9b6ece5500 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 9 Sep 2019 15:40:49 -0400 Subject: added local overlay for free form views. added start of minimap. --- src/client/views/OverlayView.tsx | 4 +++- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 ++++++++++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 23 ++++++++++++++++++---- src/client/views/nodes/DocumentView.tsx | 2 -- src/server/index.ts | 2 +- 5 files changed, 36 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index fe06e4440..da4b71e5c 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -197,7 +197,9 @@ export class OverlayView extends React.Component { render() { return (
- {this._elements} +
+ {this._elements} +
{this.overlayDocs}
); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2df2a3464..2ec2b0671 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -3,7 +3,7 @@ import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; import { action, computed, IReactionDisposer, observable, reaction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; @@ -225,8 +225,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return bounds; } + @computed get actualContentBounds() { + return this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined; + } + @computed get contentBounds() { - let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined; + let bounds = this.actualContentBounds; let res = { panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0, panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0, @@ -775,7 +779,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const initScript = this.Document.arrangeInit; const script = this.Document.arrangeScript; let state: any = undefined; - const docs = this.childDocs; + let docs = this.childDocs; + let overlayDocs = DocListCast(this.props.Document.localOverlays); + overlayDocs && docs.push(...overlayDocs); let elements: ViewDefResult[] = []; if (initScript) { const initResult = initScript.script.run({ docs, collection: this.Document }); @@ -967,6 +973,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } render() { + 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); 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/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index f07584b4f..c059ff50d 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,7 +1,7 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; @@ -77,6 +77,21 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; render() { + let txf = this.transform; + let w = this.width; + let h = this.height; + let renderScript = this.Document.renderScript; + if (renderScript) { + let someView = Cast(this.Document.someView, Doc); + let minimap = Cast(this.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; + w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); + h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); + txf = `translate(${x}px,${y}px)`; + } + } const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return (
(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" { console.log("reading " + page); - let viewport = page.getViewport({scale: 1}); + let viewport = page.getViewport(1 as any); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From 5941e6f9904b0285fa983248535bead284aa16eb Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 9 Sep 2019 15:51:01 -0400 Subject: fixed portal navigation to restore collection pan/zoom --- src/client/views/nodes/DocumentView.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1763c664e..a65f16e1c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -375,8 +375,17 @@ export class DocumentView extends DocComponent(Docu 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), 1000); // then after the 1sec animation, open up the target in a new tab + 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); } -- cgit v1.2.3-70-g09d2 From 2d21fff15510d6eeb8975cc2459f69ca28d86d1d Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 9 Sep 2019 17:26:27 -0400 Subject: added stand-in spatial parser. --- src/client/util/TooltipTextMenu.tsx | 11 +++++++++++ src/client/views/DocumentDecorations.tsx | 6 ++++++ src/client/views/InkingControl.tsx | 7 ++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 13 +++++++++++++ .../views/collections/collectionFreeForm/MarqueeView.tsx | 1 + src/client/views/nodes/FormattedTextBox.tsx | 10 ++++++++++ 6 files changed, 47 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 020c51c36..c376b6f86 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -18,6 +18,7 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; +import { Cast, NumCast } from '../../new_fields/Types'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -495,10 +496,20 @@ 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 heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } } else { let fontName = this.fontStylesToName.get(markType); if (fontName) { this.updateFontStyleDropdown(fontName); } + let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } } //actually apply font return toggleMark(markType)(view.state, view.dispatch, view); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 700a4b49d..7cdb16f52 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -428,6 +428,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1])); 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)}%`); + SelectionManager.SelectedDocuments().map(dv => { + let cv = dv.props.ContainingCollectionView; + let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); + let heading = NumCast(dv.props.Document.heading); + cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleRounding_" + heading] = StrCast(dv.props.Document.borderRounding)); + }) e.stopPropagation(); e.preventDefault(); } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 3f40642b5..eb6312e78 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -9,7 +9,7 @@ import { SelectionManager } from "../util/SelectionManager"; import { InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { undoBatch, UndoManager } from "../util/UndoManager"; -import { StrCast } from "../../new_fields/Types"; +import { StrCast, NumCast, Cast } from "../../new_fields/Types"; import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { MainOverlayTextBox } from "./MainOverlayTextBox"; @@ -50,6 +50,11 @@ export class InkingControl extends React.Component { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let oldColor = StrCast(targetDoc.backgroundColor); 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); + cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor); + } return { target: targetDoc, previous: oldColor diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2ec2b0671..f7c1bedbb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -256,6 +256,19 @@ 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 i = 0; i < this.childDocs.length; i++) { + if (this.childDocs[i].heading == 1) { + newBox.heading = 2; + } + } + let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); + if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document; + let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); + let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); + round && (newBox.borderRounding = round); + col && (newBox.backgroundColor = col); + newBox.ruleProvider = ruleProvider; this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5015ee39a..100e6d817 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -329,6 +329,7 @@ export class MarqueeView extends React.Component 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/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ae1393fc4..93c97fa23 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -665,6 +665,16 @@ 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) { + let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); + if (ruleProvider instanceof Doc) { + let font = StrCast(ruleProvider["ruleFont_" + heading]); + let size = NumCast(ruleProvider["ruleSize_" + heading]); + size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]); + font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]); + } + } } componentWillUnmount() { -- cgit v1.2.3-70-g09d2 From a8be7ef95403a1f4559aeff9695e67ad2030e3b9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 9 Sep 2019 21:34:34 -0400 Subject: restored input parsing rules back to prosemirror and added some news --- src/client/util/RichTextRules.ts | 52 ++++++++++++++++++----------- src/client/util/prosemirrorPatches.js | 47 ++++++++++++++++++++++++++ src/client/views/nodes/FormattedTextBox.tsx | 3 +- src/server/index.ts | 4 +-- 4 files changed, 83 insertions(+), 23 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 8c4c76027..979b76988 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -1,14 +1,6 @@ -import { - inputRules, - wrappingInputRule, - textblockTypeInputRule, - smartQuotes, - emDash, - ellipsis -} from "prosemirror-inputrules"; -import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"; - +import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules"; import { schema } from "./RichTextSchema"; +import { wrappingInputRule } from "./prosemirrorPatches"; export const inpRules = { rules: [ @@ -21,17 +13,29 @@ export const inpRules = { // 1. ordered list wrappingInputRule( - /^(\d+)\.\s$/, + /^1\.\s$/, schema.nodes.ordered_list, - match => ({ order: +match[1] }), - (match, node) => node.childCount + node.attrs.order === +match[1] + () => { + return ({ mapStyle: "decimal", bulletStyle: 1 }) + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } }) ), // a. alphabbetical list wrappingInputRule( - /^([a-z]+)\.\s$/, - schema.nodes.alphabet_list, - match => ({ order: +match[1] }), - (match, node) => node.childCount + node.attrs.order === +match[1] + /^a\.\s$/, + schema.nodes.ordered_list, + // match => { + () => { + return ({ mapStyle: "alpha", bulletStyle: 1 }) + // return ({ order: +match[1] }) + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } }) ), // * bullet list @@ -42,9 +46,17 @@ export const inpRules = { // # heading textblockTypeInputRule( - new RegExp("^(#{1,6})\\s$"), + new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, - match => ({ level: match[1].length }) - ) + match => { + return ({ level: match[1].length }); + } + ), + + new InputRule( + new RegExp(/^#([0-9]+)\s$/), + (state, match, start, end) => { + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) + }), ] }; diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js index 6bf4395ad..188e3e1c5 100644 --- a/src/client/util/prosemirrorPatches.js +++ b/src/client/util/prosemirrorPatches.js @@ -2,11 +2,13 @@ Object.defineProperty(exports, '__esModule', { value: true }); +var prosemirrorInputRules = require('prosemirror-inputrules'); var prosemirrorTransform = require('prosemirror-transform'); var prosemirrorModel = require('prosemirror-model'); exports.liftListItem = liftListItem; exports.sinkListItem = sinkListItem; +exports.wrappingInputRule = wrappingInputRule; // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool // Create a command to lift the list item around the selection up into // a wrapping list. @@ -89,4 +91,49 @@ function sinkListItem(itemType) { } return true } +} + +function findWrappingOutside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var around = parent.contentMatchAt(startIndex).findWrapping(type); + if (!around) { return null } + var outer = around.length ? around[0] : type; + return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null +} + +function findWrappingInside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var inner = parent.child(startIndex); + var inside = type.contentMatch.findWrapping(inner.type); + if (!inside) { return null } + var lastType = inside.length ? inside[inside.length - 1] : type; + var innerMatch = lastType.contentMatch; + for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); } + if (!innerMatch || !innerMatch.validEnd) { return null } + return inside +} +function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) { + if (innerRange === void 0) innerRange = range; + let withAttrs = (type) => ({ type: type, attrs: null }); + var around = findWrappingOutside(range, nodeType); + var inner = around && findWrappingInside(innerRange, nodeType); + if (!inner) { return null } + return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs)) +} +function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) { + return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) { + var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; + var tr = state.tr.delete(start, end); + var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs); + if (!wrapping) { return null } + tr.wrap(range, wrapping); + var before = tr.doc.resolve(start - 1).nodeBefore; + if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) && + (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); } + return tr + }) } \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 93c97fa23..6a6000dc5 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 { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; +import { inputRules } from 'prosemirror-inputrules'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -355,8 +356,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, - inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ + inputRules(inpRules), this.tooltipTextMenuPlugin(), history(), keymap(this._keymap), diff --git a/src/server/index.ts b/src/server/index.ts index 082e9422d..50ce2b14e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -811,8 +811,8 @@ const EndpointHandlerMap = new Map { - let sector: GoogleApiServerUtils.Service = req.params.sector; - let action: GoogleApiServerUtils.Action = req.params.action; + let sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service; + let action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action; GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { -- cgit v1.2.3-70-g09d2 From 5ea7e3318620865146318e0f3826b6f13aec0675 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 10 Sep 2019 13:38:16 -0400 Subject: added more support for creating stylized layouts --- src/Utils.ts | 92 ++++++++++++++++++++++ src/client/util/ProsemirrorExampleTransfer.ts | 5 -- src/client/util/RichTextRules.ts | 40 ++++++++++ src/client/util/RichTextSchema.tsx | 19 +++++ src/client/views/ContextMenu.tsx | 1 + src/client/views/DocumentDecorations.tsx | 5 ++ src/client/views/InkingControl.tsx | 32 +++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 27 ++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 26 +++++- src/client/views/nodes/FormattedTextBox.tsx | 48 ++++++++--- .../authentication/models/current_user_utils.ts | 20 ++--- 11 files changed, 270 insertions(+), 45 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/Utils.ts b/src/Utils.ts index f805ae872..3921a49c3 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -53,6 +53,98 @@ export class Utils { document.body.removeChild(textArea); } + public static fromRGBAstr(rgba: string) { + let rm = rgba.match(/rgb[a]?\(([0-9]+)/); + let r = rm ? Number(rm[1]) : 0; + let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/); + let g = gm ? Number(gm[1]) : 0; + let bm = rgba.match(/rgb[a]?\([0-9]+,[0-9]+,([0-9]+)/); + let b = bm ? Number(bm[1]) : 0; + let am = rgba.match(/rgba?\([0-9]+,[0-9]+,[0-9]+,([0-9]+)/); + let a = am ? Number(am[1]) : 0; + return { r: r, g: g, b: b, a: a }; + } + public static toRGBAstr(col: { r: number, g: number, b: number, a?: number }) { + return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")"; + } + + public static HSLtoRGB(h: number, s: number, l: number) { + // Must be fractions of 1 + // s /= 100; + // l /= 100; + + let c = (1 - Math.abs(2 * l - 1)) * s, + x = c * (1 - Math.abs((h / 60) % 2 - 1)), + m = l - c / 2, + r = 0, + g = 0, + b = 0; + if (0 <= h && h < 60) { + r = c; g = x; b = 0; + } else if (60 <= h && h < 120) { + r = x; g = c; b = 0; + } else if (120 <= h && h < 180) { + r = 0; g = c; b = x; + } else if (180 <= h && h < 240) { + r = 0; g = x; b = c; + } else if (240 <= h && h < 300) { + r = x; g = 0; b = c; + } else if (300 <= h && h < 360) { + r = c; g = 0; b = x; + } + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + return { r: r, g: g, b: b }; + } + + public static RGBToHSL(r: number, g: number, b: number) { + // Make r, g, and b fractions of 1 + r /= 255; + g /= 255; + b /= 255; + + // Find greatest and smallest channel values + let cmin = Math.min(r, g, b), + cmax = Math.max(r, g, b), + delta = cmax - cmin, + h = 0, + s = 0, + l = 0; + // Calculate hue + + // No difference + if (delta == 0) + h = 0; + // Red is max + else if (cmax == r) + h = ((g - b) / delta) % 6; + // Green is max + else if (cmax == g) + h = (b - r) / delta + 2; + // Blue is max + else + h = (r - g) / delta + 4; + + h = Math.round(h * 60); + + // Make negative hues positive behind 360° + if (h < 0) + h += 360; // Calculate lightness + + l = (cmax + cmin) / 2; + + // Calculate saturation + s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + + // Multiply l and s by 100 + // s = +(s * 100).toFixed(1); + // l = +(l * 100).toFixed(1); + + return { h: h, s: s, l: l }; + } + + public static GetClipboardText(): string { var textArea = document.createElement("textarea"); document.body.appendChild(textArea); diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index bac0177ad..1d2d33800 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -14,7 +14,6 @@ export type KeyMap = { [key: string]: any }; export default function buildKeymap>(schema: S, mapKeys?: KeyMap): KeyMap { let keys: { [key: string]: any } = {}, type; - keys["ACTIVE"] = false; function bind(key: string, cmd: any) { if (mapKeys) { let mapped = mapKeys[key]; @@ -148,10 +147,6 @@ export default function buildKeymap>(schema: S, mapKeys?: return tx; } bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu - dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); - return true; - } var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 5d1131410..00e671db9 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -2,6 +2,9 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from import { schema } from "./RichTextSchema"; import { wrappingInputRule } from "./prosemirrorPatches"; import { NodeSelection } from "prosemirror-state"; +import { NumCast, Cast } from "../../new_fields/Types"; +import { Doc } from "../../new_fields/Doc"; +import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; export const inpRules = { rules: [ @@ -57,6 +60,12 @@ export const inpRules = { new InputRule( 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 heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) }), new InputRule( @@ -64,9 +73,40 @@ 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 heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "center"; + } return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), + new InputRule( + new RegExp(/^\[\[\s$/), + (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 heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "left"; + } + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + }), + new InputRule( + new RegExp(/^\]\]\s$/), + (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 heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "right"; + } + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + }), new InputRule( new RegExp(/\^f\s$/), (state, match, start, end) => { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3def4a579..f027a4bf7 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -12,6 +12,7 @@ import { DocServer } from "../DocServer"; import { Cast, NumCast } from "../../new_fields/Types"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; +import { times } from "async"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -422,6 +423,24 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return codeDOM; } }, + // pFontFamily: { + // attrs: { + // style: { default: 'font-family: "Times New Roman", Times, serif;' }, + // }, + // parseDOM: [{ + // tag: "span", getAttrs(dom: any) { + // if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` }; + // if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` }; + // if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` }; + // if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` }; + // if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` }; + // } + // }], + // toDOM: (node: any) => ['span', { + // style: node.attrs.style + // }] + // }, + /* FONTS */ timesNewRoman: { diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 890bfdfb7..68b97f2b6 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -250,6 +250,7 @@ export class ContextMenu extends React.Component { const item = this.flatItems[this.selectedIndex]; item && item.event(); this.closeMenu(); + e.preventDefault(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7cdb16f52..94aab8b2f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -621,6 +621,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> doc.y = (doc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... let fixedAspect = e.ctrlKey || (!BoolCast(doc.ignoreAspect) && nwidth && nheight); + if (fixedAspect && e.ctrlKey && BoolCast(doc.ignoreAspect)) { + doc.ignoreAspect = false; + proto.nativeWidth = nwidth = doc.width || 0; + proto.nativeHeight = nheight = doc.height || 0; + } if (fixedAspect && (!nwidth || !nheight)) { proto.nativeWidth = nwidth = doc.width || 0; proto.nativeHeight = nheight = doc.height || 0; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index eb6312e78..519792308 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -10,8 +10,10 @@ import { InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { undoBatch, UndoManager } from "../util/UndoManager"; import { StrCast, NumCast, Cast } from "../../new_fields/Types"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { MainOverlayTextBox } from "./MainOverlayTextBox"; +import { listSpec } from "../../new_fields/Schema"; +import { List } from "../../new_fields/List"; +import { Utils } from "../../Utils"; library.add(faPen, faHighlighter, faEraser, faBan); @@ -49,11 +51,34 @@ 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); - targetDoc.backgroundColor = this._selectedColor; + if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) { + let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[]; + let closest = 0; + let dist = 10000000; + let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor)); + for (let i = 0; i < cp.length; i++) { + let cpcol = Utils.fromRGBAstr(cp[i]); + let d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g)); + if (d < dist) { + dist = d; + closest = i; + } + } + cp[closest] = "rgb(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + ")"; + 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.Document.ruleProvider, Doc) as Doc); - cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor); + let parback = cv && StrCast(cv.props.Document.backgroundColor); + cv && parback && ((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); + // } } return { target: targetDoc, @@ -67,7 +92,6 @@ export class InkingControl extends React.Component { }); } }); - @action switchWidth = (width: string): void => { this._selectedWidth = width; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f7c1bedbb..c9b2c30b1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,7 @@ 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 } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs } from "../../../documents/Documents"; @@ -262,14 +262,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { newBox.heading = 2; } } - let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); - if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document; - let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); - round && (newBox.borderRounding = round); - col && (newBox.backgroundColor = col); - newBox.ruleProvider = ruleProvider; - this.addDocument(newBox, false); + 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)]); + col && (Doc.GetProto(newBox).backgroundColor = col); + + let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); + round && (newBox.borderRounding = round); + newBox.ruleProvider = ruleProvider; + this.addDocument(newBox, false); + }); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 100e6d817..56d8127e2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,11 +1,11 @@ import * as htmlToImage from "html-to-image"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, FieldResult } from "../../../../new_fields/Doc"; +import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; -import { Cast, NumCast } from "../../../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; import { Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; @@ -20,6 +20,8 @@ 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"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -272,14 +274,30 @@ export class MarqueeView extends React.Component return d; }); } + let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", + "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; + let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); + 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); + if (palette.indexOf(bg) !== -1) { + palette.splice(palette.indexOf(bg), 1); + if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); + else usedPaletted.set(bg, 1); + } + }); + let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); + let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0]; let inkData = this.ink ? this.ink.inkData : undefined; let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panX: 0, panY: 0, - backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", - defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", + backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, + defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, width: bounds.width, height: bounds.height, title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 6a6000dc5..0ea36cdc2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -13,7 +13,7 @@ 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 { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Utils, numberRange, timenow } from '../../../Utils'; import { DocServer } from "../../DocServer"; @@ -39,6 +39,7 @@ import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { inputRules } from 'prosemirror-inputrules'; +import { select } from 'async'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -203,8 +204,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } - this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu - this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); @@ -353,7 +352,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe _keymap: any = undefined; @computed get config() { this._keymap = buildKeymap(schema); - this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, plugins: this.props.isOverlay ? [ @@ -659,7 +657,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (this.props.Document[Id] === FormattedTextBox.SelectOnLoad) { + let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad; + if (selectOnLoad) { FormattedTextBox.SelectOnLoad = ""; this.props.select(false); } @@ -667,15 +666,38 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // 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) { - let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); - if (ruleProvider instanceof Doc) { - let font = StrCast(ruleProvider["ruleFont_" + heading]); - let size = NumCast(ruleProvider["ruleSize_" + heading]); - size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]); - font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]); - } + 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) { + case "Arial": return schema.marks.arial.create(); + case "Times New Roman": return schema.marks.timesNewRoman.create(); + case "Georgia": return schema.marks.georgia.create(); + case "Comic Sans MS": return schema.marks.comicSans.create(); + case "Tahoma": return schema.marks.tahoma.create(); + case "Impact": return schema.marks.impact.create(); + case "ACrimson Textrial": return schema.marks.crimson.create(); } + return schema.marks.arial.create(); } componentWillUnmount() { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 9866e22eb..9d35d36d3 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -115,17 +115,17 @@ export class CurrentUserUtils { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } }); - try { - const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" }); - NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json()); - await Gateway.Instance.ClearCatalog(); - const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []); - let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc))); - let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras); - // if (catprom) await Promise.all(catprom); - } catch (e) { + // try { + // const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" }); + // NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json()); + // await Gateway.Instance.ClearCatalog(); + // const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []); + // let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc))); + // let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras); + // // if (catprom) await Promise.all(catprom); + // } catch (e) { - } + // } } /* Northstar catalog ... really just for testing so this should eventually go away */ -- 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/client/views/nodes') 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 a709e21384cef80a85eac9220739c854a96d5313 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 20:15:40 -0400 Subject: fixed several search issues with text boxes and highlighting. --- .../collections/collectionFreeForm/MarqueeView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 16 ++++--- src/client/views/search/SearchBox.tsx | 6 +-- src/client/views/search/SearchItem.tsx | 56 ++-------------------- src/server/Search.ts | 3 ++ src/server/index.ts | 3 ++ 6 files changed, 27 insertions(+), 62 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 56d8127e2..0c4860be1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -288,8 +288,11 @@ export class MarqueeView extends React.Component else usedPaletted.set(bg, 1); } }); + usedPaletted.delete("#f1efeb"); + usedPaletted.delete("white"); + usedPaletted.delete("rgba(255,255,255,1)"); let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); - let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0]; + let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; let inkData = this.ink ? this.ink.inkData : undefined; let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 633455f63..1e755f121 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -644,12 +644,16 @@ export class DocumentView extends DocComponent(Docu 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: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) }); cm.addItem({ - description: "Download document", icon: "download", event: () => { - 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(); + description: "Download document", icon: "download", event: async () => { + let y = 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(); } }); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2ad69daca..2e29838e6 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -141,7 +141,7 @@ export class SearchBox extends React.Component { private get filterQuery() { const types = FilterBox.Instance.filterTypes; const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); + return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); } @@ -304,14 +304,14 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = ; + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = ; + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 386b5fe74..0b722c086 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -28,7 +28,7 @@ import "./SelectorContextMenu.scss"; export interface SearchItemProps { doc: Doc; - query?: string; + query: string; highlighting: string[]; } @@ -128,68 +128,26 @@ export class LinkContextMenu extends React.Component { export class SearchItem extends React.Component { @observable _selected: boolean = false; - private _previewDoc?: Doc; onClick = () => { // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like DocumentManager.Instance.jumpToDocument(this.props.doc, false); - if (this.props.doc.data instanceof RichTextField) { - this.highlightTextBox(this.props.doc); - } - // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined); } @observable _useIcons = true; @observable _displayDim = 50; - highlightTextBox = (doc: Doc) => { - if (this.props.query) { - const fieldkey = 'search_string'; - if (Object.keys(doc).indexOf(fieldkey) === -1) { - doc.search_string = this.props.query; - } - else { - doc.search_string = undefined; - } - - } - } - - fitToBox = () => { - let bounds = Doc.ComputeContentBounds([this.props.doc]); - return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim]; - } - componentWillUnmount() { - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } + this.props.doc.search_string = undefined; } - //@computed @action public DocumentIcon() { let layoutresult = StrCast(this.props.doc.type); if (!this._useIcons) { - let renderDoc = this.props.doc; - //let box: number[] = []; - if (layoutresult.indexOf(DocumentType.COL) !== -1) { - renderDoc = Doc.MakeDelegate(renderDoc); - let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { - var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim]; - } let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); let returnYDimension = () => this._displayDim; - let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); - let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt - this._previewDoc = newRenderDoc; + let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension()); const docview =
{ this._useIcons = !this._useIcons; @@ -219,15 +177,9 @@ export class SearchItem extends React.Component { ContentScaling={scale} />
; - const data = renderDoc.data; - if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); - newRenderDoc.preview = true; - newRenderDoc.search_string = this.props.query; + this.props.doc.search_string = this.props.query; return docview; } - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote : diff --git a/src/server/Search.ts b/src/server/Search.ts index 723dc101b..4a408405a 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -32,10 +32,13 @@ export class Search { public async search(query: any) { try { + console.log("SEARCH " + query + " " + (this.url + "dash/select") + " " + query.q); + console.log(query); const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: query })); const { docs, numFound } = searchResults.response; + console.log("RESULTS " + numFound); const ids = docs.map((field: any) => field.id); return { ids, numFound, highlighting: searchResults.highlighting }; } catch { diff --git a/src/server/index.ts b/src/server/index.ts index 50ce2b14e..149bacf0f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -198,12 +198,15 @@ const solrURL = "http://localhost:8983/solr/#/dash"; app.get("/search", async (req, res) => { const solrQuery: any = {}; + console.log("GOT SEARCH"); ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; } + console.log("CALLING SEARCH") let results = await Search.Instance.search(solrQuery); + console.log("RETURNING SEARCH") res.send(results); }); -- cgit v1.2.3-70-g09d2 From 4df85ecd5026127c27b147b34398822307715e54 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 21:36:42 -0400 Subject: fixed search highlighting --- .../views/collections/CollectionTreeView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 13 ++++++-- src/client/views/search/SearchBox.tsx | 38 +++++++++++----------- src/client/views/search/SearchItem.tsx | 12 ++++--- src/server/Search.ts | 3 -- src/server/index.ts | 3 -- 6 files changed, 38 insertions(+), 32 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 50f03005c..e31fa0b40 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -373,6 +373,7 @@ class TreeView extends React.Component { style={{ color: this.props.document.isMinimized ? "red" : "black", background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0", + fontWeight: this.props.document.search_string ? "bold" : undefined, outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" }} > diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1e755f121..73426b3dc 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -780,6 +780,10 @@ export class DocumentView extends DocComponent(Docu let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding); let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; + let searchHighlight = (!this.props.Document.search_fields ? (null) : +
+ {StrCast(this.props.Document.search_fields)} +
); return (
(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} > - {!showTitle && !showCaption ? this.contents : + {!showTitle && !showCaption ? + this.props.Document.search_fields ?
+ {this.contents} + {searchHighlight} +
: + this.contents :
-
{this.contents}
@@ -828,6 +836,7 @@ export class DocumentView extends DocComponent(Docu
} + {searchHighlight}
}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2e29838e6..b30fd83e8 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,25 +1,23 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action, runInAction, flow, computed } from 'mobx'; -import "./SearchBox.scss"; -import "./FilterBox.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { SetupDrag } from '../../util/DragManager'; -import { Docs } from '../../documents/Documents'; -import { NumCast, Cast } from '../../../new_fields/Types'; -import { Doc } from '../../../new_fields/Doc'; -import { SearchItem } from './SearchItem'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import * as rp from 'request-promise'; +import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; -import { SearchUtil } from '../../util/SearchUtil'; +import { Cast, NumCast } from '../../../new_fields/Types'; import { RouteStore } from '../../../server/RouteStore'; -import { FilterBox } from './FilterBox'; -import { ReadStream } from 'fs'; -import * as $ from 'jquery'; -import { MainView } from '../MainView'; import { Utils } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { SetupDrag } from '../../util/DragManager'; +import { SearchUtil } from '../../util/SearchUtil'; +import { MainView } from '../MainView'; +import { FilterBox } from './FilterBox'; +import "./FilterBox.scss"; +import "./SearchBox.scss"; +import { SearchItem } from './SearchItem'; library.add(faTimes); @@ -304,14 +302,16 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = ; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = ; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 0b722c086..30e0454f3 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -136,8 +136,13 @@ export class SearchItem extends React.Component { @observable _useIcons = true; @observable _displayDim = 50; + componentDidMount() { + this.props.doc.search_string = this.props.query; + this.props.doc.search_fields = this.props.highlighting.join(", "); + } componentWillUnmount() { this.props.doc.search_string = undefined; + this.props.doc.search_fields = undefined; } //@computed @@ -177,7 +182,6 @@ export class SearchItem extends React.Component { ContentScaling={scale} />
; - this.props.doc.search_string = this.props.query; return docview; } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : @@ -231,8 +235,7 @@ export class SearchItem extends React.Component { Doc.BrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element => - Doc.BrushDoc(element.props.Document)); + Doc.BrushDoc(this.props.doc); } } @@ -246,8 +249,7 @@ export class SearchItem extends React.Component { Doc.UnBrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc). - forEach(element => Doc.UnBrushDoc(element.props.Document)); + Doc.UnBrushDoc(this.props.doc); } } diff --git a/src/server/Search.ts b/src/server/Search.ts index 4a408405a..723dc101b 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -32,13 +32,10 @@ export class Search { public async search(query: any) { try { - console.log("SEARCH " + query + " " + (this.url + "dash/select") + " " + query.q); - console.log(query); const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: query })); const { docs, numFound } = searchResults.response; - console.log("RESULTS " + numFound); const ids = docs.map((field: any) => field.id); return { ids, numFound, highlighting: searchResults.highlighting }; } catch { diff --git a/src/server/index.ts b/src/server/index.ts index 149bacf0f..50ce2b14e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -198,15 +198,12 @@ const solrURL = "http://localhost:8983/solr/#/dash"; app.get("/search", async (req, res) => { const solrQuery: any = {}; - console.log("GOT SEARCH"); ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; } - console.log("CALLING SEARCH") let results = await Search.Instance.search(solrQuery); - console.log("RETURNING SEARCH") res.send(results); }); -- cgit v1.2.3-70-g09d2 From edec708b4396cd3b21ea22296812d5014b1359db Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 22:18:46 -0400 Subject: got rid of zoomBasis remnants. cleaned up (just a little bit) renderScript stuff in colelctionfreeformdocumentview --- src/client/views/DocumentDecorations.tsx | 3 +- .../views/collections/ParentDocumentSelector.tsx | 4 +- .../CollectionFreeFormLinkView.tsx | 8 ++-- .../CollectionFreeFormLinksView.tsx | 4 +- src/client/views/linking/LinkFollowBox.tsx | 12 +++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 50 +++++++++++----------- src/client/views/search/SearchBox.tsx | 1 - src/client/views/search/SearchItem.tsx | 4 +- src/scraping/buxton/scraper.py | 1 - 9 files changed, 41 insertions(+), 46 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 94aab8b2f..773ab8b9f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -397,8 +397,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } moveIconDoc(iconDoc: Doc) { let selView = SelectionManager.SelectedDocuments()[0]; - let zoom = NumCast(selView.props.Document.zoomBasis, 1); - let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).scale(1 / zoom). + let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()). transformPoint(this._minimizedX - 12, this._minimizedY - 12); iconDoc.x = where[0] + NumCast(selView.props.Document.x); iconDoc.y = where[1] + NumCast(selView.props.Document.y); diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 17111af58..d8475a467 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -38,8 +38,8 @@ export class SelectorContextMenu extends React.Component { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(target.x) + NumCast(target.width) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / 2; col.panX = newPanX; col.panY = newPanY; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 6af87b138..790c6694b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -39,10 +39,10 @@ export class CollectionFreeFormLinkView extends React.Component)[]) => field.findIndex(brush => { diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index d5ed01f53..f8807641b 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -180,8 +180,8 @@ export class LinkFollowBox extends React.Component { openColFullScreen = (options: { context: Doc }) => { if (LinkFollowBox.destinationDoc) { if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } @@ -209,8 +209,8 @@ export class LinkFollowBox extends React.Component { if (LinkFollowBox.destinationDoc) { options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } @@ -286,8 +286,8 @@ export class LinkFollowBox extends React.Component { if (LinkFollowBox.destinationDoc) { options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 9692dd8a9..eb7ab64f8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -19,7 +19,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { } const schema = createSchema({ - zoomBasis: "number", zIndex: "number", }); @@ -29,22 +28,36 @@ const FreeformDocument = makeInterface(schema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(FreeformDocument) { - @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg) scale(${this.zoom}) `; } - @computed get X() { return this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this.props.y !== undefined ? this.props.y : this.Document.y || 0; } - @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } - @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.height !== undefined ? this.props.height : this.Document.height || 0; } - @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); } + @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 renderScriptDim() { + if (this.Document.renderScript) { + let someView = Cast(this.Document.someView, Doc); + let minimap = Cast(this.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; + let w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); + let h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); + return { x: x, y: y, width: w, height: h }; + } + } + return undefined; + } + contentScaling = () => this.nativeWidth > 0 && !BoolCast(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.zoom / this.scaleToOverridingWidth) + .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth) animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { this.props.bringToFront(this.props.Document); @@ -77,21 +90,6 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; render() { - let txf = this.transform; - let w = this.width; - let h = this.height; - let renderScript = this.Document.renderScript; - if (renderScript) { - let someView = Cast(this.Document.someView, Doc); - let minimap = Cast(this.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; - w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); - h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); - txf = `translate(${x}px,${y}px)`; - } - } const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return (
1000) { x = 0; diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 30e0454f3..c56d093fa 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -71,8 +71,8 @@ export class SelectorContextMenu extends React.Component { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(target.x) + NumCast(target.width) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / 2; col.panX = newPanX; col.panY = newPanY; } diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 807216ef1..a9256073b 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -88,7 +88,6 @@ def write_collection(parse_results, display_fields, storage_key, viewType=2): "height": 600, "panX": 0, "panY": 0, - "zoomBasis": 1, "zIndex": 2, "libraryBrush": False, "viewType": viewType -- cgit v1.2.3-70-g09d2 From f4df8cedd38dbf09e282315604ecaa6cde8185e5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Sep 2019 00:56:26 -0400 Subject: added start of parameterized button maker --- src/client/views/ContextMenuItem.tsx | 4 ++-- src/client/views/EditableView.tsx | 2 +- src/client/views/ScriptBox.tsx | 41 +++++++++++++++++++++++++++++++++ src/client/views/nodes/ButtonBox.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 1 + 5 files changed, 47 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 0366a6a30..ac055be5b 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -10,7 +10,7 @@ library.add(faAngleRight); export interface OriginalMenuProps { description: string; - event: () => void; + event: (stuff?: any) => void; undoable?: boolean; icon: IconProp; //maybe should be optional (icon?) closeMenu?: () => void; @@ -44,7 +44,7 @@ export class ContextMenuItem extends React.Component { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); return (
{this.props.contents}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 7afba5e01..375a5cc93 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -10,6 +10,8 @@ import { emptyFunction } from "../../Utils"; import { ScriptCast } from "../../new_fields/Types"; import { CompileScript } from "../util/Scripting"; import { ScriptField } from "../../new_fields/ScriptField"; +import { DragManager } from "../util/DragManager"; +import { EditableView } from "./EditableView"; export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; @@ -66,6 +68,45 @@ 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]);} + public static EditButtonScript(doc: Doc, fieldKey: string, content: any, clientX: number, clientY: number) { + let overlayDisposer: () => void = emptyFunction; + const script = ScriptCast(doc[fieldKey]); + let originalText = script && script.script.originalScript; + // tslint:disable-next-line: no-unnecessary-callback-wrapper + let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + const script = CompileScript(text, { + params: { this: Doc.name }, + typecheck: false, + editable: true, + transformer: DocumentIconContainer.getTransformer() + }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join("\n")); + return; + } + + DragManager.StartButtonDrag([], text, "a script", + {}, this._params, (button: Doc) => { }, clientX, clientY); + + doc[fieldKey] = new ScriptField(script); + overlayDisposer(); + }} showDocumentIcons />; + let params = ""} + SetValue={(value: string) => (this._params = value.split(" ").filter(s => s !== " ")) ? true : true} + />; + let box =
+ {scriptingBox} + {params} +
+ overlayDisposer = OverlayView.Instance.addWindow(box, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` }); + } + static _params: string[] = []; public static EditClickScript(doc: Doc, fieldKey: string, prewrapper?: string, postwrapper?: string) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 54848344b..db4bb7972 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -44,8 +44,8 @@ export class ButtonBox extends DocComponent(Butt @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { - Doc.GetProto(this.dataDoc).source = new List(de.data.droppedDocuments); + if (de.data instanceof DragManager.DocumentDragData && e.target) { + Doc.GetProto(this.dataDoc)[(e.target as any).textContent] = new List(de.data.droppedDocuments); e.stopPropagation(); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 73426b3dc..84169cc93 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -601,6 +601,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: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, 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({ -- 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/client/views/nodes') 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 d7d73856ed405cef01b4cf727ca56f4d9a31e894 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 15:45:37 -0400 Subject: cleaned up ScriptBox EditScript's restructured menus for dealing with onClicks... exposed more layout paramters for EditableViews. --- src/client/views/ContextMenuItem.tsx | 4 +- src/client/views/EditableView.tsx | 5 +- src/client/views/ScriptBox.tsx | 67 +++++++--------------- .../views/collections/CollectionSchemaCells.tsx | 3 +- .../views/collections/CollectionStackingView.tsx | 6 +- .../views/collections/CollectionTreeView.tsx | 5 +- src/client/views/nodes/ButtonBox.tsx | 19 +++++- src/client/views/nodes/DocumentView.tsx | 42 ++++++++------ src/client/views/nodes/KeyValuePair.tsx | 3 +- 9 files changed, 82 insertions(+), 72 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index ac055be5b..5f673b3f3 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -93,13 +93,13 @@ export class ContextMenuItem extends React.Component )}
; return ( -
+
{this.props.icon ? ( ) : null} -
+
{this.props.description}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 1bdb26b3d..e9db4b048 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -28,7 +28,8 @@ export interface EditableProps { contents: any; fontStyle?: string; fontSize?: number; - height?: number; + height?: number | "auto"; + maxHeight?: number; display?: string; autosuggestProps?: { resetValue: () => void; @@ -145,7 +146,7 @@ export class EditableView extends React.Component { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); return (
{this.props.contents}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 1f3673390..8f08224c8 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -18,6 +18,7 @@ export interface ScriptBoxProps { onCancel?: () => void; initialText?: string; showDocumentIcons?: boolean; + setParams?: (p: string[]) => void; } @observer @@ -58,56 +59,30 @@ export class ScriptBox extends React.Component { onFocus = this.onFocus; onBlur = this.onBlur; } + let params = ""} + SetValue={(value: string) => this.props.setParams && this.props.setParams(value.split(" ").filter(s => s !== " ")) ? true : true} + />; return (
+
+ +
{params}
+
-
); } //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]); - let originalText = script && script.script.originalScript; - // tslint:disable-next-line: no-unnecessary-callback-wrapper - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { - const script = CompileScript(text, { - params: { this: Doc.name }, - typecheck: false, - editable: true, - transformer: DocumentIconContainer.getTransformer() - }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join("\n")); - return; - } - - DragManager.StartButtonDrag([], text, "a script", - {}, this._params, (button: Doc) => { }, clientX, clientY); - - doc[fieldKey] = new ScriptField(script); - overlayDisposer(); - }} showDocumentIcons />; - let params = ""} - SetValue={(value: string) => (this._params = value.split(" ").filter(s => s !== " ")) ? true : true} - />; - let box =
- {scriptingBox} - {params} -
- overlayDisposer = OverlayView.Instance.addWindow(box, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` }); - } - static _params: string[] = []; - public static EditClickScript(doc: Doc, fieldKey: string, prewrapper?: string, postwrapper?: string) { + public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, prewrapper?: string, postwrapper?: string) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); let originalText: string | undefined = undefined; @@ -121,10 +96,9 @@ export class ScriptBox extends React.Component { } } // tslint:disable-next-line: no-unnecessary-callback-wrapper - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { - if (prewrapper) { - text = prewrapper + text + (postwrapper ? postwrapper : ""); - } + let params: string[] = []; + let setParams = (p: string[]) => params.splice(0, params.length, ...p); + let scriptingBox = overlayDisposer()} onSave={(text, onError) => { const script = CompileScript(text, { params: { this: Doc.name }, typecheck: false, @@ -135,9 +109,12 @@ export class ScriptBox extends React.Component { onError(script.errors.map(error => error.messageText).join("\n")); return; } + + params.length && DragManager.StartButtonDrag([], text, "a script", {}, params, (button: Doc) => { }, clientX, clientY); + doc[fieldKey] = new ScriptField(script); overlayDisposer(); }} showDocumentIcons />; - overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` }); + overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: title }); } } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 9c26a08f0..c59107b53 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -214,7 +214,8 @@ export class CollectionSchemaCell extends React.Component { isEditingCallback={this.isEditingCallback} display={"inline"} contents={contents} - height={Number(MAX_ROW_HEIGHT)} + height={"auto"} + maxHeight={Number(MAX_ROW_HEIGHT)} GetValue={() => { let field = props.Document[props.fieldKey]; if (Field.IsField(field)) { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 654ff2279..91e10b0ac 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -362,8 +362,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" }); subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" }); subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" }); - subItems.push({ description: "Edit onChildClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onChildClick") }); ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" }); + + let existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index e31fa0b40..8539b3fcc 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -592,13 +592,14 @@ export class CollectionTreeView extends CollectionSubView(Document) {
(e.target as any).scrollHeight > (e.target as any).clientHeight && e.stopPropagation()} + onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} onDrop={this.onTreeDrop} ref={this.createTreeDropTarget}> StrCast(this.resolvedDataDoc.title)} SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)} OnFillDown={undoBatch((value: string) => { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index db4bb7972..68d3b8ae1 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -13,6 +13,8 @@ import { undoBatch } from '../../util/UndoManager'; import { DocComponent } from '../DocComponent'; import './ButtonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ContextMenu } from '../ContextMenu'; library.add(faEdit as any); @@ -41,11 +43,24 @@ export class ButtonBox extends DocComponent(Butt this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); } } + + specificContextMenu = (e: React.MouseEvent): void => { + let funcs: ContextMenuProps[] = []; + 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) + }, icon: "trash" + }); + + ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" }); + } + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData && e.target) { - Doc.GetProto(this.dataDoc)[(e.target as any).textContent] = new List(de.data.droppedDocuments); + this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments); e.stopPropagation(); } } @@ -55,7 +70,7 @@ export class ButtonBox extends DocComponent(Butt 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 ... return ( -
+
{(this.Document.text || this.Document.title)} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ca8fb573f..9c2cf5f01 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -446,10 +446,10 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeCustomViewClicked = (): void => { - let options = { title: "data", width: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) }; + 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 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 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); @@ -460,15 +460,21 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); - doc.isButton = !BoolCast(doc.isButton); - if (doc.isButton) { - if (!doc.nativeWidth) { - doc.nativeWidth = this.props.Document[WidthSym](); - doc.nativeHeight = this.props.Document[HeightSym](); - } + if (doc.isButton || doc.onClick) { + doc.isButton = false; + doc.onClick = undefined; } else { - doc.nativeWidth = doc.nativeHeight = undefined; + doc.isButton = true; } + + // 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 @@ -623,21 +629,25 @@ export class DocumentView extends DocComponent(Docu 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" }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); + 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: 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: "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: "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: this.props.Document.isButton || this.props.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) => ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n") }); + !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; - 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" }); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 5afd4d834..a27dbd83d 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -112,7 +112,8 @@ export class KeyValuePair extends React.Component {
{ return Field.toKeyValueString(props.Document, props.fieldKey); }} -- cgit v1.2.3-70-g09d2 From 4722644d09a561e394bd72c92af5561a2020776e Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 16:28:14 -0400 Subject: fixed collection iteration onClick script --- src/client/views/ScriptBox.tsx | 3 +++ src/client/views/collections/CollectionBaseView.tsx | 3 --- src/client/views/nodes/DocumentView.tsx | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 8f08224c8..8f06cf770 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -99,6 +99,9 @@ export class ScriptBox extends React.Component { let params: string[] = []; let setParams = (p: string[]) => params.splice(0, params.length, ...p); 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 bd8d56851..5829f0626 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -104,9 +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; } - 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/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9c2cf5f01..940a66b36 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -643,7 +643,12 @@ export class DocumentView extends DocComponent(Docu let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; 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: "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) => ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n") }); + 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; + ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n"); + } + }); !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); -- cgit v1.2.3-70-g09d2 From 68e554cafb6107bfde9526773b3e0e667d582c88 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 17:24:48 -0400 Subject: added default note type collection --- src/client/documents/Documents.ts | 1 + src/client/views/Main.tsx | 1 - src/client/views/PreviewCursor.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 +++++++++------- .../views/collections/collectionFreeForm/MarqueeView.tsx | 6 ++++++ src/client/views/nodes/FormattedTextBox.tsx | 3 ++- src/server/authentication/models/current_user_utils.ts | 12 +++++++++++- 7 files changed, 30 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ae65fde1e..602a7f9ad 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -66,6 +66,7 @@ export interface DocumentOptions { page?: number; scale?: number; layout?: string; + isTemplate?: boolean; templates?: List; viewType?: number; backgroundColor?: string; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 0e687737d..11ec6f0c9 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -37,7 +37,6 @@ let swapDocs = async () => { (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled"; - CurrentUserUtils.UserDocument.chromeStatus = "disabled"; await swapDocs(); document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 45a8556bf..1aed51e64 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -102,7 +102,7 @@ export class PreviewCursor extends React.Component<{}> { (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys !e.key.startsWith("Arrow") && !e.defaultPrevented) { - if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { + if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); PreviewCursor.Visible = false; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 21f119d57..2591bdd8d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -39,6 +39,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { DocServer } from "../../../DocServer"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); @@ -272,7 +273,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // 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)]); - col && (Doc.GetProto(newBox).backgroundColor = col); + (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col); let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); round && (Doc.GetProto(newBox).borderRounding = round); @@ -945,17 +946,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); let noteItems: ContextMenuProps[] = []; - noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); - noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); - noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); - noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); + 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" }) ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } - createText = (noteStyle: string, color: string) => { + createText = (noteStyle: number) => { let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color })) + 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 = () => [ diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0c4860be1..fe48a3485 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -22,6 +22,7 @@ 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; @@ -97,6 +98,11 @@ export class MarqueeView extends React.Component } else if (!e.ctrlKey) { this.props.addLiveTextDocument( Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" })); + } else if (e.keyCode > 48 && e.keyCode <= 57) { + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + let text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }); + text.layout = notes[(e.keyCode - 49) % notes.length]; + this.props.addLiveTextDocument(text); } e.stopPropagation(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0ea36cdc2..194026a08 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -289,7 +289,8 @@ 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) !== "") { - this.props.Document.layout = draggedDoc; + if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc; + else this.props.Document.layout = draggedDoc; draggedDoc.isTemplate = true; e.stopPropagation(); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 9d35d36d3..af5774ebe 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -7,7 +7,7 @@ import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/nort import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil"; import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView"; import { CollectionView } from "../../../client/views/collections/CollectionView"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types"; @@ -50,6 +50,16 @@ export class CurrentUserUtils { doc.workspaces = workspaces; } PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true)); + if (doc.noteTypes === undefined) { + let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }), + Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }), + Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }), + Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })]; + const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 }); + noteTypes.excludeFromLibrary = true; + doc.noteTypes = noteTypes; + } + PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(vals => DocListCast(vals))); if (doc.recentlyClosed === undefined) { const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 }); recentlyClosed.excludeFromLibrary = true; -- 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/client/views/nodes') 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 daeb624194c324600674198ebd9ec758383019d4 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Sep 2019 08:46:33 -0400 Subject: added custom views for documents --- src/client/views/collections/CollectionView.tsx | 6 ++-- src/client/views/nodes/DocumentView.tsx | 45 +++++++++++++++++++------ 2 files changed, 38 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 6182e82f4..a17899b8b 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -87,7 +87,8 @@ export class CollectionView extends React.Component { onContextMenu = (e: React.MouseEvent): void => { if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - let subItems: ContextMenuProps[] = []; + let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); + let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; delete this.props.Document.usePivotLayout; }, icon: "signature" }); if (CollectionBaseView.InSafeMode()) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); @@ -103,7 +104,8 @@ export class CollectionView extends React.Component { break; } } - ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); + !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); + let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c2143bb82..6960689b4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -9,7 +9,7 @@ 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 } from "../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { RouteStore } from '../../../server/RouteStore'; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; @@ -444,17 +444,32 @@ export class DocumentView extends DocComponent(Docu this.props.addDocTab(kvp, this.dataDoc, "onRight"); } + @undoBatch + makeNativeViewClicked = (): void => { + this.props.Document.customLayout = this.props.Document.layout; + this.props.Document.layout = this.props.Document.nativeLayout; + this.props.Document.type = this.props.Document.nativeType; + } @undoBatch makeCustomViewClicked = (): void => { - 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 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.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); + this.props.Document.nativeLayout = this.props.Document.layout; + this.props.Document.nativeType = this.props.Document.type; + PromiseValue(this.props.Document.customLayout).then(custom => { + if (custom) { + this.props.Document.type = DocumentType.TEMPLATE; + this.props.Document.layout = custom; + } 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 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.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); + } + }); } @undoBatch @@ -630,10 +645,18 @@ 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" }); + let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); + let vms: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; + if (this.props.Document.type !== DocumentType.COL && this.props.Document.type !== DocumentType.TEMPLATE) { + vms.push({ description: "Custom Document View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); + } else if (this.props.Document.nativeLayout) { + vms.push({ description: "Native Document View", event: this.makeNativeViewClicked, icon: "concierge-bell" }); + } + !existingVm && cm.addItem({ description: "View Modes...", subitems: vms, icon: "eye" }); + 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: 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), 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" }); -- cgit v1.2.3-70-g09d2 From 91480dbd1b734795f514281ee0a2dac2442d6e84 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 12 Sep 2019 11:06:23 -0400 Subject: cleaned up menus. simplified making custom views through template dropdown menu --- src/client/views/ContextMenu.scss | 4 +- src/client/views/ContextMenuItem.tsx | 4 +- src/client/views/DocumentDecorations.scss | 2 +- src/client/views/DocumentDecorations.tsx | 18 +----- src/client/views/TemplateMenu.tsx | 70 ++++++++++++---------- src/client/views/Templates.tsx | 38 +----------- src/client/views/collections/CollectionView.tsx | 5 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 44 +++++++------- src/client/views/nodes/DocumentView.tsx | 59 ++++-------------- src/client/views/nodes/ImageBox.tsx | 5 +- src/client/views/nodes/KeyValueBox.tsx | 27 ++++----- 11 files changed, 96 insertions(+), 180 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index e2c0de8af..8f112de0c 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -124,7 +124,9 @@ } .icon-background { - pointer-events: none; + pointer-events: all; + height:100%; + margin-top: 15px; background-color: transparent; width: 35px; text-align: center; diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 5f673b3f3..330b94afa 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -93,9 +93,9 @@ export class ContextMenuItem extends React.Component )}
; return ( -
+
{this.props.icon ? ( - + ) : null} diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index ac8497bd0..4ab5d733f 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -257,7 +257,7 @@ $linkGap : 3px; padding: 2px 12px; list-style: none; - .templateToggle { + .templateToggle, .chromeToggle { text-align: left; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 814d718be..6451fdf5e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -849,24 +849,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let templates: Map = new Map(); Array.from(Object.values(Templates.TemplateList)).map(template => { - let sorted = SelectionManager.ViewsSortedVertically(); - let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => { - let temps = doc.props.Document.templates; - if (temps instanceof List) { - temps.map(temp => { - if (temp !== Templates.Bullet.Layout || i === 0) { - res.push(temp); - } - }); - } - return res; - }, [] as string[]); let checked = false; - docTemps.forEach(temp => { - if (template.Layout === temp) { - checked = true; - } - }); + SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.props.Document["show" + template.Name] !== undefined)); templates.set(template, checked); }); diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 393e97a7e..0586b31e4 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,16 +1,14 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; -import './DocumentDecorations.scss'; -import { DocumentView } from "./nodes/DocumentView"; -import { Template } from "./Templates"; -import React = require("react"); -import { undoBatch } from "../util/UndoManager"; +import { DocumentType } from "../documents/DocumentTypes"; import { DocumentManager } from "../util/DocumentManager"; -import { NumCast } from "../../new_fields/Types"; import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; +import { undoBatch } from "../util/UndoManager"; +import './DocumentDecorations.scss'; +import { DocumentView } from "./nodes/DocumentView"; +import { Template, Templates } from "./Templates"; +import React = require("react"); const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -30,6 +28,17 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool } } } +@observer +class ChromeToggle extends React.Component<{ checked: boolean, toggle: (event: React.ChangeEvent) => void }> { + render() { + return ( +
  • + this.props.toggle(event)} /> + Chrome +
  • + ); + } +} export interface TemplateMenuProps { docs: DocumentView[]; @@ -45,6 +54,16 @@ export class TemplateMenu extends React.Component { 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(); + } + }); + } + toggleFloat = (e: React.MouseEvent): void => { SelectionManager.DeselectAll(); let topDocView = this.props.docs[0]; @@ -80,55 +99,42 @@ export class TemplateMenu extends React.Component { @action toggleTemplate = (event: React.ChangeEvent, template: Template): void => { if (event.target.checked) { - if (template.Name === "Bullet") { - let topDocView = this.props.docs[0]; - topDocView.addTemplate(template); - topDocView.props.Document.subBulletDocs = new List(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document)); - } else { - this.props.docs.map(d => d.addTemplate(template)); - } - this.props.templates.set(template, true); + this.props.docs.map(d => d.props.Document["show" + template.Name] = template.Name.toLowerCase()); } else { - if (template.Name === "Bullet") { - let topDocView = this.props.docs[0]; - topDocView.removeTemplate(template); - topDocView.props.Document.subBulletDocs = undefined; - } else { - this.props.docs.map(d => d.removeTemplate(template)); - } - this.props.templates.set(template, false); + this.props.docs.map(d => d.props.Document["show" + template.Name] = undefined); } } @undoBatch @action clearTemplates = (event: React.MouseEvent) => { - this.props.docs.map(d => d.clearTemplates()); - Array.from(this.props.templates.keys()).map(t => this.props.templates.set(t, false)); + Templates.TemplateList.map(template => this.props.docs.map(d => d.props.Document["show" + template.Name] = false)); } @action - componentWillReceiveProps(nextProps: TemplateMenuProps) { - // this._templates = nextProps.templates; + toggleTemplateActivity = (): void => { + this._hidden = !this._hidden; } + @undoBatch @action - toggleTemplateActivity = (): void => { - this._hidden = !this._hidden; + toggleChrome = (): void => { + this.props.docs.map(dv => dv.layoutDoc.chromeStatus = (dv.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled")); } render() { let templateMenu: Array = []; this.props.templates.forEach((checked, template) => templateMenu.push()); - + templateMenu.push(); return (
    this.toggleTemplateActivity()}>+
      {templateMenu} + - + {/* */}
    ); diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 236704fa2..ef78b60d4 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -57,43 +57,7 @@ export namespace Templates {
    ` ); - export const Header = new Template("Header", TemplatePosition.InnerTop, - `
    -
    - -
    -
    {layout}
    -
    ` ); - - export const Bullet = new Template("Bullet", TemplatePosition.InnerTop, - `< div > -
    {layout}
    -
    - -
    -
    ` - ); - - export function ImageOverlay(width: number, height: number, field: string = "thumbnail") { - return (`< div > -
    {layout}
    -
    - -
    -
    `); - } - - export function TitleBar(datastring: string) { - return (`
    -
    - ${datastring} -
    -
    -
    {layout}
    -
    -
    ` ); - } - export const TemplateList: Template[] = [Title, Header, Caption, Bullet]; + export const TemplateList: Template[] = [Title, Caption]; export function sortTemplates(a: Template, b: Template) { if (a.Position < b.Position) { return -1; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a17899b8b..9caa4ea37 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -110,11 +110,6 @@ export class CollectionView extends React.Component { let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); - - let makes = ContextMenu.Instance.findByDescription("Make..."); - let makeItems: ContextMenuProps[] = makes && "subitems" in makes ? makes.subitems : []; - makeItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); - !makes && ContextMenu.Instance.addItem({ description: "Make...", subitems: makeItems, icon: "hand-point-right" }); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2591bdd8d..24be5963f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -894,6 +894,30 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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" }); + } + 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: "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: () => { const input = document.createElement("input"); @@ -924,26 +948,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { input.click(); } }); - 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: "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.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" - }); - 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: "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" }); let noteItems: ContextMenuProps[] = []; let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6960689b4..2d17c09e5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -147,14 +147,6 @@ export class DocumentView extends DocComponent(Docu public get ContentDiv() { return this._mainCont.current; } @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @computed get topMost(): boolean { return this.props.renderDepth === 0; } - @computed get templates(): List { - let field = this.props.Document.templates; - if (field && field instanceof List) { - return field; - } - return new List(); - } - set templates(templates: List) { this.props.Document.templates = templates; } screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); @action @@ -449,6 +441,7 @@ export class DocumentView extends DocComponent(Docu 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.nativeLayout = undefined; } @undoBatch makeCustomViewClicked = (): void => { @@ -553,28 +546,6 @@ export class DocumentView extends DocComponent(Docu } } - @action - addTemplate = (template: Template) => { - this.templates.push(template.Layout); - this.templates = this.templates; - } - - @action - removeTemplate = (template: Template) => { - for (let i = 0; i < this.templates.length; i++) { - if (this.templates[i] === template.Layout) { - this.templates.splice(i, 1); - break; - } - } - this.templates = this.templates; - } - @action - clearTemplates = () => { - this.templates.length = 0; - this.templates = this.templates; - } - @undoBatch @action freezeNativeDimensions = (): void => { @@ -645,25 +616,11 @@ 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" }); - let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); - let vms: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; - if (this.props.Document.type !== DocumentType.COL && this.props.Document.type !== DocumentType.TEMPLATE) { - vms.push({ description: "Custom Document View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); - } else if (this.props.Document.nativeLayout) { - vms.push({ description: "Native Document View", event: this.makeNativeViewClicked, icon: "concierge-bell" }); - } - !existingVm && cm.addItem({ description: "View Modes...", subitems: vms, icon: "eye" }); - - 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: "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: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); onClicks.push({ @@ -676,6 +633,10 @@ 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.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" }); @@ -685,6 +646,11 @@ export class DocumentView extends DocComponent(Docu 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: this.makeCustomViewClicked, icon: "concierge-bell" }); + } else if (this.props.Document.nativeLayout) { + layoutItems.push({ description: "Use Native Layout", event: this.makeNativeViewClicked, icon: "concierge-bell" }); + } !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); if (!ClientUtils.RELEASE) { let copies: ContextMenuProps[] = []; @@ -698,7 +664,6 @@ export class DocumentView extends DocComponent(Docu !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); 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: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) }); cm.addItem({ description: "Download document", icon: "download", event: async () => { let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 6fc94a140..19788c21a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -216,12 +216,13 @@ export class ImageBox extends DocComponent(ImageD funcs.push({ description: "Record 1sec audio", event: this.recordAudioAnnotation, icon: "expand-arrows-alt" }); funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" }); - let modes: ContextMenuProps[] = []; + let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); + 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" }) ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" }); - ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" }); } } diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 653c5c27f..f80f414b1 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -2,26 +2,21 @@ import { action, computed, 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 { CompileScript, ScriptOptions, CompiledScript } from "../../util/Scripting"; -import { FieldView, FieldViewProps } from './FieldView'; -import "./KeyValueBox.scss"; -import { KeyValuePair } from "./KeyValuePair"; -import React = require("react"); -import { NumCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types"; -import { Doc, Field, FieldResult, DocListCastAsync } from "../../../new_fields/Doc"; -import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; -import { SetupDrag } from "../../util/DragManager"; -import { Docs } from "../../documents/Documents"; -import { RawDataOperationParameters } from "../../northstar/model/idea/idea"; -import { Templates } from "../Templates"; +import { Doc, Field, FieldResult } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; -import { TextField } from "../../util/ProsemirrorCopy/prompt"; import { RichTextField } from "../../../new_fields/RichTextField"; -import { ImageField } from "../../../new_fields/URLField"; -import { SelectionManager } from "../../util/SelectionManager"; import { listSpec } from "../../../new_fields/Schema"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { ImageField } from "../../../new_fields/URLField"; +import { Docs } from "../../documents/Documents"; +import { SetupDrag } from "../../util/DragManager"; +import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting"; import { undoBatch } from "../../util/UndoManager"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./KeyValueBox.scss"; +import { KeyValuePair } from "./KeyValuePair"; +import React = require("react"); export type KVPScript = { script: CompiledScript; -- cgit v1.2.3-70-g09d2 From 03579f16e2c18ef8af578f5e65da3939ee9860ee Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 12 Sep 2019 14:13:34 -0400 Subject: fixe template custom toggling. --- src/client/views/nodes/DocumentView.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2d17c09e5..4bb31ef1d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -441,16 +441,28 @@ export class DocumentView extends DocComponent(Docu 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; + 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; } @undoBatch makeCustomViewClicked = (): void => { this.props.Document.nativeLayout = this.props.Document.layout; this.props.Document.nativeType = this.props.Document.type; - PromiseValue(this.props.Document.customLayout).then(custom => { + this.props.Document.nativeNativeWidth = this.props.Document.nativeWidth; + this.props.Document.nativeNativeHeight = this.props.Document.nativeHeight; + this.props.Document.nativeIgnoreAspect = this.props.Document.ignoreAspect; + PromiseValue(Cast(this.props.Document.customLayout, Doc)).then(custom => { if (custom) { this.props.Document.type = DocumentType.TEMPLATE; this.props.Document.layout = custom; + !custom.nativeWidth && (this.props.Document.nativeWidth = 0); + !custom.nativeHeight && (this.props.Document.nativeHeight = 0); + !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); -- cgit v1.2.3-70-g09d2 From 612dbe02514f7f34c066aee3b5dd09c4a99b113a Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 12 Sep 2019 14:45:24 -0400 Subject: moved customLayout to data doc. need to allow for multiple customLayouts now. --- src/client/views/nodes/DocumentView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4bb31ef1d..3f0b62511 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -438,7 +438,7 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeNativeViewClicked = (): void => { - this.props.Document.customLayout = this.props.Document.layout; + (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; -- cgit v1.2.3-70-g09d2 From 147f1a6bed7f273b6248d55eee670713bfbf5e7d Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 12 Sep 2019 17:07:59 -0400 Subject: better template inferencing support --- src/client/util/RichTextRules.ts | 9 +-- src/client/views/GlobalKeyHandler.ts | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 ++-- src/client/views/nodes/FormattedTextBox.scss | 1 - src/client/views/nodes/FormattedTextBox.tsx | 86 ++++++++++++---------- 5 files changed, 61 insertions(+), 52 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 00e671db9..7e3d435a7 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -78,8 +78,7 @@ export const inpRules = { if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "center"; } - return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; + return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), new InputRule( new RegExp(/^\[\[\s$/), @@ -91,8 +90,7 @@ export const inpRules = { if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "left"; } - return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; + return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), new InputRule( new RegExp(/^\]\]\s$/), @@ -104,8 +102,7 @@ export const inpRules = { if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "right"; } - return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; + return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), new InputRule( new RegExp(/\^f\s$/), diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d0464bd5f..f9ee22f61 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -30,7 +30,7 @@ export default class KeyManager { } public handle = async (e: KeyboardEvent) => { - let keyname = e.key.toLowerCase(); + let keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); if (modifiers.includes(keyname)) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 24be5963f..24f2a60a9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,7 @@ 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"; @@ -257,12 +257,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 i = 0; i < this.childDocs.length; i++) { - if (this.childDocs[i].heading == 1) { - newBox.heading = 2; - } + 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; + 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 = NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } + newBox.heading = heading; + PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { if (!ruleProvider) ruleProvider = this.props.Document; // saturation shift 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 0ea36cdc2..444b91b28 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -39,7 +39,6 @@ import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { inputRules } from 'prosemirror-inputrules'; -import { select } from 'async'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -79,15 +78,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 = ""; @@ -119,14 +122,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; } @@ -253,12 +255,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) => { @@ -376,6 +372,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentDidMount() { + if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { @@ -458,6 +455,39 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); + + + this._rulesReactionDisposer = reaction(() => { + let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); + 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 { align: "", font: "Arial", size: 13 }; + }, + action((rules: any) => { + this._fontFamily = rules.font; + this._fontSize = rules.size; + setTimeout(() => { + let tr = this._editorView!.state.tr; + let n = new NodeSelection(this._editorView!.state.doc.resolve(0)); + if (this._editorView!.state.doc.textContent === "") { + tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). + replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true). + setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); + } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) { + tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }); + } + this._editorView!.dispatch(tr); + this.tryUpdateHeight(); + }, 0); + }), { fireImmediately: true } + ); + setTimeout(() => this.tryUpdateHeight(), 0); } @@ -665,27 +695,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) { @@ -701,6 +710,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(); @@ -866,7 +876,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"); @@ -885,7 +895,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - render() { let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; @@ -901,7 +910,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit", pointerEvents: interactive, - fontSize: "13px" + fontSize: this._fontSize, + fontFamily: this._fontFamily, }} onKeyDown={this.onKeyPress} onFocus={this.onFocused} -- cgit v1.2.3-70-g09d2 From 47ca25c49d7d9f1fee22b256f86e296dac42b47b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Sep 2019 21:42:07 -0400 Subject: cleaned up rulerProvider a bit and added menu item to turn it on. --- src/client/views/DocumentDecorations.tsx | 13 +++++---- src/client/views/InkingControl.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 31 +++++++--------------- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 3 +++ src/client/views/nodes/DocumentView.tsx | 12 ++++++--- src/client/views/nodes/FormattedTextBox.tsx | 4 +-- 7 files changed, 31 insertions(+), 36 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 6451fdf5e..4ab2ade8e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -424,23 +424,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.addEventListener("pointermove", this.onRadiusMove); document.addEventListener("pointerup", this.onRadiusUp); } - if (!this._isMoving) { - 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 = "0%"); - } } onRadiusMove = (e: PointerEvent): void => { this._isMoving = true; let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1])); - 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)}%`); + dist = dist < 3 ? 0 : dist; + let usingRule = false; SelectionManager.SelectedDocuments().map(dv => { let cv = dv.props.ContainingCollectionView; let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); let heading = NumCast(dv.props.Document.heading); - cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleRounding_" + heading] = StrCast(dv.props.Document.borderRounding)); + 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(); e.preventDefault(); } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index aa573f16b..867735c0b 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -73,7 +73,7 @@ export class InkingControl extends React.Component { 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 && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)); + cv && parback && (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/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 24f2a60a9..9a8ae3535 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -262,27 +262,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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 = NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); + heading = !sorted.length ? 1 : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } newBox.heading = 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); - }); + if (Cast(this.props.Document.ruleProvider, Doc) as Doc) { + newBox.ruleProvider = Doc.GetProto(Cast(this.props.Document.ruleProvider, Doc) as Doc); + } + this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); @@ -898,7 +885,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; - if (this.childDocs.some(d => d.isTemplate)) { + if (this.childDocs.some(d => BoolCast(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" }); } layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); @@ -913,9 +900,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, 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, + 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: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index fe48a3485..4308497a1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -356,7 +356,7 @@ export class MarqueeView extends React.Component this.props.addLiveTextDocument(summary); } else { - newCollection.ruleProvider = this.props.container.props.Document; + 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 07dd1cae7..082e5c5e3 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -74,7 +74,10 @@ export class CollectionFreeFormDocumentView extends DocComponent { + let ruleProvider = this.props.Document.ruleProvider as Doc; + 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; 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)); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3f0b62511..7b9ed12a7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -793,9 +793,15 @@ export class DocumentView extends DocComponent(Docu render() { - let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ? + let ruleProvider = this.props.Document.ruleProvider as Doc; + 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; + let clusterCol = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground; + + let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ? this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : - 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%"; @@ -811,7 +817,7 @@ export class DocumentView extends DocComponent(Docu } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 444b91b28..658e2a04d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -284,8 +284,8 @@ 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; + if (draggedDoc && draggedDoc.type === DocumentType.TEXT) { + this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; draggedDoc.isTemplate = true; e.stopPropagation(); } -- cgit v1.2.3-70-g09d2 From 26eed39d2fe140e6bfc3d572bd1aa4717ab52926 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Sep 2019 22:04:03 -0400 Subject: fixed title/caption menu for templates. --- src/client/views/TemplateMenu.tsx | 7 ++++--- src/client/views/nodes/FormattedTextBox.tsx | 22 ++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 0586b31e4..0ef1a137d 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -9,6 +9,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; @@ -99,16 +100,16 @@ 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 => Doc.GetProto(d.layoutDoc)["show" + template.Name] = template.Name.toLowerCase()); } else { - this.props.docs.map(d => d.props.Document["show" + template.Name] = undefined); + this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = undefined); } } @undoBatch @action clearTemplates = (event: React.MouseEvent) => { - Templates.TemplateList.map(template => this.props.docs.map(d => d.props.Document["show" + template.Name] = false)); + Templates.TemplateList.map(template => this.props.docs.map(d => d.layoutDoc["show" + template.Name] = false)); } @action diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 658e2a04d..c07461e13 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -473,17 +473,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._fontFamily = rules.font; this._fontSize = rules.size; setTimeout(() => { - let tr = this._editorView!.state.tr; - let n = new NodeSelection(this._editorView!.state.doc.resolve(0)); - if (this._editorView!.state.doc.textContent === "") { - tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). - replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true). - setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); - } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) { - tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }); + if (this._editorView!.state.doc.childCount) { + let tr = this._editorView!.state.tr; + let n = new NodeSelection(this._editorView!.state.doc.resolve(0)); + if (this._editorView!.state.doc.textContent === "") { + tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). + replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true). + setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); + } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) { + tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }); + } + this._editorView!.dispatch(tr); + this.tryUpdateHeight(); } - this._editorView!.dispatch(tr); - this.tryUpdateHeight(); }, 0); }), { fireImmediately: true } ); -- 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/client/views/nodes') 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 b3d9c4e3d8c7c425df41b2d8555a7d242771a823 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Sep 2019 09:06:51 -0400 Subject: small fixes to text editing --- src/client/util/RichTextRules.ts | 4 +++- src/client/views/nodes/FormattedTextBox.tsx | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 7e3d435a7..8ceb56f2f 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -77,8 +77,10 @@ export const inpRules = { let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "center"; + return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; } - return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; }), new InputRule( new RegExp(/^\[\[\s$/), diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 444b91b28..6020ad583 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -467,23 +467,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe size: NumCast(ruleProvider["ruleSize_" + heading], 13) }; } - return { align: "", font: "Arial", size: 13 }; + return undefined; }, action((rules: any) => { - this._fontFamily = rules.font; - this._fontSize = rules.size; - setTimeout(() => { - let tr = this._editorView!.state.tr; - let n = new NodeSelection(this._editorView!.state.doc.resolve(0)); - if (this._editorView!.state.doc.textContent === "") { - tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). - replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true). - setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); - } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) { - tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }); + 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(); } - this._editorView!.dispatch(tr); - this.tryUpdateHeight(); }, 0); }), { fireImmediately: true } ); -- 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/client/views/nodes') 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/client/views/nodes') 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 f508d5987e91e8297258905d8e8c9dfc405c50e9 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Sep 2019 12:30:20 -0400 Subject: changed link following to follow links that aren't shown that don't have an anchor first. changed text pointerevents when its a button. --- src/client/documents/Documents.ts | 3 ++- src/client/views/nodes/DocumentView.tsx | 4 +++- src/client/views/nodes/FormattedTextBox.tsx | 9 +++++---- 3 files changed, 10 insertions(+), 6 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9db2ac558..2eff73b87 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -630,7 +630,7 @@ export namespace DocUtils { } }); } - export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string) { + export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) { if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; let sv = DocumentManager.Instance.getDocumentView(source); if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; @@ -649,6 +649,7 @@ export namespace DocUtils { linkDocProto.anchor1 = source; linkDocProto.anchor1Page = source.curPage; linkDocProto.anchor1Groups = new List([]); + linkDocProto.anchor1anchored = anchored1; linkDocProto.anchor2 = target; linkDocProto.anchor2Page = target.curPage; linkDocProto.anchor2Groups = new List([]); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0a1367b56..0816cb813 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -354,7 +354,9 @@ 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)); + 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 diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index b2d44f14b..d39291743 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -189,7 +189,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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); + 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 }); @@ -898,8 +898,9 @@ 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 + //|| (this.props.Document.isButton && !this.props.isSelected()) + ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); return (
    this._entered = true)} onPointerLeave={action(() => this._entered = false)} > -
    +
    ); } -- 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/client/views/nodes') 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 dd7679295b84ceba49b8d581bb64f97cc1a86fbb Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Sep 2019 17:30:02 -0400 Subject: more rule provider fixes --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/InkingControl.tsx | 8 +++++++- src/client/views/collections/CollectionSchemaCells.tsx | 1 + src/client/views/collections/CollectionSchemaView.tsx | 5 ++++- src/client/views/collections/CollectionStackingView.tsx | 1 + src/client/views/collections/CollectionTreeView.tsx | 3 +++ src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 2 ++ src/client/views/nodes/KeyValuePair.tsx | 1 + 10 files changed, 22 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index ebdf2a749..ac103b2ea 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -848,7 +848,7 @@ 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.props.Document["show" + template.Name] !== undefined)); + SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.layoutDoc["show" + template.Name] !== undefined)); templates.set(template, checked); }); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 86d0fc0be..94cc1f06c 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -51,7 +51,13 @@ 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 && view.props.ContainingCollectionView.props.Document.colorPalette) { + if (view.props.ContainingCollectionView) { + if (!view.props.ContainingCollectionView.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 cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[]; let closest = 0; let dist = 10000000; diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index c59107b53..17a3f4f7c 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -149,6 +149,7 @@ 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, isSelected: returnFalse, select: emptyFunction, diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index dca1d7c1d..1a84f94c8 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -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} @@ -901,6 +903,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; @@ -995,7 +998,7 @@ export class CollectionSchemaPreview extends React.Component 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/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index f5bb76966..b1e063997 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -37,6 +37,7 @@ 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; @@ -324,6 +325,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} @@ -491,6 +493,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} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e46e8cb88..cc5e887b2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -287,7 +287,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); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index cc04c5a9f..591a507eb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -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 && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); + ruleColor ? 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 a0dc054cf..ffb829825 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -285,6 +285,8 @@ 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; e.stopPropagation(); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index a27dbd83d..7e0f3735d 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -55,6 +55,7 @@ export class KeyValuePair extends React.Component { Document: this.props.doc, DataDoc: this.props.doc, ContainingCollectionView: undefined, + ruleProvider: undefined, fieldKey: this.props.keyName, fieldExt: "", isSelected: returnFalse, -- 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/client/views/nodes') 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/client/views/nodes') 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/client/views/nodes') 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 e125267541968eff8b2bd463b23daf2f79a841b9 Mon Sep 17 00:00:00 2001 From: bob Date: Sat, 14 Sep 2019 11:26:46 -0400 Subject: typo --- src/client/views/nodes/DocumentView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d90224eae..a302a7a07 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -445,8 +445,8 @@ export class DocumentView extends DocComponent(Docu 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.customWidth = this.props.Document.width; + this.props.Document.customHeight = this.props.Document.height; this.props.Document.customNativeWidth = this.props.Document.nativeWidth; this.props.Document.customNativeHeight = this.props.Document.nativeHeight; this.props.Document.customIgnoreAspect = this.props.Document.ignoreAspect; -- cgit v1.2.3-70-g09d2 From 445b77d13e382542b262cf12e647ed20160dfeaf Mon Sep 17 00:00:00 2001 From: bob Date: Sat, 14 Sep 2019 11:43:34 -0400 Subject: fixed autoHeight on custom view --- src/client/views/nodes/DocumentView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a302a7a07..f7e4b3d21 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -494,13 +494,14 @@ export class DocumentView extends DocComponent(Docu 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 options = { title: "data", width: NumCast(this.props.Document.width), 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) : 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; + fieldTemplate.autoHeight = true; 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); -- cgit v1.2.3-70-g09d2 From 2f1dbc8906d6428e8b3b9a973d6bab807eabd025 Mon Sep 17 00:00:00 2001 From: bob Date: Sat, 14 Sep 2019 12:58:45 -0400 Subject: fixed toggle custom on click behavior --- src/client/views/TemplateMenu.tsx | 12 +--- src/client/views/nodes/DocumentView.tsx | 116 +++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 35 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 060191e29..af3fcaa24 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -52,17 +52,7 @@ export class TemplateMenu extends React.Component { dragRef = React.createRef(); toggleCustom = (e: React.MouseEvent): void => { - this.props.docs.map(dv => { - 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(); - } - } - }); + this.props.docs.map(dv => dv.toggleCustomView()); } toggleFloat = (e: React.MouseEvent): void => { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f7e4b3d21..e95f484a4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,6 +40,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); +import { CompileScript, 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); @@ -440,29 +441,7 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeNativeViewClicked = (): void => { - this.props.Document.layout = this.props.Document.nativeLayout; - this.props.Document.nativeLayout = undefined; - this.props.Document.type = this.props.Document.nativeType; - - this.props.Document.customAutoHeight = this.props.Document.autoHeight; - this.props.Document.customWidth = this.props.Document.width; - this.props.Document.customHeight = this.props.Document.height; - 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; + makeNativeView(this.props.Document); } @undoBatch makeCustomViewClicked = (): void => { @@ -620,6 +599,19 @@ export class DocumentView extends DocComponent(Docu }) } } + @undoBatch + @action + toggleCustomView = (): void => { + if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { + Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc) + } else { + if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { + this.makeCustomViewClicked(); + } else if (this.Document.nativeLayout) { + this.makeNativeViewClicked(); + } + } + } @undoBatch @action @@ -671,6 +663,18 @@ 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: 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: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); @@ -921,4 +925,68 @@ export class DocumentView extends DocComponent(Docu
    ); } -} \ No newline at end of file +} + + +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; + } +} +Scripting.addGlobal(function toggleDetail(doc: any) { + if (doc.type !== DocumentType.COL && doc.type !== DocumentType.TEMPLATE) { + makeCustomView(doc); + } else if (doc.nativeLayout) { + makeNativeView(doc); + } +}); \ No newline at end of file -- cgit v1.2.3-70-g09d2