From d0f130d21c3e029592f376ff205b6f82b76b4e6e Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 4 Jun 2019 20:04:19 -0400 Subject: initial commit --- src/client/views/MainView.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a093ffdec..408d454f4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -52,6 +52,15 @@ export class MainView extends React.Component { } } + componentWillMount() { + document.removeEventListener("keydown", this.globalKeyHandler); + document.addEventListener("keydown", this.globalKeyHandler); + } + + componentWillUnMount() { + document.removeEventListener("keydown", this.globalKeyHandler); + } + constructor(props: Readonly<{}>) { super(props); MainView.Instance = this; @@ -299,6 +308,19 @@ export class MainView extends React.Component { this.isSearchVisible = !this.isSearchVisible; } + globalKeyHandler = (e: KeyboardEvent) => { + if (e.key === "Control" || !e.ctrlKey) return; + + e.preventDefault(); + e.stopPropagation(); + + switch (e.key) { + case "ArrowRight": + CollectionDockingView.Instance.AddRightSplit(this.mainContainer!); + console.log("split screen right"); + } + } + render() { return (
-- cgit v1.2.3-70-g09d2 From a30cbfd90f3b5207fc790a1c8dc61e58f69f4e38 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 5 Jun 2019 22:13:34 -0400 Subject: tab focus shifting on tab drag over and beginnings of global key handling, including splitting with control + rightarrow --- src/client/views/MainView.tsx | 36 +++++++++++++++++++--- .../views/collections/CollectionDockingView.tsx | 20 ++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 408d454f4..67a026897 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -26,13 +26,14 @@ import { PreviewCursor } from './PreviewCursor'; import { SearchBox } from './SearchBox'; import { SelectionManager } from '../util/SelectionManager'; import { FieldResult, Field, Doc, Opt, DocListCast } from '../../new_fields/Doc'; -import { Cast, FieldValue, StrCast } from '../../new_fields/Types'; +import { Cast, FieldValue, StrCast, PromiseValue } from '../../new_fields/Types'; import { DocServer } from '../DocServer'; import { listSpec } from '../../new_fields/Schema'; import { Id } from '../../new_fields/FieldSymbols'; import { HistoryUtil } from '../util/History'; import { CollectionBaseView } from './collections/CollectionBaseView'; - +import { timingSafeEqual } from 'crypto'; +import * as _ from "lodash"; @observer export class MainView extends React.Component { @@ -43,6 +44,14 @@ export class MainView extends React.Component { @computed private get mainContainer(): Opt { return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc)); } + @computed private get mainFreeform(): Opt { + let docs = DocListCast(this.mainContainer!.data); + return (docs && docs.length > 1) ? docs[1] : undefined; + } + private globalDisplayFlags = observable({ + jumpToVisible: false + }); + private set mainContainer(doc: Opt) { if (doc) { if (!("presentationView" in doc)) { @@ -308,6 +317,7 @@ export class MainView extends React.Component { this.isSearchVisible = !this.isSearchVisible; } + @action globalKeyHandler = (e: KeyboardEvent) => { if (e.key === "Control" || !e.ctrlKey) return; @@ -316,11 +326,28 @@ export class MainView extends React.Component { switch (e.key) { case "ArrowRight": - CollectionDockingView.Instance.AddRightSplit(this.mainContainer!); - console.log("split screen right"); + if (this.mainFreeform) { + CollectionDockingView.Instance.AddRightSplit(this.mainFreeform!); + } + break; + case "ArrowLeft": + if (this.mainFreeform) { + CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform!); + } + break; + case "o": + this.globalDisplayFlags.jumpToVisible = true; + break; + case "escape": + _.mapValues(this.globalDisplayFlags, () => false) + break; } } + renderJumpTo = () => { + return
JUMP TO
; + } + render() { return (
@@ -332,6 +359,7 @@ export class MainView extends React.Component { {this.miscButtons} + {this.globalDisplayFlags.jumpToVisible ? this.renderJumpTo() : (null)}
); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index dcc1bd95d..4b7868e81 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -44,6 +44,7 @@ export class CollectionDockingView extends React.Component(); private _flush: boolean = false; private _ignoreStateChange = ""; + private _isPointerDown = false; constructor(props: SubCollectionViewProps) { super(props); @@ -247,6 +248,7 @@ export class CollectionDockingView extends React.Component { + this._isPointerDown = false; if (this._flush) { this._flush = false; setTimeout(() => this.stateChanged(), 10); @@ -254,6 +256,7 @@ export class CollectionDockingView extends React.Component { + this._isPointerDown = true; var className = (e.target as any).className; if (className === "messageCounter") { e.stopPropagation(); @@ -334,6 +337,23 @@ export class CollectionDockingView extends React.Component { + if (!this._isPointerDown) return; + var activeContentItem = tab.header.parent.getActiveContentItem(); + if (tab.contentItem !== activeContentItem) { + tab.header.parent.setActiveContentItem(tab.contentItem); + } + tab.setActive(true); + } + // tab.element[0].ondragenter = (e: any) => { + // console.log("DRAGGING OVER DETECTED!"); + // console.log(e); + // } + // tab.element[0].ondrag = (e: any) => { + // console.log("DRAGGING!"); + // console.log(e); + // } ReactDOM.render( CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv); tab.reactComponents = [upDiv]; tab.element.append(upDiv); -- cgit v1.2.3-70-g09d2 From 79b37db46fda36cd779645256b03d9d074141eb6 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 5 Jun 2019 22:13:49 -0400 Subject: cleanup and documentation --- src/client/views/collections/CollectionDockingView.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 4b7868e81..1adb73bcf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -337,7 +337,7 @@ export class CollectionDockingView extends React.Component { if (!this._isPointerDown) return; var activeContentItem = tab.header.parent.getActiveContentItem(); @@ -346,14 +346,6 @@ export class CollectionDockingView extends React.Component { - // console.log("DRAGGING OVER DETECTED!"); - // console.log(e); - // } - // tab.element[0].ondrag = (e: any) => { - // console.log("DRAGGING!"); - // console.log(e); - // } ReactDOM.render( CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv); tab.reactComponents = [upDiv]; tab.element.append(upDiv); -- cgit v1.2.3-70-g09d2 From c789df5ae7a9e364f0d95b54f4a2f330b536a393 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 11 Jun 2019 13:29:48 -0400 Subject: some inline documentation and new template skeletons --- src/client/views/Templates.tsx | 12 +++- .../views/collections/CollectionBaseView.tsx | 13 ++-- .../caption_toggle/DetailedCaptionToggle.tsx | 72 ++++++++++++++++++++++ .../document_templates/image_card/ImageCard.tsx | 18 ++++++ src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/FormattedTextBox.tsx | 1 - src/documentation/collection_hierarchies.txt | 50 +++++++++++++++ 8 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx create mode 100644 src/client/views/document_templates/image_card/ImageCard.tsx create mode 100644 src/documentation/collection_hierarchies.txt (limited to 'src') diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 0cd367bcb..df53284ed 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -39,12 +39,18 @@ export class Template { export namespace Templates { // export const BasicLayout = new Template("Basic layout", "{layout}"); + // export const Caption = new Template("Caption", TemplatePosition.OutterBottom, + // `
+ //
{layout}
+ //
+ // + //
+ //
` ); + export const Caption = new Template("Caption", TemplatePosition.OutterBottom, `
{layout}
-
- -
+
` ); export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop, diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 734669893..a3019f23e 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -106,14 +106,19 @@ export class CollectionBaseView extends React.Component { } if (!this.createsCycle(doc, props.Document)) { //TODO This won't create the field if it doesn't already exist - const value = Cast(props.Document[props.fieldKey], listSpec(Doc)); + const childDocs = DocListCast(props.Document[props.fieldKey]); let alreadyAdded = true; - if (value !== undefined) { - if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) { + if (childDocs !== undefined) { + // if this is not the first document added to the collection + if (allowDuplicates || !childDocs.some(v => v instanceof Doc && v[Id] === doc[Id])) { alreadyAdded = false; - value.push(doc); + childDocs.push(doc); } + // if we're here, we've tried to add a duplicate } else { + // if we are the first, set up a new list for this and all + // future child documents stored in the associated collection document at the fieldKey (likely .data) + // passed in via props alreadyAdded = false; Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new List([doc])); } diff --git a/src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx b/src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx new file mode 100644 index 000000000..2172f2852 --- /dev/null +++ b/src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { FontWeightProperty, FontStyleProperty, FontSizeProperty, ColorProperty } from 'csstype'; +import { observer } from 'mobx-react'; +import { observable, action, runInAction } from 'mobx'; +import { FormattedTextBox, FormattedTextBoxProps } from '../../nodes/FormattedTextBox'; +import { FieldViewProps } from '../../nodes/FieldView'; + +interface DetailedCaptionDataProps { + captionFieldKey?: string, + detailsFieldKey?: string, +} + +interface DetailedCaptionStylingProps { + sharedFontColor?: ColorProperty; + captionFontStyle?: FontStyleProperty + detailsFontStyle?: FontStyleProperty + toggleSize?: number +} + +@observer +export default class DetailedCaptionToggle extends React.Component { + @observable loaded: boolean = false; + @observable detailsExpanded: boolean = false; + + @action toggleDetails = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + this.detailsExpanded = !this.detailsExpanded; + } + + componentDidMount() { + runInAction(() => this.loaded = true); + } + + render() { + let size = this.props.toggleSize || 20; + return ( +
+ {/* caption */} +
+ +
+ {/* details */} +
+ +
+ {/* toggle */} +
+ +
+
+ ); + } + +} diff --git a/src/client/views/document_templates/image_card/ImageCard.tsx b/src/client/views/document_templates/image_card/ImageCard.tsx new file mode 100644 index 000000000..9931515f3 --- /dev/null +++ b/src/client/views/document_templates/image_card/ImageCard.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { DocComponent } from '../../DocComponent'; +import { FieldViewProps } from '../../nodes/FieldView'; +import { createSchema, makeInterface } from '../../../../new_fields/Schema'; +import { createInterface } from 'readline'; +import { ImageBox } from '../../nodes/ImageBox'; + +export default class ImageCard extends React.Component { + + render() { + return ( +
+ +
+ ); + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 02396c3af..b6c150854 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -23,6 +23,7 @@ import { FieldViewProps } from "./FieldView"; import { Without, OmitKeys } from "../../../Utils"; import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { List } from "../../../new_fields/List"; +import DetailedCaptionToggle from "../document_templates/caption_toggle/DetailedCaptionToggle"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without; @@ -103,7 +104,7 @@ export class DocumentContentsView extends React.Component DragManager.SetupDrag() +DragManager.SetupDrag.onRowMove() => DragManager.StartDocumentDrag() +DragManager.StartDrag() + +... (USER IS DRAGGING DOCUMENT AROUND VIA BUTTON) +... (USER DROPS THE DOCUMENT IN THE TARGET COLLECTION) + +CollectionSubView.drop() + + + { + Nodes themselves, both base types and collections, are actually always rendered by using a JSXParser to parse a stringified JSX element layout (see + FieldView.LayoutString()). Typically, way back in the initial drag phase, where the buttons maintained document creation + functions like Documents.ImageDocument(), the layout string will have always been set, because of the way that new node + documents are created. The ImageDocument() function creates a delegate from the imageProto (image document prototype) which is itself created at the time + Dash is loaded. Since the delegate inherits the prototype's layout string, the layoutKey field will be set and effectively always, the JSXParser will + parse the existing layout string to return the appropriate JSX element to be rendered as a child of the collection sub view. On the off chance that this + layout field has not been set, the layout() getter just returns a generic FieldView element to the JSXParser, and internally, this component decides based + on the nature of the document it receives, which node view to assign. This is basically a fallback. + } + + + // all of the below extend + + + + + + \ No newline at end of file -- cgit v1.2.3-70-g09d2 From c0aa7c79258ea8409611da710dc802e3481c34d8 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 11 Jun 2019 14:19:51 -0400 Subject: fixed the temporary inability to add documents :( --- src/client/views/Templates.tsx | 18 +++++++++--------- src/client/views/collections/CollectionBaseView.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index df53284ed..e4a9258b7 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -39,20 +39,20 @@ export class Template { export namespace Templates { // export const BasicLayout = new Template("Basic layout", "{layout}"); - // export const Caption = new Template("Caption", TemplatePosition.OutterBottom, - // `
- //
{layout}
- //
- // - //
- //
` ); - export const Caption = new Template("Caption", TemplatePosition.OutterBottom, `
{layout}
- +
+ +
` ); + // export const Caption = new Template("Caption", TemplatePosition.OutterBottom, + // `
+ //
{layout}
+ // + //
` ); + export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop, `
{layout}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index a3019f23e..4d6721dc1 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -106,7 +106,7 @@ export class CollectionBaseView extends React.Component { } if (!this.createsCycle(doc, props.Document)) { //TODO This won't create the field if it doesn't already exist - const childDocs = DocListCast(props.Document[props.fieldKey]); + const childDocs = Cast(props.Document[props.fieldKey], listSpec(Doc)); let alreadyAdded = true; if (childDocs !== undefined) { // if this is not the first document added to the collection diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index b6c150854..27843d33a 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -104,7 +104,7 @@ export class DocumentContentsView extends React.Component Date: Wed, 12 Jun 2019 23:22:58 -0400 Subject: beginnings of nested golden layout --- src/client/documents/Documents.ts | 18 +++ src/client/util/DocumentManager.ts | 4 +- src/client/util/DragManager.ts | 8 +- src/client/util/TooltipTextMenu.tsx | 2 +- src/client/views/MainOverlayTextBox.tsx | 4 +- src/client/views/MainView.tsx | 20 ++- .../views/collections/CollectionBaseView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 162 ++++++++++++++------- .../views/collections/CollectionTreeView.tsx | 2 +- .../views/collections/ParentDocumentSelector.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 12 +- 11 files changed, 158 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ab61b915c..b346e1570 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -35,6 +35,7 @@ import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; import { RouteStore } from "../../server/RouteStore"; +import { CollectionDockingView } from "../views/collections/CollectionDockingView"; var requestImageSize = require('request-image-size'); var path = require('path'); @@ -315,6 +316,23 @@ export namespace Docs { export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); } + export type DocConfig = { + doc: Doc, + initialWidth?: number + } + export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = "row") { + let layoutConfig = { + content: [ + { + type: type, + content: [ + ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth)) + ] + } + ] + }; + return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id); + } export function CaptionDocument(doc: Doc) { const captionDoc = Doc.MakeAlias(doc); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 65c4b9e4b..72d9f7e76 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -134,7 +134,7 @@ export class DocumentManager { const actualDoc = Doc.MakeAlias(docDelegate); actualDoc.libraryBrush = true; if (linkPage !== undefined) actualDoc.curPage = linkPage; - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc); + (dockFunc || CollectionDockingView.AddRightSplit)(actualDoc); } else { let contextView: DocumentView | null; docDelegate.libraryBrush = true; @@ -142,7 +142,7 @@ export class DocumentManager { contextDoc.panTransformType = "Ease"; contextView.props.focus(contextDoc); } else { - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc); + (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc); } } } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 1e84a0db0..7625b0463 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -26,8 +26,8 @@ export function SetupDrag(_reference: React.RefObject, docFunc: () // if (this.props.isSelected() || this.props.isTopMost) { if (e.button === 0) { e.stopPropagation(); - if (e.shiftKey && CollectionDockingView.Instance) { - CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e); + if (e.shiftKey && CollectionDockingView.TopLevel) { + CollectionDockingView.TopLevel.StartOtherDrag([await docFunc()], e); } else { document.addEventListener("pointermove", onRowMove); document.addEventListener("pointerup", onRowUp); @@ -264,9 +264,9 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined; } - if (e.shiftKey && CollectionDockingView.Instance) { + if (e.shiftKey && CollectionDockingView.TopLevel) { AbortDrag(); - CollectionDockingView.Instance.StartOtherDrag(docs, { + CollectionDockingView.TopLevel.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index f517f757a..fa2483db5 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -194,7 +194,7 @@ export class TooltipTextMenu { if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f); } - else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f); + else if (CollectionDockingView.TopLevel) CollectionDockingView.AddRightSplit(f); } })); } diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 24327b995..718979123 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -86,7 +86,7 @@ export class MainOverlayTextBox extends React.Component addDocTab = (doc: Doc, location: string) => { if (true) { // location === "onRight") { need to figure out stack to add "inTab" - CollectionDockingView.Instance.AddRightSplit(doc); + CollectionDockingView.AddRightSplit(doc); } } render() { @@ -102,6 +102,6 @@ export class MainOverlayTextBox extends React.Component
; } - else return (null); Z + else return (null); } } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 67a026897..426e2440a 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 { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt, faBell } from '@fortawesome/free-solid-svg-icons'; +import { faClone, faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt, faBell } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; @@ -106,6 +106,7 @@ export class MainView extends React.Component { library.add(faFilm); library.add(faMusic); library.add(faTree); + library.add(faClone) this.initEventListeners(); this.initAuthenticationRouters(); } @@ -151,8 +152,11 @@ export class MainView extends React.Component { const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc)); if (list) { let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` }); - var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] }; - let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id); + let configs = [ + { doc: CurrentUserUtils.UserDocument, initialWidth: 150 }, + { doc: freeformDoc, initialWidth: 600 } + ] + let mainDoc = Docs.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id); list.push(mainDoc); // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) setTimeout(() => { @@ -183,8 +187,8 @@ export class MainView extends React.Component { } openNotifsCol = () => { - if (this._notifsCol && CollectionDockingView.Instance) { - CollectionDockingView.Instance.AddRightSplit(this._notifsCol); + if (this._notifsCol && CollectionDockingView.TopLevel) { + CollectionDockingView.AddRightSplit(this._notifsCol); } } @@ -240,6 +244,7 @@ export class MainView extends React.Component { let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" })); let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); + let addDockingNode = action(() => Docs.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" })); let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" })); let addTreeNode = action(() => CurrentUserUtils.UserDocument); //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" })); @@ -260,6 +265,7 @@ export class MainView extends React.Component { [React.createRef(), "object-group", "Add Collection", addColNode], [React.createRef(), "tree", "Add Tree", addTreeNode], [React.createRef(), "table", "Add Schema", addSchemaNode], + [React.createRef(), "clone", "Add Docking Frame", addDockingNode] ]; return < div id="add-nodes-menu" > @@ -327,12 +333,12 @@ export class MainView extends React.Component { switch (e.key) { case "ArrowRight": if (this.mainFreeform) { - CollectionDockingView.Instance.AddRightSplit(this.mainFreeform!); + CollectionDockingView.AddRightSplit(this.mainFreeform!); } break; case "ArrowLeft": if (this.mainFreeform) { - CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform!); + CollectionDockingView.CloseRightSplit(this.mainFreeform!); } break; case "o": diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 4d6721dc1..096c65092 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -16,7 +16,7 @@ export enum CollectionViewType { Schema, Docking, Tree, - Stacking + Stacking, } export interface CollectionRenderProps { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1adb73bcf..dfb8fac35 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,9 +1,9 @@ import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, observable, reaction, Lambda } from "mobx"; +import { action, observable, reaction, Lambda, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; -import Measure from "react-measure"; +import Measure, { ContentRect } from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc"; import { FieldId } from "../../../new_fields/RefField"; @@ -26,7 +26,7 @@ import { CurrentUserUtils } from '../../../server/authentication/models/current_ @observer export class CollectionDockingView extends React.Component { - public static Instance: CollectionDockingView; + public static TopLevel: CollectionDockingView; public static makeDocumentConfig(document: Doc, width?: number) { return { type: 'react-component', @@ -35,11 +35,16 @@ export class CollectionDockingView extends React.Component { + const config = CollectionDockingView.makeDocumentConfig(document, width); + (config.props as any).parent = this; + return config; + } + private _goldenLayout: any = null; private _containerRef = React.createRef(); private _flush: boolean = false; @@ -48,7 +53,7 @@ export class CollectionDockingView extends React.Component - this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. + CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); } - @action - public OpenFullScreen(document: Doc) { + private openFullScreen = (document: Doc) => { let newItemStackConfig = { type: 'stack', - content: [CollectionDockingView.makeDocumentConfig(document)] + content: [this.makeDocConfig(document)] }; var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); this._goldenLayout.root.contentItems[0].addChild(docconfig); docconfig.callDownwards('_$init'); this._goldenLayout._$maximiseItem(docconfig); - this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); + this._ignoreStateChange = this.retrieveConfiguration(); this.stateChanged(); } + @action + public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) { + dockingView.openFullScreen(document); + } + + initializeConfiguration = (configText: string) => { + let configuration: any = JSON.parse(configText); + this.injectParentProp(configuration.content); + this._goldenLayout = new GoldenLayout(configuration); + } + + retrieveConfiguration = () => { + let configuration: any = this._goldenLayout.toConfig(); + this.injectParentProp(configuration.content, true); + return JSON.stringify(configuration); + } + + injectParentProp = (contentArray: any[], reverse: boolean = false) => { + if (!contentArray || contentArray.length == 0) return; + contentArray.forEach(member => { + let baseCase = Object.keys(member).includes("props"); + if (!baseCase) { + this.injectParentProp(member.content, reverse) + } else { + reverse ? delete member.props.parent : member.props.parent = this; + } + }); + } + @undoBatch @action - public CloseRightSplit = (document: Doc): boolean => { + public static CloseRightSplit = (document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel): boolean => { let retVal = false; - if (this._goldenLayout.root.contentItems[0].isRow) { - retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { + if (dockingView._goldenLayout.root.contentItems[0].isRow) { + retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { child.contentItems[0].remove(); - this.layoutChanged(document); + dockingView.layoutChanged(document); return true; } else { Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { child.contentItems[j].remove(); child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); - let docs = Cast(this.props.Document.data, listSpec(Doc)); + let docs = Cast(dockingView.props.Document.data, listSpec(Doc)); docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); return true; } @@ -103,7 +136,7 @@ export class CollectionDockingView extends React.Component { + public static AddRightSplit = (document: Doc, minimize: boolean = false, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) => { + return dockingView.addRightSplit(document, minimize); + } + + private addRightSplit(document: Doc, minimize = false) { let docs = Cast(this.props.Document.data, listSpec(Doc)); if (docs) { docs.push(document); } let newItemStackConfig = { type: 'stack', - content: [CollectionDockingView.makeDocumentConfig(document)] + content: [this.makeDocConfig(document)] }; var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); @@ -157,13 +193,17 @@ export class CollectionDockingView extends React.Component { + + public static AddTab = (stack: any, document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) => { + dockingView.addTab(stack, document); + } + + private addTab = (stack: any, document: Doc) => { let docs = Cast(this.props.Document.data, listSpec(Doc)); if (docs) { docs.push(document); } - let docContentConfig = CollectionDockingView.makeDocumentConfig(document); + let docContentConfig = this.makeDocConfig(document); var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout); stack.addChild(newContentItem.contentItems[0], undefined); this.layoutChanged(); @@ -173,10 +213,10 @@ export class CollectionDockingView extends React.Component void = () => { if (this._containerRef.current) { this.reactionDisposer = reaction( () => StrCast(this.props.Document.dockingConfig), () => { - if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) { + if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) { // Because this is in a set timeout, if this component unmounts right after mounting, // we will leak a GoldenLayout, because we try to destroy it before we ever create it setTimeout(() => this.setupGoldenLayout(), 1); @@ -218,7 +258,7 @@ export class CollectionDockingView extends React.Component void = () => { @@ -232,18 +272,19 @@ export class CollectionDockingView extends React.Component { - var cur = this._containerRef.current; - + onResize = (size: ContentRect) => { // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed - this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height); + // this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height); + if (this._goldenLayout) { + this._goldenLayout.updateSize(size.offset!.width, size.offset!.height); + } } @action @@ -301,12 +342,12 @@ export class CollectionDockingView extends React.Component { - let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc)); - CollectionDockingView.Instance._removedDocs.map(theDoc => + let docs = Cast(CollectionDockingView.TopLevel.props.Document.data, listSpec(Doc)); + CollectionDockingView.TopLevel._removedDocs.map(theDoc => docs && docs.indexOf(theDoc) !== -1 && docs.splice(docs.indexOf(theDoc), 1)); - CollectionDockingView.Instance._removedDocs.length = 0; - var json = JSON.stringify(this._goldenLayout.toConfig()); + CollectionDockingView.TopLevel._removedDocs.length = 0; + var json = this.retrieveConfiguration(); this.props.Document.dockingConfig = json; if (this.undohack && !this.hack) { this.undohack.end(); @@ -315,18 +356,18 @@ export class CollectionDockingView extends React.Component { + private itemDropped = () => { this.stateChanged(); } - htmlToElement(html: string) { + private htmlToElement(html: string) { var template = document.createElement('template'); html = html.trim(); // Never return a text node of whitespace as the result template.innerHTML = html; return template.content.firstChild; } - tabCreated = async (tab: any) => { + private tabCreated = async (tab: any) => { if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { if (tab.contentItem.config.fixed) { tab.contentItem.parent.config.fixed = true; @@ -346,7 +387,7 @@ export class CollectionDockingView extends React.Component CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv); + ReactDOM.render( CollectionDockingView.AddTab(stack, doc)} />, upDiv); tab.reactComponents = [upDiv]; tab.element.append(upDiv); counter.DashDocId = tab.contentItem.config.props.documentId; @@ -368,13 +409,13 @@ export class CollectionDockingView extends React.Component { + private tabDestroyed = (tab: any) => { if (tab.reactComponents) { for (const ele of tab.reactComponents) { ReactDOM.unmountComponentAtNode(ele); @@ -383,7 +424,7 @@ export class CollectionDockingView extends React.Component { + private stackCreated = (stack: any) => { //stack.header.controlsContainer.find('.lm_popout').hide(); stack.header.controlsContainer.find('.lm_close') //get the close icon .off('click') //unbind the current click handler @@ -394,7 +435,7 @@ export class CollectionDockingView extends React.Component + + {({ measureRef }) => ( +
+ + )} + ); } @@ -419,16 +466,23 @@ export class CollectionDockingView extends React.Component { _mainCont = React.createRef(); @observable private _panelWidth = 0; @observable private _panelHeight = 0; @observable private _document: Opt; + private get parentProps(): SubCollectionViewProps { + return this.props.parent.props; + } + get _stack(): any { - let parent = (this.props as any).glContainer.parent.parent; + let parent = this.props.glContainer.parent.parent; if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) return parent.parent.contentItems[1]; return parent; @@ -451,7 +505,7 @@ export class DockedFrameRenderer extends React.Component { if (this._mainCont.current && this._mainCont.current.children) { let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement); scale = Utils.GetScreenTransform(this._mainCont.current).scale; - return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); + return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); } return Transform.Identity(); } @@ -469,9 +523,9 @@ export class DockedFrameRenderer extends React.Component { addDocTab = (doc: Doc, location: string) => { if (location === "onRight") { - CollectionDockingView.Instance.AddRightSplit(doc); + CollectionDockingView.AddRightSplit(doc); } else { - CollectionDockingView.Instance.AddTab(this._stack, doc); + CollectionDockingView.AddTab(this._stack, doc); } } get content() { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 48da52ffa..b0a310ec1 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -123,7 +123,7 @@ class TreeView extends React.Component { return true; }} />); - let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : []; + let dataDocs = CollectionDockingView.TopLevel ? Cast(CollectionDockingView.TopLevel.props.Document.data, listSpec(Doc), []) : []; let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index f11af04a3..95183bd2c 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -29,8 +29,8 @@ export class SelectorContextMenu extends React.Component { allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); docs.forEach(doc => map.delete(doc)); runInAction(() => { - this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document })); - this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); + this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.TopLevel.props.Document)).map(doc => ({ col: doc, target: this.props.Document })); + this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.TopLevel.props.Document)).map(([col, target]) => ({ col, target })); }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7750b9173..efba26c2c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -238,11 +238,11 @@ export class DocumentView extends DocComponent(Docu expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized); } } - if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) { - let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data); + if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.TopLevel) { + let dataDocs = DocListCast(CollectionDockingView.TopLevel.props.Document.data); if (dataDocs) { expandedDocs.forEach(maxDoc => - (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) && + (!CollectionDockingView.CloseRightSplit(Doc.GetProto(maxDoc)) && this.props.addDocTab(getDispDoc(maxDoc), maxLocation))); } } else { @@ -270,8 +270,8 @@ export class DocumentView extends DocComponent(Docu 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([Doc.MakeAlias(this.props.Document)], e); + if (e.shiftKey && e.buttons === 1 && CollectionDockingView.TopLevel) { + CollectionDockingView.TopLevel.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e); e.stopPropagation(); } else { if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. @@ -316,7 +316,7 @@ export class DocumentView extends DocComponent(Docu } } fullScreenClicked = (): void => { - CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false)); + CollectionDockingView.TopLevel && CollectionDockingView.OpenFullScreen(Doc.MakeCopy(this.props.Document, false)); SelectionManager.DeselectAll(); } -- cgit v1.2.3-70-g09d2 From d2c9550f23c4e5654822ac01b973bb965e3f6dec Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 14 Jun 2019 20:49:12 -0400 Subject: cleaned up Docs namespace and thoroughly documented DocServer.GetRefFields --- .vscode/settings.json | 3 +- src/client/DocServer.ts | 80 ++- src/client/documents/Documents.ts | 758 ++++++++++++--------- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/Main.tsx | 2 +- src/client/views/MainView.tsx | 24 +- src/client/views/SearchBox.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 386 ++++------- .../views/collections/CollectionSchemaView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 16 +- .../views/collections/CollectionTreeView.tsx | 2 +- .../views/collections/CollectionVideoView.tsx | 2 +- .../views/collections/DockedFrameRenderer.tsx | 116 ++++ .../collections/collectionFreeForm/MarqueeView.tsx | 18 +- src/client/views/nodes/DocumentView.tsx | 4 +- src/mobile/ImageUpload.tsx | 4 +- src/new_fields/Doc.ts | 12 + src/new_fields/util.ts | 1 + .../authentication/models/current_user_utils.ts | 8 +- 19 files changed, 835 insertions(+), 607 deletions(-) create mode 100644 src/client/views/collections/DockedFrameRenderer.tsx (limited to 'src') diff --git a/.vscode/settings.json b/.vscode/settings.json index fc315ffaf..5df697fee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,6 @@ "editor.formatOnSave": true, "editor.detectIndentation": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, - "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, + "search.usePCRE2": true } \ No newline at end of file diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index cbcf751ee..d759b4757 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -47,36 +47,81 @@ export namespace DocServer { } } + /** + * Given a list of Doc GUIDs, this utility function will asynchronously attempt to fetch each document + * associated with a given input id, first looking in the RefField cache and then communicating with + * the server if the document was not found there. + * + * @param ids the ids that map to the reqested documents + */ export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt }> { const requestedIds: string[] = []; const waitingIds: string[] = []; const promises: Promise>[] = []; const map: { [id: string]: Opt } = {}; + + // 1) An initial pass through the cache to determine which documents need to be fetched, + // which are already in the process of being fetched and which already exist in the + // cache for (const id of ids) { const cached = _cache[id]; + if (cached === undefined) { + // NOT CACHED => we'll have to send a request to the server requestedIds.push(id); } else if (cached instanceof Promise) { + // BEING CACHED => someone else previously (likely recently) called GetRefFields, + // and requested one of the documents I'm looking for. Shouldn't fetch again, just + // wait until this promise is resolved (see the second to last line of the function) promises.push(cached); waitingIds.push(id); } else { + // CACHED => great, let's just add it to the field map map[id] = cached; } } - const prom = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds).then(fields => { + + // 2) Synchronously, we emit a single callback to the server requesting the documents for the given ids. + // This returns a promise, which, when resolved, indicates that all the JSON serialized versions of + // the fields have been returned from the server + const fieldsReceived: Promise = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); + + // 3) When the serialized RefFields have been received, go head and begin deserializing them into objects. + // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all + // future .proto calls won't have to go farther than the cache to get their actual value. + const fieldsDeserialized = fieldsReceived.then(async fields => { const fieldMap: { [id: string]: RefField } = {}; + const deserializedFields: any = []; for (const field of fields) { if (field !== undefined) { - fieldMap[field.id] = SerializationHelper.Deserialize(field); + // deserialize + let deserialized: any = SerializationHelper.Deserialize(field); + fieldMap[field.id] = deserialized; + deserializedFields.push(deserialized.proto); } } - + // this actually handles the loeading of prototypes + await Promise.all(deserializedFields); return fieldMap; }); - requestedIds.forEach(id => _cache[id] = prom.then(fields => fields[id])); - const fields = await prom; + + // 4) Here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + requestedIds.forEach(id => _cache[id] = fieldsDeserialized.then(fields => fields[id])); + + // 5) At this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose + // prototype documents, if any, have also been fetched and cached. + const fields = await fieldsDeserialized; + + // 6) With this confidence, we can now go through and update the cache at the ids of the fields that + // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given + // id to the soon to be returned field mapping. requestedIds.forEach(id => { const field = fields[id]; + // either way, overwrite or delete any promises that we inserted as flags + // to indicate that the field was in the process of being fetched. Now everything + // should be an actual value within or entirely absent from the cache. if (field !== undefined) { _cache[id] = field; } else { @@ -84,14 +129,23 @@ export namespace DocServer { } map[id] = field; }); - await Promise.all(requestedIds.map(async id => { - const field = fields[id]; - if (field) { - await (field as any).proto; - } - })); - const otherFields = await Promise.all(promises); - waitingIds.forEach((id, index) => map[id] = otherFields[index]); + + // 7) Those promises we encountered in the else if of 1), which represent + // other callers having already submitted a request to the server for (a) document(s) + // in which we're interested, must still be awaited so that we can return the proper + // values for those as well. + // + // Fortunately, those other callers will also hit their own version of 6) and clean up + // the shared cache when these promises resolve, so all we have to do is... + const otherCallersFetching = await Promise.all(promises); + // ...extract the RefFields returned from the resolution of those promises and add them to our + // own map. + waitingIds.forEach((id, index) => map[id] = otherCallersFetching[index]); + + // Now, we return our completed mapping from all of the ids that were passed into the method + // to their actual RefField | undefined values. This return value either becomes the input + // argument to the caller's promise (i.e. GetRefFields.then(map => //do something with map...)) + // or it is the direct return result if the promise is awaited. return map; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b346e1570..b10954636 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -18,7 +18,6 @@ import { action } from "mobx"; import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel"; import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel"; import { AggregateFunction } from "../northstar/model/idea/idea"; -import { Template } from "../views/Templates"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { IconBox } from "../views/nodes/IconBox"; import { Field, Doc, Opt } from "../../new_fields/Doc"; @@ -30,7 +29,7 @@ import { Cast, NumCast } from "../../new_fields/Types"; import { IconField } from "../../new_fields/IconField"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; -import { StrokeData, InkField } from "../../new_fields/InkField"; +import { InkField } from "../../new_fields/InkField"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; @@ -67,345 +66,486 @@ export interface DocumentOptions { dbDoc?: Doc; // [key: string]: Opt; } -const delegateKeys = ["x", "y", "width", "height", "panX", "panY"]; -export namespace DocUtils { - export function MakeLink(source: Doc, target: Doc) { - let protoSrc = source.proto ? source.proto : source; - let protoTarg = target.proto ? target.proto : target; - UndoManager.RunInBatch(() => { - let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 }); - //let linkDoc = new Doc; - linkDoc.proto!.title = "-link name-"; - linkDoc.proto!.linkDescription = ""; - linkDoc.proto!.linkTags = "Default"; +export namespace Docs { - linkDoc.proto!.linkedTo = target; - linkDoc.proto!.linkedToPage = target.curPage; - linkDoc.proto!.linkedFrom = source; - linkDoc.proto!.linkedFromPage = source.curPage; + export namespace Prototypes { - let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc)); - if (!linkedFrom) { - protoTarg.linkedFromDocs = linkedFrom = new List(); - } - linkedFrom.push(linkDoc); + // the complete list of document prototypes and their ids + export let textProto: Doc; const textProtoId = "textProto"; + export let histoProto: Doc; const histoProtoId = "histoProto"; + export let imageProto: Doc; const imageProtoId = "imageProto"; + export let webProto: Doc; const webProtoId = "webProto"; + export let collProto: Doc; const collProtoId = "collectionProto"; + export let kvpProto: Doc; const kvpProtoId = "kvpProto"; + export let videoProto: Doc; const videoProtoId = "videoProto"; + export let audioProto: Doc; const audioProtoId = "audioProto"; + export let pdfProto: Doc; const pdfProtoId = "pdfProto"; + export let iconProto: Doc; const iconProtoId = "iconProto"; - let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc)); - if (!linkedTo) { - protoSrc.linkedToDocs = linkedTo = new List(); + /** + * This function loads or initializes the prototype for each docment type. + * + * This is an asynchronous function because it has to attempt + * to fetch the prototype documents from the server. + * + * Once we have this object that maps the prototype ids to a potentially + * undefined document, we either initialize our private prototype + * variables with the document returned from the server or, if prototypes + * haven't been initialized, the newly initialized prototype document. + */ + export async function initialize(): Promise { + // non-guid string ids for each document prototype + let protoIds = [textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId] + // fetch the actual prototype documents from the server + let actualProtos = await DocServer.GetRefFields(protoIds); + + // initialize prototype documents + textProto = actualProtos[textProtoId] as Doc || CreateTextProto(); + histoProto = actualProtos[histoProtoId] as Doc || CreateHistogramProto(); + collProto = actualProtos[collProtoId] as Doc || CreateCollectionProto(); + imageProto = actualProtos[imageProtoId] as Doc || CreateImageProto(); + webProto = actualProtos[webProtoId] as Doc || CreateWebProto(); + kvpProto = actualProtos[kvpProtoId] as Doc || CreateKVPProto(); + videoProto = actualProtos[videoProtoId] as Doc || CreateVideoProto(); + audioProto = actualProtos[audioProtoId] as Doc || CreateAudioProto(); + pdfProto = actualProtos[pdfProtoId] as Doc || CreatePdfProto(); + iconProto = actualProtos[iconProtoId] as Doc || CreateIconProto(); + } + + /** + * This is a convenience method that is used to initialize + * prototype documents for the first time. + * + * @param protoId the id of the prototype, indicating the specific prototype + * to initialize (see the *protoId list at the top of the namespace) + * @param title the prototype document's title, follows *-PROTO + * @param layout the layout key for this prototype and thus the + * layout key that all delegates will inherit + * @param options any value specified in the DocumentOptions object likewise + * becomes the default value for that key for all delegates + */ + function buildPrototype(protoId: string, title: string, layout: string, options: DocumentOptions): Doc { + return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout }); + } + + // INDIVIDUAL INITIALIZERS + + function CreateImageProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + nativeWidth: 600, + width: 300, + backgroundLayout: ImageBox.LayoutString(), + curPage: 0 + }; + return buildPrototype(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"), defaultAttrs); + } + + function CreateHistogramProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 300, + backgroundColor: "black", + backgroundLayout: + HistogramBox.LayoutString() + }; + return buildPrototype(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"), defaultAttrs); + } + + function CreateIconProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: Number(MINIMIZED_ICON_SIZE), + height: Number(MINIMIZED_ICON_SIZE) + }; + return buildPrototype(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), defaultAttrs); + } + + function CreateTextProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 150, + backgroundColor: "#f1efeb" + }; + return buildPrototype(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), defaultAttrs); + } + + function CreatePdfProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + nativeWidth: 1200, + width: 300, + backgroundLayout: PDFBox.LayoutString(), + curPage: 1 + }; + return buildPrototype(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"), defaultAttrs); + } + + function CreateWebProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 300 + }; + return buildPrototype(webProtoId, "WEB_PROTO", WebBox.LayoutString(), defaultAttrs); + } + + function CreateCollectionProto(): Doc { + let defaultAttrs = { + panX: 0, + panY: 0, + scale: 1, + width: 500, + height: 500 + }; + return buildPrototype(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"), defaultAttrs); + } + + function CreateKVPProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 150 + }; + return buildPrototype(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(), defaultAttrs); + } + + function CreateVideoProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + nativeWidth: 600, + width: 300, + backgroundLayout: VideoBox.LayoutString(), + curPage: 0 + }; + return buildPrototype(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"), defaultAttrs); + } + + function CreateAudioProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 150 } - linkedTo.push(linkDoc); - return linkDoc; - }, "make link"); + return buildPrototype(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(), defaultAttrs); + } } + /** + * Encapsulates the factory used to create new document instances + * delegated from top-level prototypes + */ + export namespace Create { -} + const delegateKeys = ["x", "y", "width", "height", "panX", "panY"]; -export namespace Docs { - let textProto: Doc; - let histoProto: Doc; - let imageProto: Doc; - let webProto: Doc; - let collProto: Doc; - let kvpProto: Doc; - let videoProto: Doc; - let audioProto: Doc; - let pdfProto: Doc; - let iconProto: Doc; - const textProtoId = "textProto"; - const histoProtoId = "histoProto"; - const pdfProtoId = "pdfProto"; - const imageProtoId = "imageProto"; - const webProtoId = "webProto"; - const collProtoId = "collectionProto"; - const kvpProtoId = "kvpProto"; - const videoProtoId = "videoProto"; - const audioProtoId = "audioProto"; - const iconProtoId = "iconProto"; - - export function initProtos(): Promise { - return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => { - textProto = fields[textProtoId] as Doc || CreateTextPrototype(); - histoProto = fields[histoProtoId] as Doc || CreateHistogramPrototype(); - collProto = fields[collProtoId] as Doc || CreateCollectionPrototype(); - imageProto = fields[imageProtoId] as Doc || CreateImagePrototype(); - webProto = fields[webProtoId] as Doc || CreateWebPrototype(); - kvpProto = fields[kvpProtoId] as Doc || CreateKVPPrototype(); - videoProto = fields[videoProtoId] as Doc || CreateVideoPrototype(); - audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype(); - pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype(); - iconProto = fields[iconProtoId] as Doc || CreateIconPrototype(); - }); - } + /** + * This function receives the relevant document prototype and uses + * it to create a new of that base-level prototype, or the + * underlying data document, which it then delegates again + * to create the view document. + * + * It also takes the opportunity to register the user + * that created the document and the time of creation. + * + * @param proto the specific document prototype off of which to model + * this new instance (textProto, imageProto, etc.) + * @param data the Field to store at this new instance's data key + * @param options any initial values to provide for this new instance + * @param delegId if applicable, an existing document id. If undefined, Doc's + * constructor just generates a new GUID. This is currently used + * only when creating a DockDocument from the current user's already existing + * main document. + */ + function CreateInstanceFromProto(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) { + const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); - function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc { - return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout }); - } - function SetInstanceOptions(doc: Doc, options: DocumentOptions, value: U) { - const deleg = Doc.MakeDelegate(doc); - deleg.data = value; - return Doc.assign(deleg, options); - } - function SetDelegateOptions(doc: Doc, options: DocumentOptions, id?: string) { - const deleg = Doc.MakeDelegate(doc, id); - return Doc.assign(deleg, options); - } + if (!("author" in protoProps)) { + protoProps.author = CurrentUserUtils.email; + } - function CreateImagePrototype(): Doc { - let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"), - { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0 }); - return imageProto; - } + if (!("creationDate" in protoProps)) { + protoProps.creationDate = new DateField; + } - function CreateHistogramPrototype(): Doc { - let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"), - { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString() }); - return histoProto; - } - function CreateIconPrototype(): Doc { - let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), - { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) }); - return iconProto; - } - function CreateTextPrototype(): Doc { - let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" }); - return textProto; - } - function CreatePdfPrototype(): Doc { - let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"), - { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 }); - return pdfProto; - } - function CreateWebPrototype(): Doc { - let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 300 }); - return webProto; - } - function CreateCollectionPrototype(): Doc { - let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"), - { panX: 0, panY: 0, scale: 1, width: 500, height: 500 }); - return collProto; - } + protoProps.isPrototype = true; - function CreateKVPPrototype(): Doc { - let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150 }); - return kvpProto; - } - function CreateVideoPrototype(): Doc { - let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"), - { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0 }); - return videoProto; - } - function CreateAudioPrototype(): Doc { - let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150 }); - return audioProto; - } + let dataDoc = MakeDataDelegate(proto, protoProps, data); + let viewDoc = Doc.MakeDelegate(dataDoc, delegId); - function CreateInstance(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) { - const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); - if (!("author" in protoProps)) { - protoProps.author = CurrentUserUtils.email; + return Doc.assign(viewDoc, delegateProps); } - if (!("creationDate" in protoProps)) { - protoProps.creationDate = new DateField; + + /** + * This function receives the relevant top level document prototype + * and models a new instance by delegating from it. + * + * Note that it stores the data it recieves at the delegate's data key, + * and applies any document options to this new delegate / instance. + * @param proto the prototype from which to model this new delegate + * @param options initial values to apply to this new delegate + * @param value the data to store in this new delegate + */ + function MakeDataDelegate(proto: Doc, options: DocumentOptions, value: D) { + const deleg = Doc.MakeDelegate(proto); + deleg.data = value; + return Doc.assign(deleg, options); } - protoProps.isPrototype = true; - return SetDelegateOptions(SetInstanceOptions(proto, protoProps, data), delegateProps, delegId); - } + export function ImageDocument(url: string, options: DocumentOptions = {}) { + let inst = CreateInstanceFromProto(Prototypes.imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options }); + requestImageSize(window.origin + RouteStore.corsProxy + "/" + url) + .then((size: any) => { + let aspect = size.height / size.width; + if (!inst.proto!.nativeWidth) { + inst.proto!.nativeWidth = size.width; + } + inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect; + inst.proto!.height = NumCast(inst.proto!.width) * aspect; + }) + .catch((err: any) => console.log(err)); + return inst; - export function ImageDocument(url: string, options: DocumentOptions = {}) { - let inst = CreateInstance(imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options }); - requestImageSize(window.origin + RouteStore.corsProxy + "/" + url) - .then((size: any) => { - let aspect = size.height / size.width; - if (!inst.proto!.nativeWidth) { - inst.proto!.nativeWidth = size.width; + // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }, + // [new URL(url), ImageField]); + // doc.SetText(KeyStore.Caption, "my caption..."); + // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption()); + // doc.SetText(KeyStore.OverlayLayout, FixedCaption()); + // return doc; + } + + export function VideoDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.videoProto, new VideoField(new URL(url)), options); + } + + export function AudioDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.audioProto, new AudioField(new URL(url)), options); + } + + export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.histoProto, new HistogramField(histoOp), options); + } + + export function TextDocument(options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.textProto, "", options); + } + + export function IconDocument(icon: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.iconProto, new IconField(icon), options); + } + + export function PdfDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.pdfProto, new PdfField(new URL(url)), options); + } + + export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) { + let schemaName = options.title ? options.title : "-no schema-"; + let ctlog = await Gateway.Instance.GetSchema(url, schemaName); + if (ctlog && ctlog.schemas) { + let schema = ctlog.schemas[0]; + let schemaDoc = Docs.Create.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! }); + let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []); + if (!schemaDocuments) { + return; } - inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect; - inst.proto!.height = NumCast(inst.proto!.width) * aspect; - }) - .catch((err: any) => console.log(err)); - return inst; - // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }, - // [new URL(url), ImageField]); - // doc.SetText(KeyStore.Caption, "my caption..."); - // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption()); - // doc.SetText(KeyStore.OverlayLayout, FixedCaption()); - // return doc; - } - export function VideoDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(videoProto, new VideoField(new URL(url)), options); - } - export function AudioDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(audioProto, new AudioField(new URL(url)), options); - } + CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc); + const docs = schemaDocuments; + CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { + DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt) => { + if (field instanceof Doc) { + docs.push(field); + } else { + var atmod = new ColumnAttributeModel(attr); + let histoOp = new HistogramOperation(schema.displayName!, + new AttributeTransformationModel(atmod, AggregateFunction.None), + new AttributeTransformationModel(atmod, AggregateFunction.Count), + new AttributeTransformationModel(atmod, AggregateFunction.Count)); + docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! })); + } + })); + }); + return schemaDoc; + } + return Docs.Create.TreeDocument([], { width: 50, height: 100, title: schemaName }); + } - export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) { - return CreateInstance(histoProto, new HistogramField(histoOp), options); - } - export function TextDocument(options: DocumentOptions = {}) { - return CreateInstance(textProto, "", options); - } - export function IconDocument(icon: string, options: DocumentOptions = {}) { - return CreateInstance(iconProto, new IconField(icon), options); - } - export function PdfDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(pdfProto, new PdfField(new URL(url)), options); - } + export function WebDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.webProto, new WebField(new URL(url)), options); + } + + export function HtmlDocument(html: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.webProto, new HtmlField(html), options); + } - export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) { - let schemaName = options.title ? options.title : "-no schema-"; - let ctlog = await Gateway.Instance.GetSchema(url, schemaName); - if (ctlog && ctlog.schemas) { - let schema = ctlog.schemas[0]; - let schemaDoc = Docs.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! }); - let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []); - if (!schemaDocuments) { - return; + export function KVPDocument(document: Doc, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.kvpProto, document, { title: document.title + ".kvp", ...options }); + } + + export function FreeformDocument(documents: Array, options: DocumentOptions, makePrototype: boolean = true) { + if (!makePrototype) { + return MakeDataDelegate(Prototypes.collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents)); } - CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc); - const docs = schemaDocuments; - CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt) => { - if (field instanceof Doc) { - docs.push(field); - } else { - var atmod = new ColumnAttributeModel(attr); - let histoOp = new HistogramOperation(schema.displayName!, - new AttributeTransformationModel(atmod, AggregateFunction.None), - new AttributeTransformationModel(atmod, AggregateFunction.Count), - new AttributeTransformationModel(atmod, AggregateFunction.Count)); - docs.push(Docs.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! })); + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform }); + } + + export function SchemaDocument(schemaColumns: string[], documents: Array, options: DocumentOptions) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema }); + } + + export function TreeDocument(documents: Array, options: DocumentOptions) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree }); + } + + export function StackingDocument(documents: Array, options: DocumentOptions) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking }); + } + + export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); + } + + export type DocConfig = { + doc: Doc, + initialWidth?: number + } + + export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = "row") { + let layoutConfig = { + content: [ + { + type: type, + content: [ + ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth)) + ] } - })); - }); - return schemaDoc; + ] + }; + return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id); } - return Docs.TreeDocument([], { width: 50, height: 100, title: schemaName }); - } - export function WebDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(webProto, new WebField(new URL(url)), options); - } - export function HtmlDocument(html: string, options: DocumentOptions = {}) { - return CreateInstance(webProto, new HtmlField(html), options); - } - export function KVPDocument(document: Doc, options: DocumentOptions = {}) { - return CreateInstance(kvpProto, document, { title: document.title + ".kvp", ...options }); - } - export function FreeformDocument(documents: Array, options: DocumentOptions, makePrototype: boolean = true) { - if (!makePrototype) { - return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents)); + + export function CaptionDocument(doc: Doc) { + const captionDoc = Doc.MakeAlias(doc); + captionDoc.overlayLayout = Templating.FixedCaption(); + captionDoc.width = Cast(doc.width, "number", 0); + captionDoc.height = Cast(doc.height, "number", 0); + return captionDoc; } - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform }); - } - export function SchemaDocument(schemaColumns: string[], documents: Array, options: DocumentOptions) { - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema }); - } - export function TreeDocument(documents: Array, options: DocumentOptions) { - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree }); - } - export function StackingDocument(documents: Array, options: DocumentOptions) { - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking }); - } - export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); - } - export type DocConfig = { - doc: Doc, - initialWidth?: number - } - export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = "row") { - let layoutConfig = { - content: [ - { - type: type, - content: [ - ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth)) - ] - } - ] - }; - return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id); } - export function CaptionDocument(doc: Doc) { - const captionDoc = Doc.MakeAlias(doc); - captionDoc.overlayLayout = FixedCaption(); - captionDoc.width = Cast(doc.width, "number", 0); - captionDoc.height = Cast(doc.height, "number", 0); - return captionDoc; - } + export namespace Templating { - // example of custom display string for an image that shows a caption. - function EmbeddedCaption() { - return `
-
` - + ImageBox.LayoutString() + - `
-
` - + FormattedTextBox.LayoutString("caption") + - `
-
`; - } - export function FixedCaption(fieldName: string = "caption") { - return `
-
` - + FormattedTextBox.LayoutString(fieldName) + - `
-
`; - } + // example of custom display string for an image that shows a caption. + export function EmbeddedCaption() { + return `
+
` + + ImageBox.LayoutString() + + `
+
` + + FormattedTextBox.LayoutString("caption") + + `
+
`; + } - function OuterCaption() { - return (` -
-
- {layout} -
-
- -
-
- `); - } - function InnerCaption() { - return (` -
-
- {layout} -
-
- -
-
+ export function FixedCaption(fieldName: string = "caption") { + return `
+
` + + FormattedTextBox.LayoutString(fieldName) + + `
+
`; + } + + export function OuterCaption() { + return (` +
+
+ {layout} +
+
+ +
+
`); - } + } - /* + export function InnerCaption() { + return (` +
+
+ {layout} +
+
+ +
+
+ `); + } - this template requires an additional style setting on the collectionView-cont to make the layout relative - -.collectionView-cont { - position: relative; - width: 100%; - height: 100%; -} - */ - function Percentaption() { - return (` -
-
- {layout} -
-
- -
-
+ /* + this template requires an additional style setting on the collectionView-cont to make the layout relative + .collectionView-cont { + position: relative; + width: 100%; + height: 100%; + } + */ + export function PercentCaption() { + return (` +
+
+ {layout} +
+
+ +
+
`); + } + + } +} + +export namespace DocUtils { + + export function MakeLink(source: Doc, target: Doc) { + let protoSrc = source.proto ? source.proto : source; + let protoTarg = target.proto ? target.proto : target; + UndoManager.RunInBatch(() => { + let linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: -1 }); + //let linkDoc = new Doc; + linkDoc.proto!.title = "-link name-"; + linkDoc.proto!.linkDescription = ""; + linkDoc.proto!.linkTags = "Default"; + + linkDoc.proto!.linkedTo = target; + linkDoc.proto!.linkedToPage = target.curPage; + linkDoc.proto!.linkedFrom = source; + linkDoc.proto!.linkedFromPage = source.curPage; + + let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc)); + if (!linkedFrom) { + protoTarg.linkedFromDocs = linkedFrom = new List(); + } + linkedFrom.push(linkDoc); + + let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc)); + if (!linkedTo) { + protoSrc.linkedToDocs = linkedTo = new List(); + } + linkedTo.push(linkDoc); + return linkDoc; + }, "make link"); } + } \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index da9b1253e..787033455 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -274,7 +274,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @undoBatch @action createIcon = (selected: DocumentView[], layoutString: string): Doc => { let doc = selected[0].props.Document; - let iconDoc = Docs.IconDocument(layoutString); + let iconDoc = Docs.Create.IconDocument(layoutString); iconDoc.isButton = true; iconDoc.proto!.title = selected.length > 1 ? "-multiple-.icon" : StrCast(doc.title) + ".icon"; iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 3d9750a85..98b14f9c8 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom'; import * as React from 'react'; (async () => { - await Docs.initProtos(); + await Docs.Prototypes.initialize(); await CurrentUserUtils.loadCurrentUser(); ReactDOM.render(, document.getElementById('root')); })(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 426e2440a..984db0426 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -55,7 +55,7 @@ export class MainView extends React.Component { private set mainContainer(doc: Opt) { if (doc) { if (!("presentationView" in doc)) { - doc.presentationView = Docs.TreeDocument([], { title: "Presentation" }); + doc.presentationView = Docs.Create.TreeDocument([], { title: "Presentation" }); } CurrentUserUtils.UserDocument.activeWorkspace = doc; } @@ -151,12 +151,12 @@ export class MainView extends React.Component { createNewWorkspace = async (id?: string) => { const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc)); if (list) { - let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` }); + let freeformDoc = Docs.Create.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` }); let configs = [ { doc: CurrentUserUtils.UserDocument, initialWidth: 150 }, { doc: freeformDoc, initialWidth: 600 } ] - let mainDoc = Docs.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id); + let mainDoc = Docs.Create.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id); list.push(mainDoc); // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) setTimeout(() => { @@ -242,18 +242,18 @@ export class MainView extends React.Component { let audiourl = "http://techslides.com/demos/samples/sample.mp3"; let videourl = "http://techslides.com/demos/sample-videos/small.mp4"; - let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" })); - let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); - let addDockingNode = action(() => Docs.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" })); - let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" })); + let addTextNode = action(() => Docs.Create.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" })); + let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); + 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(["title"], [], { width: 200, height: 200, title: "a schema collection" })); let addTreeNode = action(() => CurrentUserUtils.UserDocument); //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 addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" })); - let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" })); - let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); - let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); - let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" })); + let addVideoNode = action(() => Docs.Create.VideoDocument(videourl, { width: 200, title: "video node" })); + let addPDFNode = action(() => Docs.Create.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" })); + let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); + let addWebNode = action(() => Docs.Create.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); + let addAudioNode = action(() => Docs.Create.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" })); let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "font", "Add Textbox", addTextNode], diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx index 63d2065e2..7164d98a4 100644 --- a/src/client/views/SearchBox.tsx +++ b/src/client/views/SearchBox.tsx @@ -166,7 +166,7 @@ export class SearchBox extends React.Component { y += 300; } } - return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` }); + return Docs.Create.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` }); } // Useful queries: diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index dfb8fac35..e2bcb10ec 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -6,50 +6,32 @@ import * as ReactDOM from 'react-dom'; import Measure, { ContentRect } from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc"; -import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; -import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; import React = require("react"); import { ParentDocSelector } from './ParentDocumentSelector'; import { DocumentManager } from '../../util/DocumentManager'; -import { CollectionViewType } from './CollectionBaseView'; import { Id } from '../../../new_fields/FieldSymbols'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { DockedFrameRenderer } from './DockedFrameRenderer'; @observer export class CollectionDockingView extends React.Component { public static TopLevel: CollectionDockingView; - public static makeDocumentConfig(document: Doc, width?: number) { - return { - type: 'react-component', - component: 'DocumentFrameRenderer', - title: document.title, - width: width, - props: { - documentId: document[Id], - } - }; - } - - private makeDocConfig = (document: Doc, width?: number) => { - const config = CollectionDockingView.makeDocumentConfig(document, width); - (config.props as any).parent = this; - return config; - } - private _goldenLayout: any = null; private _containerRef = React.createRef(); + reactionDisposer?: IReactionDisposer; + _removedDocs: Doc[] = []; private _flush: boolean = false; private _ignoreStateChange = ""; private _isPointerDown = false; + hack: boolean = false; + undohack: any = null; constructor(props: SubCollectionViewProps) { super(props); @@ -57,32 +39,93 @@ export class CollectionDockingView extends React.Component - CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. - onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); + + componentDidMount: () => void = () => { + if (this._containerRef.current) { + this.reactionDisposer = reaction( + () => StrCast(this.props.Document.dockingConfig), + () => { + if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) { + // Because this is in a set timeout, if this component unmounts right after mounting, + // we will leak a GoldenLayout, because we try to destroy it before we ever create it + setTimeout(() => this.setupGoldenLayout(), 1); + } + this._ignoreStateChange = ""; + }, { fireImmediately: true }); + + // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + } } - private openFullScreen = (document: Doc) => { - let newItemStackConfig = { - type: 'stack', - content: [this.makeDocConfig(document)] - }; - var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); - this._goldenLayout.root.contentItems[0].addChild(docconfig); - docconfig.callDownwards('_$init'); - this._goldenLayout._$maximiseItem(docconfig); - this._ignoreStateChange = this.retrieveConfiguration(); - this.stateChanged(); + componentWillUnmount: () => void = () => { + try { + this._goldenLayout.unbind('itemDropped', this.itemDropped); + this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); + } catch (e) { + console.log("Unable to unbind Golden Layout event listener...", e); + } + if (this._goldenLayout) this._goldenLayout.destroy(); + this._goldenLayout = null; + + if (this.reactionDisposer) { + this.reactionDisposer(); + } } - @action - public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) { - dockingView.openFullScreen(document); + setupGoldenLayout() { + var config = StrCast(this.props.Document.dockingConfig); + if (config) { + if (!this._goldenLayout) { + this.initializeConfiguration(config); + } + else { + if (config === this.retrieveConfiguration()) { + return; + } + try { + this._goldenLayout.unbind('itemDropped', this.itemDropped); + this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + } catch (e) { } + this._goldenLayout.destroy(); + this.initializeConfiguration(config); + } + this._goldenLayout.on('itemDropped', this.itemDropped); + this._goldenLayout.on('tabCreated', this.tabCreated); + this._goldenLayout.on('tabDestroyed', this.tabDestroyed); + this._goldenLayout.on('stackCreated', this.stackCreated); + this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer); + this._goldenLayout.container = this._containerRef.current; + if (this._goldenLayout.config.maximisedItemId === '__glMaximised') { + try { + this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise(); + } catch (e) { + this._goldenLayout.config.maximisedItemId = null; + } + } + this._goldenLayout.init(); + } + } + + private makeDocConfig = (document: Doc, width?: number) => { + const config = CollectionDockingView.makeDocumentConfig(document, width); + (config.props as any).parent = this; + return config; + } + + public static makeDocumentConfig(document: Doc, width?: number) { + return { + type: 'react-component', + component: 'DocumentFrameRenderer', + title: document.title, + width: width, + props: { + documentId: document[Id], + } + }; } initializeConfiguration = (configText: string) => { @@ -109,44 +152,29 @@ export class CollectionDockingView extends React.Component { - let retVal = false; - if (dockingView._goldenLayout.root.contentItems[0].isRow) { - retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { - if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && - Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { - child.contentItems[0].remove(); - dockingView.layoutChanged(document); - return true; - } else { - Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { - if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { - child.contentItems[j].remove(); - child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); - let docs = Cast(dockingView.props.Document.data, listSpec(Doc)); - docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); - return true; - } - return false; - }); - } - return false; - }); - } - if (retVal) { - dockingView.stateChanged(); - } - return retVal; + public StartOtherDrag(dragDocs: Doc[], e: any) { + this.hack = true; + this.undohack = UndoManager.StartBatch("goldenDrag"); + dragDocs.map(dragDoc => + CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. + onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); } @action - layoutChanged(removed?: Doc) { - this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); - this._goldenLayout.emit('stateChanged'); + public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) { + dockingView.openFullScreen(document); + } + + private openFullScreen = (document: Doc) => { + let newItemStackConfig = { + type: 'stack', + content: [this.makeDocConfig(document)] + }; + var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); + this._goldenLayout.root.contentItems[0].addChild(docconfig); + docconfig.callDownwards('_$init'); + this._goldenLayout._$maximiseItem(docconfig); this._ignoreStateChange = this.retrieveConfiguration(); - if (removed) CollectionDockingView.TopLevel._removedDocs.push(removed); this.stateChanged(); } @@ -209,75 +237,47 @@ export class CollectionDockingView extends React.Component { + let retVal = false; + if (dockingView._goldenLayout.root.contentItems[0].isRow) { + retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { + if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && + Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { + child.contentItems[0].remove(); + dockingView.layoutChanged(document); + return true; + } else { + Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { + if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { + child.contentItems[j].remove(); + child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); + let docs = Cast(dockingView.props.Document.data, listSpec(Doc)); + docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); + return true; + } + return false; + }); } - } - this._goldenLayout.init(); + return false; + }); } - } - reactionDisposer?: IReactionDisposer; - componentDidMount: () => void = () => { - if (this._containerRef.current) { - this.reactionDisposer = reaction( - () => StrCast(this.props.Document.dockingConfig), - () => { - if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) { - // Because this is in a set timeout, if this component unmounts right after mounting, - // we will leak a GoldenLayout, because we try to destroy it before we ever create it - setTimeout(() => this.setupGoldenLayout(), 1); - } - this._ignoreStateChange = ""; - }, { fireImmediately: true }); - - // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + if (retVal) { + dockingView.stateChanged(); } + return retVal; } - componentWillUnmount: () => void = () => { - try { - this._goldenLayout.unbind('itemDropped', this.itemDropped); - this._goldenLayout.unbind('tabCreated', this.tabCreated); - this._goldenLayout.unbind('stackCreated', this.stackCreated); - this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); - } catch (e) { - } - if (this._goldenLayout) this._goldenLayout.destroy(); - this._goldenLayout = null; - // window.removeEventListener('resize', this.onResize); - - if (this.reactionDisposer) { - this.reactionDisposer(); - } + @action + layoutChanged(removed?: Doc) { + this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); + this._goldenLayout.emit('stateChanged'); + this._ignoreStateChange = this.retrieveConfiguration(); + if (removed) CollectionDockingView.TopLevel._removedDocs.push(removed); + this.stateChanged(); } + @action onResize = (size: ContentRect) => { // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed @@ -387,6 +387,9 @@ export class CollectionDockingView extends React.Component { + console.log("DROPPPP THE BASS!", e); + } ReactDOM.render( CollectionDockingView.AddTab(stack, doc)} />, upDiv); tab.reactComponents = [upDiv]; tab.element.append(upDiv); @@ -422,7 +425,6 @@ export class CollectionDockingView extends React.Component { //stack.header.controlsContainer.find('.lm_popout').hide(); @@ -462,102 +464,4 @@ export class CollectionDockingView extends React.Component { - _mainCont = React.createRef(); - @observable private _panelWidth = 0; - @observable private _panelHeight = 0; - @observable private _document: Opt; - private get parentProps(): SubCollectionViewProps { - return this.props.parent.props; - } - - get _stack(): any { - let parent = this.props.glContainer.parent.parent; - if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) - return parent.parent.contentItems[1]; - return parent; - } - constructor(props: any) { - super(props); - DocServer.GetRefField(this.props.documentId).then(action((f: Opt) => this._document = f as Doc)); - } - - nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth); - nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight); - contentScaling = () => { - const nativeH = this.nativeHeight(); - const nativeW = this.nativeWidth(); - let wscale = this._panelWidth / nativeW; - return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale; - } - - ScreenToLocalTransform = () => { - if (this._mainCont.current && this._mainCont.current.children) { - let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement); - scale = Utils.GetScreenTransform(this._mainCont.current).scale; - return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); - } - return Transform.Identity(); - } - get scaleToFitMultiplier() { - let docWidth = NumCast(this._document!.width); - let docHeight = NumCast(this._document!.height); - if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1; - if (StrCast(this._document!.layout).indexOf("Collection") === -1 || - NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1; - let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ? - this._panelHeight / docHeight : this._panelWidth / docWidth); - return scaling; - } - get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } - - addDocTab = (doc: Doc, location: string) => { - if (location === "onRight") { - CollectionDockingView.AddRightSplit(doc); - } else { - CollectionDockingView.AddTab(this._stack, doc); - } - } - get content() { - if (!this._document) { - return (null); - } - return ( -
- -
); - } - - render() { - let theContent = this.content; - return !this._document ? (null) : - { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}> - {({ measureRef }) =>
{theContent}
} -
; - } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 11d71d023..477879b79 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -261,7 +261,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { let dbName = StrCast(this.props.Document.title); let res = await Gateway.Instance.PostSchema(csv, dbName); if (self.props.CollectionView.props.addDocument) { - let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document }); + let schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document }); if (schemaDoc) { //self.props.CollectionView.props.addDocument(schemaDoc, false); self.props.Document.schemaDoc = schemaDoc; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index be37efd3d..440a2410b 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -113,20 +113,20 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise> { let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise)) | undefined = undefined; if (type.indexOf("image") !== -1) { - ctor = Docs.ImageDocument; + ctor = Docs.Create.ImageDocument; } if (type.indexOf("video") !== -1) { - ctor = Docs.VideoDocument; + ctor = Docs.Create.VideoDocument; } if (type.indexOf("audio") !== -1) { - ctor = Docs.AudioDocument; + ctor = Docs.Create.AudioDocument; } if (type.indexOf("pdf") !== -1) { - ctor = Docs.PdfDocument; + ctor = Docs.Create.PdfDocument; options.nativeWidth = 1200; } if (type.indexOf("excel") !== -1) { - ctor = Docs.DBDocument; + ctor = Docs.Create.DBDocument; options.dropAction = "copy"; } if (type.indexOf("html") !== -1) { @@ -145,7 +145,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { }); return undefined; } - ctor = Docs.WebDocument; + ctor = Docs.Create.WebDocument; options = { height: options.width, ...options, title: path, nativeWidth: undefined }; } return ctor ? ctor(path, options) : undefined; @@ -175,13 +175,13 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { return; } if (html && html.indexOf(" { 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 ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) }); - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" }); if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" }); ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" }); diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 7853544d5..bd5cd5450 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -98,7 +98,7 @@ export class CollectionVideoView extends React.Component { SearchBox.convertDataUri(dataUrl, filename).then((returnedFilename) => { if (returnedFilename) { let url = DocServer.prepend(returnedFilename); - let imageSummary = Docs.ImageDocument(url, { + let imageSummary = Docs.Create.ImageDocument(url, { x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" }); diff --git a/src/client/views/collections/DockedFrameRenderer.tsx b/src/client/views/collections/DockedFrameRenderer.tsx new file mode 100644 index 000000000..25d4b2a49 --- /dev/null +++ b/src/client/views/collections/DockedFrameRenderer.tsx @@ -0,0 +1,116 @@ +import 'golden-layout/src/css/goldenlayout-base.css'; +import 'golden-layout/src/css/goldenlayout-dark-theme.css'; +import { action, observable, reaction, Lambda, IReactionDisposer } from "mobx"; +import { observer } from "mobx-react"; +import Measure, { ContentRect } from "react-measure"; +import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc"; +import { FieldId } from "../../../new_fields/RefField"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction, returnTrue, Utils } from "../../../Utils"; +import { DocServer } from "../../DocServer"; +import { Transform } from '../../util/Transform'; +import { DocumentView } from "../nodes/DocumentView"; +import "./CollectionDockingView.scss"; +import { SubCollectionViewProps } from "./CollectionSubView"; +import React = require("react"); +import { CollectionViewType } from './CollectionBaseView'; +import { Id } from '../../../new_fields/FieldSymbols'; +import { CollectionDockingView } from './CollectionDockingView'; + +interface DockedFrameProps { + documentId: FieldId; + glContainer: any; + glEventHub: any; + parent: CollectionDockingView; +} + +@observer +export class DockedFrameRenderer extends React.Component { + _mainCont = React.createRef(); + @observable private _panelWidth = 0; + @observable private _panelHeight = 0; + @observable private _document: Opt; + private get parentProps(): SubCollectionViewProps { + return this.props.parent.props; + } + + get _stack(): any { + let parent = this.props.glContainer.parent.parent; + if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) + return parent.parent.contentItems[1]; + return parent; + } + constructor(props: any) { + super(props); + DocServer.GetRefField(this.props.documentId).then(action((f: Opt) => this._document = f as Doc)); + } + + nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth); + nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight); + contentScaling = () => { + const nativeH = this.nativeHeight(); + const nativeW = this.nativeWidth(); + let wscale = this._panelWidth / nativeW; + return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale; + } + + ScreenToLocalTransform = () => { + if (this._mainCont.current && this._mainCont.current.children) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement); + scale = Utils.GetScreenTransform(this._mainCont.current).scale; + return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); + } + return Transform.Identity(); + } + get scaleToFitMultiplier() { + let docWidth = NumCast(this._document!.width); + let docHeight = NumCast(this._document!.height); + if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1; + if (StrCast(this._document!.layout).indexOf("Collection") === -1 || + NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1; + let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ? + this._panelHeight / docHeight : this._panelWidth / docWidth); + return scaling; + } + get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } + + addDocTab = (doc: Doc, location: string) => { + if (location === "onRight") { + CollectionDockingView.AddRightSplit(doc); + } else { + CollectionDockingView.AddTab(this._stack, doc); + } + } + get content() { + if (!this._document) { + return (null); + } + return ( +
+ +
); + } + + render() { + let theContent = this.content; + return !this._document ? (null) : + { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}> + {({ measureRef }) =>
{theContent}
} +
; + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 29734fa19..cd386abfa 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -79,7 +79,7 @@ export class MarqueeView extends React.Component } ns.map(line => { let indent = line.search(/\S|$/); - let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); + let newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); this.props.addDocument(newBox, false); y += 40 * this.props.getTransform().Scale; }); @@ -89,13 +89,13 @@ export class MarqueeView extends React.Component navigator.clipboard.readText().then(text => { let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== ""); if (ns.length === 1 && text.startsWith("http")) { - this.props.addDocument(Docs.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer + this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer } else { this.pasteTable(ns, x, y); } }); } else { - let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); + let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); this.props.addLiveTextDocument(newBox); } e.stopPropagation(); @@ -136,7 +136,7 @@ export class MarqueeView extends React.Component doc.width = 200; docList.push(doc); } - let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 }); + let newCol = Docs.Create.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 }); this.props.addDocument(newCol, false); } @@ -259,7 +259,7 @@ export class MarqueeView extends React.Component let ink = Cast(this.props.container.props.Document.ink, InkField); let inkData = ink ? ink.inkData : undefined; let zoomBasis = NumCast(this.props.container.props.Document.scale, 1); - let newCollection = Docs.FreeformDocument(selected, { + let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panX: 0, @@ -283,14 +283,14 @@ export class MarqueeView extends React.Component d.page = -1; return d; }); - let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); newCollection.proto!.summaryDoc = summary; selected = [newCollection]; newCollection.x = bounds.left + bounds.width; summary.proto!.subBulletDocs = new List(selected); //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" summary.templates = new List([Templates.Bullet.Layout]); - let container = Docs.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" }); + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" }); container.viewType = CollectionViewType.Stacking; this.props.addLiveTextDocument(container); // }); @@ -303,11 +303,11 @@ export class MarqueeView extends React.Component d.page = -1; return d; }); - let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => { if (returnedFilename) { let url = DocServer.prepend(returnedFilename); - let imageSummary = Docs.ImageDocument(url, { + let imageSummary = Docs.Create.ImageDocument(url, { x: bounds.left, y: bounds.top + 100 / zoomBasis, width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-" }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index efba26c2c..16e40000d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -302,7 +302,7 @@ export class DocumentView extends DocComponent(Docu } deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); } - fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") }; + fieldsClicked = (): void => { this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") }; makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); doc.isButton = !BoolCast(doc.isButton, false); @@ -418,7 +418,7 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Find aliases", event: async () => { const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document); - this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight"); + this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), "onRight"); }, icon: "search" }); cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" }); diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index bfc1738fc..a8f94b746 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -33,7 +33,7 @@ class Uploader extends React.Component { onClick = async () => { try { this.status = "initializing protos"; - await Docs.initProtos(); + await Docs.Prototypes.initialize(); let imgPrev = document.getElementById("img_preview"); if (imgPrev) { let files: FileList | null = inputRef.current!.files; @@ -53,7 +53,7 @@ class Uploader extends React.Component { const json = await res.json(); json.map(async (file: any) => { let path = window.location.origin + file; - var doc = Docs.ImageDocument(path, { nativeWidth: 200, width: 200, title: name }); + var doc = Docs.Create.ImageDocument(path, { nativeWidth: 200, width: 200, title: name }); this.status = "getting user document"; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 7f7263cf1..af65f5482 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -172,6 +172,18 @@ export namespace Doc { } return protos; } + + /** + * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies + * the values of the properties of a source object into the target. + * + * This is just a specific, Dash-authored version that serves the same role for our + * Doc class. + * + * @param doc the target document into which you'd like to insert the new fields + * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key + * to a potentially undefined field, where each entry in this mapping is optional. + */ export function assign(doc: Doc, fields: Partial>>) { for (const key in fields) { if (fields.hasOwnProperty(key)) { diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 2b304c373..8caceb063 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -60,6 +60,7 @@ export function getter(target: any, prop: string | symbol | number, receiver: an } return getField(target, prop); } + function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) { if (!protoField) return undefined; let field = protoField[prop]; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index e5b7a025b..169be3b99 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -33,8 +33,8 @@ export class CurrentUserUtils { doc.title = this.email; doc.data = new List(); doc.excludeFromLibrary = true; - doc.optionalRightCollection = Docs.StackingDocument([], { title: "New mobile uploads" }); - // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` }); + doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" }); + // doc.library = Docs.Create.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` }); // (doc.library as Doc).excludeFromLibrary = true; return doc; } @@ -94,12 +94,12 @@ export class CurrentUserUtils { // new AttributeTransformationModel(atmod, AggregateFunction.None), // new AttributeTransformationModel(atmod, AggregateFunction.Count), // new AttributeTransformationModel(atmod, AggregateFunction.Count)); - // schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! })); + // schemaDocuments.push(Docs.Create.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! })); // } // }))); // return promises; // }, [] as Promise[])); - // return CurrentUserUtils._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })); + // return CurrentUserUtils._northstarSchemas.push(Docs.Create.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })); // }); // } } -- cgit v1.2.3-70-g09d2 From a3e1f7332e0cb96dae0abd80a2972ae74ac31104 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 17 Jun 2019 13:30:37 -0400 Subject: Doc server documentation completed --- src/Utils.ts | 10 +- src/client/DocServer.ts | 285 +++++++++++++++++++++++++-------- src/client/util/SerializationHelper.ts | 4 +- src/new_fields/Doc.ts | 4 +- src/new_fields/util.ts | 4 +- src/server/index.ts | 12 +- 6 files changed, 235 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 611c61135..657bd673e 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -73,14 +73,14 @@ export class Utils { }; } - public static Emit(socket: Socket | SocketIOClient.Socket, message: Message, args: T) { + public static emit(socket: Socket | SocketIOClient.Socket, message: Message, args: T) { this.log("Emit", message.Name, args, false); socket.emit(message.Message, args); } - public static EmitCallback(socket: Socket | SocketIOClient.Socket, message: Message, args: T): Promise; - public static EmitCallback(socket: Socket | SocketIOClient.Socket, message: Message, args: T, fn: (args: any) => any): void; - public static EmitCallback(socket: Socket | SocketIOClient.Socket, message: Message, args: T, fn?: (args: any) => any): void | Promise { + public static emitCallback(socket: Socket | SocketIOClient.Socket, message: Message, args: T): Promise; + public static emitCallback(socket: Socket | SocketIOClient.Socket, message: Message, args: T, fn: (args: any) => any): void; + public static emitCallback(socket: Socket | SocketIOClient.Socket, message: Message, args: T, fn?: (args: any) => any): void | Promise { this.log("Emit", message.Name, args, false); if (fn) { socket.emit(message.Message, args, this.loggingCallback('Receiving', fn, message.Name)); @@ -89,7 +89,7 @@ export class Utils { } } - public static AddServerHandler(socket: Socket | SocketIOClient.Socket, message: Message, handler: (args: T) => any) { + public static addServerHandler(socket: Socket | SocketIOClient.Socket, message: Message, handler: (args: T) => any) { socket.on(message.Message, this.loggingCallback('Incoming', handler, message.Name)); } diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index d759b4757..ad7c706b6 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,35 +1,116 @@ import * as OpenSocket from 'socket.io-client'; -import { MessageStore } from "./../server/Message"; +import { MessageStore, Diff } from "./../server/Message"; import { Opt } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../new_fields/RefField'; import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; +/** + * This class encapsulates the transfer and cross-client synchronization of + * data stored only in documents (RefFields). In the process, it also + * creates and maintains a cache of documents so that they can be accessed + * more efficiently. Currently, there is no cache eviction scheme in place. + * + * NOTE: while this class is technically abstracted to work with any [RefField], because + * [Doc] instances are the only [RefField] we need / have implemented at the moment, the documentation + * will treat all data used here as [Doc]s + * + * Any time we want to write a new field to the database (via the server) + * or update ourselves based on the server's update message, that occurs here + */ export namespace DocServer { + // a document cache for efficient document retrieval const _cache: { [id: string]: RefField | Promise> } = {}; + // the handle / client side endpoint of the web socket (https://bit.ly/2TeALea for more info) connection established with the server const _socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`); + // this client's distinct GUID created at initialization const GUID: string = Utils.GenerateGuid(); + // indicates whether or not a document is currently being udpated, and, if so, its id + let updatingId: string | undefined; - export function makeReadOnly() { - _CreateField = emptyFunction; - _UpdateField = emptyFunction; - _respondToUpdate = emptyFunction; - } + export namespace Util { - export function prepend(extension: string): string { - return window.location.origin + extension; - } + /** + * Whenever the server sends us its handshake message on our + * websocket, we use the above function to return the handshake. + */ + Utils.addServerHandler(_socket, MessageStore.Foo, onConnection); + + /** + * This function emits a message (with this client's + * unique GUID) to the server + * indicating that this client has connected + */ + function onConnection() { + _socket.emit(MessageStore.Bar.Message, GUID); + } + + /** + * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the + * requested extension + * @param extension the specified sub-path to append to the window origin + */ + export function prepend(extension: string): string { + return window.location.origin + extension; + } + + /** + * Emits a message to the server that wipes + * all documents in the database. + */ + export function deleteDatabase() { + Utils.emit(_socket, MessageStore.DeleteAll, {}); + } + + /** + * This disables this client's ability to write new fields, + * update existing fields, and update and reflect the changes if + * other clients update shared fields. Thus, the client can only read + * a static snapshot of their workspaces + * + * Currently this is conditionally called in MainView.tsx when analyzing + * the document's url. + */ + export function makeReadOnly() { + // replaces default functionality with no-ops + _createField = emptyFunction; + _emitFieldUpdate = emptyFunction; + _respondToUpdate = emptyFunction; + } - export function DeleteDatabase() { - Utils.Emit(_socket, MessageStore.DeleteAll, {}); } - export async function GetRefField(id: string): Promise> { + // RETRIEVE DOCS FROM SERVER + + /** + * Given a single Doc GUID, this utility function will asynchronously attempt to fetch the id's associated + * field, first looking in the RefField cache and then communicating with + * the server if the document has not been cached. + * @param id the id of the requested document + */ + export async function getRefField(id: string): Promise> { + // an initial pass through the cache to determine whether the document needs to be fetched, + // is already in the process of being fetched or already exists in the + // cache let cached = _cache[id]; if (cached === undefined) { - const prom = Utils.EmitCallback(_socket, MessageStore.GetRefField, id).then(async fieldJson => { + // NOT CACHED => we'll have to send a request to the server + + // synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) + // field for the given ids. This returns a promise, which, when resolved, indicates the the JSON serialized version of + // the field has been returned from the server + const getSerializedField = Utils.emitCallback(_socket, MessageStore.GetRefField, id); + + // when the serialized RefField has been received, go head and begin deserializing it into an object. + // Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all + // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. + const deserializeField = getSerializedField.then(async fieldJson => { + // deserialize const field = SerializationHelper.Deserialize(fieldJson); + // either way, overwrite or delete any promises cached at this id (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. if (field !== undefined) { await field.proto; _cache[id] = field; @@ -38,41 +119,45 @@ export namespace DocServer { } return field; }); - _cache[id] = prom; - return prom; + // here, indicate that the document associated with this id is currently + // being retrieved and cached + _cache[id] = deserializeField; + return deserializeField; } else if (cached instanceof Promise) { + // BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s), + // and requested the document I'm looking for. Shouldn't fetch again, just + // return this promise which will resolve to the field itself (see 7) return cached; } else { + // CACHED => great, let's just return the cached field we have return cached; } } /** - * Given a list of Doc GUIDs, this utility function will asynchronously attempt to fetch each document - * associated with a given input id, first looking in the RefField cache and then communicating with - * the server if the document was not found there. - * + * Given a list of Doc GUIDs, this utility function will asynchronously attempt to each id's associated + * field, first looking in the RefField cache and then communicating with + * the server if the document has not been cached. * @param ids the ids that map to the reqested documents */ - export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt }> { + export async function getRefFields(ids: string[]): Promise<{ [id: string]: Opt }> { const requestedIds: string[] = []; const waitingIds: string[] = []; const promises: Promise>[] = []; const map: { [id: string]: Opt } = {}; - // 1) An initial pass through the cache to determine which documents need to be fetched, + // 1) an initial pass through the cache to determine which documents need to be fetched, // which are already in the process of being fetched and which already exist in the // cache for (const id of ids) { const cached = _cache[id]; - if (cached === undefined) { // NOT CACHED => we'll have to send a request to the server requestedIds.push(id); } else if (cached instanceof Promise) { - // BEING CACHED => someone else previously (likely recently) called GetRefFields, + // BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s), // and requested one of the documents I'm looking for. Shouldn't fetch again, just - // wait until this promise is resolved (see the second to last line of the function) + // wait until this promise is resolved (see 7) promises.push(cached); waitingIds.push(id); } else { @@ -81,46 +166,49 @@ export namespace DocServer { } } - // 2) Synchronously, we emit a single callback to the server requesting the documents for the given ids. - // This returns a promise, which, when resolved, indicates that all the JSON serialized versions of + // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) + // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of // the fields have been returned from the server - const fieldsReceived: Promise = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); + const getSerializedFields: Promise = Utils.emitCallback(_socket, MessageStore.GetRefFields, requestedIds); - // 3) When the serialized RefFields have been received, go head and begin deserializing them into objects. + // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects. // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all - // future .proto calls won't have to go farther than the cache to get their actual value. - const fieldsDeserialized = fieldsReceived.then(async fields => { + // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. + const deserializeFields = getSerializedFields.then(async fields => { const fieldMap: { [id: string]: RefField } = {}; - const deserializedFields: any = []; + const protosToLoad: any = []; for (const field of fields) { if (field !== undefined) { // deserialize let deserialized: any = SerializationHelper.Deserialize(field); fieldMap[field.id] = deserialized; - deserializedFields.push(deserialized.proto); + // adds to a list of promises that will be awaited asynchronously + protosToLoad.push(deserialized.proto); } } - // this actually handles the loeading of prototypes - await Promise.all(deserializedFields); + // this actually handles the loading of prototypes + await Promise.all(protosToLoad); return fieldMap; }); - // 4) Here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) // we set the value at the field's id to a promise that will resolve to the field. // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - requestedIds.forEach(id => _cache[id] = fieldsDeserialized.then(fields => fields[id])); + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + requestedIds.forEach(id => _cache[id] = deserializeFields.then(fields => fields[id])); - // 5) At this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose + // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose // prototype documents, if any, have also been fetched and cached. - const fields = await fieldsDeserialized; + const fields = await deserializeFields; - // 6) With this confidence, we can now go through and update the cache at the ids of the fields that + // 6) with this confidence, we can now go through and update the cache at the ids of the fields that // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given // id to the soon to be returned field mapping. requestedIds.forEach(id => { const field = fields[id]; - // either way, overwrite or delete any promises that we inserted as flags - // to indicate that the field was in the process of being fetched. Now everything + // either way, overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything // should be an actual value within or entirely absent from the cache. if (field !== undefined) { _cache[id] = field; @@ -130,78 +218,141 @@ export namespace DocServer { map[id] = field; }); - // 7) Those promises we encountered in the else if of 1), which represent + // 7) those promises we encountered in the else if of 1), which represent // other callers having already submitted a request to the server for (a) document(s) // in which we're interested, must still be awaited so that we can return the proper // values for those as well. // - // Fortunately, those other callers will also hit their own version of 6) and clean up + // fortunately, those other callers will also hit their own version of 6) and clean up // the shared cache when these promises resolve, so all we have to do is... const otherCallersFetching = await Promise.all(promises); // ...extract the RefFields returned from the resolution of those promises and add them to our // own map. waitingIds.forEach((id, index) => map[id] = otherCallersFetching[index]); - // Now, we return our completed mapping from all of the ids that were passed into the method + // now, we return our completed mapping from all of the ids that were passed into the method // to their actual RefField | undefined values. This return value either becomes the input // argument to the caller's promise (i.e. GetRefFields.then(map => //do something with map...)) // or it is the direct return result if the promise is awaited. return map; } - let _UpdateField = (id: string, diff: any) => { + // WRITE A NEW DOCUMENT TO THE SERVER + + /** + * A wrapper around the function local variable _createField. + * This allows us to swap in different executions while comfortably + * calling the same function throughout the code base (such as in Util.makeReadonly()) + * @param field the [RefField] to be serialized and sent to the server to be stored in the database + */ + export function createField(field: RefField) { + _createField(field); + } + + /** + * The default behavior for field creation. This inserts the [Doc] instance + * in the cache at its id, serializes the [Doc]'s initial state + * and finally sends that seruialized data to the server. + * @param field the [RefField] to be serialized and sent to the server to be stored in the database + */ + let _createField = (field: RefField) => { + _cache[field[Id]] = field; + const initialState = SerializationHelper.serialize(field); + Utils.emit(_socket, MessageStore.CreateField, initialState); + }; + + // NOTIFY THE SERVER OF AN UPDATE TO A DOC'S STATE + + /** + * A wrapper around the function local variable _emitFieldUpdate. + * This allows us to swap in different executions while comfortably + * calling the same function throughout the code base (such as in Util.makeReadonly()) + * @param id the id of the [Doc] whose state has been updated in our client + * @param updatedState the new value of the document. At some point, this + * should actually be a proper diff, to improve efficiency + */ + export function emitFieldUpdate(id: string, updatedState: any) { + _emitFieldUpdate(id, updatedState); + } + + /** + * The default behavior for indicating to the server that we've locally updated + * a document. + * @param id the id of the [Doc] whose state has been updated in our client + * @param updatedState the new value of the document. At some point, this + * should actually be a proper diff, to improve efficiency + */ + let _emitFieldUpdate = (id: string, updatedState: any) => { + // don't emit a duplicate message if the server is already + // (asynchronously) still updating this document's state. if (id === updatingId) { return; } - Utils.Emit(_socket, MessageStore.UpdateField, { id, diff }); + // creates the diff object to send to the server + let diff: Diff = { id, diff: updatedState }; + // emit this diff to notify server + Utils.emit(_socket, MessageStore.UpdateField, diff); }; - export function UpdateField(id: string, diff: any) { - _UpdateField(id, diff); - } + // RESPOND TO THE SERVER'S INDICATION THAT A DOC'S STATE HAS BEEN UPDATED - let _CreateField = (field: RefField) => { - _cache[field[Id]] = field; - const initialState = SerializationHelper.Serialize(field); - Utils.Emit(_socket, MessageStore.CreateField, initialState); - }; + /** + * Whenever the client receives an update, execute the + * current behavior. + */ + Utils.addServerHandler(_socket, MessageStore.UpdateField, respondToUpdate); - export function CreateField(field: RefField) { - _CreateField(field); + /** + * A wrapper around the function local variable _respondToUpdate. + * This allows us to swap in different executions while comfortably + * calling the same function throughout the code base (such as in Util.makeReadonly()) + * @param diff kept as [any], but actually the [Diff] object sent from the server containing + * the [Doc]'s id and its new state + */ + function respondToUpdate(diff: any) { + _respondToUpdate(diff); } - let updatingId: string | undefined; + /** + * The default behavior for responding to another client's indication + * that it has updated the state of a [Doc] that is also in use by + * this client + * @param diff kept as [any], but actually the [Diff] object sent from the server containing + * the [Doc]'s id and its new state + */ let _respondToUpdate = (diff: any) => { const id = diff.id; + // to be valid, the Diff object must reference + // a document's id if (id === undefined) { return; } - const field = _cache[id]; const update = (f: Opt) => { + // if the RefField is absent from the cache or + // its promise in the cache resolves to undefined, there + // can't be anything to update if (f === undefined) { return; } + // extract this Doc's update handler const handler = f[HandleUpdate]; if (handler) { + // set the 'I'm currently updating this Doc' flag updatingId = id; handler.call(f, diff.diff); + // reset to indicate no ongoing updates updatingId = undefined; } }; + // check the cache for the field + const field = _cache[id]; if (field instanceof Promise) { + // if the field is still being retrieved, update when the promise is resolved field.then(update); } else { + // otherwise, just execute the update update(field); } }; - function respondToUpdate(diff: any) { - _respondToUpdate(diff); - } - - function connected() { - _socket.emit(MessageStore.Bar.Message, GUID); - } - Utils.AddServerHandler(_socket, MessageStore.Foo, connected); - Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate); } \ No newline at end of file diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 7ded85e43..ea8af3834 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -7,7 +7,7 @@ export namespace SerializationHelper { return serializing > 0; } - export function Serialize(obj: Field): any { + export function serialize(obj: Field): any { if (obj === undefined || obj === null) { return undefined; } @@ -124,7 +124,7 @@ export namespace Deserializable { export function autoObject(): PropSchema { return custom( - (s) => SerializationHelper.Serialize(s), + (s) => SerializationHelper.serialize(s), (s) => SerializationHelper.Deserialize(s) ); } \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index af65f5482..9da8912fe 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -80,7 +80,7 @@ export class Doc extends RefField { }); this[SelfProxy] = doc; if (!id || forceSave) { - DocServer.CreateField(doc); + DocServer.createField(doc); } return doc; } @@ -108,7 +108,7 @@ export class Doc extends RefField { private ___fields: any = {}; private [Update] = (diff: any) => { - DocServer.UpdateField(this[Id], diff); + DocServer.emitFieldUpdate(this[Id], diff); } private [Self] = this; diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 8caceb063..7709d6c24 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -43,7 +43,7 @@ export const setter = action(function (target: any, prop: string | symbol | numb } else { target.__fields[prop] = value; } - target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); + target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.serialize(value) : (value === undefined ? null : value) } }); UndoManager.AddEvent({ redo: () => receiver[prop] = value, undo: () => receiver[prop] = curValue @@ -103,7 +103,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any let current = ObjectField.MakeCopy(value); return (diff?: any) => { if (true || !diff) { - diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; + diff = { '$set': { ["fields." + prop]: SerializationHelper.serialize(value) } }; const oldValue = current; const newValue = ObjectField.MakeCopy(value); current = newValue; diff --git a/src/server/index.ts b/src/server/index.ts index fd66c90b4..d9fe9d93d 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -301,16 +301,16 @@ let clients: Map = {}; server.on("connection", function (socket: Socket) { console.log("a user has connected"); - Utils.Emit(socket, MessageStore.Foo, "handshooken"); + Utils.emit(socket, MessageStore.Foo, "handshooken"); - Utils.AddServerHandler(socket, MessageStore.Bar, barReceived); - Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)); + Utils.addServerHandler(socket, MessageStore.Bar, barReceived); + Utils.addServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)); Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); - Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields); + Utils.addServerHandler(socket, MessageStore.DeleteAll, deleteFields); - Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField); - Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff)); + Utils.addServerHandler(socket, MessageStore.CreateField, CreateField); + Utils.addServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff)); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); }); -- cgit v1.2.3-70-g09d2 From de0304b2966ebdede9d9db8c510e19020046115c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 17 Jun 2019 13:38:15 -0400 Subject: peripheral renaming fixes --- src/client/documents/Documents.ts | 4 ++-- src/client/util/History.ts | 6 +++--- src/client/util/SearchUtil.ts | 4 ++-- src/client/util/TooltipTextMenu.tsx | 8 ++++---- src/client/views/MainView.tsx | 10 +++++----- src/client/views/SearchBox.tsx | 6 +++--- src/client/views/collections/CollectionDockingView.tsx | 10 +++++----- src/client/views/collections/CollectionSubView.tsx | 8 ++++---- src/client/views/collections/CollectionVideoView.tsx | 2 +- src/client/views/collections/DockedFrameRenderer.tsx | 2 +- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- .../views/collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 6 +++--- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 2 +- src/debug/Viewer.tsx | 2 +- src/mobile/ImageUpload.tsx | 4 ++-- src/new_fields/Proxy.ts | 2 +- src/server/authentication/models/current_user_utils.ts | 6 +++--- 20 files changed, 46 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b10954636..758291b9b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,7 +98,7 @@ export namespace Docs { // non-guid string ids for each document prototype let protoIds = [textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId] // fetch the actual prototype documents from the server - let actualProtos = await DocServer.GetRefFields(protoIds); + let actualProtos = await DocServer.getRefFields(protoIds); // initialize prototype documents textProto = actualProtos[textProtoId] as Doc || CreateTextProto(); @@ -363,7 +363,7 @@ export namespace Docs { CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc); const docs = schemaDocuments; CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt) => { + DocServer.getRefField(attr.displayName! + ".alias").then(action((field: Opt) => { if (field instanceof Doc) { docs.push(field); } else { diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 545ea8629..94bfcbe09 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -88,7 +88,7 @@ export namespace HistoryUtil { } export function createUrl(params: ParsedUrl): string { - let baseUrl = DocServer.prepend(`/${params.type}`); + let baseUrl = DocServer.Util.prepend(`/${params.type}`); switch (params.type) { case "doc": const initializers = encodeURIComponent(JSON.stringify(params.initializers)); @@ -103,7 +103,7 @@ export namespace HistoryUtil { } export async function initDoc(id: string, initializer: DocInitializerList) { - const doc = await DocServer.GetRefField(id); + const doc = await DocServer.getRefField(id); if (!(doc instanceof Doc)) { return; } @@ -111,7 +111,7 @@ export namespace HistoryUtil { } async function onDocUrl(url: DocUrl) { - const field = await DocServer.GetRefField(url.docId); + const field = await DocServer.getRefField(url.docId); await Promise.all(Object.keys(url.initializers).map(id => initDoc(id, url.initializers[id]))); if (field instanceof Doc) { MainView.Instance.openWorkspace(field, true); diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 28ec8ca14..9dd9acbb7 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -7,13 +7,13 @@ export namespace SearchUtil { export function Search(query: string, returnDocs: true): Promise; export function Search(query: string, returnDocs: false): Promise; export async function Search(query: string, returnDocs: boolean) { - const ids = JSON.parse(await rp.get(DocServer.prepend("/search"), { + const ids = JSON.parse(await rp.get(DocServer.Util.prepend("/search"), { qs: { query } })); if (!returnDocs) { return ids; } - const docMap = await DocServer.GetRefFields(ids); + const docMap = await DocServer.getRefFields(ids); return ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc); } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index fa2483db5..36219a99e 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -187,9 +187,9 @@ export class TooltipTextMenu { let link = node && node.marks.find(m => m.type.name === "link"); if (link) { let href: string = link.attrs.href; - if (href.indexOf(DocServer.prepend("/doc/")) === 0) { - let docid = href.replace(DocServer.prepend("/doc/"), ""); - DocServer.GetRefField(docid).then(action((f: Opt) => { + if (href.indexOf(DocServer.Util.prepend("/doc/")) === 0) { + let docid = href.replace(DocServer.Util.prepend("/doc/"), ""); + DocServer.getRefField(docid).then(action((f: Opt) => { if (f instanceof Doc) { if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f); @@ -218,7 +218,7 @@ export class TooltipTextMenu { handlers: { dragComplete: action(() => { let m = dragData.droppedDocuments; - this.makeLink(DocServer.prepend("/doc/" + m[0][Id])); + this.makeLink(DocServer.Util.prepend("/doc/" + m[0][Id])); }), }, hideSource: false diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 984db0426..734961b56 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -76,11 +76,11 @@ export class MainView extends React.Component { // causes errors to be generated when modifying an observable outside of an action configure({ enforceActions: "observed" }); if (window.location.search.includes("readonly")) { - DocServer.makeReadOnly(); + DocServer.Util.makeReadOnly(); } if (window.location.search.includes("safe")) { if (!window.location.search.includes("nro")) { - DocServer.makeReadOnly(); + DocServer.Util.makeReadOnly(); } CollectionBaseView.SetSafeMode(true); } @@ -141,7 +141,7 @@ export class MainView extends React.Component { this.createNewWorkspace(); } } else { - DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => + DocServer.getRefField(CurrentUserUtils.MainDocId).then(field => field instanceof Doc ? this.openWorkspace(field) : this.createNewWorkspace(CurrentUserUtils.MainDocId)); } @@ -294,7 +294,7 @@ export class MainView extends React.Component { let logoutRef = React.createRef(); return [ - , + ,
+
]; } diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx index 7164d98a4..973715876 100644 --- a/src/client/views/SearchBox.tsx +++ b/src/client/views/SearchBox.tsx @@ -56,13 +56,13 @@ export class SearchBox extends React.Component { @action getResults = async (query: string) => { - let response = await rp.get(DocServer.prepend('/search'), { + let response = await rp.get(DocServer.Util.prepend('/search'), { qs: { query } }); let res: string[] = JSON.parse(response); - const fields = await DocServer.GetRefFields(res); + const fields = await DocServer.getRefFields(res); const docs: Doc[] = []; for (const id of res) { const field = fields[id]; @@ -74,7 +74,7 @@ export class SearchBox extends React.Component { } public static async convertDataUri(imageUri: string, returnedFilename: string) { try { - let posting = DocServer.prepend(RouteStore.dataUriToImage); + let posting = DocServer.Util.prepend(RouteStore.dataUriToImage); const returnedUri = await rp.post(posting, { body: { uri: imageUri, diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e2bcb10ec..4f5837590 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -306,7 +306,7 @@ export class CollectionDockingView extends React.Component) => + DocServer.getRefField(docid).then(action(async (sourceDoc: Opt) => (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc))); } else if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) { @@ -320,7 +320,7 @@ export class CollectionDockingView extends React.Component) => { + DocServer.getRefField(docid).then(action((f: Opt) => { if (f instanceof Doc) { DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y, { @@ -372,7 +372,7 @@ export class CollectionDockingView extends React.Component { + DocServer.getRefField(tab.contentItem.config.props.documentId).then(async doc => { if (doc instanceof Doc) { let counter: any = this.htmlToElement(`0
`); tab.element.append(counter); @@ -409,7 +409,7 @@ export class CollectionDockingView extends React.Component { - let doc = await DocServer.GetRefField(contentItem.config.props.documentId); + let doc = await DocServer.getRefField(contentItem.config.props.documentId); if (doc instanceof Doc) { let theDoc = doc; CollectionDockingView.TopLevel._removedDocs.push(theDoc); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 440a2410b..36e276d13 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -133,7 +133,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { if (path.includes(window.location.hostname)) { let s = path.split('/'); let id = s[s.length - 1]; - DocServer.GetRefField(id).then(field => { + DocServer.getRefField(id).then(field => { if (field instanceof Doc) { let alias = Doc.MakeAlias(field); alias.x = options.x || 0; @@ -170,8 +170,8 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { if (html && html.indexOf(document.location.origin)) { // prosemirror text containing link to dash document let start = html.indexOf(window.location.origin); let path = html.substr(start, html.length - start); - let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => (f instanceof Doc) && this.props.addDocument(f, false)); + let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.Util.prepend("/doc/"), "").split("?")[0]; + DocServer.getRefField(docid).then(f => (f instanceof Doc) && this.props.addDocument(f, false)); return; } if (html && html.indexOf("(schemaCtor: (doc: Doc) => T) { if (item.kind === "string" && item.type.indexOf("uri") !== -1) { let str: string; let prom = new Promise(resolve => e.dataTransfer.items[i].getAsString(resolve)) - .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s))))) + .then(action((s: string) => rp.head(DocServer.Util.prepend(RouteStore.corsProxy + "/" + (str = s))))) .then(result => { let type = result["content-type"]; if (type) { diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index bd5cd5450..ccbac9915 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -97,7 +97,7 @@ export class CollectionVideoView extends React.Component { let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, ""); SearchBox.convertDataUri(dataUrl, filename).then((returnedFilename) => { if (returnedFilename) { - let url = DocServer.prepend(returnedFilename); + let url = DocServer.Util.prepend(returnedFilename); let imageSummary = Docs.Create.ImageDocument(url, { x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" diff --git a/src/client/views/collections/DockedFrameRenderer.tsx b/src/client/views/collections/DockedFrameRenderer.tsx index 25d4b2a49..1e7c5661b 100644 --- a/src/client/views/collections/DockedFrameRenderer.tsx +++ b/src/client/views/collections/DockedFrameRenderer.tsx @@ -42,7 +42,7 @@ export class DockedFrameRenderer extends React.Component { } constructor(props: any) { super(props); - DocServer.GetRefField(this.props.documentId).then(action((f: Opt) => this._document = f as Doc)); + DocServer.getRefField(this.props.documentId).then(action((f: Opt) => this._document = f as Doc)); } nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9d19df540..cd613e6ab 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -235,8 +235,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { e.preventDefault(); let start = html.indexOf(window.location.origin); let path = html.substr(start, html.length - start); - let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => { + let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.Util.prepend("/doc/"), "").split("?")[0]; + DocServer.getRefField(docid).then(f => { if (f instanceof Doc) { f.x = pt[0]; f.y = pt[1]; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index cd386abfa..07a58ed64 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -306,7 +306,7 @@ export class MarqueeView extends React.Component let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => { if (returnedFilename) { - let url = DocServer.prepend(returnedFilename); + let url = DocServer.Util.prepend(returnedFilename); let imageSummary = Docs.Create.ImageDocument(url, { x: bounds.left, y: bounds.top + 100 / zoomBasis, width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-" diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 16e40000d..fdcb20e9a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -422,7 +422,7 @@ export class DocumentView extends DocComponent(Docu }, icon: "search" }); cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" }); - cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "link" }); + cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.Util.prepend("/doc/" + this.props.Document[Id])), icon: "link" }); cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); if (!this.topMost) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d00a4b928..6a14a04f7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -237,9 +237,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe href = parent.childNodes[0].href; } if (href) { - if (href.indexOf(DocServer.prepend("/doc/")) === 0) { - let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => { + if (href.indexOf(DocServer.Util.prepend("/doc/")) === 0) { + let docid = href.replace(DocServer.Util.prepend("/doc/"), "").split("?")[0]; + DocServer.getRefField(docid).then(f => { (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab")) }); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index aa29a7170..df9e49b64 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -257,7 +257,7 @@ export class PDFBox extends DocComponent(PdfDocumen .then(action((dataUrl: string) => { SearchBox.convertDataUri(dataUrl, "icon" + this.Document[Id] + "_" + this.curPage).then((returnedFilename) => { if (returnedFilename) { - let url = DocServer.prepend(returnedFilename); + let url = DocServer.Util.prepend(returnedFilename); this.props.Document.thumbnail = new ImageField(new URL(url)); } runInAction(() => this._renderAsSvg = true); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 35ecf12f6..9ab607e91 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -97,7 +97,7 @@ export class VideoBox extends DocComponent(VideoD }; try { let responseSchema: any = {}; - const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig); + const videoInfoResponse = await rp.get(DocServer.Util.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig); const dataHtml = videoInfoResponse; const start = dataHtml.indexOf('ytplayer.config = ') + 18; const end = dataHtml.indexOf(';ytplayer.load'); diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index b22300d0b..753149756 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -146,7 +146,7 @@ class Viewer extends React.Component { @action onKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter") { - DocServer.GetRefField(this.idToAdd).then(action((field: any) => { + DocServer.getRefField(this.idToAdd).then(action((field: any) => { if (field !== undefined) { this.fields.push(field); } diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index a8f94b746..df597e0a9 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -57,11 +57,11 @@ class Uploader extends React.Component { this.status = "getting user document"; - const res = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)); + const res = await rp.get(DocServer.Util.prepend(RouteStore.getUserDocumentId)); if (!res) { throw new Error("No user id returned"); } - const field = await DocServer.GetRefField(res); + const field = await DocServer.getRefField(res); let pending: Opt; if (field instanceof Doc) { pending = await Cast(field.optionalRightCollection, Doc); diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts index 130ec066e..230e4ab8b 100644 --- a/src/new_fields/Proxy.ts +++ b/src/new_fields/Proxy.ts @@ -57,7 +57,7 @@ export class ProxyField extends ObjectField { return undefined; } if (!this.promise) { - this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => { + this.promise = DocServer.getRefField(this.fieldId).then(action((field: any) => { this.promise = undefined; this.cache = field; if (field === undefined) this.failed = true; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 169be3b99..95c20d2db 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -40,7 +40,7 @@ export class CurrentUserUtils { } public static async loadCurrentUser(): Promise { - let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => { + let userPromise = rp.get(DocServer.Util.prepend(RouteStore.getCurrUser)).then(response => { if (response) { let obj = JSON.parse(response); CurrentUserUtils.curr_id = obj.id as string; @@ -49,9 +49,9 @@ export class CurrentUserUtils { throw new Error("There should be a user! Why does Dash think there isn't one?"); } }); - let userDocPromise = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => { + let userDocPromise = await rp.get(DocServer.Util.prepend(RouteStore.getUserDocumentId)).then(id => { if (id) { - return DocServer.GetRefField(id).then(field => + return DocServer.getRefField(id).then(field => runInAction(() => this.user_document = field instanceof Doc ? field : this.createUserDocument(id))); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); -- cgit v1.2.3-70-g09d2 From 4bec1d89eff45d6dcbb4041bc211db88d9da1c8f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 18 Jun 2019 22:02:58 -0400 Subject: fixed serialization typo and added draft of python word doc scraper to git directory for safety --- src/buxton/scraper.py | 128 +++++++++++++++++++++++++++++++++ src/client/DocServer.ts | 2 +- src/client/util/SerializationHelper.ts | 4 +- src/new_fields/util.ts | 4 +- 4 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 src/buxton/scraper.py (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py new file mode 100644 index 000000000..0abebb485 --- /dev/null +++ b/src/buxton/scraper.py @@ -0,0 +1,128 @@ +import os +import docx2txt +from docx import Document +from docx.opc.constants import RELATIONSHIP_TYPE as RT +import re +from pymongo import MongoClient +import shutil +import uuid + +source = "./source" +dist = "./Dash-Web/src/server/public/files" + +collection_handle = MongoClient("localhost", 27017)["Dash"]["buxton"] + +def extract_links(fileName): + links = [] + doc = Document(fileName) + rels = doc.part.rels + for rel in rels: + item = rels[rel] + if item.reltype == RT.HYPERLINK and ".aspx" not in item._target: + links.append(item._target) + return links + +def extract_value(kv_string): + return kv_string.split(":")[1].strip() + +def mkdir_if_absent(path): + try: + if not os.path.exists(path): + os.mkdir(path) + except OSError: + print("Failed to create the appropriate directory structures for %s" % file_name) + +def parse_document(file_name: str): + result = {} + pure_name = file_name.split(".")[0] + + dir_path = dist + "/" + pure_name + mkdir_if_absent(dir_path) + + raw = str(docx2txt.process(source + "/" + file_name, dir_path)) + + sanitize = lambda line: re.sub("[\n\t]+", "", line).strip().replace(u"\u00A0", " ").replace(u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''') + remove_empty = lambda line: len(line) > 1 + + lines = list(map(sanitize, raw.split("\n"))) + lines = list(filter(remove_empty, lines)) + + result["file_name"] = file_name + result["title"] = lines[2] + result["short_description"] = lines[3].replace("Short Description: ", "") + + cur = 5 + notes = "" + while lines[cur] != "Device Details": + notes += lines[cur] + " " + cur += 1 + result["buxton_notes"] = notes.strip() + + cur += 1 + clean = list(map(lambda data: data.strip().split(":"), lines[cur].split("|"))) + result["company"] = clean[0][1].strip() + result["year"] = clean[1][1].strip() + result["original_price"] = clean[2][1].strip() + + cur += 1 + result["degrees_of_freedom"] = extract_value(lines[cur]) + cur += 1 + result["dimensions"] = extract_value(lines[cur]) + + cur += 2 + result["primary_key"] = extract_value(lines[cur]) + cur += 1 + result["secondary_key"] = extract_value(lines[cur]) + + result["hyperlinks"] = extract_links(source + "/" + file_name) + + cur += 2 + link_descriptions = [] + while lines[cur] != "Image": + link_descriptions.append(lines[cur]) + cur += 1 + result["link_descriptions"] = link_descriptions + + images = [] + captions = [] + cur += 3 + while cur + 1 < len(lines) and lines[cur] != "NOTES:": + images.append(lines[cur]) + captions.append(lines[cur + 1]) + cur += 2 + result["images"] = images + result["captions"] = captions + + notes = [] + if (cur < len(lines) and lines[cur] == "NOTES:"): + cur += 1 + while cur < len(lines): + notes.append(lines[cur]) + cur += 1 + result["notes"] = notes + + return result + +def upload(document): + wrapper = {} + wrapper["_id"] = str(uuid.uuid4()) + wrapper["fields"] = document + wrapper["__type"] = "Doc" + collection_handle.insert_one(wrapper) + +if os.path.exists(dist): + shutil.rmtree(dist) +while (os.path.exists(dist)): + pass +os.mkdir(dist) + +for file_name in os.listdir(source): + if file_name.endswith('.docx'): + upload(parse_document(file_name)) + +lines = ['*', '!.gitignore'] +with open(dist + "/.gitignore", 'w') as f: + f.write('\n'.join(lines)) + + + diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index ad7c706b6..3b33657eb 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -257,7 +257,7 @@ export namespace DocServer { */ let _createField = (field: RefField) => { _cache[field[Id]] = field; - const initialState = SerializationHelper.serialize(field); + const initialState = SerializationHelper.Serialize(field); Utils.emit(_socket, MessageStore.CreateField, initialState); }; diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index ea8af3834..7ded85e43 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -7,7 +7,7 @@ export namespace SerializationHelper { return serializing > 0; } - export function serialize(obj: Field): any { + export function Serialize(obj: Field): any { if (obj === undefined || obj === null) { return undefined; } @@ -124,7 +124,7 @@ export namespace Deserializable { export function autoObject(): PropSchema { return custom( - (s) => SerializationHelper.serialize(s), + (s) => SerializationHelper.Serialize(s), (s) => SerializationHelper.Deserialize(s) ); } \ No newline at end of file diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 7709d6c24..8caceb063 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -43,7 +43,7 @@ export const setter = action(function (target: any, prop: string | symbol | numb } else { target.__fields[prop] = value; } - target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.serialize(value) : (value === undefined ? null : value) } }); + target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); UndoManager.AddEvent({ redo: () => receiver[prop] = value, undo: () => receiver[prop] = curValue @@ -103,7 +103,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any let current = ObjectField.MakeCopy(value); return (diff?: any) => { if (true || !diff) { - diff = { '$set': { ["fields." + prop]: SerializationHelper.serialize(value) } }; + diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; const oldValue = current; const newValue = ObjectField.MakeCopy(value); current = newValue; -- cgit v1.2.3-70-g09d2 From 5147528ef76ed069d7c5f1fc1feb7404c92227bc Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 19 Jun 2019 02:19:03 -0400 Subject: first pass at all documents, improved scraping for handling variation --- src/.DS_Store | Bin 6148 -> 6148 bytes src/buxton/scraper.py | 69 ++++++++++++++------- src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx | Bin 0 -> 1675500 bytes src/buxton/source/Bill_Notes_Braun_T3.docx | Bin 0 -> 1671968 bytes src/buxton/source/Bill_Notes_CasioC801.docx | Bin 0 -> 574664 bytes src/buxton/source/Bill_Notes_Casio_Mini.docx | Bin 0 -> 581069 bytes .../source/Bill_Notes_FingerWorks_Prototype.docx | Bin 0 -> 585090 bytes .../source/Bill_Notes_Fingerworks_TouchStream.docx | Bin 0 -> 1722555 bytes src/buxton/source/Bill_Notes_FrogPad.docx | Bin 0 -> 840173 bytes src/buxton/source/Bill_Notes_Gavilan_SC.docx | Bin 0 -> 1695290 bytes .../source/Bill_Notes_Grandjean_Stenotype.docx | Bin 0 -> 2094142 bytes src/buxton/source/Bill_Notes_Matias.docx | Bin 0 -> 590407 bytes src/buxton/source/Bill_Notes_MousePen.docx | Bin 0 -> 505322 bytes src/buxton/source/Bill_Notes_NewO.docx | Bin 0 -> 2264571 bytes src/buxton/source/Bill_Notes_OLPC.docx | Bin 0 -> 6883659 bytes src/buxton/source/Bill_Notes_PARCkbd.docx | Bin 0 -> 631959 bytes .../source/Bill_Notes_Philco_Mystery_Control.docx | Bin 0 -> 1994439 bytes src/buxton/source/Bill_Notes_TASA_Kbd.docx | Bin 0 -> 461199 bytes src/buxton/source/Bill_Notes_The_Tap.docx | Bin 0 -> 711321 bytes 19 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx create mode 100644 src/buxton/source/Bill_Notes_Braun_T3.docx create mode 100644 src/buxton/source/Bill_Notes_CasioC801.docx create mode 100644 src/buxton/source/Bill_Notes_Casio_Mini.docx create mode 100644 src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx create mode 100644 src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx create mode 100644 src/buxton/source/Bill_Notes_FrogPad.docx create mode 100644 src/buxton/source/Bill_Notes_Gavilan_SC.docx create mode 100644 src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx create mode 100644 src/buxton/source/Bill_Notes_Matias.docx create mode 100644 src/buxton/source/Bill_Notes_MousePen.docx create mode 100644 src/buxton/source/Bill_Notes_NewO.docx create mode 100644 src/buxton/source/Bill_Notes_OLPC.docx create mode 100644 src/buxton/source/Bill_Notes_PARCkbd.docx create mode 100644 src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx create mode 100644 src/buxton/source/Bill_Notes_TASA_Kbd.docx create mode 100644 src/buxton/source/Bill_Notes_The_Tap.docx (limited to 'src') diff --git a/src/.DS_Store b/src/.DS_Store index d70e95c0a..071dafa1e 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py index 0abebb485..854c99379 100644 --- a/src/buxton/scraper.py +++ b/src/buxton/scraper.py @@ -8,9 +8,12 @@ import shutil import uuid source = "./source" -dist = "./Dash-Web/src/server/public/files" +dist = "../server/public/files" + +db = MongoClient("localhost", 27017)["Dash"] +db.buxton.drop() +collection_handle = db.buxton -collection_handle = MongoClient("localhost", 27017)["Dash"]["buxton"] def extract_links(fileName): links = [] @@ -22,8 +25,11 @@ def extract_links(fileName): links.append(item._target) return links + def extract_value(kv_string): - return kv_string.split(":")[1].strip() + pieces = kv_string.split(":") + return (pieces[1] if len(pieces) > 1 else kv_string).strip() + def mkdir_if_absent(path): try: @@ -32,6 +38,7 @@ def mkdir_if_absent(path): except OSError: print("Failed to create the appropriate directory structures for %s" % file_name) + def parse_document(file_name: str): result = {} pure_name = file_name.split(".")[0] @@ -41,15 +48,18 @@ def parse_document(file_name: str): raw = str(docx2txt.process(source + "/" + file_name, dir_path)) - sanitize = lambda line: re.sub("[\n\t]+", "", line).strip().replace(u"\u00A0", " ").replace(u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''') - remove_empty = lambda line: len(line) > 1 + def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( + u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() + + def remove_empty(line): return len(line) > 1 lines = list(map(sanitize, raw.split("\n"))) lines = list(filter(remove_empty, lines)) result["file_name"] = file_name - result["title"] = lines[2] - result["short_description"] = lines[3].replace("Short Description: ", "") + result["title"] = lines[2].strip() + result["short_description"] = lines[3].strip().replace( + "Short Description: ", "") cur = 5 notes = "" @@ -57,32 +67,44 @@ def parse_document(file_name: str): notes += lines[cur] + " " cur += 1 result["buxton_notes"] = notes.strip() - + cur += 1 - clean = list(map(lambda data: data.strip().split(":"), lines[cur].split("|"))) - result["company"] = clean[0][1].strip() - result["year"] = clean[1][1].strip() - result["original_price"] = clean[2][1].strip() + clean = list( + map(lambda data: data.strip().split(":"), lines[cur].split("|"))) + result["company"] = clean[0][len(clean[0]) - 1].strip() + result["year"] = clean[1][len(clean[1]) - 1].strip() + result["original_price"] = clean[2][len(clean[2]) - 1].strip() cur += 1 result["degrees_of_freedom"] = extract_value(lines[cur]) cur += 1 - result["dimensions"] = extract_value(lines[cur]) - cur += 2 + dimensions = lines[cur].lower() + if dimensions.startswith("dimensions"): + result["dimensions"] = dimensions[11:].strip() + cur += 1 + while lines[cur] != "Key Words": + result["dimensions"] += (" " + lines[cur].strip()) + cur += 1 + + cur += 1 result["primary_key"] = extract_value(lines[cur]) cur += 1 result["secondary_key"] = extract_value(lines[cur]) - result["hyperlinks"] = extract_links(source + "/" + file_name) + while lines[cur] != "Links": + result["secondary_key"] += (" " + extract_value(lines[cur]).strip()) + cur += 1 - cur += 2 + cur += 1 link_descriptions = [] while lines[cur] != "Image": - link_descriptions.append(lines[cur]) + link_descriptions.append(lines[cur].strip()) cur += 1 result["link_descriptions"] = link_descriptions + result["hyperlinks"] = extract_links(source + "/" + file_name) + images = [] captions = [] cur += 3 @@ -99,9 +121,11 @@ def parse_document(file_name: str): while cur < len(lines): notes.append(lines[cur]) cur += 1 - result["notes"] = notes + if len(notes) > 0: + result["notes"] = notes + + return result - return result def upload(document): wrapper = {} @@ -110,11 +134,13 @@ def upload(document): wrapper["__type"] = "Doc" collection_handle.insert_one(wrapper) + if os.path.exists(dist): shutil.rmtree(dist) -while (os.path.exists(dist)): +while os.path.exists(dist): pass os.mkdir(dist) +mkdir_if_absent(source) for file_name in os.listdir(source): if file_name.endswith('.docx'): @@ -123,6 +149,3 @@ for file_name in os.listdir(source): lines = ['*', '!.gitignore'] with open(dist + "/.gitignore", 'w') as f: f.write('\n'.join(lines)) - - - diff --git a/src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx b/src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx new file mode 100644 index 000000000..06094b4d3 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx differ diff --git a/src/buxton/source/Bill_Notes_Braun_T3.docx b/src/buxton/source/Bill_Notes_Braun_T3.docx new file mode 100644 index 000000000..356697092 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Braun_T3.docx differ diff --git a/src/buxton/source/Bill_Notes_CasioC801.docx b/src/buxton/source/Bill_Notes_CasioC801.docx new file mode 100644 index 000000000..cd89fb97b Binary files /dev/null and b/src/buxton/source/Bill_Notes_CasioC801.docx differ diff --git a/src/buxton/source/Bill_Notes_Casio_Mini.docx b/src/buxton/source/Bill_Notes_Casio_Mini.docx new file mode 100644 index 000000000..a503cddfc Binary files /dev/null and b/src/buxton/source/Bill_Notes_Casio_Mini.docx differ diff --git a/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx b/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx new file mode 100644 index 000000000..4d13a8cf5 Binary files /dev/null and b/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx differ diff --git a/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx b/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx new file mode 100644 index 000000000..578a1be08 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx differ diff --git a/src/buxton/source/Bill_Notes_FrogPad.docx b/src/buxton/source/Bill_Notes_FrogPad.docx new file mode 100644 index 000000000..d01e1bf5c Binary files /dev/null and b/src/buxton/source/Bill_Notes_FrogPad.docx differ diff --git a/src/buxton/source/Bill_Notes_Gavilan_SC.docx b/src/buxton/source/Bill_Notes_Gavilan_SC.docx new file mode 100644 index 000000000..7bd28b376 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Gavilan_SC.docx differ diff --git a/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx b/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx new file mode 100644 index 000000000..0615c4953 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx differ diff --git a/src/buxton/source/Bill_Notes_Matias.docx b/src/buxton/source/Bill_Notes_Matias.docx new file mode 100644 index 000000000..547603256 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Matias.docx differ diff --git a/src/buxton/source/Bill_Notes_MousePen.docx b/src/buxton/source/Bill_Notes_MousePen.docx new file mode 100644 index 000000000..4e1056636 Binary files /dev/null and b/src/buxton/source/Bill_Notes_MousePen.docx differ diff --git a/src/buxton/source/Bill_Notes_NewO.docx b/src/buxton/source/Bill_Notes_NewO.docx new file mode 100644 index 000000000..a514926d2 Binary files /dev/null and b/src/buxton/source/Bill_Notes_NewO.docx differ diff --git a/src/buxton/source/Bill_Notes_OLPC.docx b/src/buxton/source/Bill_Notes_OLPC.docx new file mode 100644 index 000000000..bfca0a9bb Binary files /dev/null and b/src/buxton/source/Bill_Notes_OLPC.docx differ diff --git a/src/buxton/source/Bill_Notes_PARCkbd.docx b/src/buxton/source/Bill_Notes_PARCkbd.docx new file mode 100644 index 000000000..c0cf6ba9a Binary files /dev/null and b/src/buxton/source/Bill_Notes_PARCkbd.docx differ diff --git a/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx b/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx new file mode 100644 index 000000000..ad06903f3 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx differ diff --git a/src/buxton/source/Bill_Notes_TASA_Kbd.docx b/src/buxton/source/Bill_Notes_TASA_Kbd.docx new file mode 100644 index 000000000..e4c659de9 Binary files /dev/null and b/src/buxton/source/Bill_Notes_TASA_Kbd.docx differ diff --git a/src/buxton/source/Bill_Notes_The_Tap.docx b/src/buxton/source/Bill_Notes_The_Tap.docx new file mode 100644 index 000000000..8ceebc71e Binary files /dev/null and b/src/buxton/source/Bill_Notes_The_Tap.docx differ -- cgit v1.2.3-70-g09d2 From 48d2ece574f613c084a083ae05148498dd0030e3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 19 Jun 2019 14:13:46 -0400 Subject: implemented simulation of view and data docs --- src/buxton/scraper.py | 122 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py index 854c99379..a3bbc75ac 100644 --- a/src/buxton/scraper.py +++ b/src/buxton/scraper.py @@ -6,13 +6,14 @@ import re from pymongo import MongoClient import shutil import uuid +import datetime +from PIL import Image source = "./source" dist = "../server/public/files" db = MongoClient("localhost", 27017)["Dash"] -db.buxton.drop() -collection_handle = db.buxton +view_doc_guids = [] def extract_links(fileName): @@ -39,7 +40,71 @@ def mkdir_if_absent(path): print("Failed to create the appropriate directory structures for %s" % file_name) +def guid(): + return str(uuid.uuid4()) + + +def write_image(folder, name): + path = f"http://localhost:1050/files/{folder}/{name}" + + data_doc_guid = guid() + view_doc_guid = guid() + + view_doc = { + "_id": view_doc_guid, + "fields": { + "proto": { + "fieldId": data_doc_guid, + "__type": "proxy" + }, + "x": 10, + "y": 10, + "width": 300, + "zIndex": 2, + "libraryBrush": False + }, + "__type": "Doc" + } + + image = Image.open(f"{dist}/{folder}/{name}") + native_width, native_height = image.size + + data_doc = { + "_id": data_doc_guid, + "fields": { + "proto": { + "_id": "imageProto", + "__type": "proxy" + }, + "data": { + "url": path, + "type": "image" + }, + "title": name, + "nativeWidth": native_width, + "author": "Bill Buxton", + "creationDate": { + "date": datetime.datetime.utcnow().microsecond, + "__type": "date" + }, + "isPrototype": True, + "page": -1, + "nativeHeight": native_height, + "height": native_height + }, + "__type": "Doc" + } + + db.newDocuments.insert_one(view_doc) + db.newDocuments.insert_one(data_doc) + + print(path) + + return view_doc_guid + + def parse_document(file_name: str): + print(f"Parsing {file_name}...") result = {} pure_name = file_name.split(".")[0] @@ -48,6 +113,11 @@ def parse_document(file_name: str): raw = str(docx2txt.process(source + "/" + file_name, dir_path)) + print("Extracting images...\n") + for image in os.listdir(dir_path): + view_doc_guids.append(write_image(pure_name, image)) + print() + def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() @@ -76,16 +146,20 @@ def parse_document(file_name: str): result["original_price"] = clean[2][len(clean[2]) - 1].strip() cur += 1 - result["degrees_of_freedom"] = extract_value(lines[cur]) + result["degrees_of_freedom"] = extract_value( + lines[cur]).replace("NA", "N/A") cur += 1 dimensions = lines[cur].lower() if dimensions.startswith("dimensions"): - result["dimensions"] = dimensions[11:].strip() + dim_concat = dimensions[11:].strip() cur += 1 while lines[cur] != "Key Words": - result["dimensions"] += (" " + lines[cur].strip()) + dim_concat += (" " + lines[cur].strip()) cur += 1 + result["dimensions"] = dim_concat + else: + result["dimensions"] = "N/A" cur += 1 result["primary_key"] = extract_value(lines[cur]) @@ -124,15 +198,22 @@ def parse_document(file_name: str): if len(notes) > 0: result["notes"] = notes + print("...contents dictionary constructed.") + return result -def upload(document): - wrapper = {} - wrapper["_id"] = str(uuid.uuid4()) - wrapper["fields"] = document - wrapper["__type"] = "Doc" - collection_handle.insert_one(wrapper) +def wrap(document): + return { + "_id": guid(), + "fields": document, + "__type": "Doc" + } + + +def upload(collection, mongofied): + for doc in mongofied: + collection.insert_one(doc) if os.path.exists(dist): @@ -142,9 +223,26 @@ while os.path.exists(dist): os.mkdir(dist) mkdir_if_absent(source) +candidates = 0 +mongofied = [] for file_name in os.listdir(source): if file_name.endswith('.docx'): - upload(parse_document(file_name)) + candidates += 1 + mongofied.append(wrap(parse_document(file_name))) + +for doc in mongofied: + db.newDocuments.insert_one(doc) + +proxified = list( + map(lambda guid: {"fieldId": guid, "type": "proxy"}, view_doc_guids)) +db.newDocuments.update_one( + {"fields.title": "WS collection 1"}, + {"$push": {"fields.data.fields": {"$each": proxified}}} +) + +print("...dictionaries written to Dash Document.\n") + +print(f"{candidates} candidates processed.") lines = ['*', '!.gitignore'] with open(dist + "/.gitignore", 'w') as f: -- cgit v1.2.3-70-g09d2 From cbb2f4191e31d72c8c727976b5616983af15af45 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 19 Jun 2019 14:38:13 -0400 Subject: Image import working! --- src/buxton/scraper.py | 9 ++++++--- src/buxton/source/Bill_Notes_Braun_T3.docx | Bin 1671968 -> 0 bytes src/buxton/source/Bill_Notes_CasioC801.docx | Bin 574664 -> 0 bytes src/buxton/source/Bill_Notes_Casio_Mini.docx | Bin 581069 -> 0 bytes .../source/Bill_Notes_FingerWorks_Prototype.docx | Bin 585090 -> 0 bytes .../source/Bill_Notes_Fingerworks_TouchStream.docx | Bin 1722555 -> 0 bytes src/buxton/source/Bill_Notes_FrogPad.docx | Bin 840173 -> 0 bytes src/buxton/source/Bill_Notes_Gavilan_SC.docx | Bin 1695290 -> 0 bytes src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx | Bin 2094142 -> 0 bytes src/buxton/source/Bill_Notes_Matias.docx | Bin 590407 -> 0 bytes src/buxton/source/Bill_Notes_MousePen.docx | Bin 505322 -> 0 bytes src/buxton/source/Bill_Notes_NewO.docx | Bin 2264571 -> 0 bytes src/buxton/source/Bill_Notes_OLPC.docx | Bin 6883659 -> 0 bytes src/buxton/source/Bill_Notes_PARCkbd.docx | Bin 631959 -> 0 bytes .../source/Bill_Notes_Philco_Mystery_Control.docx | Bin 1994439 -> 0 bytes src/buxton/source/Bill_Notes_TASA_Kbd.docx | Bin 461199 -> 0 bytes src/buxton/source/Bill_Notes_The_Tap.docx | Bin 711321 -> 0 bytes src/buxton/source/Extra/Bill_Notes_Braun_T3.docx | Bin 0 -> 1671968 bytes src/buxton/source/Extra/Bill_Notes_CasioC801.docx | Bin 0 -> 574664 bytes src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx | Bin 0 -> 581069 bytes .../Extra/Bill_Notes_FingerWorks_Prototype.docx | Bin 0 -> 585090 bytes .../Extra/Bill_Notes_Fingerworks_TouchStream.docx | Bin 0 -> 1722555 bytes src/buxton/source/Extra/Bill_Notes_FrogPad.docx | Bin 0 -> 840173 bytes src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx | Bin 0 -> 1695290 bytes .../source/Extra/Bill_Notes_Grandjean_Stenotype.docx | Bin 0 -> 2094142 bytes src/buxton/source/Extra/Bill_Notes_Matias.docx | Bin 0 -> 590407 bytes src/buxton/source/Extra/Bill_Notes_MousePen.docx | Bin 0 -> 505322 bytes src/buxton/source/Extra/Bill_Notes_NewO.docx | Bin 0 -> 2264571 bytes src/buxton/source/Extra/Bill_Notes_OLPC.docx | Bin 0 -> 6883659 bytes src/buxton/source/Extra/Bill_Notes_PARCkbd.docx | Bin 0 -> 631959 bytes .../Extra/Bill_Notes_Philco_Mystery_Control.docx | Bin 0 -> 1994439 bytes src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx | Bin 0 -> 461199 bytes src/buxton/source/Extra/Bill_Notes_The_Tap.docx | Bin 0 -> 711321 bytes 33 files changed, 6 insertions(+), 3 deletions(-) delete mode 100644 src/buxton/source/Bill_Notes_Braun_T3.docx delete mode 100644 src/buxton/source/Bill_Notes_CasioC801.docx delete mode 100644 src/buxton/source/Bill_Notes_Casio_Mini.docx delete mode 100644 src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx delete mode 100644 src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx delete mode 100644 src/buxton/source/Bill_Notes_FrogPad.docx delete mode 100644 src/buxton/source/Bill_Notes_Gavilan_SC.docx delete mode 100644 src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx delete mode 100644 src/buxton/source/Bill_Notes_Matias.docx delete mode 100644 src/buxton/source/Bill_Notes_MousePen.docx delete mode 100644 src/buxton/source/Bill_Notes_NewO.docx delete mode 100644 src/buxton/source/Bill_Notes_OLPC.docx delete mode 100644 src/buxton/source/Bill_Notes_PARCkbd.docx delete mode 100644 src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx delete mode 100644 src/buxton/source/Bill_Notes_TASA_Kbd.docx delete mode 100644 src/buxton/source/Bill_Notes_The_Tap.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_Braun_T3.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_CasioC801.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_FrogPad.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_Matias.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_MousePen.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_NewO.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_OLPC.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_PARCkbd.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx create mode 100644 src/buxton/source/Extra/Bill_Notes_The_Tap.docx (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py index a3bbc75ac..c7efd8f09 100644 --- a/src/buxton/scraper.py +++ b/src/buxton/scraper.py @@ -78,7 +78,7 @@ def write_image(folder, name): }, "data": { "url": path, - "type": "image" + "__type": "image" }, "title": name, "nativeWidth": native_width, @@ -105,9 +105,10 @@ def write_image(folder, name): def parse_document(file_name: str): print(f"Parsing {file_name}...") - result = {} pure_name = file_name.split(".")[0] + result = {} + dir_path = dist + "/" + pure_name mkdir_if_absent(dir_path) @@ -116,6 +117,8 @@ def parse_document(file_name: str): print("Extracting images...\n") for image in os.listdir(dir_path): view_doc_guids.append(write_image(pure_name, image)) + os.rename(dir_path + "/" + image, dir_path + + "/" + image.replace(".", "_m.", 1)) print() def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( @@ -234,7 +237,7 @@ for doc in mongofied: db.newDocuments.insert_one(doc) proxified = list( - map(lambda guid: {"fieldId": guid, "type": "proxy"}, view_doc_guids)) + map(lambda guid: {"fieldId": guid, "__type": "proxy"}, view_doc_guids)) db.newDocuments.update_one( {"fields.title": "WS collection 1"}, {"$push": {"fields.data.fields": {"$each": proxified}}} diff --git a/src/buxton/source/Bill_Notes_Braun_T3.docx b/src/buxton/source/Bill_Notes_Braun_T3.docx deleted file mode 100644 index 356697092..000000000 Binary files a/src/buxton/source/Bill_Notes_Braun_T3.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_CasioC801.docx b/src/buxton/source/Bill_Notes_CasioC801.docx deleted file mode 100644 index cd89fb97b..000000000 Binary files a/src/buxton/source/Bill_Notes_CasioC801.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Casio_Mini.docx b/src/buxton/source/Bill_Notes_Casio_Mini.docx deleted file mode 100644 index a503cddfc..000000000 Binary files a/src/buxton/source/Bill_Notes_Casio_Mini.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx b/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx deleted file mode 100644 index 4d13a8cf5..000000000 Binary files a/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx b/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx deleted file mode 100644 index 578a1be08..000000000 Binary files a/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_FrogPad.docx b/src/buxton/source/Bill_Notes_FrogPad.docx deleted file mode 100644 index d01e1bf5c..000000000 Binary files a/src/buxton/source/Bill_Notes_FrogPad.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Gavilan_SC.docx b/src/buxton/source/Bill_Notes_Gavilan_SC.docx deleted file mode 100644 index 7bd28b376..000000000 Binary files a/src/buxton/source/Bill_Notes_Gavilan_SC.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx b/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx deleted file mode 100644 index 0615c4953..000000000 Binary files a/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Matias.docx b/src/buxton/source/Bill_Notes_Matias.docx deleted file mode 100644 index 547603256..000000000 Binary files a/src/buxton/source/Bill_Notes_Matias.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_MousePen.docx b/src/buxton/source/Bill_Notes_MousePen.docx deleted file mode 100644 index 4e1056636..000000000 Binary files a/src/buxton/source/Bill_Notes_MousePen.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_NewO.docx b/src/buxton/source/Bill_Notes_NewO.docx deleted file mode 100644 index a514926d2..000000000 Binary files a/src/buxton/source/Bill_Notes_NewO.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_OLPC.docx b/src/buxton/source/Bill_Notes_OLPC.docx deleted file mode 100644 index bfca0a9bb..000000000 Binary files a/src/buxton/source/Bill_Notes_OLPC.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_PARCkbd.docx b/src/buxton/source/Bill_Notes_PARCkbd.docx deleted file mode 100644 index c0cf6ba9a..000000000 Binary files a/src/buxton/source/Bill_Notes_PARCkbd.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx b/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx deleted file mode 100644 index ad06903f3..000000000 Binary files a/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_TASA_Kbd.docx b/src/buxton/source/Bill_Notes_TASA_Kbd.docx deleted file mode 100644 index e4c659de9..000000000 Binary files a/src/buxton/source/Bill_Notes_TASA_Kbd.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_The_Tap.docx b/src/buxton/source/Bill_Notes_The_Tap.docx deleted file mode 100644 index 8ceebc71e..000000000 Binary files a/src/buxton/source/Bill_Notes_The_Tap.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_Braun_T3.docx b/src/buxton/source/Extra/Bill_Notes_Braun_T3.docx new file mode 100644 index 000000000..356697092 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_Braun_T3.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_CasioC801.docx b/src/buxton/source/Extra/Bill_Notes_CasioC801.docx new file mode 100644 index 000000000..cd89fb97b Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_CasioC801.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx b/src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx new file mode 100644 index 000000000..a503cddfc Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx b/src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx new file mode 100644 index 000000000..4d13a8cf5 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx b/src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx new file mode 100644 index 000000000..578a1be08 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_FrogPad.docx b/src/buxton/source/Extra/Bill_Notes_FrogPad.docx new file mode 100644 index 000000000..d01e1bf5c Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_FrogPad.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx b/src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx new file mode 100644 index 000000000..7bd28b376 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx b/src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx new file mode 100644 index 000000000..0615c4953 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_Matias.docx b/src/buxton/source/Extra/Bill_Notes_Matias.docx new file mode 100644 index 000000000..547603256 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_Matias.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_MousePen.docx b/src/buxton/source/Extra/Bill_Notes_MousePen.docx new file mode 100644 index 000000000..4e1056636 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_MousePen.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_NewO.docx b/src/buxton/source/Extra/Bill_Notes_NewO.docx new file mode 100644 index 000000000..a514926d2 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_NewO.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_OLPC.docx b/src/buxton/source/Extra/Bill_Notes_OLPC.docx new file mode 100644 index 000000000..bfca0a9bb Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_OLPC.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_PARCkbd.docx b/src/buxton/source/Extra/Bill_Notes_PARCkbd.docx new file mode 100644 index 000000000..c0cf6ba9a Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_PARCkbd.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx b/src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx new file mode 100644 index 000000000..ad06903f3 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx b/src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx new file mode 100644 index 000000000..e4c659de9 Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_The_Tap.docx b/src/buxton/source/Extra/Bill_Notes_The_Tap.docx new file mode 100644 index 000000000..8ceebc71e Binary files /dev/null and b/src/buxton/source/Extra/Bill_Notes_The_Tap.docx differ -- cgit v1.2.3-70-g09d2 From 5202cb26929a4bfe7b0881473ebcdebb06e91248 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 19 Jun 2019 17:44:56 -0400 Subject: proof of concept, imports metadata-embedded schemas --- src/buxton/scraper.py | 113 +++++++++++++++------ src/buxton/source/Bill_Notes_Braun_T3.docx | Bin 0 -> 1671968 bytes src/buxton/source/Bill_Notes_CasioC801.docx | Bin 0 -> 574664 bytes src/buxton/source/Bill_Notes_Casio_Mini.docx | Bin 0 -> 581069 bytes .../source/Bill_Notes_FingerWorks_Prototype.docx | Bin 0 -> 585090 bytes .../source/Bill_Notes_Fingerworks_TouchStream.docx | Bin 0 -> 1722555 bytes src/buxton/source/Bill_Notes_FrogPad.docx | Bin 0 -> 840173 bytes src/buxton/source/Bill_Notes_Gavilan_SC.docx | Bin 0 -> 1695290 bytes .../source/Bill_Notes_Grandjean_Stenotype.docx | Bin 0 -> 2094142 bytes src/buxton/source/Bill_Notes_Matias.docx | Bin 0 -> 590407 bytes src/buxton/source/Bill_Notes_MousePen.docx | Bin 0 -> 505322 bytes src/buxton/source/Bill_Notes_NewO.docx | Bin 0 -> 2264571 bytes src/buxton/source/Bill_Notes_OLPC.docx | Bin 0 -> 6883659 bytes src/buxton/source/Bill_Notes_PARCkbd.docx | Bin 0 -> 631959 bytes .../source/Bill_Notes_Philco_Mystery_Control.docx | Bin 0 -> 1994439 bytes src/buxton/source/Bill_Notes_TASA_Kbd.docx | Bin 0 -> 461199 bytes src/buxton/source/Bill_Notes_The_Tap.docx | Bin 0 -> 711321 bytes src/buxton/source/Extra/Bill_Notes_Braun_T3.docx | Bin 1671968 -> 0 bytes src/buxton/source/Extra/Bill_Notes_CasioC801.docx | Bin 574664 -> 0 bytes src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx | Bin 581069 -> 0 bytes .../Extra/Bill_Notes_FingerWorks_Prototype.docx | Bin 585090 -> 0 bytes .../Extra/Bill_Notes_Fingerworks_TouchStream.docx | Bin 1722555 -> 0 bytes src/buxton/source/Extra/Bill_Notes_FrogPad.docx | Bin 840173 -> 0 bytes src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx | Bin 1695290 -> 0 bytes .../Extra/Bill_Notes_Grandjean_Stenotype.docx | Bin 2094142 -> 0 bytes src/buxton/source/Extra/Bill_Notes_Matias.docx | Bin 590407 -> 0 bytes src/buxton/source/Extra/Bill_Notes_MousePen.docx | Bin 505322 -> 0 bytes src/buxton/source/Extra/Bill_Notes_NewO.docx | Bin 2264571 -> 0 bytes src/buxton/source/Extra/Bill_Notes_OLPC.docx | Bin 6883659 -> 0 bytes src/buxton/source/Extra/Bill_Notes_PARCkbd.docx | Bin 631959 -> 0 bytes .../Extra/Bill_Notes_Philco_Mystery_Control.docx | Bin 1994439 -> 0 bytes src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx | Bin 461199 -> 0 bytes src/buxton/source/Extra/Bill_Notes_The_Tap.docx | Bin 711321 -> 0 bytes 33 files changed, 80 insertions(+), 33 deletions(-) create mode 100644 src/buxton/source/Bill_Notes_Braun_T3.docx create mode 100644 src/buxton/source/Bill_Notes_CasioC801.docx create mode 100644 src/buxton/source/Bill_Notes_Casio_Mini.docx create mode 100644 src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx create mode 100644 src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx create mode 100644 src/buxton/source/Bill_Notes_FrogPad.docx create mode 100644 src/buxton/source/Bill_Notes_Gavilan_SC.docx create mode 100644 src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx create mode 100644 src/buxton/source/Bill_Notes_Matias.docx create mode 100644 src/buxton/source/Bill_Notes_MousePen.docx create mode 100644 src/buxton/source/Bill_Notes_NewO.docx create mode 100644 src/buxton/source/Bill_Notes_OLPC.docx create mode 100644 src/buxton/source/Bill_Notes_PARCkbd.docx create mode 100644 src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx create mode 100644 src/buxton/source/Bill_Notes_TASA_Kbd.docx create mode 100644 src/buxton/source/Bill_Notes_The_Tap.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_Braun_T3.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_CasioC801.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_FrogPad.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_Matias.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_MousePen.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_NewO.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_OLPC.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_PARCkbd.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx delete mode 100644 src/buxton/source/Extra/Bill_Notes_The_Tap.docx (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py index c7efd8f09..c89961f54 100644 --- a/src/buxton/scraper.py +++ b/src/buxton/scraper.py @@ -13,7 +13,7 @@ source = "./source" dist = "../server/public/files" db = MongoClient("localhost", 27017)["Dash"] -view_doc_guids = [] +schema_guids = [] def extract_links(fileName): @@ -24,7 +24,7 @@ def extract_links(fileName): item = rels[rel] if item.reltype == RT.HYPERLINK and ".aspx" not in item._target: links.append(item._target) - return links + return listify(links) def extract_value(kv_string): @@ -44,6 +44,66 @@ def guid(): return str(uuid.uuid4()) +def listify(list): + return { + "fields": list, + "__type": "list" + } + + +def protofy(fieldId): + return { + "fieldId": fieldId, + "__type": "proxy" + } + + +def write_schema(parse_results): + view_guids = parse_results["view_guids"] + + data_doc = parse_results["schema"] + fields = data_doc["fields"] + + view_doc_guid = guid() + + view_doc = { + "_id": view_doc_guid, + "fields": { + "proto": protofy(data_doc["_id"]), + "x": 10, + "y": 10, + "width": 900, + "height": 600, + "panX": 0, + "panY": 0, + "zoomBasis": 0.5, + "zIndex": 2, + "libraryBrush": False, + "viewType": 2 + }, + "__type": "Doc" + } + + fields["proto"] = protofy("collectionProto") + fields["data"] = listify(proxify_guids(view_guids)) + fields["schemaColumns"] = listify(["title", "data"]) + fields["backgroundColor"] = "white" + fields["scale"] = 0.5 + fields["viewType"] = 2 + fields["author"] = "Bill Buxton" + fields["creationDate"] = { + "date": datetime.datetime.utcnow().microsecond, + "__type": "date" + } + fields["isPrototype"] = True + fields["page"] = -1 + + db.newDocuments.insert_one(data_doc) + db.newDocuments.insert_one(view_doc) + + return view_doc_guid + + def write_image(folder, name): path = f"http://localhost:1050/files/{folder}/{name}" @@ -53,10 +113,7 @@ def write_image(folder, name): view_doc = { "_id": view_doc_guid, "fields": { - "proto": { - "fieldId": data_doc_guid, - "__type": "proxy" - }, + "proto": protofy(data_doc_guid), "x": 10, "y": 10, "width": 300, @@ -72,10 +129,7 @@ def write_image(folder, name): data_doc = { "_id": data_doc_guid, "fields": { - "proto": { - "_id": "imageProto", - "__type": "proxy" - }, + "proto": protofy("imageProto"), "data": { "url": path, "__type": "image" @@ -115,8 +169,9 @@ def parse_document(file_name: str): raw = str(docx2txt.process(source + "/" + file_name, dir_path)) print("Extracting images...\n") + view_guids = [] for image in os.listdir(dir_path): - view_doc_guids.append(write_image(pure_name, image)) + view_guids.append(write_image(pure_name, image)) os.rename(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_m.", 1)) print() @@ -178,7 +233,7 @@ def parse_document(file_name: str): while lines[cur] != "Image": link_descriptions.append(lines[cur].strip()) cur += 1 - result["link_descriptions"] = link_descriptions + result["link_descriptions"] = listify(link_descriptions) result["hyperlinks"] = extract_links(source + "/" + file_name) @@ -189,8 +244,8 @@ def parse_document(file_name: str): images.append(lines[cur]) captions.append(lines[cur + 1]) cur += 2 - result["images"] = images - result["captions"] = captions + result["images"] = listify(images) + result["captions"] = listify(captions) notes = [] if (cur < len(lines) and lines[cur] == "NOTES:"): @@ -199,24 +254,22 @@ def parse_document(file_name: str): notes.append(lines[cur]) cur += 1 if len(notes) > 0: - result["notes"] = notes + result["notes"] = listify(notes) print("...contents dictionary constructed.") - return result - - -def wrap(document): return { - "_id": guid(), - "fields": document, - "__type": "Doc" + "schema": { + "_id": guid(), + "fields": result, + "__type": "Doc" + }, + "view_guids": view_guids } -def upload(collection, mongofied): - for doc in mongofied: - collection.insert_one(doc) +def proxify_guids(guids): + return list(map(lambda guid: {"fieldId": guid, "__type": "proxy"}, guids)) if os.path.exists(dist): @@ -227,20 +280,14 @@ os.mkdir(dist) mkdir_if_absent(source) candidates = 0 -mongofied = [] for file_name in os.listdir(source): if file_name.endswith('.docx'): candidates += 1 - mongofied.append(wrap(parse_document(file_name))) - -for doc in mongofied: - db.newDocuments.insert_one(doc) + schema_guids.append(write_schema(parse_document(file_name))) -proxified = list( - map(lambda guid: {"fieldId": guid, "__type": "proxy"}, view_doc_guids)) db.newDocuments.update_one( {"fields.title": "WS collection 1"}, - {"$push": {"fields.data.fields": {"$each": proxified}}} + {"$push": {"fields.data.fields": {"$each": proxify_guids(schema_guids)}}} ) print("...dictionaries written to Dash Document.\n") diff --git a/src/buxton/source/Bill_Notes_Braun_T3.docx b/src/buxton/source/Bill_Notes_Braun_T3.docx new file mode 100644 index 000000000..356697092 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Braun_T3.docx differ diff --git a/src/buxton/source/Bill_Notes_CasioC801.docx b/src/buxton/source/Bill_Notes_CasioC801.docx new file mode 100644 index 000000000..cd89fb97b Binary files /dev/null and b/src/buxton/source/Bill_Notes_CasioC801.docx differ diff --git a/src/buxton/source/Bill_Notes_Casio_Mini.docx b/src/buxton/source/Bill_Notes_Casio_Mini.docx new file mode 100644 index 000000000..a503cddfc Binary files /dev/null and b/src/buxton/source/Bill_Notes_Casio_Mini.docx differ diff --git a/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx b/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx new file mode 100644 index 000000000..4d13a8cf5 Binary files /dev/null and b/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx differ diff --git a/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx b/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx new file mode 100644 index 000000000..578a1be08 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx differ diff --git a/src/buxton/source/Bill_Notes_FrogPad.docx b/src/buxton/source/Bill_Notes_FrogPad.docx new file mode 100644 index 000000000..d01e1bf5c Binary files /dev/null and b/src/buxton/source/Bill_Notes_FrogPad.docx differ diff --git a/src/buxton/source/Bill_Notes_Gavilan_SC.docx b/src/buxton/source/Bill_Notes_Gavilan_SC.docx new file mode 100644 index 000000000..7bd28b376 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Gavilan_SC.docx differ diff --git a/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx b/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx new file mode 100644 index 000000000..0615c4953 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx differ diff --git a/src/buxton/source/Bill_Notes_Matias.docx b/src/buxton/source/Bill_Notes_Matias.docx new file mode 100644 index 000000000..547603256 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Matias.docx differ diff --git a/src/buxton/source/Bill_Notes_MousePen.docx b/src/buxton/source/Bill_Notes_MousePen.docx new file mode 100644 index 000000000..4e1056636 Binary files /dev/null and b/src/buxton/source/Bill_Notes_MousePen.docx differ diff --git a/src/buxton/source/Bill_Notes_NewO.docx b/src/buxton/source/Bill_Notes_NewO.docx new file mode 100644 index 000000000..a514926d2 Binary files /dev/null and b/src/buxton/source/Bill_Notes_NewO.docx differ diff --git a/src/buxton/source/Bill_Notes_OLPC.docx b/src/buxton/source/Bill_Notes_OLPC.docx new file mode 100644 index 000000000..bfca0a9bb Binary files /dev/null and b/src/buxton/source/Bill_Notes_OLPC.docx differ diff --git a/src/buxton/source/Bill_Notes_PARCkbd.docx b/src/buxton/source/Bill_Notes_PARCkbd.docx new file mode 100644 index 000000000..c0cf6ba9a Binary files /dev/null and b/src/buxton/source/Bill_Notes_PARCkbd.docx differ diff --git a/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx b/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx new file mode 100644 index 000000000..ad06903f3 Binary files /dev/null and b/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx differ diff --git a/src/buxton/source/Bill_Notes_TASA_Kbd.docx b/src/buxton/source/Bill_Notes_TASA_Kbd.docx new file mode 100644 index 000000000..e4c659de9 Binary files /dev/null and b/src/buxton/source/Bill_Notes_TASA_Kbd.docx differ diff --git a/src/buxton/source/Bill_Notes_The_Tap.docx b/src/buxton/source/Bill_Notes_The_Tap.docx new file mode 100644 index 000000000..8ceebc71e Binary files /dev/null and b/src/buxton/source/Bill_Notes_The_Tap.docx differ diff --git a/src/buxton/source/Extra/Bill_Notes_Braun_T3.docx b/src/buxton/source/Extra/Bill_Notes_Braun_T3.docx deleted file mode 100644 index 356697092..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_Braun_T3.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_CasioC801.docx b/src/buxton/source/Extra/Bill_Notes_CasioC801.docx deleted file mode 100644 index cd89fb97b..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_CasioC801.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx b/src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx deleted file mode 100644 index a503cddfc..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_Casio_Mini.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx b/src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx deleted file mode 100644 index 4d13a8cf5..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_FingerWorks_Prototype.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx b/src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx deleted file mode 100644 index 578a1be08..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_Fingerworks_TouchStream.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_FrogPad.docx b/src/buxton/source/Extra/Bill_Notes_FrogPad.docx deleted file mode 100644 index d01e1bf5c..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_FrogPad.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx b/src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx deleted file mode 100644 index 7bd28b376..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_Gavilan_SC.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx b/src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx deleted file mode 100644 index 0615c4953..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_Grandjean_Stenotype.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_Matias.docx b/src/buxton/source/Extra/Bill_Notes_Matias.docx deleted file mode 100644 index 547603256..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_Matias.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_MousePen.docx b/src/buxton/source/Extra/Bill_Notes_MousePen.docx deleted file mode 100644 index 4e1056636..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_MousePen.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_NewO.docx b/src/buxton/source/Extra/Bill_Notes_NewO.docx deleted file mode 100644 index a514926d2..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_NewO.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_OLPC.docx b/src/buxton/source/Extra/Bill_Notes_OLPC.docx deleted file mode 100644 index bfca0a9bb..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_OLPC.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_PARCkbd.docx b/src/buxton/source/Extra/Bill_Notes_PARCkbd.docx deleted file mode 100644 index c0cf6ba9a..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_PARCkbd.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx b/src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx deleted file mode 100644 index ad06903f3..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_Philco_Mystery_Control.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx b/src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx deleted file mode 100644 index e4c659de9..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_TASA_Kbd.docx and /dev/null differ diff --git a/src/buxton/source/Extra/Bill_Notes_The_Tap.docx b/src/buxton/source/Extra/Bill_Notes_The_Tap.docx deleted file mode 100644 index 8ceebc71e..000000000 Binary files a/src/buxton/source/Extra/Bill_Notes_The_Tap.docx and /dev/null differ -- cgit v1.2.3-70-g09d2 From 7a1358929bb0ad8d0ce9ec4688d37d83e7a37e2c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 19 Jun 2019 19:43:04 -0400 Subject: finished importing! --- src/buxton/scraper.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py index c89961f54..1795f33d1 100644 --- a/src/buxton/scraper.py +++ b/src/buxton/scraper.py @@ -8,6 +8,7 @@ import shutil import uuid import datetime from PIL import Image +import math source = "./source" dist = "../server/public/files" @@ -58,8 +59,8 @@ def protofy(fieldId): } -def write_schema(parse_results): - view_guids = parse_results["view_guids"] +def write_schema(parse_results, display_fields): + view_guids = parse_results["child_guids"] data_doc = parse_results["schema"] fields = data_doc["fields"] @@ -86,7 +87,7 @@ def write_schema(parse_results): fields["proto"] = protofy("collectionProto") fields["data"] = listify(proxify_guids(view_guids)) - fields["schemaColumns"] = listify(["title", "data"]) + fields["schemaColumns"] = listify(display_fields) fields["backgroundColor"] = "white" fields["scale"] = 0.5 fields["viewType"] = 2 @@ -179,6 +180,14 @@ def parse_document(file_name: str): def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() + def sanitize_price(raw): + res = math.nan + try: + res = float(raw.replace("$", "")) + except: + res = math.nan + return res + def remove_empty(line): return len(line) > 1 lines = list(map(sanitize, raw.split("\n"))) @@ -201,7 +210,8 @@ def parse_document(file_name: str): map(lambda data: data.strip().split(":"), lines[cur].split("|"))) result["company"] = clean[0][len(clean[0]) - 1].strip() result["year"] = clean[1][len(clean[1]) - 1].strip() - result["original_price"] = clean[2][len(clean[2]) - 1].strip() + result["original_price"] = sanitize_price( + clean[2][len(clean[2]) - 1].strip()) cur += 1 result["degrees_of_freedom"] = extract_value( @@ -264,7 +274,7 @@ def parse_document(file_name: str): "fields": result, "__type": "Doc" }, - "view_guids": view_guids + "child_guids": view_guids } @@ -283,13 +293,24 @@ candidates = 0 for file_name in os.listdir(source): if file_name.endswith('.docx'): candidates += 1 - schema_guids.append(write_schema(parse_document(file_name))) + schema_guids.append(write_schema( + parse_document(file_name), ["title", "data"])) + +parent_guid = write_schema({ + "schema": { + "_id": guid(), + "fields": {}, + "__type": "Doc" + }, + "child_guids": schema_guids +}, ["title", "short_description"]) db.newDocuments.update_one( {"fields.title": "WS collection 1"}, - {"$push": {"fields.data.fields": {"$each": proxify_guids(schema_guids)}}} + {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}} ) + print("...dictionaries written to Dash Document.\n") print(f"{candidates} candidates processed.") -- cgit v1.2.3-70-g09d2 From 462c3c69ef9649a60fc5fa8af947fa1a85c5cfa3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 20 Jun 2019 12:19:21 -0400 Subject: Cleaned up script and included price sanitization --- src/buxton/scraper.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py index 1795f33d1..20124e157 100644 --- a/src/buxton/scraper.py +++ b/src/buxton/scraper.py @@ -9,6 +9,7 @@ import uuid import datetime from PIL import Image import math +import sys source = "./source" dist = "../server/public/files" @@ -102,6 +103,10 @@ def write_schema(parse_results, display_fields): db.newDocuments.insert_one(data_doc) db.newDocuments.insert_one(view_doc) + data_doc_guid = data_doc["_id"] + print( + f"Uploaded {view_doc_guid} (view) and {data_doc_guid} (data) to http://localhost:27017...\n") + return view_doc_guid @@ -153,8 +158,6 @@ def write_image(folder, name): db.newDocuments.insert_one(view_doc) db.newDocuments.insert_one(data_doc) - print(path) - return view_doc_guid @@ -169,24 +172,29 @@ def parse_document(file_name: str): raw = str(docx2txt.process(source + "/" + file_name, dir_path)) - print("Extracting images...\n") + print("Extracting images...") view_guids = [] for image in os.listdir(dir_path): view_guids.append(write_image(pure_name, image)) os.rename(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_m.", 1)) - print() def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() - def sanitize_price(raw): - res = math.nan - try: - res = float(raw.replace("$", "")) - except: - res = math.nan - return res + def sanitize_price(raw: str): + raw = raw.replace(",", "") + start = raw.find("$") + if start > -1: + i = start + 1 + while (i < len(raw) and re.match(r"[0-9\.]", raw[i])): + i += 1 + price = raw[start + 1: i + 1] + return float(price) + elif (raw.lower().find("nfs")): + return -1 + else: + return math.nan def remove_empty(line): return len(line) > 1 @@ -266,8 +274,6 @@ def parse_document(file_name: str): if len(notes) > 0: result["notes"] = listify(notes) - print("...contents dictionary constructed.") - return { "schema": { "_id": guid(), @@ -296,6 +302,7 @@ for file_name in os.listdir(source): schema_guids.append(write_schema( parse_document(file_name), ["title", "data"])) +print("Writing parent schema...") parent_guid = write_schema({ "schema": { "_id": guid(), @@ -303,18 +310,18 @@ parent_guid = write_schema({ "__type": "Doc" }, "child_guids": schema_guids -}, ["title", "short_description"]) +}, ["title", "short_description", "original_price"]) +print("Appending parent schema to main workspace...\n") db.newDocuments.update_one( {"fields.title": "WS collection 1"}, {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}} ) - -print("...dictionaries written to Dash Document.\n") - -print(f"{candidates} candidates processed.") - +print("Rewriting .gitignore...\n") lines = ['*', '!.gitignore'] with open(dist + "/.gitignore", 'w') as f: f.write('\n'.join(lines)) + +suffix = "" if candidates == 1 else "s" +print(f"Done. {candidates} candidate{suffix} processed.") -- cgit v1.2.3-70-g09d2 From 4be53f12fffa099f3381813192e60415898183d1 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 20 Jun 2019 16:51:16 -0400 Subject: updated print statements --- src/buxton/scraper.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py index 20124e157..043fd3cf4 100644 --- a/src/buxton/scraper.py +++ b/src/buxton/scraper.py @@ -39,7 +39,7 @@ def mkdir_if_absent(path): if not os.path.exists(path): os.mkdir(path) except OSError: - print("Failed to create the appropriate directory structures for %s" % file_name) + print("failed to create the appropriate directory structures for %s" % file_name) def guid(): @@ -104,8 +104,8 @@ def write_schema(parse_results, display_fields): db.newDocuments.insert_one(view_doc) data_doc_guid = data_doc["_id"] - print( - f"Uploaded {view_doc_guid} (view) and {data_doc_guid} (data) to http://localhost:27017...\n") + print(f"inserted view document ({view_doc_guid})") + print(f"inserted data document ({data_doc_guid})\n") return view_doc_guid @@ -162,7 +162,7 @@ def write_image(folder, name): def parse_document(file_name: str): - print(f"Parsing {file_name}...") + print(f"parsing {file_name}...") pure_name = file_name.split(".")[0] result = {} @@ -172,12 +172,14 @@ def parse_document(file_name: str): raw = str(docx2txt.process(source + "/" + file_name, dir_path)) - print("Extracting images...") view_guids = [] + count = 0 for image in os.listdir(dir_path): + count += 1 view_guids.append(write_image(pure_name, image)) os.rename(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_m.", 1)) + print(f"extracted {count} images...") def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() @@ -274,6 +276,8 @@ def parse_document(file_name: str): if len(notes) > 0: result["notes"] = listify(notes) + print("writing child schema...") + return { "schema": { "_id": guid(), @@ -302,7 +306,7 @@ for file_name in os.listdir(source): schema_guids.append(write_schema( parse_document(file_name), ["title", "data"])) -print("Writing parent schema...") +print("writing parent schema...") parent_guid = write_schema({ "schema": { "_id": guid(), @@ -312,16 +316,16 @@ parent_guid = write_schema({ "child_guids": schema_guids }, ["title", "short_description", "original_price"]) -print("Appending parent schema to main workspace...\n") +print("appending parent schema to main workspace...\n") db.newDocuments.update_one( {"fields.title": "WS collection 1"}, {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}} ) -print("Rewriting .gitignore...\n") +print("rewriting .gitignore...\n") lines = ['*', '!.gitignore'] with open(dist + "/.gitignore", 'w') as f: f.write('\n'.join(lines)) suffix = "" if candidates == 1 else "s" -print(f"Done. {candidates} candidate{suffix} processed.") +print(f"conversion complete. {candidates} candidate{suffix} processed.") -- cgit v1.2.3-70-g09d2 From 9ea5aa5a5da3f098afbc92343c63bc21afaf4b1f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 20 Jun 2019 19:39:18 -0400 Subject: fixed missed pointer up event bug --- src/client/views/collections/CollectionDockingView.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 69b9e77eb..3113ada73 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -250,7 +250,6 @@ export class CollectionDockingView extends React.Component { - this._isPointerDown = false; if (this._flush) { this._flush = false; setTimeout(() => this.stateChanged(), 10); @@ -259,6 +258,14 @@ export class CollectionDockingView extends React.Component { this._isPointerDown = true; + console.log("DOWN") + + let onPointerUp = action(() => { + window.removeEventListener("pointerup", onPointerUp) + this._isPointerDown = false + console.log("UP") + }) + window.addEventListener("pointerup", onPointerUp); var className = (e.target as any).className; if (className === "messageCounter") { e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From 06bd486c72342b4d979245c9f4051156e6492541 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 25 Jun 2019 21:22:29 -0400 Subject: scraping progress --- src/buxton/scraper.py | 331 --------------------- src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx | Bin 1675500 -> 0 bytes src/buxton/source/Bill_Notes_Braun_T3.docx | Bin 1671968 -> 0 bytes src/buxton/source/Bill_Notes_CasioC801.docx | Bin 574664 -> 0 bytes src/buxton/source/Bill_Notes_Casio_Mini.docx | Bin 581069 -> 0 bytes .../source/Bill_Notes_FingerWorks_Prototype.docx | Bin 585090 -> 0 bytes .../source/Bill_Notes_Fingerworks_TouchStream.docx | Bin 1722555 -> 0 bytes src/buxton/source/Bill_Notes_FrogPad.docx | Bin 840173 -> 0 bytes src/buxton/source/Bill_Notes_Gavilan_SC.docx | Bin 1695290 -> 0 bytes .../source/Bill_Notes_Grandjean_Stenotype.docx | Bin 2094142 -> 0 bytes src/buxton/source/Bill_Notes_Matias.docx | Bin 590407 -> 0 bytes src/buxton/source/Bill_Notes_MousePen.docx | Bin 505322 -> 0 bytes src/buxton/source/Bill_Notes_NewO.docx | Bin 2264571 -> 0 bytes src/buxton/source/Bill_Notes_OLPC.docx | Bin 6883659 -> 0 bytes src/buxton/source/Bill_Notes_PARCkbd.docx | Bin 631959 -> 0 bytes .../source/Bill_Notes_Philco_Mystery_Control.docx | Bin 1994439 -> 0 bytes src/buxton/source/Bill_Notes_TASA_Kbd.docx | Bin 461199 -> 0 bytes src/buxton/source/Bill_Notes_The_Tap.docx | Bin 711321 -> 0 bytes src/client/util/ClientUtils.ts | 4 + src/scraping/acm/chromedriver.exe | Bin 0 -> 7477760 bytes src/scraping/acm/citations.txt | 2 + src/scraping/acm/debug.log | 38 +++ src/scraping/acm/index.js | 88 ++++++ src/scraping/acm/package.json | 17 ++ src/scraping/acm/results.txt | 64 ++++ src/scraping/buxton/scraper.py | 331 +++++++++++++++++++++ .../buxton/source/Bill_Notes_Bill_Notes_CyKey.docx | Bin 0 -> 1675500 bytes .../buxton/source/Bill_Notes_Braun_T3.docx | Bin 0 -> 1671968 bytes .../buxton/source/Bill_Notes_CasioC801.docx | Bin 0 -> 574664 bytes .../buxton/source/Bill_Notes_Casio_Mini.docx | Bin 0 -> 581069 bytes .../source/Bill_Notes_FingerWorks_Prototype.docx | Bin 0 -> 585090 bytes .../source/Bill_Notes_Fingerworks_TouchStream.docx | Bin 0 -> 1722555 bytes src/scraping/buxton/source/Bill_Notes_FrogPad.docx | Bin 0 -> 840173 bytes .../buxton/source/Bill_Notes_Gavilan_SC.docx | Bin 0 -> 1695290 bytes .../source/Bill_Notes_Grandjean_Stenotype.docx | Bin 0 -> 2094142 bytes src/scraping/buxton/source/Bill_Notes_Matias.docx | Bin 0 -> 590407 bytes .../buxton/source/Bill_Notes_MousePen.docx | Bin 0 -> 505322 bytes src/scraping/buxton/source/Bill_Notes_NewO.docx | Bin 0 -> 2264571 bytes src/scraping/buxton/source/Bill_Notes_OLPC.docx | Bin 0 -> 6883659 bytes src/scraping/buxton/source/Bill_Notes_PARCkbd.docx | Bin 0 -> 631959 bytes .../source/Bill_Notes_Philco_Mystery_Control.docx | Bin 0 -> 1994439 bytes .../buxton/source/Bill_Notes_TASA_Kbd.docx | Bin 0 -> 461199 bytes src/scraping/buxton/source/Bill_Notes_The_Tap.docx | Bin 0 -> 711321 bytes 43 files changed, 544 insertions(+), 331 deletions(-) delete mode 100644 src/buxton/scraper.py delete mode 100644 src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx delete mode 100644 src/buxton/source/Bill_Notes_Braun_T3.docx delete mode 100644 src/buxton/source/Bill_Notes_CasioC801.docx delete mode 100644 src/buxton/source/Bill_Notes_Casio_Mini.docx delete mode 100644 src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx delete mode 100644 src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx delete mode 100644 src/buxton/source/Bill_Notes_FrogPad.docx delete mode 100644 src/buxton/source/Bill_Notes_Gavilan_SC.docx delete mode 100644 src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx delete mode 100644 src/buxton/source/Bill_Notes_Matias.docx delete mode 100644 src/buxton/source/Bill_Notes_MousePen.docx delete mode 100644 src/buxton/source/Bill_Notes_NewO.docx delete mode 100644 src/buxton/source/Bill_Notes_OLPC.docx delete mode 100644 src/buxton/source/Bill_Notes_PARCkbd.docx delete mode 100644 src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx delete mode 100644 src/buxton/source/Bill_Notes_TASA_Kbd.docx delete mode 100644 src/buxton/source/Bill_Notes_The_Tap.docx create mode 100644 src/client/util/ClientUtils.ts create mode 100644 src/scraping/acm/chromedriver.exe create mode 100644 src/scraping/acm/citations.txt create mode 100644 src/scraping/acm/debug.log create mode 100644 src/scraping/acm/index.js create mode 100644 src/scraping/acm/package.json create mode 100644 src/scraping/acm/results.txt create mode 100644 src/scraping/buxton/scraper.py create mode 100644 src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Braun_T3.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_CasioC801.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Casio_Mini.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_FrogPad.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Matias.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_MousePen.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_NewO.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_OLPC.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_PARCkbd.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docx create mode 100644 src/scraping/buxton/source/Bill_Notes_The_Tap.docx (limited to 'src') diff --git a/src/buxton/scraper.py b/src/buxton/scraper.py deleted file mode 100644 index 043fd3cf4..000000000 --- a/src/buxton/scraper.py +++ /dev/null @@ -1,331 +0,0 @@ -import os -import docx2txt -from docx import Document -from docx.opc.constants import RELATIONSHIP_TYPE as RT -import re -from pymongo import MongoClient -import shutil -import uuid -import datetime -from PIL import Image -import math -import sys - -source = "./source" -dist = "../server/public/files" - -db = MongoClient("localhost", 27017)["Dash"] -schema_guids = [] - - -def extract_links(fileName): - links = [] - doc = Document(fileName) - rels = doc.part.rels - for rel in rels: - item = rels[rel] - if item.reltype == RT.HYPERLINK and ".aspx" not in item._target: - links.append(item._target) - return listify(links) - - -def extract_value(kv_string): - pieces = kv_string.split(":") - return (pieces[1] if len(pieces) > 1 else kv_string).strip() - - -def mkdir_if_absent(path): - try: - if not os.path.exists(path): - os.mkdir(path) - except OSError: - print("failed to create the appropriate directory structures for %s" % file_name) - - -def guid(): - return str(uuid.uuid4()) - - -def listify(list): - return { - "fields": list, - "__type": "list" - } - - -def protofy(fieldId): - return { - "fieldId": fieldId, - "__type": "proxy" - } - - -def write_schema(parse_results, display_fields): - view_guids = parse_results["child_guids"] - - data_doc = parse_results["schema"] - fields = data_doc["fields"] - - view_doc_guid = guid() - - view_doc = { - "_id": view_doc_guid, - "fields": { - "proto": protofy(data_doc["_id"]), - "x": 10, - "y": 10, - "width": 900, - "height": 600, - "panX": 0, - "panY": 0, - "zoomBasis": 0.5, - "zIndex": 2, - "libraryBrush": False, - "viewType": 2 - }, - "__type": "Doc" - } - - fields["proto"] = protofy("collectionProto") - fields["data"] = listify(proxify_guids(view_guids)) - fields["schemaColumns"] = listify(display_fields) - fields["backgroundColor"] = "white" - fields["scale"] = 0.5 - fields["viewType"] = 2 - fields["author"] = "Bill Buxton" - fields["creationDate"] = { - "date": datetime.datetime.utcnow().microsecond, - "__type": "date" - } - fields["isPrototype"] = True - fields["page"] = -1 - - db.newDocuments.insert_one(data_doc) - db.newDocuments.insert_one(view_doc) - - data_doc_guid = data_doc["_id"] - print(f"inserted view document ({view_doc_guid})") - print(f"inserted data document ({data_doc_guid})\n") - - return view_doc_guid - - -def write_image(folder, name): - path = f"http://localhost:1050/files/{folder}/{name}" - - data_doc_guid = guid() - view_doc_guid = guid() - - view_doc = { - "_id": view_doc_guid, - "fields": { - "proto": protofy(data_doc_guid), - "x": 10, - "y": 10, - "width": 300, - "zIndex": 2, - "libraryBrush": False - }, - "__type": "Doc" - } - - image = Image.open(f"{dist}/{folder}/{name}") - native_width, native_height = image.size - - data_doc = { - "_id": data_doc_guid, - "fields": { - "proto": protofy("imageProto"), - "data": { - "url": path, - "__type": "image" - }, - "title": name, - "nativeWidth": native_width, - "author": "Bill Buxton", - "creationDate": { - "date": datetime.datetime.utcnow().microsecond, - "__type": "date" - }, - "isPrototype": True, - "page": -1, - "nativeHeight": native_height, - "height": native_height - }, - "__type": "Doc" - } - - db.newDocuments.insert_one(view_doc) - db.newDocuments.insert_one(data_doc) - - return view_doc_guid - - -def parse_document(file_name: str): - print(f"parsing {file_name}...") - pure_name = file_name.split(".")[0] - - result = {} - - dir_path = dist + "/" + pure_name - mkdir_if_absent(dir_path) - - raw = str(docx2txt.process(source + "/" + file_name, dir_path)) - - view_guids = [] - count = 0 - for image in os.listdir(dir_path): - count += 1 - view_guids.append(write_image(pure_name, image)) - os.rename(dir_path + "/" + image, dir_path + - "/" + image.replace(".", "_m.", 1)) - print(f"extracted {count} images...") - - def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( - u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() - - def sanitize_price(raw: str): - raw = raw.replace(",", "") - start = raw.find("$") - if start > -1: - i = start + 1 - while (i < len(raw) and re.match(r"[0-9\.]", raw[i])): - i += 1 - price = raw[start + 1: i + 1] - return float(price) - elif (raw.lower().find("nfs")): - return -1 - else: - return math.nan - - def remove_empty(line): return len(line) > 1 - - lines = list(map(sanitize, raw.split("\n"))) - lines = list(filter(remove_empty, lines)) - - result["file_name"] = file_name - result["title"] = lines[2].strip() - result["short_description"] = lines[3].strip().replace( - "Short Description: ", "") - - cur = 5 - notes = "" - while lines[cur] != "Device Details": - notes += lines[cur] + " " - cur += 1 - result["buxton_notes"] = notes.strip() - - cur += 1 - clean = list( - map(lambda data: data.strip().split(":"), lines[cur].split("|"))) - result["company"] = clean[0][len(clean[0]) - 1].strip() - result["year"] = clean[1][len(clean[1]) - 1].strip() - result["original_price"] = sanitize_price( - clean[2][len(clean[2]) - 1].strip()) - - cur += 1 - result["degrees_of_freedom"] = extract_value( - lines[cur]).replace("NA", "N/A") - cur += 1 - - dimensions = lines[cur].lower() - if dimensions.startswith("dimensions"): - dim_concat = dimensions[11:].strip() - cur += 1 - while lines[cur] != "Key Words": - dim_concat += (" " + lines[cur].strip()) - cur += 1 - result["dimensions"] = dim_concat - else: - result["dimensions"] = "N/A" - - cur += 1 - result["primary_key"] = extract_value(lines[cur]) - cur += 1 - result["secondary_key"] = extract_value(lines[cur]) - - while lines[cur] != "Links": - result["secondary_key"] += (" " + extract_value(lines[cur]).strip()) - cur += 1 - - cur += 1 - link_descriptions = [] - while lines[cur] != "Image": - link_descriptions.append(lines[cur].strip()) - cur += 1 - result["link_descriptions"] = listify(link_descriptions) - - result["hyperlinks"] = extract_links(source + "/" + file_name) - - images = [] - captions = [] - cur += 3 - while cur + 1 < len(lines) and lines[cur] != "NOTES:": - images.append(lines[cur]) - captions.append(lines[cur + 1]) - cur += 2 - result["images"] = listify(images) - result["captions"] = listify(captions) - - notes = [] - if (cur < len(lines) and lines[cur] == "NOTES:"): - cur += 1 - while cur < len(lines): - notes.append(lines[cur]) - cur += 1 - if len(notes) > 0: - result["notes"] = listify(notes) - - print("writing child schema...") - - return { - "schema": { - "_id": guid(), - "fields": result, - "__type": "Doc" - }, - "child_guids": view_guids - } - - -def proxify_guids(guids): - return list(map(lambda guid: {"fieldId": guid, "__type": "proxy"}, guids)) - - -if os.path.exists(dist): - shutil.rmtree(dist) -while os.path.exists(dist): - pass -os.mkdir(dist) -mkdir_if_absent(source) - -candidates = 0 -for file_name in os.listdir(source): - if file_name.endswith('.docx'): - candidates += 1 - schema_guids.append(write_schema( - parse_document(file_name), ["title", "data"])) - -print("writing parent schema...") -parent_guid = write_schema({ - "schema": { - "_id": guid(), - "fields": {}, - "__type": "Doc" - }, - "child_guids": schema_guids -}, ["title", "short_description", "original_price"]) - -print("appending parent schema to main workspace...\n") -db.newDocuments.update_one( - {"fields.title": "WS collection 1"}, - {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}} -) - -print("rewriting .gitignore...\n") -lines = ['*', '!.gitignore'] -with open(dist + "/.gitignore", 'w') as f: - f.write('\n'.join(lines)) - -suffix = "" if candidates == 1 else "s" -print(f"conversion complete. {candidates} candidate{suffix} processed.") diff --git a/src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx b/src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx deleted file mode 100644 index 06094b4d3..000000000 Binary files a/src/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Braun_T3.docx b/src/buxton/source/Bill_Notes_Braun_T3.docx deleted file mode 100644 index 356697092..000000000 Binary files a/src/buxton/source/Bill_Notes_Braun_T3.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_CasioC801.docx b/src/buxton/source/Bill_Notes_CasioC801.docx deleted file mode 100644 index cd89fb97b..000000000 Binary files a/src/buxton/source/Bill_Notes_CasioC801.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Casio_Mini.docx b/src/buxton/source/Bill_Notes_Casio_Mini.docx deleted file mode 100644 index a503cddfc..000000000 Binary files a/src/buxton/source/Bill_Notes_Casio_Mini.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx b/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx deleted file mode 100644 index 4d13a8cf5..000000000 Binary files a/src/buxton/source/Bill_Notes_FingerWorks_Prototype.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx b/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx deleted file mode 100644 index 578a1be08..000000000 Binary files a/src/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_FrogPad.docx b/src/buxton/source/Bill_Notes_FrogPad.docx deleted file mode 100644 index d01e1bf5c..000000000 Binary files a/src/buxton/source/Bill_Notes_FrogPad.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Gavilan_SC.docx b/src/buxton/source/Bill_Notes_Gavilan_SC.docx deleted file mode 100644 index 7bd28b376..000000000 Binary files a/src/buxton/source/Bill_Notes_Gavilan_SC.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx b/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx deleted file mode 100644 index 0615c4953..000000000 Binary files a/src/buxton/source/Bill_Notes_Grandjean_Stenotype.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Matias.docx b/src/buxton/source/Bill_Notes_Matias.docx deleted file mode 100644 index 547603256..000000000 Binary files a/src/buxton/source/Bill_Notes_Matias.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_MousePen.docx b/src/buxton/source/Bill_Notes_MousePen.docx deleted file mode 100644 index 4e1056636..000000000 Binary files a/src/buxton/source/Bill_Notes_MousePen.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_NewO.docx b/src/buxton/source/Bill_Notes_NewO.docx deleted file mode 100644 index a514926d2..000000000 Binary files a/src/buxton/source/Bill_Notes_NewO.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_OLPC.docx b/src/buxton/source/Bill_Notes_OLPC.docx deleted file mode 100644 index bfca0a9bb..000000000 Binary files a/src/buxton/source/Bill_Notes_OLPC.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_PARCkbd.docx b/src/buxton/source/Bill_Notes_PARCkbd.docx deleted file mode 100644 index c0cf6ba9a..000000000 Binary files a/src/buxton/source/Bill_Notes_PARCkbd.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx b/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx deleted file mode 100644 index ad06903f3..000000000 Binary files a/src/buxton/source/Bill_Notes_Philco_Mystery_Control.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_TASA_Kbd.docx b/src/buxton/source/Bill_Notes_TASA_Kbd.docx deleted file mode 100644 index e4c659de9..000000000 Binary files a/src/buxton/source/Bill_Notes_TASA_Kbd.docx and /dev/null differ diff --git a/src/buxton/source/Bill_Notes_The_Tap.docx b/src/buxton/source/Bill_Notes_The_Tap.docx deleted file mode 100644 index 8ceebc71e..000000000 Binary files a/src/buxton/source/Bill_Notes_The_Tap.docx and /dev/null differ diff --git a/src/client/util/ClientUtils.ts b/src/client/util/ClientUtils.ts new file mode 100644 index 000000000..425bde14a --- /dev/null +++ b/src/client/util/ClientUtils.ts @@ -0,0 +1,4 @@ +//AUTO-GENERATED FILE: DO NOT EDIT +export namespace ClientUtils { + export const RELEASE = false; +} \ No newline at end of file diff --git a/src/scraping/acm/chromedriver.exe b/src/scraping/acm/chromedriver.exe new file mode 100644 index 000000000..6a362fd43 Binary files /dev/null and b/src/scraping/acm/chromedriver.exe differ diff --git a/src/scraping/acm/citations.txt b/src/scraping/acm/citations.txt new file mode 100644 index 000000000..e5018ddef --- /dev/null +++ b/src/scraping/acm/citations.txt @@ -0,0 +1,2 @@ +321046 +2412979 \ No newline at end of file diff --git a/src/scraping/acm/debug.log b/src/scraping/acm/debug.log new file mode 100644 index 000000000..8c0a148f4 --- /dev/null +++ b/src/scraping/acm/debug.log @@ -0,0 +1,38 @@ +[0625/170004.768:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/170004.769:ERROR:exception_snapshot_win.cc(98)] thread ID 17604 not found in process +[0625/171124.644:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/171124.645:ERROR:exception_snapshot_win.cc(98)] thread ID 14348 not found in process +[0625/171853.989:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/171853.990:ERROR:exception_snapshot_win.cc(98)] thread ID 12080 not found in process +[0625/171947.744:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/171947.745:ERROR:exception_snapshot_win.cc(98)] thread ID 16160 not found in process +[0625/172007.424:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/172007.425:ERROR:exception_snapshot_win.cc(98)] thread ID 13472 not found in process +[0625/172059.353:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/172059.354:ERROR:exception_snapshot_win.cc(98)] thread ID 6396 not found in process +[0625/172402.795:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/172402.796:ERROR:exception_snapshot_win.cc(98)] thread ID 10720 not found in process +[0625/172618.850:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/172618.850:ERROR:exception_snapshot_win.cc(98)] thread ID 21136 not found in process +[0625/172819.875:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/172819.876:ERROR:exception_snapshot_win.cc(98)] thread ID 17624 not found in process +[0625/172953.674:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/172953.675:ERROR:exception_snapshot_win.cc(98)] thread ID 15180 not found in process +[0625/173412.182:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/173412.182:ERROR:exception_snapshot_win.cc(98)] thread ID 13952 not found in process +[0625/173447.806:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/173447.807:ERROR:exception_snapshot_win.cc(98)] thread ID 1572 not found in process +[0625/173516.188:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/173516.189:ERROR:exception_snapshot_win.cc(98)] thread ID 5472 not found in process +[0625/173528.446:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/173528.447:ERROR:exception_snapshot_win.cc(98)] thread ID 20420 not found in process +[0625/173539.436:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/173539.437:ERROR:exception_snapshot_win.cc(98)] thread ID 16192 not found in process +[0625/173643.139:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/173643.140:ERROR:exception_snapshot_win.cc(98)] thread ID 15716 not found in process +[0625/173659.376:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/173659.377:ERROR:exception_snapshot_win.cc(98)] thread ID 11828 not found in process +[0625/201137.209:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/201137.210:ERROR:exception_snapshot_win.cc(98)] thread ID 7688 not found in process +[0625/210240.476:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) +[0625/210240.477:ERROR:exception_snapshot_win.cc(98)] thread ID 20828 not found in process diff --git a/src/scraping/acm/index.js b/src/scraping/acm/index.js new file mode 100644 index 000000000..81f775617 --- /dev/null +++ b/src/scraping/acm/index.js @@ -0,0 +1,88 @@ +const { Builder, By, Key, until } = require('selenium-webdriver'); +const fs = require("fs"); + +let driver; +fs.readFile("./citations.txt", { encoding: "utf8" }, scrapeTargets); +results = [] + +async function scrapeTargets(error, data) { + if (error) { + console.log("\nUnable to collect target citations from a citations.txt file stored in this directory.\nPlease make sure one is provided.") + return; + } + + driver = await new Builder().forBrowser('chrome').build(); + + let references = data.split("\n").map(entry => entry.replace("\r", "")).filter(line => line.match(/\d+/g)); + + let results = [] + let pdfs = [] + for (let id of references) { + let result = {} + let lines = [] + try { + let url = `https://dl.acm.org/citation.cfm?id=${id}`; + await driver.get(url); + await driver.sleep(500) + let candidates = await driver.findElements(By.xpath('.//a[contains(@href, "ft_gateway.cfm?id=")]')); + if (candidates.length > 0) { + pdfs.push(candidates[0]) + } + let webElements = await driver.findElements(By.id("abstract-body")) + for (let el of webElements) { + let text = await el.getText() + lines.push(text) + } + result.url = url + result.abstract = lines.join(" "); + await driver.findElement(By.xpath(`//*[@id="tab-1014-btnInnerEl"]/span`)).click() + await driver.sleep(500) + let authors = await driver.findElement(By.xpath('//*[@id="tabpanel-1009-body"]')).getText() + let sanitize = line => line.length > 0 && !(line.startsWith("No contact information") || line.startsWith("View colleagues of") || line.startsWith("Bibliometrics:")) + authorLines = authors.split("\n").map(line => line.trim()).filter(sanitize) + + let i = 0; + let allAuthors = [] + while (i < authorLines.length) { + let individual = []; + while (!authorLines[i].startsWith("Average citations")) { + individual.push(authorLines[i]) + i++ + } + individual.push(authorLines[i]) + allAuthors.push(individual); + i++ + } + result.authors = allAuthors.map(metadata => { + let publicationYears = metadata[1].substring(18).split("-"); + author = { + name: metadata[0], + publication_start: parseInt(publicationYears[0]), + publication_end: parseInt(publicationYears[1]) + }; + for (let count = 2; count < metadata.length; count++) { + let attr = metadata[count] + let char = attr.length - 1; + while (attr[char] != " ") { + char-- + } + let key = attr.substring(0, char).toLowerCase().replace(/ /g, "_").replace(/[\(\)]/g, ""); + let value = parseFloat(attr.substring(char + 1).replace(/,/g, "")); + author[key] = value + } + return author + }) + } catch (e) { + console.log(e) + await driver.quit(); + } + results.push(result) + } + + let output = ""; + results.forEach(res => output += (JSON.stringify(res, null, 4) + "\n")); + + fs.writeFile("./results.txt", output, function errorHandler(exception) { console.log(exception || "results successfully written") }) + + await driver.quit(); +} \ No newline at end of file diff --git a/src/scraping/acm/package.json b/src/scraping/acm/package.json new file mode 100644 index 000000000..10f4d2156 --- /dev/null +++ b/src/scraping/acm/package.json @@ -0,0 +1,17 @@ +{ + "name": "scraper", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^0.19.0", + "cheerio": "^1.0.0-rc.3", + "selenium-webdriver": "^4.0.0-alpha.4" + } +} diff --git a/src/scraping/acm/results.txt b/src/scraping/acm/results.txt new file mode 100644 index 000000000..05bb2be8b --- /dev/null +++ b/src/scraping/acm/results.txt @@ -0,0 +1,64 @@ +{ + "url": "https://dl.acm.org/citation.cfm?id=321046", + "abstract": "It has been observed by many people that a striking number of quite diverse mathematical problems can be formulated as problems in integer programming, that is, linear programming problems in which some or all of the variables are required to assume integral values. This fact is rendered quite interesting by recent research on such problems, notably by R. E. Gomory [2, 3], which gives promise of yielding efficient computational techniques for their solution. The present paper provides yet another example of the versatility of integer programming as a mathematical modeling device by representing a generalization of the well-known “Travelling Salesman Problem” in integer programming terms. The authors have developed several such models, of which the one presented here is the most efficient in terms of generality, number of variables, and number of constraints. This model is due to the second author [4] and was presented briefly at the Symposium on Combinatorial Problems held at Princeton University, April 1960, sponsored by SIAM and IBM. The problem treated is: (1) A salesman is required to visit each of n cities, indexed by 1, … , n. He leaves from a “base city” indexed by 0, visits each of the n other cities exactly once, and returns to city 0. During his travels he must return to 0 exactly t times, including his final return (here t may be allowed to vary), and he must visit no more than p cities in one tour. (By a tour we mean a succession of visits to cities without stopping at city 0.) It is required to find such an itinerary which minimizes the total distance traveled by the salesman. Note that if t is fixed, then for the problem to have a solution we must have tp ≧ n. For t = 1, p ≧ n, we have the standard traveling salesman problem. Let dij (i ≠ j = 0, 1, … , n) be the distance covered in traveling from city i to city j. The following integer programming problem will be shown to be equivalent to (1): (2) Minimize the linear form ∑0≦i≠j≦n∑ dijxij over the set determined by the relations ∑ni=0i≠j xij = 1 (j = 1, … , n) ∑nj=0j≠i xij = 1 (i = 1, … , n) ui - uj + pxij ≦ p - 1 (1 ≦ i ≠ j ≦ n) where the xij are non-negative integers and the ui (i = 1, …, n) are arbitrary real numbers. (We shall see that it is permissible to restrict the ui to be non-negative integers as well.) If t is fixed it is necessary to add the additional relation: ∑nu=1 xi0 = t Note that the constraints require that xij = 0 or 1, so that a natural correspondence between these two problems exists if the xij are interpreted as follows: The salesman proceeds from city i to city j if and only if xij = 1. Under this correspondence the form to be minimized in (2) is the total distance to be traveled by the salesman in (1), so the burden of proof is to show that the two feasible sets correspond; i.e., a feasible solution to (2) has xij which do define a legitimate itinerary in (1), and, conversely a legitimate itinerary in (1) defines xij, which, together with appropriate ui, satisfy the constraints of (2). Consider a feasible solution to (2). The number of returns to city 0 is given by ∑ni=1 xi0. The constraints of the form ∑ xij = 1, all xij non-negative integers, represent the conditions that each city (other than zero) is visited exactly once. The ui play a role similar to node potentials in a network and the inequalities involving them serve to eliminate tours that do not begin and end at city 0 and tours that visit more than p cities. Consider any xr0r1 = 1 (r1 ≠ 0). There exists a unique r2 such that xr1r2 = 1. Unless r2 = 0, there is a unique r3 with xr2r3 = 1. We proceed in this fashion until some rj = 0. This must happen since the alternative is that at some point we reach an rk = rj, j + 1 < k. Since none of the r's are zero we have uri - uri + 1 + pxriri + 1 ≦ p - 1 or uri - uri + 1 ≦ - 1. Summing from i = j to k - 1, we have urj - urk = 0 ≦ j + 1 - k, which is a contradiction. Thus all tours include city 0. It remains to observe that no tours is of length greater than p. Suppose such a tour exists, x0r1 , xr1r2 , … , xrprp+1 = 1 with all ri ≠ 0. Then, as before, ur1 - urp+1 ≦ - p or urp+1 - ur1 ≧ p. But we have urp+1 - ur1 + pxrp+1r1 ≦ p - 1 or urp+1 - ur1 ≦ p (1 - xrp+1r1) - 1 ≦ p - 1, which is a contradiction. Conversely, if the xij correspond to a legitimate itinerary, it is clear that the ui can be adjusted so that ui = j if city i is the jth city visited in the tour which includes city i, for we then have ui - uj = - 1 if xij = 1, and always ui - uj ≦ p - 1. The above integer program involves n2 + n constraints (if t is not fixed) in n2 + 2n variables. Since the inequality form of constraint is fundamental for integer programming calculations, one may eliminate 2n variables, say the xi0 and x0j, by means of the equation constraints and produce an equivalent problem with n2 + n inequalities and n2 variables. The currently known integer programming procedures are sufficiently regular in their behavior to cast doubt on the heuristic value of machine experiments with our model. However, it seems appropriate to report the results of the five machine experiments we have conducted so far. The solution procedure used was the all-integer algorithm of R. E. Gomory [3] without the ranking procedure he describes. The first three experiments were simple model verification tests on a four-city standard traveling salesman problem with distance matrix [ 20 23 4 30 7 27 25 5 25 3 21 26 ] The first experiment was with a model, now obsolete, using roughly twice as many constraints and variables as the current model (for this problem, 28 constraints in 21 variables). The machine was halted after 4000 pivot steps had failed to produce a solution. The second experiment used the earlier model with the xi0 and x0j eliminated, resulting in a 28-constraint, 15-variable problem. Here the machine produced the optimal solution in 41 pivot steps. The third experiment used the current formulation with the xi0 and x0j eliminated, yielding 13 constraints and 9 variables. The optimal solution was reached in 7 pivot steps. The fourth and fifth experiments were used on a standard ten-city problem, due to Barachet, solved by Dantzig, Johnson and Fulkerson [1]. The current formulation was used, yielding 91 constraints in 81 variables. The fifth problem differed from the fourth only in that the ordering of the rows was altered to attempt to introduce more favorable pivot choices. In each case the machine was stopped after over 250 pivot steps had failed to produce the solution. In each case the last 100 pivot steps had failed to change the value of the objective function. It seems hopeful that more efficient integer programming procedures now under development will yield a satisfactory algorithmic solution to the traveling salesman problem, when applied to this model. In any case, the model serves to illustrate how problems of this sort may be succinctly formulated in integer programming terms.", + "authors": [ + { + "name": "C. E. Miller", + "publication_start": 1960, + "publication_end": 1960, + "publication_count": 1, + "citation_count": 179, + "available_for_download": 1, + "downloads_6_weeks": 132, + "downloads_12_months": 993, + "downloads_cumulative": 9781, + "average_downloads_per_article": 9781, + "average_citations_per_article": 179 + }, + { + "name": "A. W. Tucker", + "publication_start": 1960, + "publication_end": 1993, + "publication_count": 5, + "citation_count": 196, + "available_for_download": 1, + "downloads_6_weeks": 132, + "downloads_12_months": 993, + "downloads_cumulative": 9781, + "average_downloads_per_article": 9781, + "average_citations_per_article": 39.2 + }, + { + "name": "R. A. Zemlin", + "publication_start": 1960, + "publication_end": 1964, + "publication_count": 2, + "citation_count": 188, + "available_for_download": 2, + "downloads_6_weeks": 132, + "downloads_12_months": 998, + "downloads_cumulative": 10012, + "average_downloads_per_article": 5006, + "average_citations_per_article": 94 + } + ] +} +{ + "url": "https://dl.acm.org/citation.cfm?id=2412979", + "abstract": "The STRUCT system utilizes the flexibility of a powerful graphics display system to provide a set of tools for program analysis. These tools allow the analysis of the static prograin structure and the dynamic execution behavior. of programs within the entire operating system/user program environment of the Brown University Graphics System (BUGS). Information is collected and presented in a manner which fully exploits two aspects of this environment. First, the operating system has been developed in a well-structured hierarcal manner following principles laid down by other researchers (2), (3). Second the programs under analysis have been written in a structured programming language following coding conventions which make available, at the source code level, valuable program control information. A new set of pictorial constructs is introduced for presenting a. program structure (static or dynamic) for inspection. These constructs combine the best features of an indented structured source code listing and the box odented nature of traditional flow charts. The graphical tools available are USed to provide for swift changes in. the desired level of detail displayed within a program structure, for traveling linearly through a program structure, for traveling through a complex program structure (following subroutine or system calls), for concurrently viewing multiple related program structures, and for presenting dynamic program behavior data using three-dimensional projections, The volume of a three-dimensional box representing a program block is proportional to the block's resource utilization. The scope of this paper is limited to a description of the STRUCT system. This system is currently being used to predict and analyze the performance advantages available through the migration of function (program modules) between levels of software and between software and firmware within BUGS. The results of this research on migration will be included in a doctoral dissertation currently being written.", + "authors": [ + { + "name": "Andries Van Dam", + "publication_start": 1975, + "publication_end": 1975, + "publication_count": 1, + "citation_count": 0, + "available_for_download": 0, + "downloads_6_weeks": 8, + "downloads_12_months": 97, + "downloads_cumulative": 97, + "average_downloads_per_article": 0, + "average_citations_per_article": 0 + } + ] +} diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py new file mode 100644 index 000000000..97af10519 --- /dev/null +++ b/src/scraping/buxton/scraper.py @@ -0,0 +1,331 @@ +import os +import docx2txt +from docx import Document +from docx.opc.constants import RELATIONSHIP_TYPE as RT +import re +from pymongo import MongoClient +import shutil +import uuid +import datetime +from PIL import Image +import math +import sys + +source = "./source" +dist = "../../server/public/files" + +db = MongoClient("localhost", 27017)["Dash"] +schema_guids = [] + + +def extract_links(fileName): + links = [] + doc = Document(fileName) + rels = doc.part.rels + for rel in rels: + item = rels[rel] + if item.reltype == RT.HYPERLINK and ".aspx" not in item._target: + links.append(item._target) + return listify(links) + + +def extract_value(kv_string): + pieces = kv_string.split(":") + return (pieces[1] if len(pieces) > 1 else kv_string).strip() + + +def mkdir_if_absent(path): + try: + if not os.path.exists(path): + os.mkdir(path) + except OSError: + print("failed to create the appropriate directory structures for %s" % file_name) + + +def guid(): + return str(uuid.uuid4()) + + +def listify(list): + return { + "fields": list, + "__type": "list" + } + + +def protofy(fieldId): + return { + "fieldId": fieldId, + "__type": "proxy" + } + + +def write_schema(parse_results, display_fields): + view_guids = parse_results["child_guids"] + + data_doc = parse_results["schema"] + fields = data_doc["fields"] + + view_doc_guid = guid() + + view_doc = { + "_id": view_doc_guid, + "fields": { + "proto": protofy(data_doc["_id"]), + "x": 10, + "y": 10, + "width": 900, + "height": 600, + "panX": 0, + "panY": 0, + "zoomBasis": 0.5, + "zIndex": 2, + "libraryBrush": False, + "viewType": 2 + }, + "__type": "Doc" + } + + fields["proto"] = protofy("collectionProto") + fields["data"] = listify(proxify_guids(view_guids)) + fields["schemaColumns"] = listify(display_fields) + fields["backgroundColor"] = "white" + fields["scale"] = 0.5 + fields["viewType"] = 2 + fields["author"] = "Bill Buxton" + fields["creationDate"] = { + "date": datetime.datetime.utcnow().microsecond, + "__type": "date" + } + fields["isPrototype"] = True + fields["page"] = -1 + + db.newDocuments.insert_one(data_doc) + db.newDocuments.insert_one(view_doc) + + data_doc_guid = data_doc["_id"] + print(f"inserted view document ({view_doc_guid})") + print(f"inserted data document ({data_doc_guid})\n") + + return view_doc_guid + + +def write_image(folder, name): + path = f"http://localhost:1050/files/{folder}/{name}" + + data_doc_guid = guid() + view_doc_guid = guid() + + view_doc = { + "_id": view_doc_guid, + "fields": { + "proto": protofy(data_doc_guid), + "x": 10, + "y": 10, + "width": 300, + "zIndex": 2, + "libraryBrush": False + }, + "__type": "Doc" + } + + image = Image.open(f"{dist}/{folder}/{name}") + native_width, native_height = image.size + + data_doc = { + "_id": data_doc_guid, + "fields": { + "proto": protofy("imageProto"), + "data": { + "url": path, + "__type": "image" + }, + "title": name, + "nativeWidth": native_width, + "author": "Bill Buxton", + "creationDate": { + "date": datetime.datetime.utcnow().microsecond, + "__type": "date" + }, + "isPrototype": True, + "page": -1, + "nativeHeight": native_height, + "height": native_height + }, + "__type": "Doc" + } + + db.newDocuments.insert_one(view_doc) + db.newDocuments.insert_one(data_doc) + + return view_doc_guid + + +def parse_document(file_name: str): + print(f"parsing {file_name}...") + pure_name = file_name.split(".")[0] + + result = {} + + dir_path = dist + "/" + pure_name + mkdir_if_absent(dir_path) + + raw = str(docx2txt.process(source + "/" + file_name, dir_path)) + + view_guids = [] + count = 0 + for image in os.listdir(dir_path): + count += 1 + view_guids.append(write_image(pure_name, image)) + os.rename(dir_path + "/" + image, dir_path + + "/" + image.replace(".", "_m.", 1)) + print(f"extracted {count} images...") + + def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( + u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() + + def sanitize_price(raw: str): + raw = raw.replace(",", "") + start = raw.find("$") + if start > -1: + i = start + 1 + while (i < len(raw) and re.match(r"[0-9\.]", raw[i])): + i += 1 + price = raw[start + 1: i + 1] + return float(price) + elif (raw.lower().find("nfs")): + return -1 + else: + return math.nan + + def remove_empty(line): return len(line) > 1 + + lines = list(map(sanitize, raw.split("\n"))) + lines = list(filter(remove_empty, lines)) + + result["file_name"] = file_name + result["title"] = lines[2].strip() + result["short_description"] = lines[3].strip().replace( + "Short Description: ", "") + + cur = 5 + notes = "" + while lines[cur] != "Device Details": + notes += lines[cur] + " " + cur += 1 + result["buxton_notes"] = notes.strip() + + cur += 1 + clean = list( + map(lambda data: data.strip().split(":"), lines[cur].split("|"))) + result["company"] = clean[0][len(clean[0]) - 1].strip() + result["year"] = clean[1][len(clean[1]) - 1].strip() + result["original_price"] = sanitize_price( + clean[2][len(clean[2]) - 1].strip()) + + cur += 1 + result["degrees_of_freedom"] = extract_value( + lines[cur]).replace("NA", "N/A") + cur += 1 + + dimensions = lines[cur].lower() + if dimensions.startswith("dimensions"): + dim_concat = dimensions[11:].strip() + cur += 1 + while lines[cur] != "Key Words": + dim_concat += (" " + lines[cur].strip()) + cur += 1 + result["dimensions"] = dim_concat + else: + result["dimensions"] = "N/A" + + cur += 1 + result["primary_key"] = extract_value(lines[cur]) + cur += 1 + result["secondary_key"] = extract_value(lines[cur]) + + while lines[cur] != "Links": + result["secondary_key"] += (" " + extract_value(lines[cur]).strip()) + cur += 1 + + cur += 1 + link_descriptions = [] + while lines[cur] != "Image": + link_descriptions.append(lines[cur].strip()) + cur += 1 + result["link_descriptions"] = listify(link_descriptions) + + result["hyperlinks"] = extract_links(source + "/" + file_name) + + images = [] + captions = [] + cur += 3 + while cur + 1 < len(lines) and lines[cur] != "NOTES:": + images.append(lines[cur]) + captions.append(lines[cur + 1]) + cur += 2 + result["images"] = listify(images) + result["captions"] = listify(captions) + + notes = [] + if (cur < len(lines) and lines[cur] == "NOTES:"): + cur += 1 + while cur < len(lines): + notes.append(lines[cur]) + cur += 1 + if len(notes) > 0: + result["notes"] = listify(notes) + + print("writing child schema...") + + return { + "schema": { + "_id": guid(), + "fields": result, + "__type": "Doc" + }, + "child_guids": view_guids + } + + +def proxify_guids(guids): + return list(map(lambda guid: {"fieldId": guid, "__type": "proxy"}, guids)) + + +if os.path.exists(dist): + shutil.rmtree(dist) +while os.path.exists(dist): + pass +os.mkdir(dist) +mkdir_if_absent(source) + +candidates = 0 +for file_name in os.listdir(source): + if file_name.endswith('.docx'): + candidates += 1 + schema_guids.append(write_schema( + parse_document(file_name), ["title", "data"])) + +print("writing parent schema...") +parent_guid = write_schema({ + "schema": { + "_id": guid(), + "fields": {}, + "__type": "Doc" + }, + "child_guids": schema_guids +}, ["title", "short_description", "original_price"]) + +print("appending parent schema to main workspace...\n") +db.newDocuments.update_one( + {"fields.title": "WS collection 1"}, + {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}} +) + +print("rewriting .gitignore...\n") +lines = ['*', '!.gitignore'] +with open(dist + "/.gitignore", 'w') as f: + f.write('\n'.join(lines)) + +suffix = "" if candidates == 1 else "s" +print(f"conversion complete. {candidates} candidate{suffix} processed.") diff --git a/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx b/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx new file mode 100644 index 000000000..06094b4d3 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Braun_T3.docx b/src/scraping/buxton/source/Bill_Notes_Braun_T3.docx new file mode 100644 index 000000000..356697092 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Braun_T3.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_CasioC801.docx b/src/scraping/buxton/source/Bill_Notes_CasioC801.docx new file mode 100644 index 000000000..cd89fb97b Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_CasioC801.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Casio_Mini.docx b/src/scraping/buxton/source/Bill_Notes_Casio_Mini.docx new file mode 100644 index 000000000..a503cddfc Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Casio_Mini.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docx b/src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docx new file mode 100644 index 000000000..4d13a8cf5 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx b/src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx new file mode 100644 index 000000000..578a1be08 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_FrogPad.docx b/src/scraping/buxton/source/Bill_Notes_FrogPad.docx new file mode 100644 index 000000000..d01e1bf5c Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_FrogPad.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx b/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx new file mode 100644 index 000000000..7bd28b376 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx b/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx new file mode 100644 index 000000000..0615c4953 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Matias.docx b/src/scraping/buxton/source/Bill_Notes_Matias.docx new file mode 100644 index 000000000..547603256 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Matias.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_MousePen.docx b/src/scraping/buxton/source/Bill_Notes_MousePen.docx new file mode 100644 index 000000000..4e1056636 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_MousePen.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_NewO.docx b/src/scraping/buxton/source/Bill_Notes_NewO.docx new file mode 100644 index 000000000..a514926d2 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_NewO.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_OLPC.docx b/src/scraping/buxton/source/Bill_Notes_OLPC.docx new file mode 100644 index 000000000..bfca0a9bb Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_OLPC.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx b/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx new file mode 100644 index 000000000..c0cf6ba9a Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docx b/src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docx new file mode 100644 index 000000000..ad06903f3 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docx b/src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docx new file mode 100644 index 000000000..e4c659de9 Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docx differ diff --git a/src/scraping/buxton/source/Bill_Notes_The_Tap.docx b/src/scraping/buxton/source/Bill_Notes_The_Tap.docx new file mode 100644 index 000000000..8ceebc71e Binary files /dev/null and b/src/scraping/buxton/source/Bill_Notes_The_Tap.docx differ -- cgit v1.2.3-70-g09d2 From 8891c0763fd86882d9e1d40c4fa4713aa14cac86 Mon Sep 17 00:00:00 2001 From: ab Date: Fri, 28 Jun 2019 15:25:39 -0400 Subject: small fix --- src/client/views/pdf/PDFViewer.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0cc81d469..63a103aa2 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -192,9 +192,10 @@ class Viewer extends React.Component { let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); this._isPage = Array(this.props.pdf.numPages); // this._textContent = Array(this.props.pdf.numPages); + const proms: Pdfjs.PDFPromise[] = []; for (let i = 0; i < this.props.pdf.numPages; i++) { - await this.props.pdf.getPage(i + 1).then(page => runInAction(() => { - pageSizes[i] = { width: page.view[2] * scale, height: page.view[3] * scale }; + proms.push(this.props.pdf.getPage(i + 1).then(page => runInAction(() => { + pageSizes[i] = { width: page.view[page.rotate === 0 ? 2 : 3] * scale, height: page.view[page.rotate === 0 ? 3 : 2] * scale }; // let x = page.getViewport(scale); // page.getTextContent().then((text: Pdfjs.TextContent) => { // // let tc = new Pdfjs.TextContentItem() @@ -205,8 +206,9 @@ class Viewer extends React.Component { // // }) // }); // pageSizes[i] = { width: x.width, height: x.height }; - })); + }))); } + await Promise.all(proms); runInAction(() => Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage)); this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages); -- cgit v1.2.3-70-g09d2 From ef3bf3c1080712c615c133217c1a6f6884d7d785 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Fri, 28 Jun 2019 15:37:32 -0400 Subject: fix --- src/client/views/pdf/PDFViewer.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 63a103aa2..a645b0041 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -195,7 +195,10 @@ class Viewer extends React.Component { const proms: Pdfjs.PDFPromise[] = []; for (let i = 0; i < this.props.pdf.numPages; i++) { proms.push(this.props.pdf.getPage(i + 1).then(page => runInAction(() => { - pageSizes[i] = { width: page.view[page.rotate === 0 ? 2 : 3] * scale, height: page.view[page.rotate === 0 ? 3 : 2] * scale }; + pageSizes[i] = { + width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]) * scale, + height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) * scale + }; // let x = page.getViewport(scale); // page.getTextContent().then((text: Pdfjs.TextContent) => { // // let tc = new Pdfjs.TextContentItem() -- cgit v1.2.3-70-g09d2 From c20afe5bf81491db78781184e03257272a1179a0 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 28 Jun 2019 16:06:54 -0400 Subject: trace --- src/client/views/pdf/PDFViewer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index a645b0041..380ba3c45 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; @@ -620,6 +620,7 @@ class Viewer extends React.Component { } render() { + trace(); let compiled = this._script; return (
-- cgit v1.2.3-70-g09d2 From 618ab13746e51eff826ad60504147b3ea9f8d90c Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 28 Jun 2019 16:18:17 -0400 Subject: test --- src/new_fields/Doc.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 27dcfba08..0340806ef 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -281,6 +281,7 @@ export namespace Doc { } export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) { + return templateLayoutDoc; let resolvedDataDoc = (templateLayoutDoc !== dataDoc) ? dataDoc : undefined; if (!dataDoc || !(templateLayoutDoc && !(Cast(templateLayoutDoc.layout, Doc) instanceof Doc) && resolvedDataDoc && resolvedDataDoc !== templateLayoutDoc)) { return templateLayoutDoc; -- cgit v1.2.3-70-g09d2 From 4e00ff1c268f0c5453b448dd1810e14c47b37969 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 28 Jun 2019 16:27:38 -0400 Subject: test2 --- src/client/views/nodes/DocumentContentsView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 0da4888a1..240b21714 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -52,7 +52,8 @@ export class DocumentContentsView extends React.Component" : + "null" : + // "" : KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : ""); } else if (typeof layout === "string") { return layout; -- cgit v1.2.3-70-g09d2 From f745ee91930c2b559098c5c6d75d5108edca2f01 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 28 Jun 2019 16:31:24 -0400 Subject: test3 --- src/client/views/nodes/KeyValueBox.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 0e798d291..eee4ec670 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -207,6 +207,7 @@ export class KeyValueBox extends React.Component { } render() { + return null; let dividerDragger = this.splitPercentage === 0 ? (null) :
-- cgit v1.2.3-70-g09d2 From ca95473dca3b577bf05aed3c76ccf800fc670220 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Fri, 28 Jun 2019 16:48:56 -0400 Subject: bleh --- src/client/views/pdf/Annotation.tsx | 144 ++++++++++++++++++++++++++++++++++++ src/client/views/pdf/PDFViewer.tsx | 132 ++------------------------------- 2 files changed, 150 insertions(+), 126 deletions(-) create mode 100644 src/client/views/pdf/Annotation.tsx (limited to 'src') diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx new file mode 100644 index 000000000..74f4be51a --- /dev/null +++ b/src/client/views/pdf/Annotation.tsx @@ -0,0 +1,144 @@ +import React = require("react"); +import { Doc, DocListCast, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { AnnotationTypes, Viewer, scale } from "./PDFViewer"; +import { observer } from "mobx-react"; +import { observable, IReactionDisposer, reaction, action } from "mobx"; +import { BoolCast, NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { List } from "../../../new_fields/List"; +import PDFMenu from "./PDFMenu"; +import { DocumentManager } from "../../util/DocumentManager"; + +interface IAnnotationProps { + anno: Doc, + index: number, + parent: Viewer +} + +export default class Annotation extends React.Component { + render() { + let annotationDocs = DocListCast(this.props.anno.annotations); + let res = annotationDocs.map(a => { + let type = NumCast(a.type); + switch (type) { + // case AnnotationTypes.Pin: + // return ; + case AnnotationTypes.Region: + return ; + default: + return
; + } + }); + return res; + } +} + +interface IRegionAnnotationProps { + x: number; + y: number; + width: number; + height: number; + index: number; + parent: Viewer; + document: Doc; +} + +@observer +class RegionAnnotation extends React.Component { + @observable private _backgroundColor: string = "red"; + + private _reactionDisposer?: IReactionDisposer; + private _scrollDisposer?: IReactionDisposer; + private _mainCont: React.RefObject; + + constructor(props: IRegionAnnotationProps) { + super(props); + + this._mainCont = React.createRef(); + } + + componentDidMount() { + this._reactionDisposer = reaction( + () => BoolCast(this.props.document.delete), + () => { + if (BoolCast(this.props.document.delete)) { + if (this._mainCont.current) { + this._mainCont.current.style.display = "none"; + } + } + }, + { fireImmediately: true } + ); + + this._scrollDisposer = reaction( + () => this.props.parent.Index, + () => { + if (this.props.parent.Index === this.props.index) { + this.props.parent.scrollTo(this.props.y - 50); + } + } + ); + } + + componentWillUnmount() { + this._reactionDisposer && this._reactionDisposer(); + this._scrollDisposer && this._scrollDisposer(); + } + + deleteAnnotation = () => { + let annotation = DocListCast(this.props.parent.props.parent.Document.annotations); + let group = FieldValue(Cast(this.props.document.group, Doc)); + if (group && annotation.indexOf(group) !== -1) { + let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); + this.props.parent.props.parent.Document.annotations = new List(newAnnotations); + } + + if (group) { + let groupAnnotations = DocListCast(group.annotations); + groupAnnotations.forEach(anno => anno.delete = true); + } + + PDFMenu.Instance.fadeOut(true); + } + + @action + onPointerDown = (e: React.PointerEvent) => { + if (e.button === 0) { + let targetDoc = Cast(this.props.document.target, Doc, null); + if (targetDoc) { + DocumentManager.Instance.jumpToDocument(targetDoc, true); + } + } + if (e.button === 2) { + PDFMenu.Instance.Status = "annotation"; + PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this); + PDFMenu.Instance.Pinned = false; + PDFMenu.Instance.AddTag = this.addTag.bind(this); + PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); + } + } + + addTag = (key: string, value: string): boolean => { + let group = FieldValue(Cast(this.props.document.group, Doc)); + if (group) { + let valNum = parseInt(value); + group[key] = isNaN(valNum) ? value : valNum; + return true; + } + return false; + } + + render() { + return ( +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index a645b0041..6875e5000 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; @@ -23,6 +23,7 @@ import { UndoManager } from "../../util/UndoManager"; import { CompileScript, CompiledScript, CompileResult } from "../../util/Scripting"; import { ScriptField } from "../../../new_fields/ScriptField"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import Annotation from "./Annotation"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); export const scale = 2; @@ -69,7 +70,7 @@ interface IViewerProps { * Handles rendering and virtualization of the pdf */ @observer -class Viewer extends React.Component { +export class Viewer extends React.Component { // _visibleElements is the array of JSX elements that gets rendered @observable.shallow private _visibleElements: JSX.Element[] = []; // _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder @@ -424,20 +425,8 @@ class Viewer extends React.Component { } } - renderAnnotation = (anno: Doc, index: number): JSX.Element[] => { - let annotationDocs = DocListCast(anno.annotations); - let res = annotationDocs.map(a => { - let type = NumCast(a.type); - switch (type) { - // case AnnotationTypes.Pin: - // return ; - case AnnotationTypes.Region: - return ; - default: - return
; - } - }); - return res; + renderAnnotation = (anno: Doc, index: number): JSX.Element => { + return } @action @@ -620,6 +609,7 @@ class Viewer extends React.Component { } render() { + trace(); let compiled = this._script; return (
@@ -683,116 +673,6 @@ export enum AnnotationTypes { Region } -interface IAnnotationProps { - x: number; - y: number; - width: number; - height: number; - index: number; - parent: Viewer; - document: Doc; -} - -@observer -class RegionAnnotation extends React.Component { - @observable private _backgroundColor: string = "red"; - - private _reactionDisposer?: IReactionDisposer; - private _scrollDisposer?: IReactionDisposer; - private _mainCont: React.RefObject; - - constructor(props: IAnnotationProps) { - super(props); - - this._mainCont = React.createRef(); - } - - componentDidMount() { - this._reactionDisposer = reaction( - () => BoolCast(this.props.document.delete), - () => { - if (BoolCast(this.props.document.delete)) { - if (this._mainCont.current) { - this._mainCont.current.style.display = "none"; - } - } - }, - { fireImmediately: true } - ); - - this._scrollDisposer = reaction( - () => this.props.parent.Index, - () => { - if (this.props.parent.Index === this.props.index) { - this.props.parent.scrollTo(this.props.y - 50); - } - } - ); - } - - componentWillUnmount() { - this._reactionDisposer && this._reactionDisposer(); - this._scrollDisposer && this._scrollDisposer(); - } - - deleteAnnotation = () => { - let annotation = DocListCast(this.props.parent.props.parent.Document.annotations); - let group = FieldValue(Cast(this.props.document.group, Doc)); - if (group && annotation.indexOf(group) !== -1) { - let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); - this.props.parent.props.parent.Document.annotations = new List(newAnnotations); - } - - if (group) { - let groupAnnotations = DocListCast(group.annotations); - groupAnnotations.forEach(anno => anno.delete = true); - } - - PDFMenu.Instance.fadeOut(true); - } - - @action - onPointerDown = (e: React.PointerEvent) => { - if (e.button === 0) { - let targetDoc = Cast(this.props.document.target, Doc, null); - if (targetDoc) { - DocumentManager.Instance.jumpToDocument(targetDoc, true); - } - } - if (e.button === 2) { - PDFMenu.Instance.Status = "annotation"; - PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this); - PDFMenu.Instance.Pinned = false; - PDFMenu.Instance.AddTag = this.addTag.bind(this); - PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); - } - } - - addTag = (key: string, value: string): boolean => { - let group = FieldValue(Cast(this.props.document.group, Doc)); - if (group) { - let valNum = parseInt(value); - group[key] = isNaN(valNum) ? value : valNum; - return true; - } - return false; - } - - render() { - return ( -
- ); - } -} - class SimpleLinkService { externalLinkTarget: any = null; externalLinkRel: any = null; -- cgit v1.2.3-70-g09d2 From 8ef971485492e3dc461cd93b2ae89e01b9995741 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 28 Jun 2019 16:57:46 -0400 Subject: undo tests --- src/client/views/nodes/DocumentContentsView.tsx | 3 +-- src/client/views/nodes/KeyValueBox.tsx | 1 - src/client/views/pdf/PDFViewer.tsx | 3 +-- src/new_fields/Doc.ts | 1 - 4 files changed, 2 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 240b21714..0da4888a1 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -52,8 +52,7 @@ export class DocumentContentsView extends React.Component" : + "" : KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : ""); } else if (typeof layout === "string") { return layout; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index eee4ec670..0e798d291 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -207,7 +207,6 @@ export class KeyValueBox extends React.Component { } render() { - return null; let dividerDragger = this.splitPercentage === 0 ? (null) :
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 380ba3c45..a645b0041 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; @@ -620,7 +620,6 @@ class Viewer extends React.Component { } render() { - trace(); let compiled = this._script; return (
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 0340806ef..27dcfba08 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -281,7 +281,6 @@ export namespace Doc { } export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) { - return templateLayoutDoc; let resolvedDataDoc = (templateLayoutDoc !== dataDoc) ? dataDoc : undefined; if (!dataDoc || !(templateLayoutDoc && !(Cast(templateLayoutDoc.layout, Doc) instanceof Doc) && resolvedDataDoc && resolvedDataDoc !== templateLayoutDoc)) { return templateLayoutDoc; -- cgit v1.2.3-70-g09d2 From 5a50af8589d9fb48d9395c124c6140b39c1a262b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 28 Jun 2019 20:38:14 -0400 Subject: fixed resizing pick correlation --- src/client/views/DocumentDecorations.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 76c2d71e8..61e9d209a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -500,7 +500,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let nheight = doc.nativeHeight || 0; let width = (doc.width || 0); let height = (doc.height || (nheight / nwidth * width)); - let scale = element.props.ScreenToLocalTransform().Scale; + let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); doc.x = (doc.x || 0) + dX * (actualdW - width); -- cgit v1.2.3-70-g09d2 From 79f301a2f74e88f1cf59064de320c199b5154827 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 28 Jun 2019 21:47:26 -0400 Subject: implemented global key handler --- src/client/util/SelectionManager.ts | 5 +- src/client/views/GlobalKeyHandler.ts | 141 +++++++++++++++++++++ src/client/views/MainView.tsx | 62 ++------- .../views/collections/CollectionDockingView.tsx | 20 ++- src/client/views/nodes/DocumentView.tsx | 4 +- 5 files changed, 176 insertions(+), 56 deletions(-) create mode 100644 src/client/views/GlobalKeyHandler.ts (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 7dbb81e76..3bc71ad42 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,11 +1,13 @@ -import { observable, action, runInAction } from "mobx"; +import { observable, action, runInAction, IReactionDisposer, reaction, autorun } from "mobx"; import { Doc } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast } from "../../new_fields/Types"; export namespace SelectionManager { + class Manager { + @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; @@ -18,6 +20,7 @@ export namespace SelectionManager { } manager.SelectedDocuments.push(docView); + // console.log(manager.SelectedDocuments); docView.props.whenActiveChanged(true); } } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts new file mode 100644 index 000000000..bb10f27cf --- /dev/null +++ b/src/client/views/GlobalKeyHandler.ts @@ -0,0 +1,141 @@ +import { UndoManager } from "../util/UndoManager"; +import { SelectionManager } from "../util/SelectionManager"; +import { CollectionDockingView } from "./collections/CollectionDockingView"; +import { MainView } from "./MainView"; +import { DragManager } from "../util/DragManager"; +import { action } from "mobx"; + +const modifiers = ["Control", "Meta", "Shift", "Alt"]; +type KeyHandler = (keycode: string) => KeyControlInfo; +type KeyControlInfo = { + preventDefault: boolean, + stopPropagation: boolean +}; + +export default class KeyManager { + public static Handler: KeyManager; + private mainView: MainView; + private router = new Map(); + + constructor(mainView: MainView) { + this.mainView = mainView; + this.router.set("0000", this.unmodified); + this.router.set("0100", this.ctrl); + this.router.set("0010", this.alt); + this.router.set("1100", this.ctrl_shift); + } + + public handle = (e: KeyboardEvent) => { + let keyname = e.key.toLowerCase(); + this.handleGreedy(keyname); + + if (modifiers.includes(keyname)) { + return; + } + + let bit = (value: boolean) => value ? "1" : "0"; + let modifierIndex = bit(e.shiftKey) + bit(e.ctrlKey) + bit(e.altKey) + bit(e.metaKey); + + let handleConstrained = this.router.get(modifierIndex); + if (!handleConstrained) { + return; + } + + let control = handleConstrained(keyname); + + control.stopPropagation && e.stopPropagation(); + control.preventDefault && e.preventDefault(); + } + + private handleGreedy = action((keyname: string) => { + switch (keyname) { + } + }); + + private unmodified = action((keyname: string) => { + switch (keyname) { + case "escape": + if (CollectionDockingView.Instance.HasFullScreen()) { + CollectionDockingView.Instance.CloseFullScreen(); + } else { + SelectionManager.DeselectAll(); + } + DragManager.AbortDrag(); + break; + } + + return { + stopPropagation: false, + preventDefault: false + }; + }); + + private alt = action((keyname: string) => { + let stopPropagation = true; + let preventDefault = true; + + switch (keyname) { + case "n": + let toggle = this.mainView.addMenuToggle.current!; + toggle.checked = !toggle.checked; + break; + } + + return { + stopPropagation: stopPropagation, + preventDefault: preventDefault + }; + }); + + private ctrl = action((keyname: string) => { + let stopPropagation = true; + let preventDefault = true; + + switch (keyname) { + case "arrowright": + this.mainView.mainFreeform && CollectionDockingView.Instance.AddRightSplit(this.mainView.mainFreeform, undefined); + break; + case "arrowleft": + this.mainView.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(this.mainView.mainFreeform); + break; + case "f": + this.mainView.isSearchVisible = !this.mainView.isSearchVisible; + break; + case "o": + let target = SelectionManager.SelectedDocuments()[0]; + target && target.fullScreenClicked(); + break; + case "r": + preventDefault = false; + break; + case "y": + UndoManager.Redo(); + break; + case "z": + UndoManager.Undo(); + break; + } + + return { + stopPropagation: stopPropagation, + preventDefault: preventDefault + }; + }); + + private ctrl_shift = action((keyname: string) => { + let stopPropagation = true; + let preventDefault = true; + + switch (keyname) { + case "z": + UndoManager.Redo(); + break; + } + + return { + stopPropagation: stopPropagation, + preventDefault: preventDefault + }; + }); + +} \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6290d8985..157512aa0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -36,18 +36,20 @@ import { CollectionBaseView } from './collections/CollectionBaseView'; import { List } from '../../new_fields/List'; import PDFMenu from './pdf/PDFMenu'; import { InkTool } from '../../new_fields/InkField'; -import * as _ from "lodash"; +import _ from "lodash"; +import KeyManager from './GlobalKeyHandler'; @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; @computed private get mainContainer(): Opt { return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc)); } - @computed private get mainFreeform(): Opt { + @computed get mainFreeform(): Opt { let docs = DocListCast(this.mainContainer!.data); return (docs && docs.length > 1) ? docs[1] : undefined; } @@ -64,12 +66,13 @@ export class MainView extends React.Component { } componentWillMount() { - document.removeEventListener("keydown", this.globalKeyHandler); - document.addEventListener("keydown", this.globalKeyHandler); + KeyManager.Handler = new KeyManager(this); + document.removeEventListener("keydown", KeyManager.Handler.handle); + document.addEventListener("keydown", KeyManager.Handler.handle); } componentWillUnMount() { - document.removeEventListener("keydown", this.globalKeyHandler); + document.removeEventListener("keydown", KeyManager.Handler.handle); } constructor(props: Readonly<{}>) { @@ -122,18 +125,6 @@ export class MainView extends React.Component { // window.addEventListener("pointermove", (e) => this.reportLocation(e)) window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler - window.addEventListener("keydown", (e) => { - if (e.key === "Escape") { - DragManager.AbortDrag(); - SelectionManager.DeselectAll(); - } else if (e.key === "z" && e.ctrlKey) { - e.preventDefault(); - UndoManager.Undo(); - } else if ((e.key === "y" && e.ctrlKey) || (e.key === "z" && e.ctrlKey && e.shiftKey)) { - e.preventDefault(); - UndoManager.Redo(); - } - }, false); // drag event handler // click interactions for the context menu document.addEventListener("pointerdown", action(function (e: PointerEvent) { @@ -292,7 +283,7 @@ export class MainView extends React.Component { ]; return < div id="add-nodes-menu" > - +
@@ -300,8 +291,7 @@ export class MainView extends React.Component {
  • -
  • )} +
  • diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 927aa363f..f5dd76b71 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -251,6 +251,8 @@ class TreeView extends React.Component { e.stopPropagation(); } } + + @undoBatch treeDrop = (e: Event, de: DragManager.DropEvent) => { let x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); let rect = this._header!.current!.getBoundingClientRect(); -- cgit v1.2.3-70-g09d2 From db34b52b66311a72f986a0406acdb935369d264f Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Jul 2019 12:38:51 -0400 Subject: added some context menu undo's --- src/client/views/TemplateMenu.tsx | 2 ++ src/client/views/nodes/DocumentView.tsx | 21 ++++++++++++++++++--- .../views/presentationview/PresentationView.tsx | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index a9bc4d3d2..a53c8749a 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -6,6 +6,7 @@ import './DocumentDecorations.scss'; import { DocumentView } from "./nodes/DocumentView"; import { Template } from "./Templates"; import React = require("react"); +import { undoBatch } from "../util/UndoManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -39,6 +40,7 @@ export class TemplateMenu extends React.Component { super(props); } + @undoBatch @action toggleTemplate = (event: React.ChangeEvent, template: Template): void => { if (event.target.checked) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9edde896f..a6f324a04 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -393,8 +393,13 @@ export class DocumentView extends DocComponent(Docu this._lastTap = Date.now(); } - deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }; - fieldsClicked = (): void => { let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.dataDoc, "onRight"); }; + @undoBatch + deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } + + @undoBatch + fieldsClicked = (): void => { let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.dataDoc, "onRight"); } + + @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); doc.isButton = !BoolCast(doc.isButton, false); @@ -407,6 +412,8 @@ export class DocumentView extends DocComponent(Docu doc.nativeWidth = doc.nativeHeight = undefined; } } + + @undoBatch public fullScreenClicked = (): void => { CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this); SelectionManager.DeselectAll(); @@ -467,6 +474,8 @@ export class DocumentView extends DocComponent(Docu this.templates = this.templates; } + @undoBatch + @action freezeNativeDimensions = (): void => { let proto = Doc.GetProto(this.props.Document); if (proto.ignoreAspect === undefined && !proto.nativeWidth) { @@ -477,6 +486,12 @@ export class DocumentView extends DocComponent(Docu proto.ignoreAspect = !BoolCast(proto.ignoreAspect, false); } + @undoBatch + @action + toggleLockPosition = (): void => { + this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; + } + @action onContextMenu = async (e: React.MouseEvent): Promise => { e.persist(); @@ -499,7 +514,7 @@ export class DocumentView extends DocComponent(Docu 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: "edit" }); cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); - cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Pos" : "Lock Pos", event: () => this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); + cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Pos" : "Lock Pos", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" }); cm.addItem({ description: "Find aliases", event: async () => { diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index 20d0e113a..a3fa553b7 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -494,6 +494,7 @@ export class PresentationView extends React.Component { /** * Adds a document to the presentation view **/ + @undoBatch @action public PinDoc(doc: Doc) { //add this new doc to props.Document -- cgit v1.2.3-70-g09d2 From 9a222ede9fafd90e1d62ed40076da68e3aa299b7 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Jul 2019 13:09:36 -0400 Subject: fixed full screen problem mistaking template --- src/client/views/nodes/DocumentView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a6f324a04..d5b329899 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -280,7 +280,7 @@ export class DocumentView extends DocComponent(Docu if (this._doubleTap && this.props.renderDepth) { let fullScreenAlias = Doc.MakeAlias(this.props.Document); fullScreenAlias.templates = new List(); - this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab"); + this.props.addDocTab(fullScreenAlias, this.props.Document === this.dataDoc ? undefined : this.dataDoc, "inTab"); SelectionManager.DeselectAll(); this.props.Document.libraryBrush = false; } -- cgit v1.2.3-70-g09d2 From 2b6e76fb60d4dafd1c5383f21f870542cc808aff Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 1 Jul 2019 13:12:10 -0400 Subject: small changes --- src/client/views/nodes/PDFBox.scss | 5 ++++- src/client/views/pdf/Annotation.tsx | 9 +++++++++ src/client/views/pdf/PDFMenu.tsx | 7 +++++-- src/client/views/pdf/Page.tsx | 7 +++++-- 4 files changed, 23 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 9a38d6241..e7655d598 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -32,15 +32,18 @@ height: 100px; } -.pdfBox-cont, .pdfBox-cont-interactive { +.pdfBox-cont, +.pdfBox-cont-interactive { display: flex; flex-direction: row; height: 100%; overflow-y: scroll; overflow-x: hidden; } + .pdfBox-cont { pointer-events: none; + .textlayer { pointer-events: none; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index ff77612d6..e88839edd 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -8,6 +8,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import PDFMenu from "./PDFMenu"; import { DocumentManager } from "../../util/DocumentManager"; +import { PresentationView } from "../presentationview/PresentationView"; interface IAnnotationProps { anno: Doc; @@ -101,6 +102,13 @@ class RegionAnnotation extends React.Component { PDFMenu.Instance.fadeOut(true); } + pinToPres = () => { + let group = FieldValue(Cast(this.props.document.group, Doc)); + if (group) { + PresentationView.Instance.PinDoc(group); + } + } + @action onPointerDown = (e: React.PointerEvent) => { if (e.button === 0) { @@ -114,6 +122,7 @@ class RegionAnnotation extends React.Component { PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this); PDFMenu.Instance.Pinned = false; PDFMenu.Instance.AddTag = this.addTag.bind(this); + PDFMenu.Instance.PinToPres = this.pinToPres; PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); } } diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index aeed5213c..f93b2e59f 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -7,6 +7,7 @@ import { emptyFunction, returnZero, returnTrue, returnFalse } from "../../../Uti import { Doc } from "../../../new_fields/Doc"; import { DragManager } from "../../util/DragManager"; import { DocUtils } from "../../documents/Documents"; +import { PresentationView } from "../presentationview/PresentationView"; @observer export default class PDFMenu extends React.Component { @@ -24,6 +25,7 @@ export default class PDFMenu extends React.Component { Delete: () => void = emptyFunction; Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; AddTag: (key: string, value: string) => boolean = returnFalse; + PinToPres: () => void = emptyFunction; @observable public Highlighting: boolean = false; @observable public Status: "pdf" | "annotation" | "snippet" | "" = ""; @@ -252,11 +254,12 @@ export default class PDFMenu extends React.Component { ] : [ , -
    + , +
    , - , + , ]; return ( diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 782dbd261..e63ac23da 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -183,8 +183,11 @@ export default class Page extends React.Component { let doc = this.props.parent.Document; let view = Doc.MakeAlias(doc); let data = Doc.MakeDelegate(doc.proto!); + data.title = StrCast(data.title) + "_snippet"; view.proto = data; view.nativeHeight = marquee.height; + view.height = (doc[WidthSym]() / NumCast(doc.nativeWidth)) * marquee.height; + view.nativeWidth = doc.nativeWidth; view.startY = marquee.top + this.props.getScrollFromPage(this.props.page); view.width = doc[WidthSym](); let dragData = new DragManager.DocumentDragData([view], [undefined]); @@ -341,7 +344,7 @@ export default class Page extends React.Component { if (PDFMenu.Instance.Highlighting) { - this.highlight(undefined, "#f4f442"); + this.highlight(undefined, "goldenrod"); } else { PDFMenu.Instance.StartDrag = this.startDrag; @@ -402,7 +405,7 @@ export default class Page extends React.Component {
    + style={{ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`, width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`, background: "red", border: "10px dashed black" }}> {/* */}
    -- cgit v1.2.3-70-g09d2 From b5fb490408830eb0e0e010bbb89f53e8990a1cef Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 1 Jul 2019 13:50:20 -0400 Subject: keyboard changes + marquee changes --- src/client/views/GlobalKeyHandler.ts | 11 +++++++++-- src/client/views/pdf/Page.tsx | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index e467d7c61..9ef9e8950 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -1,4 +1,4 @@ -import { UndoManager } from "../util/UndoManager"; +import { UndoManager, undoBatch } from "../util/UndoManager"; import { SelectionManager } from "../util/SelectionManager"; import { CollectionDockingView } from "./collections/CollectionDockingView"; import { MainView } from "./MainView"; @@ -100,7 +100,7 @@ export default class KeyManager { MainView.Instance.mainFreeform && CollectionDockingView.Instance.AddRightSplit(MainView.Instance.mainFreeform, undefined); break; case "arrowleft": - MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform); + MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform) break; case "f": MainView.Instance.isSearchVisible = !MainView.Instance.isSearchVisible; @@ -118,6 +118,13 @@ export default class KeyManager { case "z": UndoManager.Undo(); break; + case "a": + case "c": + case "v": + case "x": + stopPropagation = false; + preventDefault = false; + break; } return { diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index e63ac23da..92f5390ae 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -405,7 +405,7 @@ export default class Page extends React.Component {
    + style={{ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`, width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`, background: "red", border: `${this._marqueeWidth === 0 ? "" : "10px dashed black"}` }}> {/* */}
    -- cgit v1.2.3-70-g09d2 From d101b95f647f39fe50a97d332cb107ae84d25759 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 1 Jul 2019 14:30:31 -0400 Subject: Added a tooltip --- src/client/views/collections/CollectionDockingView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 13b0579d2..a97fd1e69 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -383,7 +383,7 @@ export class CollectionDockingView extends React.Component { e.preventDefault(); e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From 6776a8d8d27c38adc5b232aab925c103cc1c59ba Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Jul 2019 18:04:17 -0400 Subject: switched dataDoc stuff around for documentviews. --- src/client/views/nodes/DocumentView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d5b329899..687c7f73d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -217,7 +217,7 @@ export class DocumentView extends DocComponent(Docu e.stopPropagation(); } - get dataDoc() { return this.props.DataDoc ? this.props.DataDoc : this.props.Document; } + get dataDoc() { return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; } startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) { if (this._mainCont.current) { let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])]; @@ -280,7 +280,7 @@ export class DocumentView extends DocComponent(Docu if (this._doubleTap && this.props.renderDepth) { let fullScreenAlias = Doc.MakeAlias(this.props.Document); fullScreenAlias.templates = new List(); - this.props.addDocTab(fullScreenAlias, this.props.Document === this.dataDoc ? undefined : this.dataDoc, "inTab"); + this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab"); SelectionManager.DeselectAll(); this.props.Document.libraryBrush = false; } -- cgit v1.2.3-70-g09d2 From ab2b2317e601b9e440f9c48b4639c143d5187949 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 1 Jul 2019 19:29:15 -0400 Subject: added tooltips and improved acm scraping --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/MainView.tsx | 6 ++-- src/client/views/TemplateMenu.tsx | 2 +- src/scraping/acm/index.js | 56 +++++++++++++++++++++++++------- src/scraping/acm/results.txt | 34 +++++++++++-------- 6 files changed, 71 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 61e9d209a..e1eff5cdc 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -692,7 +692,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> {this._edtingTitle ? :
    {`${this.selectionTitle}`}
    } -
    X
    +
    X
    e.preventDefault()}>
    e.preventDefault()}>
    e.preventDefault()}>
    diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index e467d7c61..4c72bd683 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -13,7 +13,7 @@ type KeyControlInfo = { }; export default class KeyManager { - public static Handler: KeyManager = new KeyManager(); + public static Instance: KeyManager = new KeyManager(); private router = new Map(); constructor() { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index fb88c284f..a9d75d8cc 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -64,8 +64,8 @@ export class MainView extends React.Component { } componentWillMount() { - window.removeEventListener("keydown", KeyManager.Handler.handle); - window.addEventListener("keydown", KeyManager.Handler.handle); + window.removeEventListener("keydown", KeyManager.Instance.handle); + window.addEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.pointerDown); window.addEventListener("pointerdown", this.pointerDown); @@ -78,7 +78,7 @@ export class MainView extends React.Component { pointerUp = (e: PointerEvent) => this.isPointerDown = false; componentWillUnMount() { - window.removeEventListener("keydown", KeyManager.Handler.handle); + window.removeEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.pointerDown); window.removeEventListener("pointerup", this.pointerUp); } diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index a9bc4d3d2..a1d59484a 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -79,7 +79,7 @@ export class TemplateMenu extends React.Component { return (
    -
    this.toggleTemplateActivity()}>+
    +
    this.toggleTemplateActivity()}>+
      {templateMenu}
    diff --git a/src/scraping/acm/index.js b/src/scraping/acm/index.js index ff4b099e7..ad0f844ba 100644 --- a/src/scraping/acm/index.js +++ b/src/scraping/acm/index.js @@ -10,6 +10,17 @@ const { const driver_pause = 500; // milliseconds const sample_line_char_max = 100; // characters const target_browser = 'chrome'; +const tab_map = { + abstract: "11", + authors: "14", + references: "15", + cited_by: "16", + index_terms: "17", + publication: "18", + reviews: "19", + comments: "20", + table_of_contents: "21" +}; // GENERAL UTILITY FUNCTIONS @@ -32,14 +43,18 @@ function log_snippet(result, quotes = true) { snippet = quotes ? `"${snippet}"` : snippet; break; case "object": - snippet = result.map(res => { - switch (typeof res) { - case "string": - return res.substring(0, sample_line_char_max / result.length); - case "object": - return res[Object.keys(res)[0]]; - } - }).join(', '); + if (Array.isArray(result)) { + snippet = result.map(res => { + switch (typeof res) { + case "string": + return res.substring(0, sample_line_char_max / result.length); + case "object": + return res[Object.keys(res)[0]]; + } + }).join(', '); + } else { + snippet = result[Object.keys(result)[0]]; + } } console.log(snippet); return result; @@ -57,6 +72,10 @@ async function click_on(ref) { await driver.sleep(driver_pause); } +async function click_on_acm_tab(target) { + await click_on(`//*[@id="tab-10${tab_map[target]}-btnInnerEl"]/span`); +} + async function locate(ref, multiple = false) { let locator = ref.startsWith("//") ? By.xpath(ref) : By.id(ref); return await multiple ? driver.findElements(locator) : driver.findElement(locator); @@ -84,8 +103,6 @@ async function logged_assign(key, value) { // TEXT SCRAPING async function read_authors() { - await click_on('//*[@id="tab-1014-btnInnerEl"]/span'); - let authors = await text_of('//*[@id="tabpanel-1009-body"]'); let sanitize = line => line.length > 0 && !(line.startsWith("No contact information") || line.startsWith("View colleagues of") || line.startsWith("Bibliometrics:")); let author_lines = authors.split("\n").map(line => line.trim()).filter(sanitize); @@ -106,6 +123,17 @@ async function read_authors() { return all_authors; } +async function read_publication() { + let publciation_elements = (await text_of("source-body")).split("\n"); + let publication_module = {}; + for (let element of publciation_elements) { + if (element.startsWith("Title")) { + publication_module.title = element.substring(6); + } + } + return publication_module; +} + // JSON / DASH CONVERSION AND EXPORT function parse_authors(metadata) { @@ -134,7 +162,7 @@ function write_results() { results.forEach(res => output += (JSON.stringify(res, null, 4) + "\n")); writeFile("./results.txt", output, function errorHandler(exception) { - console.log(exception || "scraped references successfully written as JSON to ./results.txt\n"); + console.log(exception || "scraped references successfully written as JSON to ./results.txt"); }); } @@ -157,12 +185,18 @@ async function scrape_targets(error, data) { let id = references[i]; let url = `https://dl.acm.org/citation.cfm?id=${id}`; console.log(`\nscraping ${i + 1}/${quota} (${id})`); + await navigate_to(url); logged_assign("url", url); logged_assign("title", await text_of('//*[@id="divmain"]/div/h1')); logged_assign("abstract", (await text_of_all("abstract-body")).join(" ")); + + await click_on_acm_tab("authors"); logged_assign("authors", (await read_authors()).map(parse_authors)); + + await click_on_acm_tab("publication"); + logged_assign("publication", await read_publication()); } catch (e) { console.log(e); await driver.quit(); diff --git a/src/scraping/acm/results.txt b/src/scraping/acm/results.txt index fffa7ff51..ba66d61a7 100644 --- a/src/scraping/acm/results.txt +++ b/src/scraping/acm/results.txt @@ -10,10 +10,10 @@ "publication_count": 1, "citation_count": 179, "available_for_download": 1, - "downloads_6_weeks": 130, - "downloads_12_months": 1004, - "downloads_cumulative": 9792, - "average_downloads_per_article": 9792, + "downloads_6_weeks": 123, + "downloads_12_months": 922, + "downloads_cumulative": 9793, + "average_downloads_per_article": 9793, "average_citations_per_article": 179 }, { @@ -23,10 +23,10 @@ "publication_count": 5, "citation_count": 196, "available_for_download": 1, - "downloads_6_weeks": 130, - "downloads_12_months": 1004, - "downloads_cumulative": 9792, - "average_downloads_per_article": 9792, + "downloads_6_weeks": 123, + "downloads_12_months": 922, + "downloads_cumulative": 9793, + "average_downloads_per_article": 9793, "average_citations_per_article": 39.2 }, { @@ -36,13 +36,16 @@ "publication_count": 2, "citation_count": 188, "available_for_download": 2, - "downloads_6_weeks": 130, - "downloads_12_months": 1009, - "downloads_cumulative": 10023, - "average_downloads_per_article": 5011.5, + "downloads_6_weeks": 123, + "downloads_12_months": 927, + "downloads_cumulative": 10024, + "average_downloads_per_article": 5012, "average_citations_per_article": 94 } - ] + ], + "publication": { + "title": "Journal of the ACM (JACM) JACM Homepage table of contents archive" + } } { "url": "https://dl.acm.org/citation.cfm?id=2412979", @@ -62,5 +65,8 @@ "average_downloads_per_article": 0, "average_citations_per_article": 0 } - ] + ], + "publication": { + "title": "IEEE Transactions on Software Engineering table of contents archive" + } } -- cgit v1.2.3-70-g09d2 From 44ff64c8186b25069c14aaf4de0ae694f872c2d3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 1 Jul 2019 19:31:13 -0400 Subject: scraping function tweak --- src/scraping/acm/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/scraping/acm/index.js b/src/scraping/acm/index.js index ad0f844ba..39938ecca 100644 --- a/src/scraping/acm/index.js +++ b/src/scraping/acm/index.js @@ -28,6 +28,10 @@ function log_read(content) { process.stdout.write("reading " + content + "..."); } +function first_value(object) { + return object[Object.keys(object)[0]]; +} + function log_snippet(result, quotes = true) { let snippet = "failed to create snippet"; switch (typeof result) { @@ -49,11 +53,11 @@ function log_snippet(result, quotes = true) { case "string": return res.substring(0, sample_line_char_max / result.length); case "object": - return res[Object.keys(res)[0]]; + return first_value(res); } }).join(', '); } else { - snippet = result[Object.keys(result)[0]]; + snippet = first_value(result); } } console.log(snippet); -- cgit v1.2.3-70-g09d2 From 055b823ac472c3831d9cca9629cd2c505310c664 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 1 Jul 2019 22:02:56 -0400 Subject: added library view pullout --- src/.DS_Store | Bin 6148 -> 6148 bytes src/client/views/Main.scss | 16 +++ src/client/views/MainView.tsx | 117 ++++++++++++++++----- .../views/collections/CollectionDockingView.tsx | 10 +- 4 files changed, 116 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/.DS_Store b/src/.DS_Store index ff00f097e..fc746835f 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 0271edcd2..59c70c674 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -224,4 +224,20 @@ ul#add-options-list { display: inline-block; padding: 0; } +} + +.mainView-libraryFlyout { + height: 100%; + position: absolute; +} + +.mainView-libraryHandle { + opacity: 0.6; + width: 20px; + height: 40px; + top: 50%; + border-radius: 20px; + position: absolute; + z-index: 1; + background: gray; } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d28e3b837..654175523 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -160,12 +160,13 @@ export class MainView extends React.Component { } } + @action createNewWorkspace = async (id?: string) => { const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc)); if (list) { let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` }); - var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; + var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id); if (!CurrentUserUtils.UserDocument.linkManagerDoc) { let linkManagerDoc = new Doc(); @@ -224,40 +225,106 @@ export class MainView extends React.Component { getPHeight = () => { return this.pheight; } - @computed - get mainContent() { + + @observable flyoutWidth: number = 250; + @computed get dockingContent() { + let flyoutWidth = this.flyoutWidth; let mainCont = this.mainContainer; - let content = !mainCont ? (null) : - ; let castRes = mainCont ? FieldValue(Cast(mainCont.presentationView, listSpec(Doc))) : undefined; return {({ measureRef }) => -
    - {content} +
    + {!mainCont ? (null) : + } {castRes ? : null}
    } ; } + _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(); + } + @action + onPointerMove = (e: PointerEvent) => { + this.flyoutWidth = e.clientX; + } + @action + onPointerUp = (e: PointerEvent) => { + if (Math.abs(e.clientX - this._downsize) < 4) { + if (this.flyoutWidth < 5) this.flyoutWidth = 250; + else this.flyoutWidth = 0; + } + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } + @computed + get mainContent() { + let addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { + if (doc.dockingConfig) { + this.openWorkspace(doc); + } else { + // CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); + } + } + let flyout = addDocTab(doc, undefined, "onRight")} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} + ContentScaling={returnOne} + PanelWidth={this.getPWidth} + PanelHeight={this.getPHeight} + renderDepth={0} + selectOnLoad={false} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + zoomToScale={emptyFunction} + getScale={returnOne}> + ; + return
    +
    + +
    +
    + {flyout} +
    + {this.dockingContent} +
    ; + } + selected = (tool: InkTool) => { if (!InkingControl.Instance || InkingControl.Instance.selectedTool === InkTool.None) return { display: "none" }; if (InkingControl.Instance.selectedTool === tool) { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a97fd1e69..ad5472a4a 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -456,8 +456,14 @@ export class CollectionDockingView extends React.Component + + {({ measureRef }) => +
    + + } + ); } -- cgit v1.2.3-70-g09d2 From 16822d88f981e7a86729d942a92b6ad8590680e4 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 1 Jul 2019 22:32:44 -0400 Subject: fixed title template view issue --- src/client/views/TemplateMenu.tsx | 8 ++++++++ src/client/views/Templates.tsx | 4 ++-- src/client/views/collections/CollectionDockingView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 5 +++++ 4 files changed, 16 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index d63f45e5a..1b32f0ddd 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -64,6 +64,13 @@ export class TemplateMenu extends React.Component { } } + @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)); + } + @action componentWillReceiveProps(nextProps: TemplateMenuProps) { // this._templates = nextProps.templates; @@ -84,6 +91,7 @@ export class TemplateMenu extends React.Component {
    this.toggleTemplateActivity()}>+
      {templateMenu} +
    ); diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 5bb8d454a..4843a70a6 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -50,7 +50,7 @@ export namespace Templates { export const Title = new Template("Title", TemplatePosition.InnerTop, `
    - {props.DataDoc.title} + {props.Document.title}
    {layout}
    @@ -58,7 +58,7 @@ export namespace Templates {
    ` ); export const Header = new Template("Header", TemplatePosition.InnerTop, - `< div style = "display:flex; flex-direction:column; height:100%;" > + `
    diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index ad5472a4a..8724216f5 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -269,7 +269,7 @@ export class CollectionDockingView extends React.Component(Docu } this.templates = this.templates; } + @action + clearTemplates = () => { + this.templates.length = 0; + this.templates = this.templates; + } @undoBatch @action -- cgit v1.2.3-70-g09d2 From 608bb47ec25272053ececb7467f6ddb1d2362fb5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 1 Jul 2019 23:35:55 -0400 Subject: from previous. --- src/client/views/MainView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index dd56cdd55..787e240b9 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -289,7 +289,7 @@ export class MainView extends React.Component { if (doc.dockingConfig) { this.openWorkspace(doc); } else { - // CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); + CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); } } let flyout = Date: Mon, 1 Jul 2019 23:53:02 -0400 Subject: fixed caption placement. --- src/client/views/Main.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 59c70c674..08f5cb8a5 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -24,6 +24,7 @@ div { .jsx-parser { width: 100%; + height:100%; pointer-events: none; border-radius: inherit; } -- cgit v1.2.3-70-g09d2 From 4d31e1038ece3fdf9565f226486aeff0e36bb48d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 2 Jul 2019 01:03:01 -0400 Subject: keyhandler and scraping --- src/client/views/GlobalKeyHandler.ts | 2 +- src/scraping/acm/index.js | 90 ++++++++++++++++++++++++++++-------- src/scraping/acm/results.txt | 47 +++++++++++++------ 3 files changed, 106 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 9a0c3a6b1..574e43ba3 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -100,7 +100,7 @@ export default class KeyManager { MainView.Instance.mainFreeform && CollectionDockingView.Instance.AddRightSplit(MainView.Instance.mainFreeform, undefined); break; case "arrowleft": - MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform) + MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform); break; case "f": MainView.Instance.isSearchVisible = !MainView.Instance.isSearchVisible; diff --git a/src/scraping/acm/index.js b/src/scraping/acm/index.js index 39938ecca..be844da31 100644 --- a/src/scraping/acm/index.js +++ b/src/scraping/acm/index.js @@ -7,9 +7,13 @@ const { writeFile } = require('fs'); +const target_source = './citations.txt'; +const target_browser = 'chrome'; +const target_dist = './results.txt'; + const driver_pause = 500; // milliseconds const sample_line_char_max = 100; // characters -const target_browser = 'chrome'; + const tab_map = { abstract: "11", authors: "14", @@ -22,16 +26,29 @@ const tab_map = { table_of_contents: "21" }; +String.prototype.removeAll = function (replacements, trim = true) { + let result = this; + for (let expression of replacements) { + result = result.replace(expression, ""); + } + return trim ? result.trim() : result; +}; + +String.prototype.remove = function (replacement, trim = true) { + let result = this.replace(replacement, ""); + return trim ? result.trim() : result; +}; + +Object.prototype.first = function () { + return this[Object.keys(this)[0]]; +}; + // GENERAL UTILITY FUNCTIONS function log_read(content) { process.stdout.write("reading " + content + "..."); } -function first_value(object) { - return object[Object.keys(object)[0]]; -} - function log_snippet(result, quotes = true) { let snippet = "failed to create snippet"; switch (typeof result) { @@ -53,11 +70,11 @@ function log_snippet(result, quotes = true) { case "string": return res.substring(0, sample_line_char_max / result.length); case "object": - return first_value(res); + return res.first(); } }).join(', '); } else { - snippet = first_value(result); + snippet = result.first(); } } console.log(snippet); @@ -130,9 +147,40 @@ async function read_authors() { async function read_publication() { let publciation_elements = (await text_of("source-body")).split("\n"); let publication_module = {}; + + let extract = (regex, target, index = 1) => regex.exec(target)[index]; + for (let element of publciation_elements) { + + let location = /Volume (\d+) Issue (\d+), ([\w.\d]+)/g; + let pages = /(\d+)-(\d+)/g; + let publication_date = /(\d{4}-\d{2}-\d{2})/g; + let publisher = /Publisher (.*)/g; + let issn = /ISSN: (\d{4}-\d{4})/g; + let eissn = /EISSN: ([\dA-Z]{4}-[\dA-Z]{4})/g; + let doi = /doi>([\.\d\/A-Z]+)/g; + if (element.startsWith("Title")) { - publication_module.title = element.substring(6); + publication_module.name = element.substring(6).removeAll(["table of contents", "archive", /\w+ Homepage/]); + } else if (element.startsWith("Volume")) { + let match = location.exec(element); + publication_module.volume = parseInt(match[1]); + publication_module.issue = parseInt(match[2]); + publication_module.month = match[3]; + } else if (element.startsWith("Pages")) { + let match = pages.exec(element); + publication_module.page_start = parseInt(match[1]); + publication_module.page_end = parseInt(match[2]); + } else if (element.startsWith("Publication Date ")) { + publication_module.publication_date = extract(publication_date, element); + } else if (element.startsWith("Publisher ")) { + publication_module.publisher = extract(publisher, element); + } else if (element.startsWith("ISSN: ")) { + publication_module.issn = extract(issn, element); + if (element.includes("EISSN: ")) { + publication_module.eissn = extract(eissn, element); + } + publication_module.doi = extract(doi, element); } } return publication_module; @@ -153,8 +201,8 @@ function parse_authors(metadata) { while (attr[char] != " ") { char--; } - let key = attr.substring(0, char).toLowerCase().replace(/ /g, "_").replace(/[\(\)]/g, ""); - let value = parseFloat(attr.substring(char + 1).replace(/,/g, "")); + let key = attr.substring(0, char).toLowerCase().replace(/ /g, "_").remove(/[\(\)]/g); + let value = parseFloat(attr.substring(char + 1).remove(/,/g)); author[key] = value; } return author; @@ -165,7 +213,7 @@ function write_results() { let output = ""; results.forEach(res => output += (JSON.stringify(res, null, 4) + "\n")); - writeFile("./results.txt", output, function errorHandler(exception) { + writeFile(target_dist, output, function errorHandler(exception) { console.log(exception || "scraped references successfully written as JSON to ./results.txt"); }); } @@ -176,7 +224,7 @@ async function scrape_targets(error, data) { return; } - let references = data.split("\n").map(entry => entry.replace("\r", "")).filter(line => line.match(/\d+/g)); + let references = data.split("\n").map(entry => entry.removeAll(["\r"])).filter(line => line.match(/\d+/g)); let quota = references.length; log_snippet(`found ${quota} references to scrape`, false); @@ -185,6 +233,7 @@ async function scrape_targets(error, data) { for (let i = 0; i < quota; i++) { try { result = {}; + let target; let id = references[i]; let url = `https://dl.acm.org/citation.cfm?id=${id}`; @@ -194,13 +243,18 @@ async function scrape_targets(error, data) { logged_assign("url", url); logged_assign("title", await text_of('//*[@id="divmain"]/div/h1')); - logged_assign("abstract", (await text_of_all("abstract-body")).join(" ")); - await click_on_acm_tab("authors"); - logged_assign("authors", (await read_authors()).map(parse_authors)); + target = "abstract"; + await click_on_acm_tab(target); + logged_assign(target, (await text_of_all("abstract-body")).join(" ")); + + target = "authors"; + await click_on_acm_tab(target); + logged_assign(target, (await read_authors()).map(parse_authors)); - await click_on_acm_tab("publication"); - logged_assign("publication", await read_publication()); + target = "publication"; + await click_on_acm_tab(target); + logged_assign(target, await read_publication()); } catch (e) { console.log(e); await driver.quit(); @@ -220,6 +274,6 @@ let result = {}; log_read("target references"); -readFile("./citations.txt", { +readFile(target_source, { encoding: "utf8" }, scrape_targets); \ No newline at end of file diff --git a/src/scraping/acm/results.txt b/src/scraping/acm/results.txt index ba66d61a7..a15da8b10 100644 --- a/src/scraping/acm/results.txt +++ b/src/scraping/acm/results.txt @@ -10,10 +10,10 @@ "publication_count": 1, "citation_count": 179, "available_for_download": 1, - "downloads_6_weeks": 123, - "downloads_12_months": 922, - "downloads_cumulative": 9793, - "average_downloads_per_article": 9793, + "downloads_6_weeks": 124, + "downloads_12_months": 923, + "downloads_cumulative": 9794, + "average_downloads_per_article": 9794, "average_citations_per_article": 179 }, { @@ -23,10 +23,10 @@ "publication_count": 5, "citation_count": 196, "available_for_download": 1, - "downloads_6_weeks": 123, - "downloads_12_months": 922, - "downloads_cumulative": 9793, - "average_downloads_per_article": 9793, + "downloads_6_weeks": 124, + "downloads_12_months": 923, + "downloads_cumulative": 9794, + "average_downloads_per_article": 9794, "average_citations_per_article": 39.2 }, { @@ -36,15 +36,25 @@ "publication_count": 2, "citation_count": 188, "available_for_download": 2, - "downloads_6_weeks": 123, - "downloads_12_months": 927, - "downloads_cumulative": 10024, - "average_downloads_per_article": 5012, + "downloads_6_weeks": 124, + "downloads_12_months": 928, + "downloads_cumulative": 10025, + "average_downloads_per_article": 5012.5, "average_citations_per_article": 94 } ], "publication": { - "title": "Journal of the ACM (JACM) JACM Homepage table of contents archive" + "name": "Journal of the ACM (JACM)", + "volume": 7, + "issue": 4, + "month": "Oct.", + "page_start": 326, + "page_end": 329, + "publication_date": "1960-10-01", + "publisher": "ACM New York, NY, USA", + "issn": "0004-5411", + "eissn": "1557-735X", + "doi": "10.1145/321043.321046" } } { @@ -67,6 +77,15 @@ } ], "publication": { - "title": "IEEE Transactions on Software Engineering table of contents archive" + "name": "IEEE Transactions on Software Engineering", + "volume": 1, + "issue": 1, + "month": "March", + "page_start": 384, + "page_end": 389, + "publication_date": "1975-03-01", + "publisher": "IEEE Press Piscataway, NJ, USA", + "issn": "0098-5589", + "doi": "10.1109/TSE.1975.6312869" } } -- cgit v1.2.3-70-g09d2 From 49cf949250fb9b01a8457c2c3dee60b19f60c036 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 2 Jul 2019 11:16:00 -0400 Subject: scraping tweaks from last night --- src/scraping/acm/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/scraping/acm/index.js b/src/scraping/acm/index.js index be844da31..51781dba8 100644 --- a/src/scraping/acm/index.js +++ b/src/scraping/acm/index.js @@ -107,13 +107,13 @@ async function text_of(ref) { return await element.getText(); } -async function text_of_all(ref) { +async function text_of_all(ref, delimiter = undefined) { let elements = await locate(ref, true); let results = []; for (let element of elements) { results.push(await element.getText()); } - return results; + return delimiter ? results.join(delimiter) : results; } async function logged_assign(key, value) { @@ -141,7 +141,7 @@ async function read_authors() { i++; } - return all_authors; + return all_authors.map(parse_author); } async function read_publication() { @@ -162,12 +162,12 @@ async function read_publication() { if (element.startsWith("Title")) { publication_module.name = element.substring(6).removeAll(["table of contents", "archive", /\w+ Homepage/]); - } else if (element.startsWith("Volume")) { + } else if (element.startsWith("Volume ")) { let match = location.exec(element); publication_module.volume = parseInt(match[1]); publication_module.issue = parseInt(match[2]); publication_module.month = match[3]; - } else if (element.startsWith("Pages")) { + } else if (element.startsWith("Pages ")) { let match = pages.exec(element); publication_module.page_start = parseInt(match[1]); publication_module.page_end = parseInt(match[2]); @@ -188,7 +188,7 @@ async function read_publication() { // JSON / DASH CONVERSION AND EXPORT -function parse_authors(metadata) { +function parse_author(metadata) { let publicationYears = metadata[1].substring(18).split("-"); author = { name: metadata[0], @@ -246,11 +246,11 @@ async function scrape_targets(error, data) { target = "abstract"; await click_on_acm_tab(target); - logged_assign(target, (await text_of_all("abstract-body")).join(" ")); + logged_assign(target, await text_of_all("abstract-body", " ")); target = "authors"; await click_on_acm_tab(target); - logged_assign(target, (await read_authors()).map(parse_authors)); + logged_assign(target, await read_authors()); target = "publication"; await click_on_acm_tab(target); -- cgit v1.2.3-70-g09d2 From b3bdb02cf0e0f9d9fe00bb2b7e54f5e81d6abf47 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 2 Jul 2019 13:17:00 -0400 Subject: pdf fix --- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 ++ src/client/views/pdf/PDFViewer.tsx | 37 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b1aba10bf..169dbe540 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -195,6 +195,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // if (!this.props.active()) { // return; // } + if (this.props.Document.type === "pdf") { + return; + } let childSelected = this.childDocs.some(doc => { var dv = DocumentManager.Instance.getDocumentView(doc); return dv && SelectionManager.IsSelected(dv) ? true : false; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 35bf1c4d7..23a4713d4 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -540,6 +540,43 @@ export class Viewer extends React.Component { console.log("rendered"); this._rendered = true; }); +<<<<<<< Updated upstream +======= + let options = { + bar: this._searchCont.current, + toggleButton: this._searchToggle.current, + findField: this._findField.current, + highlightAllCheckbox: this._highlightAll.current, + caseSensitiveCheckbox: this._caseSensitivity.current, + entireWordCheckbox: this._entireWord.current, + findMsg: this._findMsg.current, + findResultsCount: this._findResults.current, + findPreviousButton: this._previousButton.current, + findNextButton: this._nextButton.current, + } + let findBar = new PDFFindBar.PDFFindBar(options, this._eventBus); + this._eventBus.on("find", (evt: any) => { + // this._pdfFindController.executeCommand('find', { + // query: "the", + // phraseSearch: true, + // caseSensitive: false, + // highlightAll: true, + // findPrevious: undefined + // }); + this._pdfFindController.executeCommand('find' + evt.type, { + query: evt.query, + phraseSearch: evt.phraseSearch, + caseSensitive: evt.caseSensitive, + entireWord: evt.entireWord, + highlightAll: evt.highlightAll, + findPrevious: evt.findPrevious + }); + }); + this._eventBus.on("updatefindcontrolstate", (evt: any) => { + // console.log("hello"); + findBar.updateUIState(evt.state, evt.previous, evt.matchesCount); + }) +>>>>>>> Stashed changes pdfViewer.setDocument(this.props.pdf); this._pdfFindController = new PDFJSViewer.PDFFindController(pdfViewer); // this._pdfFindController._linkService = pdfLinkService; -- cgit v1.2.3-70-g09d2 From b137eab7391be6f610081f67e68a9e7572f2ae14 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 2 Jul 2019 13:54:35 -0400 Subject: zoom --- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 37 ------------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index e88839edd..9718c1406 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -114,7 +114,7 @@ class RegionAnnotation extends React.Component { if (e.button === 0) { let targetDoc = Cast(this.props.document.target, Doc, null); if (targetDoc) { - DocumentManager.Instance.jumpToDocument(targetDoc, true); + DocumentManager.Instance.jumpToDocument(targetDoc, false); } } if (e.button === 2) { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 23a4713d4..35bf1c4d7 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -540,43 +540,6 @@ export class Viewer extends React.Component { console.log("rendered"); this._rendered = true; }); -<<<<<<< Updated upstream -======= - let options = { - bar: this._searchCont.current, - toggleButton: this._searchToggle.current, - findField: this._findField.current, - highlightAllCheckbox: this._highlightAll.current, - caseSensitiveCheckbox: this._caseSensitivity.current, - entireWordCheckbox: this._entireWord.current, - findMsg: this._findMsg.current, - findResultsCount: this._findResults.current, - findPreviousButton: this._previousButton.current, - findNextButton: this._nextButton.current, - } - let findBar = new PDFFindBar.PDFFindBar(options, this._eventBus); - this._eventBus.on("find", (evt: any) => { - // this._pdfFindController.executeCommand('find', { - // query: "the", - // phraseSearch: true, - // caseSensitive: false, - // highlightAll: true, - // findPrevious: undefined - // }); - this._pdfFindController.executeCommand('find' + evt.type, { - query: evt.query, - phraseSearch: evt.phraseSearch, - caseSensitive: evt.caseSensitive, - entireWord: evt.entireWord, - highlightAll: evt.highlightAll, - findPrevious: evt.findPrevious - }); - }); - this._eventBus.on("updatefindcontrolstate", (evt: any) => { - // console.log("hello"); - findBar.updateUIState(evt.state, evt.previous, evt.matchesCount); - }) ->>>>>>> Stashed changes pdfViewer.setDocument(this.props.pdf); this._pdfFindController = new PDFJSViewer.PDFFindController(pdfViewer); // this._pdfFindController._linkService = pdfLinkService; -- cgit v1.2.3-70-g09d2 From 9334c8d12df76df60eedcc093b986f42d1f7f67d Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 2 Jul 2019 14:03:36 -0400 Subject: Refactored some search stuff and fixed treeview flyout min X --- src/client/util/SearchUtil.ts | 28 ++++++++++++++++------ src/client/views/MainView.tsx | 2 +- .../views/collections/ParentDocumentSelector.tsx | 4 ++-- src/client/views/search/SearchBox.tsx | 15 +----------- src/client/views/search/SearchItem.tsx | 4 ++-- src/server/Search.ts | 8 +++---- 6 files changed, 31 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 27d27a3b8..ac2eb72e7 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -4,27 +4,41 @@ import { Doc } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; export namespace SearchUtil { - export function Search(query: string, returnDocs: true): Promise; - export function Search(query: string, returnDocs: false): Promise; + export interface IdSearchResult { + ids: string[]; + numFound: number; + } + + export interface DocSearchResult { + docs: Doc[]; + numFound: number; + } + + export function Search(query: string, returnDocs: true): Promise; + export function Search(query: string, returnDocs: false): Promise; export async function Search(query: string, returnDocs: boolean) { - const ids = JSON.parse(await rp.get(DocServer.prepend("/search"), { + const result: IdSearchResult = JSON.parse(await rp.get(DocServer.prepend("/search"), { qs: { query } })); if (!returnDocs) { - return ids; + return result; } + const { ids, numFound } = result; const docMap = await DocServer.GetRefFields(ids); - return ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc); + const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc); + return { docs, numFound }; } export async function GetAliasesOfDocument(doc: Doc): Promise { const proto = await Doc.GetT(doc, "proto", Doc, true); const protoId = (proto || doc)[Id]; - return Search(`proto_i:"${protoId}"`, true); + const result = await Search(`proto_i:"${protoId}"`, true); + return result.docs; // return Search(`{!join from=id to=proto_i}id:${protoId}`, true); } export async function GetViewsOfDocument(doc: Doc): Promise { - return Search(`proto_i:"${doc[Id]}"`, true); + const results = await Search(`proto_i:"${doc[Id]}"`, true); + return results.docs; } } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 787e240b9..04ecc47cf 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -272,7 +272,7 @@ export class MainView extends React.Component { } @action onPointerMove = (e: PointerEvent) => { - this.flyoutWidth = e.clientX; + this.flyoutWidth = Math.max(e.clientX, 0); } @action onPointerUp = (e: PointerEvent) => { diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index b29a30069..c0f489cd8 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -23,9 +23,9 @@ export class SelectorContextMenu extends React.Component { async fetchDocuments() { let aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document)).filter(doc => doc !== this.props.Document); - const docs = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true); + const { docs } = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true); const map: Map = new Map; - const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true))); + const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true).then(result => result.docs))); allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); docs.forEach(doc => map.delete(doc)); runInAction(() => { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index a2556133b..1f6835c26 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -102,20 +102,7 @@ export class SearchBox extends React.Component { @action getResults = async (query: string) => { - let response = await rp.get(DocServer.prepend('/search'), { - qs: { - query - } - }); - let res: string[] = JSON.parse(response); - const fields = await DocServer.GetRefFields(res); - const docs: Doc[] = []; - for (const id of res) { - const field = fields[id]; - if (field instanceof Doc) { - docs.push(field); - } - } + const { docs } = await SearchUtil.Search(query, true); return FilterBox.Instance.filterDocsByType(docs); } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index f8a0c7b16..5a7bdd24d 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -48,9 +48,9 @@ export class SelectorContextMenu extends React.Component { async fetchDocuments() { let aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc); - const docs = await SearchUtil.Search(`data_l:"${this.props.doc[Id]}"`, true); + const { docs } = await SearchUtil.Search(`data_l:"${this.props.doc[Id]}"`, true); const map: Map = new Map; - const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true))); + const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true).then(result => result.docs))); allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); docs.forEach(doc => map.delete(doc)); runInAction(() => { diff --git a/src/server/Search.ts b/src/server/Search.ts index 2db2b242c..e2a74b737 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -27,11 +27,11 @@ export class Search { start: start } })); - const fields = searchResults.response.docs; - const ids = fields.map((field: any) => field.id); - return ids; + const { docs, numFound } = searchResults.response; + const ids = docs.map((field: any) => field.id); + return { ids, numFound }; } catch { - return []; + return { ids: [], numFound: -1 }; } } -- cgit v1.2.3-70-g09d2 From 9b6a8b6685e1bb98960f650997663e5bb8501c4a Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 2 Jul 2019 15:17:52 -0400 Subject: made color selection undoable and display color updates based on first item in selected documents --- src/client/util/SelectionManager.ts | 14 ++++++++++++-- src/client/views/GlobalKeyHandler.ts | 1 + src/client/views/InkingControl.tsx | 33 ++++++++++++++++++++++++++++----- src/client/views/MainView.tsx | 6 +++--- 4 files changed, 44 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 3bc71ad42..41e6ec786 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,8 +1,9 @@ import { observable, action, runInAction, IReactionDisposer, reaction, autorun } from "mobx"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, Opt } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { NumCast } from "../../new_fields/Types"; +import { NumCast, StrCast } from "../../new_fields/Types"; +import { InkingControl } from "../views/InkingControl"; export namespace SelectionManager { @@ -11,6 +12,7 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; + @action SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it @@ -41,6 +43,14 @@ export namespace SelectionManager { } const manager = new Manager(); + reaction(() => manager.SelectedDocuments, sel => { + let firstView = sel[0]; + let doc = firstView.props.Document; + let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); + let targetColor = StrCast(targetDoc.backgroundColor); + targetColor = targetColor.length === 0 ? "#FFFFFFFF" : targetColor; + InkingControl.Instance.updateSelectedColor(targetColor); + }); export function DeselectDoc(docView: DocumentView): void { manager.DeselectDoc(docView); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 574e43ba3..fb4a107ad 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -65,6 +65,7 @@ export default class KeyManager { SelectionManager.DeselectAll(); } } + MainView.Instance.toggleColorPicker(true); break; } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index ec228ce98..0461d7299 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -1,4 +1,4 @@ -import { observable, action, computed } from "mobx"; +import { observable, action, computed, runInAction } from "mobx"; import { ColorResult } from 'react-color'; import React = require("react"); import { observer } from "mobx-react"; @@ -8,6 +8,8 @@ import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-s 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"; library.add(faPen, faHighlighter, faEraser, faBan); @@ -36,11 +38,27 @@ export class InkingControl extends React.Component { return number.toString(16).toUpperCase(); } - @action - switchColor = (color: ColorResult): void => { + @undoBatch + switchColor = action((color: ColorResult): void => { this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff"); - if (InkingControl.Instance.selectedTool === InkTool.None) SelectionManager.SelectedDocuments().forEach(doc => (doc.props.Document.isTemplate ? doc.props.Document : Doc.GetProto(doc.props.Document)).backgroundColor = this._selectedColor); - } + if (InkingControl.Instance.selectedTool === InkTool.None) { + let selected = SelectionManager.SelectedDocuments(); + let oldColors = selected.map(view => { + let targetDoc = view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); + let oldColor = StrCast(targetDoc.backgroundColor); + targetDoc.backgroundColor = this._selectedColor; + return { + target: targetDoc, + previous: oldColor + }; + }); + let captured = this._selectedColor; + UndoManager.AddEvent({ + undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous), + redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured) + }); + } + }); @action switchWidth = (width: string): void => { @@ -57,6 +75,11 @@ export class InkingControl extends React.Component { return this._selectedColor; } + @action + updateSelectedColor(value: string) { + this._selectedColor = value; + } + @computed get selectedWidth() { return this._selectedWidth; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 787e240b9..62377a3b0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -291,7 +291,7 @@ export class MainView extends React.Component { } else { CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); } - } + }; let flyout = { - this._colorPickerDisplay = !this._colorPickerDisplay; + toggleColorPicker = (close = false) => { + this._colorPickerDisplay = close ? false : !this._colorPickerDisplay; } /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ -- cgit v1.2.3-70-g09d2 From 5edfd42cea089c0691e5568c604a220d5abab301 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 2 Jul 2019 18:45:17 -0400 Subject: fixed color changing bug on empty selection --- src/client/util/SelectionManager.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 41e6ec786..3c396362e 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -44,11 +44,14 @@ export namespace SelectionManager { const manager = new Manager(); reaction(() => manager.SelectedDocuments, sel => { - let firstView = sel[0]; - let doc = firstView.props.Document; - let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); - let targetColor = StrCast(targetDoc.backgroundColor); - targetColor = targetColor.length === 0 ? "#FFFFFFFF" : targetColor; + let targetColor = "#FFFFFF"; + if (sel.length > 0) { + let firstView = sel[0]; + let doc = firstView.props.Document; + let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); + let stored = StrCast(targetDoc.backgroundColor); + stored.length > 0 && (targetColor = stored); + } InkingControl.Instance.updateSelectedColor(targetColor); }); -- cgit v1.2.3-70-g09d2 From a8664face6f19cd2373bd928c2a2b3043bb6278d Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 2 Jul 2019 19:02:51 -0400 Subject: Added arrange script to freeform views --- src/client/views/MainView.tsx | 4 +- src/client/views/OverlayView.tsx | 46 ++++++++++++++++++++++ src/client/views/ScriptBox.tsx | 36 +++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 41 ++++++++++++++++++- .../views/nodes/CollectionFreeFormDocumentView.tsx | 12 ++++-- src/client/views/nodes/KeyValueBox.tsx | 12 ++++-- 6 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 src/client/views/OverlayView.tsx create mode 100644 src/client/views/ScriptBox.tsx (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 04ecc47cf..cc39dc04c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -38,6 +38,7 @@ import PDFMenu from './pdf/PDFMenu'; import { InkTool } from '../../new_fields/InkField'; import _ from "lodash"; import KeyManager from './GlobalKeyHandler'; +import { OverlayView } from './OverlayView'; @observer export class MainView extends React.Component { @@ -291,7 +292,7 @@ export class MainView extends React.Component { } else { CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); } - } + }; let flyout = +
    ); } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx new file mode 100644 index 000000000..72f1068ce --- /dev/null +++ b/src/client/views/OverlayView.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import { observer } from "mobx-react"; +import { observable, action } from "mobx"; + +export type OverlayDisposer = () => void; + +export type OverlayElementOptions = { + x: number; + y: number; + width?: number; + height?: number; +}; + +@observer +export class OverlayView extends React.Component { + public static Instance: OverlayView; + @observable.shallow + private _elements: { ele: JSX.Element, options: OverlayElementOptions }[] = []; + + constructor(props: any) { + super(props); + if (!OverlayView.Instance) { + OverlayView.Instance = this; + } + } + + @action + addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer { + const eleWithPosition = { ele, options }; + this._elements.push(eleWithPosition); + return action(() => { + const index = this._elements.indexOf(eleWithPosition); + if (index !== -1) this._elements.splice(index, 1); + }); + } + + render() { + return ( +
    + {this._elements.map(({ ele, options: { x, y, width, height } }) => ( +
    {ele}
    + ))} +
    + ); + } +} \ No newline at end of file diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx new file mode 100644 index 000000000..aea9d52a4 --- /dev/null +++ b/src/client/views/ScriptBox.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { observer } from "mobx-react"; +import { observable, action } from "mobx"; + +export interface ScriptBoxProps { + onSave: (text: string, onError: (error: string) => void) => void; + onCancel?: () => void; +} + +@observer +export class ScriptBox extends React.Component { + @observable + private _scriptText: string = ""; + + @action + onChange = (e: React.ChangeEvent) => { + this._scriptText = e.target.value; + } + + @action + onError = (error: string) => { + console.log(error); + } + + render() { + return ( +
    +
    + + +
    + +
    + ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 169dbe540..23c0fdd0d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -27,6 +27,10 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); +import { ScriptField } from "../../../../new_fields/ScriptField"; +import { OverlayView } from "../../OverlayView"; +import { ScriptBox } from "../../ScriptBox"; +import { CompileScript } from "../../../util/Scripting"; export const panZoomSchema = createSchema({ @@ -388,6 +392,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } + getCalculatedPositions(doc: Doc, index: number, collection: Doc): { x?: number, y?: number, width?: number, height?: number } | undefined { + const script = Cast(this.props.Document.arrangeScript, ScriptField); + if (!script) { + return undefined; + } + const result = script.script.run({ doc, index, collection }); + if (!result.success) { + return undefined; + } + return result.result; + } + @computed.struct get views() { let curPage = FieldValue(this.Document.curPage, -1); @@ -397,7 +413,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (Math.round(page) === Math.round(curPage) || page === -1) { let minim = BoolCast(doc.isMinimized, false); if (minim === undefined || !minim) { - prev.push(); + const pos = this.getCalculatedPositions(doc, prev.length, this.Document) || {}; + prev.push(); } } return prev; @@ -438,6 +455,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } }); + ContextMenu.Instance.addItem({ + description: "Add freeform arrangement", + event: () => { + let overlayDisposer: () => void; + let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + const script = CompileScript(text, { + params: { + doc: "Doc", index: "number", collection: "Doc" + }, + requiredType: "{x: number, y: number, width?: number, height?: number}", + typecheck: false + }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join("\n")); + return; + } + this.props.Document.arrangeScript = new ScriptField(script); + overlayDisposer(); + }} />; + overlayDisposer = OverlayView.Instance.addElement(scriptingBox, { x: 100, y: 100, width: 200, height: 200 }); + } + }); } private childViews = () => [ diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 858959d81..8556817cc 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -9,6 +9,10 @@ import "./DocumentView.scss"; import React = require("react"); export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { + x?: number; + y?: number; + width?: number; + height?: number; } const schema = createSchema({ @@ -23,13 +27,13 @@ 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 X() { return FieldValue(this.Document.x, 0); } - @computed get Y() { return FieldValue(this.Document.y, 0); } + @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 nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); } @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); } - @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.width, 0); } - @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.height, 0); } set width(w: number) { this.Document.width = w; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 0e798d291..9407d742c 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -9,7 +9,7 @@ import { KeyValuePair } from "./KeyValuePair"; import React = require("react"); import { NumCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types"; import { Doc, Field, FieldResult } from "../../../new_fields/Doc"; -import { ComputedField } from "../../../new_fields/ScriptField"; +import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; import { SetupDrag } from "../../util/DragManager"; import { Docs } from "../../documents/Documents"; import { RawDataOperationParameters } from "../../northstar/model/idea/idea"; @@ -50,7 +50,7 @@ export class KeyValueBox extends React.Component { let eq = value.startsWith("="); let target = eq ? doc : Doc.GetProto(doc); value = eq ? value.substr(1) : value; - let dubEq = value.startsWith(":="); + let dubEq = value.startsWith(":=") ? 1 : value.startsWith(";=") ? 2 : 0; value = dubEq ? value.substr(2) : value; let options: ScriptOptions = { addReturn: true, params: { this: "Doc" } }; if (dubEq) options.typecheck = false; @@ -58,8 +58,12 @@ export class KeyValueBox extends React.Component { if (!script.compiled) { return false; } - let field = new ComputedField(script); - if (!dubEq) { + let field: Field; + if (dubEq === 1) { + field = new ComputedField(script); + } else if (dubEq === 2) { + field = new ScriptField(script); + } else { let res = script.run({ this: target }); if (!res.success) return false; field = res.result; -- cgit v1.2.3-70-g09d2 From ee7572ae3d5715ddbce57db3940204a3d0a3d826 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 2 Jul 2019 19:03:01 -0400 Subject: fixed collapsing of tree view items on resize. --- src/client/views/MainView.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ab9fe0118..9dccb883e 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -284,7 +284,7 @@ export class MainView extends React.Component { document.removeEventListener("pointerup", this.onPointerUp); } @computed - get mainContent() { + get flyout() { let addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { if (doc.dockingConfig) { this.openWorkspace(doc); @@ -292,7 +292,7 @@ export class MainView extends React.Component { CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); } }; - let flyout = ; + } + @computed + get mainContent() { return
    - {flyout} + {this.flyout}
    {this.dockingContent}
    ; -- cgit v1.2.3-70-g09d2 From d059c64a7b3ce424b8c41c485c7c58c0d4bfbae8 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 2 Jul 2019 21:42:08 -0400 Subject: fixed doc decorations border for documents laid out with arrangeScripts --- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 8556817cc..5171f7a04 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -52,7 +52,7 @@ export class CollectionFreeFormDocumentView extends DocComponent this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.zoom) + .scale(1 / this.contentScaling()).scale(1 / this.zoom * NumCast(this.props.Document.width, this.width) / this.width) animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { this.props.bringToFront(this.props.Document); -- cgit v1.2.3-70-g09d2 From 5e35e1895bb54568bbf8cc3991480e20edd1de83 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 2 Jul 2019 23:28:57 -0400 Subject: fixed issue with editing applied template text boxes. --- src/client/views/MainOverlayTextBox.tsx | 7 +++++-- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 3 ++- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index d31319429..7d15702a2 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -30,6 +30,7 @@ export class MainOverlayTextBox extends React.Component private _textBox: FormattedTextBox | undefined; private _tooltip?: HTMLElement; @observable public TextDoc?: Doc; + @observable public TextDataDoc?: Doc; updateTooltip = () => { this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip); @@ -43,13 +44,15 @@ export class MainOverlayTextBox extends React.Component (box?: FormattedTextBox) => { this._textBox = box; if (box) { - this.TextDoc = box.props.DataDoc; + this.TextDoc = box.props.Document; + this.TextDataDoc = box.props.DataDoc; let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined); let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); }; this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content"); } else { this.TextDoc = undefined; + this.TextDataDoc = undefined; this.setTextDoc(); } }); @@ -114,7 +117,7 @@ export class MainOverlayTextBox extends React.Component } } render() { - this.TextDoc; + this.TextDoc; this.TextDataDoc; if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { let textRect = this._textTargetDiv.getBoundingClientRect(); let s = this._textXf().Scale; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 5171f7a04..3f9270597 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -47,12 +47,13 @@ export class CollectionFreeFormDocumentView extends DocComponent this.nativeWidth > 0 ? 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 * NumCast(this.props.Document.width, this.width) / this.width) + .scale(1 / this.contentScaling()).scale(1 / this.zoom / this.scaleToOverridingWidth) animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { this.props.bringToFront(this.props.Document); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 07cd43ce3..1eeb04755 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -211,7 +211,7 @@ 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}}`; }, - field => this._editorView && !this._applyingChange && this.props.Document[this.props.fieldKey] instanceof RichTextField && + field => this._editorView && !this._applyingChange && this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); this.setupEditor(config, this.dataDoc, this.props.fieldKey); -- cgit v1.2.3-70-g09d2 From c619bad0281ea6f248c48b8d418f324f67f530dd Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 3 Jul 2019 12:00:59 -0400 Subject: added to fitToBox to treeviews. fixed some fitToBox issues. moved notifications button to library tree view. --- src/client/northstar/dash-nodes/HistogramBox.tsx | 2 +- .../dash-nodes/HistogramBoxPrimitives.tsx | 2 - src/client/views/Main.scss | 4 +- src/client/views/MainView.tsx | 84 ++++++++-------------- .../views/collections/CollectionSchemaView.tsx | 4 +- .../views/collections/CollectionTreeView.tsx | 38 +++++++++- .../views/collections/CollectionVideoView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 5 +- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/LinkEditor.tsx | 1 - src/client/views/search/SearchItem.tsx | 26 +++---- src/new_fields/Doc.ts | 12 ++++ 13 files changed, 98 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index a60eaea85..b81eafbee 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -36,7 +36,7 @@ export class HistogramBox extends React.Component { @computed public get HistogramResult(): HistogramResult { return this.HistoOp.Result as HistogramResult; } @observable public SizeConverter: SizeConverter = new SizeConverter(); - @computed get createOperationParamsCache() { trace(); return this.HistoOp.CreateOperationParameters(); } + @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); } @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; } @computed get ChartType() { return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ? diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx index 350987695..5a16b3782 100644 --- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx +++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx @@ -62,7 +62,6 @@ export class HistogramBoxPrimitives extends React.Component); } render() { - trace(); return
    {this.xaxislines} {this.yaxislines} diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 08f5cb8a5..44c1cb9fc 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -111,8 +111,8 @@ button:hover { //toolbar stuff #toolbar { position: absolute; - bottom: 62px; - left: 24px; + right: 8px; + top: 5px; .toolbar-button { display: block; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 19a04595a..1542fedef 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,44 +1,43 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faArrowDown, faArrowUp, faCheck, faPenNib, faThumbtack, faRedoAlt, faTable, faTree, faUndoAlt, faBell, faCommentAlt, faCut, faExclamation } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDown, faArrowUp, faBell, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, observable, runInAction, trace } from 'mobx'; +import { action, computed, configure, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { CirclePicker, SliderPicker, BlockPicker, TwitterPicker, SketchPicker } from 'react-color'; import "normalize.css"; import * as React from 'react'; +import { SketchPicker } from 'react-color'; import Measure from 'react-measure'; import * as request from 'request'; +import { Doc, DocListCast, Opt } from '../../new_fields/Doc'; +import { Id } from '../../new_fields/FieldSymbols'; +import { InkTool } from '../../new_fields/InkField'; +import { List } from '../../new_fields/List'; +import { listSpec } from '../../new_fields/Schema'; +import { Cast, FieldValue } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; -import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils'; -import { Docs, DocTypes } from '../documents/Documents'; -import { SetupDrag, DragManager } from '../util/DragManager'; +import { emptyFunction, returnOne, returnTrue } from '../../Utils'; +import { DocServer } from '../DocServer'; +import { Docs } from '../documents/Documents'; +import { SetupDrag } from '../util/DragManager'; +import { HistoryUtil } from '../util/History'; import { Transform } from '../util/Transform'; import { UndoManager } from '../util/UndoManager'; -import { PresentationView } from './presentationview/PresentationView'; +import { CollectionBaseView } from './collections/CollectionBaseView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { ContextMenu } from './ContextMenu'; import { DocumentDecorations } from './DocumentDecorations'; +import KeyManager from './GlobalKeyHandler'; import { InkingControl } from './InkingControl'; import "./Main.scss"; 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 { SelectionManager } from '../util/SelectionManager'; -import { FieldResult, Field, Doc, Opt, DocListCast } from '../../new_fields/Doc'; -import { Cast, FieldValue, StrCast, PromiseValue } from '../../new_fields/Types'; -import { DocServer } from '../DocServer'; -import { listSpec } from '../../new_fields/Schema'; -import { Id } from '../../new_fields/FieldSymbols'; -import { HistoryUtil } from '../util/History'; -import { CollectionBaseView } from './collections/CollectionBaseView'; -import { List } from '../../new_fields/List'; -import PDFMenu from './pdf/PDFMenu'; -import { InkTool } from '../../new_fields/InkField'; -import _ from "lodash"; -import KeyManager from './GlobalKeyHandler'; -import { OverlayView } from './OverlayView'; +import { CollectionTreeView } from './collections/CollectionTreeView'; @observer export class MainView extends React.Component { @@ -184,8 +183,6 @@ export class MainView extends React.Component { } } - @observable _notifsCol: Opt; - @action openWorkspace = async (doc: Doc, fromHistory = false) => { CurrentUserUtils.MainDocId = doc[Id]; @@ -197,18 +194,12 @@ export class MainView extends React.Component { if (col) { const l = Cast(col.data, listSpec(Doc)); if (l) { - runInAction(() => this._notifsCol = col); + runInAction(() => CollectionTreeView.NotifsCol = col); } } }, 100); } - openNotifsCol = () => { - if (this._notifsCol && CollectionDockingView.Instance) { - CollectionDockingView.Instance.AddRightSplit(this._notifsCol, undefined); - } - } - onDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); @@ -284,24 +275,25 @@ export class MainView extends React.Component { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); } + flyoutWidthFunc = () => this.flyoutWidth; + addDocTabFunc = (doc: Doc) => { + if (doc.dockingConfig) { + this.openWorkspace(doc); + } else { + CollectionDockingView.Instance.AddRightSplit(doc, undefined); + } + }; @computed get flyout() { - let addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { - if (doc.dockingConfig) { - this.openWorkspace(doc); - } else { - CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); - } - }; return addDocTab(doc, undefined, "onRight")} + addDocTab={this.addDocTabFunc} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} ContentScaling={returnOne} - PanelWidth={this.getPWidth} + PanelWidth={this.flyoutWidthFunc} PanelHeight={this.getPHeight} renderDepth={0} selectOnLoad={false} @@ -403,23 +395,9 @@ export class MainView extends React.Component { /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ @computed get miscButtons() { - const length = this._notifsCol ? DocListCast(this._notifsCol.data).length : 0; - const notifsRef = React.createRef(); - const dragNotifs = action(() => this._notifsCol!); let logoutRef = React.createRef(); return [ -
    -
    - -
    0 ? { "display": "initial" } : { "display": "none" }}> - {length} -
    -
    -
    , this.isSearchVisible ?
    : null,
    diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index b0d46953c..d06d1dab6 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -334,7 +334,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @computed get reactTable() { - trace(); let previewWidth = this.previewWidth() + 2 * this.borderWidth + this.DIVIDER_WIDTH + 1; return doc) { } render() { - trace(); return (
    this.onDrop(e, {})} onContextMenu={this.onContextMenu} ref={this.createTarget}> @@ -404,6 +402,7 @@ interface CollectionSchemaPreviewProps { Document?: Doc; DataDocument?: Doc; childDocs?: Doc[]; + fitToBox?: () => number[]; renderDepth: number; width: () => number; height: () => number; @@ -472,6 +471,7 @@ export class CollectionSchemaPreview extends React.Component { } ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); e.stopPropagation(); + e.preventDefault(); } } @@ -312,6 +314,11 @@ class TreeView extends React.Component { return ele; } + fitToBox = () => { + let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc); + let bounds = Doc.ComputeContentBounds(layoutDoc); + return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Math.min(this.props.panelHeight() / (bounds.b - bounds.y), this.props.panelWidth() / (bounds.r - bounds.x))]; + } render() { let contentElement: (JSX.Element | null) = null; let docList = Cast(this.resolvedDataDoc[this._chosenKey], listSpec(Doc)); @@ -333,6 +340,7 @@ class TreeView extends React.Component { Document={layoutDoc} DataDocument={this.resolvedDataDoc} renderDepth={this.props.renderDepth} + fitToBox={this.fitToBox} width={docWidth} height={layoutDoc[HeightSym]} getTransform={this.docTransform} @@ -456,6 +464,31 @@ export class CollectionTreeView extends CollectionSubView(Document) { outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {}); + + @observable static NotifsCol: Opt; + + openNotifsCol = () => { + if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) { + CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined); + } + } + @computed get notifsButton() { + const length = CollectionTreeView.NotifsCol ? DocListCast(CollectionTreeView.NotifsCol.data).length : 0; + const notifsRef = React.createRef(); + const dragNotifs = action(() => CollectionTreeView.NotifsCol!); + return
    +
    + +
    0 ? { "display": "initial" } : { "display": "none" }}> + {length} +
    +
    +
    ; + } + render() { 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); @@ -480,6 +513,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { TreeView.loadId = doc[Id]; Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true); }} /> + {this.props.Document.excludeFromLibrary ? this.notifsButton : (null)}
      { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index c1a6ca44e..1984965ba 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -122,7 +122,6 @@ export class CollectionVideoView extends React.Component { } render() { - trace(); return ( {this.subView} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 23c0fdd0d..cb7be3f28 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -54,9 +54,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed get nativeHeight() { return this.Document.nativeHeight || 0; } public get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; } private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } - private panX = () => this.props.fitToBox ? this.props.fitToBox[0] : this.Document.panX || 0; - private panY = () => this.props.fitToBox ? this.props.fitToBox[1] : this.Document.panY || 0; - private zoomScaling = () => this.props.fitToBox ? this.props.fitToBox[2] : this.Document.scale || 1; + private panX = () => this.props.fitToBox ? this.props.fitToBox()[0] : this.Document.panX || 0; + private panY = () => this.props.fitToBox ? this.props.fitToBox()[1] : this.Document.panY || 0; + private zoomScaling = () => this.props.fitToBox ? this.props.fitToBox()[2] : this.Document.scale || 1; private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0; // shift so pan position is at center of window for non-overlay collections private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform()); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3673cb0e2..8b75d0d25 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -71,7 +71,7 @@ export interface DocumentViewProps { ContainingCollectionView: Opt; Document: Doc; DataDoc?: Doc; - fitToBox?: number[]; + fitToBox?: () => number[]; addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; removeDocument?: (doc: Doc) => boolean; moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; @@ -573,8 +573,7 @@ export class DocumentView extends DocComponent(Docu @computed get nativeWidth() { return this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.Document.nativeHeight || 0; } @computed get contents() { - return ( - ); + return (); } render() { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 3f5a2e744..c5fc6c65a 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -31,7 +31,7 @@ export interface FieldViewProps { fieldKey: string; fieldExt: string; leaveNativeSize?: boolean; - fitToBox?: number[]; + fitToBox?: () => number[]; ContainingCollectionView: Opt; Document: Doc; DataDoc?: Doc; diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx index 22da732cf..e6cc50620 100644 --- a/src/client/views/nodes/LinkEditor.tsx +++ b/src/client/views/nodes/LinkEditor.tsx @@ -271,7 +271,6 @@ export class LinkGroupEditor extends React.Component { ); } - trace(); return (
      diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 5a7bdd24d..129d71b3b 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -101,34 +101,23 @@ export class SearchItem extends React.Component { @observable _useIcons = true; @observable _displayDim = 50; + 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]; + } @computed public get DocumentIcon() { - let layoutresult = StrCast(this.props.doc.type); if (!this._useIcons) { - let renderDoc = this.props.doc; - let box: number[] = []; - if (layoutresult.indexOf(DocTypes.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 }); - 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 scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension()); return
      { this._useIcons = !this._useIcons; this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); })} onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} onPointerLeave={action(() => this._displayDim = 50)} > {
      ; } + let layoutresult = StrCast(this.props.doc.type); let button = layoutresult.indexOf(DocTypes.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocTypes.IMG) !== -1 ? faImage : layoutresult.indexOf(DocTypes.TEXT) !== -1 ? faStickyNote : diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 734a90731..29d35e19f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -249,6 +249,18 @@ export namespace Doc { return true; } + export function ComputeContentBounds(doc: Doc) { + let bounds = DocListCast(doc.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 }); + return bounds; + } + // // Resolves a reference to a field by returning 'doc' if o field extension is specified, // otherwise, it returns the extension document stored in doc._ext. -- cgit v1.2.3-70-g09d2 From 6c5468eee0ec59d4ddaf116e67d067b567ccb87a Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 3 Jul 2019 12:12:08 -0400 Subject: pdf next/prev annotation fixes/improvements --- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 26 ++++++++++++++++---------- 3 files changed, 18 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 83dedb71d..cc02bb282 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -149,7 +149,7 @@ export class PDFBox extends DocComponent(PdfDocumen scrollTo(y: number) { if (this._mainCont.current) { - this._mainCont.current.scrollTo({ top: y }); + this._mainCont.current.scrollTo({ top: y, behavior: "auto" }); } } diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 9718c1406..0a1661a1a 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -75,7 +75,7 @@ class RegionAnnotation extends React.Component { () => this.props.parent.Index, () => { if (this.props.parent.Index === this.props.index) { - this.props.parent.scrollTo(this.props.y - 50); + this.props.parent.scrollTo(this.props.y * scale - (NumCast(this.props.parent.props.parent.Document.pdfHeight) / 2)); } } ); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 35bf1c4d7..ad062a54d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -166,6 +166,7 @@ export class Viewer extends React.Component { } }); } + this.Index = -1; }); } ); @@ -234,11 +235,15 @@ export class Viewer extends React.Component { mainAnnoDoc.title = "Annotation on " + StrCast(this.props.parent.Document.title); mainAnnoDoc.pdfDoc = this.props.parent.Document; + let minY = Number.MAX_VALUE; this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { let annoDoc = new Doc(); if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale; - if (anno.style.top) annoDoc.y = parseInt(anno.style.top) / scale; + if (anno.style.top) { + annoDoc.y = parseInt(anno.style.top) / scale; + minY = Math.min(parseInt(anno.style.top), minY); + } if (anno.style.height) annoDoc.height = parseInt(anno.style.height) / scale; if (anno.style.width) annoDoc.width = parseInt(anno.style.width) / scale; annoDoc.page = key; @@ -251,12 +256,13 @@ export class Viewer extends React.Component { } }); - mainAnnoDoc.y = Math.max((NumCast(annoDocs[0].y) * scale) - 100, 0); + mainAnnoDoc.y = Math.max(minY, 0); mainAnnoDoc.annotations = new List(annoDocs); if (sourceDoc) { DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); } this._savedAnnotations.clear(); + this.Index = -1; return mainAnnoDoc; } @@ -562,9 +568,10 @@ export class Viewer extends React.Component { prevAnnotation = (e: React.MouseEvent) => { e.stopPropagation(); - if (this.Index > 0) { - this.Index--; - } + // if (this.Index > 0) { + // this.Index--; + // } + this.Index = Math.max(this.Index - 1, 0); } @action @@ -572,7 +579,7 @@ export class Viewer extends React.Component { e.stopPropagation(); let compiled = this._script; - if (this.Index < this._annotations.filter(anno => { + let filtered = this._annotations.filter(anno => { if (compiled && compiled.compiled) { let run = compiled.run({ this: anno }); if (run.success) { @@ -580,9 +587,8 @@ export class Viewer extends React.Component { } } return true; - }).length - 1) { - this.Index++; - } + }); + this.Index = Math.min(this.Index + 1, filtered.length - 1) } nextResult = () => { @@ -630,7 +636,7 @@ export class Viewer extends React.Component { } } return true; - }).map((anno: Doc, index: number) => this.renderAnnotation(anno, index))} + }).sort((a: Doc, b: Doc) => NumCast(a.y) - NumCast(b.y)).map((anno: Doc, index: number) => this.renderAnnotation(anno, index))}
      e.stopPropagation()} -- cgit v1.2.3-70-g09d2 From 88cec4b18b8e49f8598cab817955ca4dccb6228c Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 3 Jul 2019 13:40:32 -0400 Subject: pdf keys error --- src/client/views/pdf/PDFViewer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index ad062a54d..8af29110f 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -432,7 +432,7 @@ export class Viewer extends React.Component { } renderAnnotation = (anno: Doc, index: number): JSX.Element => { - return ; + return ; } @action @@ -636,7 +636,8 @@ export class Viewer extends React.Component { } } return true; - }).sort((a: Doc, b: Doc) => NumCast(a.y) - NumCast(b.y)).map((anno: Doc, index: number) => this.renderAnnotation(anno, index))} + }).sort((a: Doc, b: Doc) => NumCast(a.y) - NumCast(b.y)) + .map((anno: Doc, index: number) => this.renderAnnotation(anno, index))}
    e.stopPropagation()} -- cgit v1.2.3-70-g09d2 From 3cdc6f11e262e289b6070d6fa75a59fff7e59b1e Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 3 Jul 2019 13:42:20 -0400 Subject: Added state to layout scripts --- src/client/views/OverlayView.tsx | 9 +-- src/client/views/ScriptBox.scss | 17 ++++++ src/client/views/ScriptBox.tsx | 10 +++- .../collectionFreeForm/CollectionFreeFormView.tsx | 69 +++++++++++++--------- 4 files changed, 72 insertions(+), 33 deletions(-) create mode 100644 src/client/views/ScriptBox.scss (limited to 'src') diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 72f1068ce..f8fc94274 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { observer } from "mobx-react"; import { observable, action } from "mobx"; +import { Utils } from "../../Utils"; export type OverlayDisposer = () => void; @@ -15,7 +16,7 @@ export type OverlayElementOptions = { export class OverlayView extends React.Component { public static Instance: OverlayView; @observable.shallow - private _elements: { ele: JSX.Element, options: OverlayElementOptions }[] = []; + private _elements: { ele: JSX.Element, id: string, options: OverlayElementOptions }[] = []; constructor(props: any) { super(props); @@ -26,7 +27,7 @@ export class OverlayView extends React.Component { @action addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer { - const eleWithPosition = { ele, options }; + const eleWithPosition = { ele, options, id: Utils.GenerateGuid() }; this._elements.push(eleWithPosition); return action(() => { const index = this._elements.indexOf(eleWithPosition); @@ -37,8 +38,8 @@ export class OverlayView extends React.Component { render() { return (
    - {this._elements.map(({ ele, options: { x, y, width, height } }) => ( -
    {ele}
    + {this._elements.map(({ ele, options: { x, y, width, height }, id }) => ( +
    {ele}
    ))}
    ); diff --git a/src/client/views/ScriptBox.scss b/src/client/views/ScriptBox.scss new file mode 100644 index 000000000..28326624a --- /dev/null +++ b/src/client/views/ScriptBox.scss @@ -0,0 +1,17 @@ +.scriptBox-outerDiv { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +.scriptBox-toolbar { + width: 100%; +} + +.scriptBox-textArea { + width: 100%; + height: 100%; + box-sizing: border-box; + resize: none; +} \ No newline at end of file diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index aea9d52a4..fa236c2da 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -2,15 +2,23 @@ import * as React from "react"; import { observer } from "mobx-react"; import { observable, action } from "mobx"; +import "./ScriptBox.scss"; + export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; onCancel?: () => void; + initialText?: string; } @observer export class ScriptBox extends React.Component { @observable - private _scriptText: string = ""; + private _scriptText: string; + + constructor(props: ScriptBoxProps) { + super(props); + this._scriptText = props.initialText || ""; + } @action onChange = (e: React.ChangeEvent) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 23c0fdd0d..edd2fb1b0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -28,7 +28,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); import { ScriptField } from "../../../../new_fields/ScriptField"; -import { OverlayView } from "../../OverlayView"; +import { OverlayView, OverlayElementOptions } from "../../OverlayView"; import { ScriptBox } from "../../ScriptBox"; import { CompileScript } from "../../../util/Scripting"; @@ -36,7 +36,9 @@ import { CompileScript } from "../../../util/Scripting"; export const panZoomSchema = createSchema({ panX: "number", panY: "number", - scale: "number" + scale: "number", + arrangeScript: ScriptField, + arrangeInit: ScriptField, }); type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; @@ -392,28 +394,34 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } - getCalculatedPositions(doc: Doc, index: number, collection: Doc): { x?: number, y?: number, width?: number, height?: number } | undefined { - const script = Cast(this.props.Document.arrangeScript, ScriptField); - if (!script) { - return undefined; - } - const result = script.script.run({ doc, index, collection }); + getCalculatedPositions(doc: Doc, index: number, collection: Doc, script: ScriptField, state: any): { x?: number, y?: number, width?: number, height?: number, state?: any } { + const result = script.script.run({ doc, index, collection, state }); if (!result.success) { - return undefined; + return {}; } - return result.result; + return result.result === undefined ? {} : result.result; } @computed.struct get views() { let curPage = FieldValue(this.Document.curPage, -1); + const initScript = this.Document.arrangeInit; + const script = this.Document.arrangeScript; + let state: any = undefined; + if (initScript) { + const initResult = initScript.script.run(); + if (initResult.success) { + state = initResult.result; + } + } let docviews = this.childDocs.reduce((prev, doc) => { if (!(doc instanceof Doc)) return prev; var page = NumCast(doc.page, -1); if (Math.round(page) === Math.round(curPage) || page === -1) { let minim = BoolCast(doc.isMinimized, false); if (minim === undefined || !minim) { - const pos = this.getCalculatedPositions(doc, prev.length, this.Document) || {}; + const pos = script ? this.getCalculatedPositions(doc, prev.length, this.Document, script, state) : {}; + state = pos.state === undefined ? state : pos.state; prev.push(); } } @@ -458,23 +466,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContextMenu.Instance.addItem({ description: "Add freeform arrangement", event: () => { - let overlayDisposer: () => void; - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { - const script = CompileScript(text, { - params: { - doc: "Doc", index: "number", collection: "Doc" - }, - requiredType: "{x: number, y: number, width?: number, height?: number}", - typecheck: false - }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join("\n")); - return; - } - this.props.Document.arrangeScript = new ScriptField(script); - overlayDisposer(); - }} />; - overlayDisposer = OverlayView.Instance.addElement(scriptingBox, { x: 100, y: 100, width: 200, height: 200 }); + let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record, requiredType?: string) => { + let overlayDisposer: () => void; + const script = this.Document[key]; + let originalText: string | undefined = undefined; + if (script) originalText = script.script.originalScript; + let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + const script = CompileScript(text, { + params, + requiredType, + typecheck: false + }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join("\n")); + return; + } + this.Document[key] = new ScriptField(script); + overlayDisposer(); + }} />; + overlayDisposer = OverlayView.Instance.addElement(scriptingBox, options); + }; + addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, undefined, undefined); + addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state:"any" }, "{x: number, y: number, width?: number, height?: number}"); } }); } -- cgit v1.2.3-70-g09d2 From c77dfd3fcb2df644fcf2c177db7e6e376ca4c3a9 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 3 Jul 2019 14:29:53 -0400 Subject: quick and dirty arrange transition animations --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 5 ++++- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cb7be3f28..6b0cb5728 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,6 @@ import { action, computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCastAsync, HeightSym, 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"; @@ -458,6 +458,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContextMenu.Instance.addItem({ description: "Add freeform arrangement", event: () => { + const docs = DocListCast(this.Document[this.props.fieldKey]); + docs.map(d => d.transition = "transform 1s"); let overlayDisposer: () => void; let scriptingBox = overlayDisposer()} onSave={(text, onError) => { const script = CompileScript(text, { @@ -473,6 +475,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.props.Document.arrangeScript = new ScriptField(script); overlayDisposer(); + setTimeout(() => docs.map(d => d.transition = undefined), 1200); }} />; overlayDisposer = OverlayView.Instance.addElement(scriptingBox, { x: 100, y: 100, width: 200, height: 200 }); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 3f9270597..30cf74f3e 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 } from "../../../new_fields/Types"; +import { BoolCast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; @@ -86,6 +86,7 @@ export class CollectionFreeFormDocumentView extends DocComponent Date: Wed, 3 Jul 2019 14:46:44 -0400 Subject: Changed how scripting works --- src/client/documents/Documents.ts | 25 +++------------------- src/client/util/Scripting.ts | 45 +++++++++++++++++++++++++++++++-------- src/new_fields/Doc.ts | 2 ++ src/new_fields/List.ts | 5 ++++- src/new_fields/RichTextField.ts | 2 ++ src/new_fields/ScriptField.ts | 4 +++- src/new_fields/URLField.ts | 11 +++++----- 7 files changed, 56 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7d7a1f02a..7a976e7d7 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -36,6 +36,7 @@ import { UndoManager } from "../util/UndoManager"; import { RouteStore } from "../../server/RouteStore"; import { LinkManager } from "../util/LinkManager"; import { DocumentManager } from "../util/DocumentManager"; +import { Scripting } from "../util/Scripting"; var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -385,26 +386,6 @@ export namespace Docs { `); } - /* - - this template requires an additional style setting on the collectionView-cont to make the layout relative - -.collectionView-cont { - position: relative; - width: 100%; - height: 100%; } - */ - function Percentaption() { - return (` -
    -
    - {layout} -
    -
    - -
    -
    - `); - } -} \ No newline at end of file + +Scripting.addGlobal("Docs", Docs); \ No newline at end of file diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 30a05154a..3156c4f43 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -7,12 +7,7 @@ let ts = (window as any).ts; // @ts-ignore import * as typescriptlib from '!!raw-loader!./type_decls.d'; -import { Docs } from "../documents/Documents"; import { Doc, Field } from '../../new_fields/Doc'; -import { ImageField, PdfField, VideoField, AudioField } from '../../new_fields/URLField'; -import { List } from '../../new_fields/List'; -import { RichTextField } from '../../new_fields/RichTextField'; -import { ScriptField, ComputedField } from '../../new_fields/ScriptField'; export interface ScriptSucccess { success: true; @@ -38,6 +33,34 @@ export interface CompileError { errors: any[]; } +export namespace Scripting { + export function addGlobal(global: { name: string }): void; + export function addGlobal(name: string, global: any): void; + export function addGlobal(nameOrGlobal: any, global?: any) { + let n: string; + let obj: any; + if (global !== undefined && typeof nameOrGlobal === "string") { + n = nameOrGlobal; + obj = global; + } else if (nameOrGlobal && typeof nameOrGlobal.name === "string") { + n = nameOrGlobal.name; + obj = nameOrGlobal; + } else { + throw new Error("Must either register an object with a name, or give a name and an object"); + } + if (scriptingGlobals.hasOwnProperty(n)) { + throw new Error(`Global with name ${n} is already registered, choose another name`); + } + scriptingGlobals[n] = obj; + } +} + +export function scriptingGlobal(constructor: { new(...args: any[]): any }) { + Scripting.addGlobal(constructor); +} + +const scriptingGlobals: { [name: string]: any } = {}; + export type CompileResult = CompiledScript | CompileError; function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error); @@ -45,9 +68,11 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an return { compiled: false, errors: diagnostics }; } - let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField, ScriptField, ComputedField, CompileScript]; - let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)]; - let params: any[] = [Docs, ...fieldTypes]; + let paramNames = Object.keys(scriptingGlobals); + let params = paramNames.map(key => scriptingGlobals[key]); + // let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField, ScriptField, ComputedField, CompileScript]; + // let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)]; + // let params: any[] = [Docs, ...fieldTypes]; let compiledFunction = new Function(...paramNames, `return ${script}`); let { capturedVariables = {} } = options; let run = (args: { [name: string]: any } = {}): ScriptResult => { @@ -178,4 +203,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics); return Run(outputText, paramNames, diagnostics, script, options); -} \ No newline at end of file +} + +Scripting.addGlobal(CompileScript); \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 29d35e19f..c361e3032 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -8,6 +8,7 @@ import { listSpec } from "./Schema"; import { ObjectField } from "./ObjectField"; import { RefField, FieldId } from "./RefField"; import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols"; +import { scriptingGlobal } from "../client/util/Scripting"; export namespace Field { export function toScriptString(field: Field): string { @@ -55,6 +56,7 @@ export function DocListCast(field: FieldResult): Doc[] { export const WidthSym = Symbol("Width"); export const HeightSym = Symbol("Height"); +@scriptingGlobal @Deserializable("doc").withFields(["id"]) export class Doc extends RefField { constructor(id?: FieldId, forceSave?: boolean) { diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts index f1e4c4721..a2133a990 100644 --- a/src/new_fields/List.ts +++ b/src/new_fields/List.ts @@ -7,6 +7,7 @@ import { ObjectField } from "./ObjectField"; import { RefField } from "./RefField"; import { ProxyField } from "./Proxy"; import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, Copy } from "./FieldSymbols"; +import { Scripting } from "../client/util/Scripting"; const listHandlers: any = { /// Mutator methods @@ -294,4 +295,6 @@ class ListImpl extends ObjectField { } } export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; -export const List: { new (fields?: T[]): List } = ListImpl as any; \ No newline at end of file +export const List: { new (fields?: T[]): List } = ListImpl as any; + +Scripting.addGlobal("List", List); \ No newline at end of file diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 89d077a47..78a3a4067 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -2,7 +2,9 @@ import { ObjectField } from "./ObjectField"; import { serializable } from "serializr"; import { Deserializable } from "../client/util/SerializationHelper"; import { Copy, ToScriptString } from "./FieldSymbols"; +import { scriptingGlobal } from "../client/util/Scripting"; +@scriptingGlobal @Deserializable("RichTextField") export class RichTextField extends ObjectField { @serializable(true) diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts index 3d56e9374..e2994ed70 100644 --- a/src/new_fields/ScriptField.ts +++ b/src/new_fields/ScriptField.ts @@ -1,5 +1,5 @@ import { ObjectField } from "./ObjectField"; -import { CompiledScript, CompileScript } from "../client/util/Scripting"; +import { CompiledScript, CompileScript, scriptingGlobal } from "../client/util/Scripting"; import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols"; import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr"; import { Deserializable } from "../client/util/SerializationHelper"; @@ -40,6 +40,7 @@ function deserializeScript(script: ScriptField) { (script as any).script = comp; } +@scriptingGlobal @Deserializable("script", deserializeScript) export class ScriptField extends ObjectField { @serializable(object(scriptSchema)) @@ -81,6 +82,7 @@ export class ScriptField extends ObjectField { } } +@scriptingGlobal @Deserializable("computed", deserializeScript) export class ComputedField extends ScriptField { //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts index 4a2841fb6..d935a61af 100644 --- a/src/new_fields/URLField.ts +++ b/src/new_fields/URLField.ts @@ -2,6 +2,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, custom } from "serializr"; import { ObjectField } from "./ObjectField"; import { ToScriptString, Copy } from "./FieldSymbols"; +import { Scripting, scriptingGlobal } from "../client/util/Scripting"; function url() { return custom( @@ -37,8 +38,8 @@ export abstract class URLField extends ObjectField { } } -@Deserializable("audio") export class AudioField extends URLField { } -@Deserializable("image") export class ImageField extends URLField { } -@Deserializable("video") export class VideoField extends URLField { } -@Deserializable("pdf") export class PdfField extends URLField { } -@Deserializable("web") export class WebField extends URLField { } \ No newline at end of file +@scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { } +@scriptingGlobal @Deserializable("image") export class ImageField extends URLField { } +@scriptingGlobal @Deserializable("video") export class VideoField extends URLField { } +@scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } +@scriptingGlobal @Deserializable("web") export class WebField extends URLField { } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From cdac71db46488a868b67572af5351eac2fde3665 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 3 Jul 2019 17:29:31 -0400 Subject: Debug commit --- src/client/views/collections/CollectionTreeView.tsx | 2 +- src/client/views/search/FilterBox.tsx | 2 +- src/client/views/search/SearchItem.tsx | 19 ++++++++++++++++--- src/debug/Viewer.tsx | 7 +++++++ 4 files changed, 25 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ef3868df6..0cc31f031 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -236,7 +236,6 @@ 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 - ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)) }); ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" }); if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" }); @@ -246,6 +245,7 @@ class TreeView extends React.Component { } ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); } else { + ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)) }); ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); } ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 23a1b31d8..02789fb27 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -58,7 +58,7 @@ export class FilterBox extends React.Component { componentDidMount = () => { document.addEventListener("pointerdown", (e) => { if (!e.defaultPrevented && e.timeStamp !== this._pointerTime) { - SearchBox.Instance.closeSearch(); + // SearchBox.Instance.closeSearch(); } }); } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 129d71b3b..7d45f9dfb 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, returnFalse, returnOne } from "../../../Utils"; +import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils"; import { DocTypes } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { SetupDrag } from "../../util/DragManager"; @@ -21,6 +21,7 @@ import { DocumentView } from "../nodes/DocumentView"; import { SearchBox } from "./SearchBox"; import "./SearchItem.scss"; import "./SelectorContextMenu.scss"; +import { ContextMenu } from "../ContextMenu"; export interface SearchItemProps { doc: Doc; @@ -178,7 +179,7 @@ export class SearchItem extends React.Component { } @action - pointerDown = (e: React.PointerEvent) => SearchBox.Instance.openSearch(e) + pointerDown = (e: React.PointerEvent) => { e.preventDefault; e.button === 0 && SearchBox.Instance.openSearch(e); } highlightDoc = (e: React.PointerEvent) => { if (this.props.doc.type === DocTypes.LINK) { @@ -214,9 +215,21 @@ export class SearchItem extends React.Component { } } + onContextMenu = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ + description: "Copy ID", event: () => { + Utils.CopyText(this.props.doc[Id]); + } + }); + ContextMenu.Instance.displayMenu(e.clientX, e.clientY); + } + render() { return ( -
    +
    diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index b22300d0b..e6d9e031a 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -10,6 +10,13 @@ import { List } from '../new_fields/List'; import { URLField } from '../new_fields/URLField'; import { EditableView } from '../client/views/EditableView'; import { CompileScript } from '../client/util/Scripting'; +import { DateField } from '../new_fields/DateField'; +import { ScriptField } from '../new_fields/ScriptField'; + +DateField; +URLField; +ScriptField; + function applyToDoc(doc: { [index: string]: FieldResult }, key: string, scriptString: string): boolean; function applyToDoc(doc: { [index: number]: FieldResult }, key: number, scriptString: string): boolean; -- cgit v1.2.3-70-g09d2 From c75de89b3ea450d473a0d81e5744063839ae9b76 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 3 Jul 2019 17:31:46 -0400 Subject: From last --- src/debug/Viewer.tsx | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index e6d9e031a..f48eb696c 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -12,10 +12,12 @@ import { EditableView } from '../client/views/EditableView'; import { CompileScript } from '../client/util/Scripting'; import { DateField } from '../new_fields/DateField'; import { ScriptField } from '../new_fields/ScriptField'; +import CursorField from '../new_fields/CursorField'; DateField; URLField; ScriptField; +CursorField; function applyToDoc(doc: { [index: string]: FieldResult }, key: string, scriptString: string): boolean; -- cgit v1.2.3-70-g09d2 From 20e57b499d309052d4a12ef2d6a2288eec8bf04f Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 3 Jul 2019 19:05:41 -0400 Subject: Couple of fixes --- src/client/views/collections/CollectionDockingView.tsx | 3 +++ src/client/views/search/FilterBox.tsx | 2 +- src/client/views/search/SearchItem.tsx | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 8724216f5..8b6f5b6a6 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -455,6 +455,9 @@ export class CollectionDockingView extends React.Component 0) { + return
    Nested workspaces can't be rendered
    ; + } return ( {({ measureRef }) => diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 02789fb27..23a1b31d8 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -58,7 +58,7 @@ export class FilterBox extends React.Component { componentDidMount = () => { document.addEventListener("pointerdown", (e) => { if (!e.defaultPrevented && e.timeStamp !== this._pointerTime) { - // SearchBox.Instance.closeSearch(); + SearchBox.Instance.closeSearch(); } }); } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 7d45f9dfb..601f4032d 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -179,7 +179,7 @@ export class SearchItem extends React.Component { } @action - pointerDown = (e: React.PointerEvent) => { e.preventDefault; e.button === 0 && SearchBox.Instance.openSearch(e); } + pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } highlightDoc = (e: React.PointerEvent) => { if (this.props.doc.type === DocTypes.LINK) { -- cgit v1.2.3-70-g09d2 From 41733ff406b639635aaccb1cd2b03ecf1f902b17 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 3 Jul 2019 19:22:16 -0400 Subject: Added params to init script and added docs param to layout script --- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ba41b5afe..4b7d462e5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -394,8 +394,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } - getCalculatedPositions(doc: Doc, index: number, collection: Doc, script: ScriptField, state: any): { x?: number, y?: number, width?: number, height?: number, state?: any } { - const result = script.script.run({ doc, index, collection, state }); + getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } { + const result = script.script.run(params); if (!result.success) { return {}; } @@ -408,19 +408,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const initScript = this.Document.arrangeInit; const script = this.Document.arrangeScript; let state: any = undefined; + const docs = this.childDocs; if (initScript) { - const initResult = initScript.script.run(); + const initResult = initScript.script.run({ docs, collection: this.Document }); if (initResult.success) { state = initResult.result; } } - let docviews = this.childDocs.reduce((prev, doc) => { + let docviews = docs.reduce((prev, doc) => { if (!(doc instanceof Doc)) return prev; var page = NumCast(doc.page, -1); if (Math.round(page) === Math.round(curPage) || page === -1) { let minim = BoolCast(doc.isMinimized, false); if (minim === undefined || !minim) { - const pos = script ? this.getCalculatedPositions(doc, prev.length, this.Document, script, state) : {}; + const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {}; state = pos.state === undefined ? state : pos.state; prev.push(); } @@ -489,8 +490,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }} />; overlayDisposer = OverlayView.Instance.addElement(scriptingBox, options); }; - addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, undefined, undefined); - addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state: "any" }, "{x: number, y: number, width?: number, height?: number}"); + addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, { collection: "Doc", docs: "Doc[]" }, undefined); + addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); } }); } -- cgit v1.2.3-70-g09d2 From 74e4909a77ac143ecdb1d038ad182aae9c710129 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 3 Jul 2019 22:15:25 -0400 Subject: implemented directory import routine --- src/client/documents/Documents.ts | 57 ++++++++- src/client/util/Import & Export/ImageImporter.tsx | 67 +++++++++++ src/client/util/Import & Export/ImportBox.tsx | 134 +++++++++++++++++++++ src/client/util/SelectionManager.ts | 2 +- src/client/views/MainView.tsx | 3 + src/client/views/collections/CollectionSubView.tsx | 46 +------ src/client/views/nodes/DocumentContentsView.tsx | 3 +- 7 files changed, 265 insertions(+), 47 deletions(-) create mode 100644 src/client/util/Import & Export/ImageImporter.tsx create mode 100644 src/client/util/Import & Export/ImportBox.tsx (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7d7a1f02a..5d637dd3a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -36,6 +36,7 @@ import { UndoManager } from "../util/UndoManager"; import { RouteStore } from "../../server/RouteStore"; import { LinkManager } from "../util/LinkManager"; import { DocumentManager } from "../util/DocumentManager"; +import ImportBox from "../util/Import & Export/ImportBox"; var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -51,7 +52,8 @@ export enum DocTypes { KVP = "kvp", VID = "video", AUDIO = "audio", - LINK = "link" + LINK = "link", + IMPORT = "import" } export interface DocumentOptions { @@ -127,6 +129,7 @@ export namespace Docs { let audioProto: Doc; let pdfProto: Doc; let iconProto: Doc; + let importProto: Doc; // let linkProto: Doc; const textProtoId = "textProto"; const histoProtoId = "histoProto"; @@ -138,6 +141,7 @@ export namespace Docs { const videoProtoId = "videoProto"; const audioProtoId = "audioProto"; const iconProtoId = "iconProto"; + const importProtoId = "importProto"; // const linkProtoId = "linkProto"; export function initProtos(): Promise { @@ -152,6 +156,7 @@ export namespace Docs { audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype(); pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype(); iconProto = fields[iconProtoId] as Doc || CreateIconPrototype(); + importProto = fields[importProtoId] as Doc || CreateImportPrototype(); }); } @@ -174,6 +179,11 @@ export namespace Docs { return imageProto; } + function CreateImportPrototype(): Doc { + let importProto = setupPrototypeOptions(importProtoId, "IMPORT_PROTO", ImportBox.LayoutString(), { x: 0, y: 0, width: 600, height: 600, type: DocTypes.IMPORT }); + return importProto; + } + function CreateHistogramPrototype(): Doc { let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"), { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString(), type: DocTypes.HIST }); @@ -261,6 +271,10 @@ export namespace Docs { return CreateInstance(audioProto, new AudioField(new URL(url)), options); } + export function DirectoryImportDocument(options: DocumentOptions = {}) { + return CreateInstance(importProto, "", options); + } + export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) { return CreateInstance(histoProto, new HistogramField(histoOp), options); } @@ -333,6 +347,47 @@ export namespace Docs { return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); } + export async function getDocumentFromType(type: string, path: string, options: DocumentOptions, addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean): Promise> { + let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise)) | undefined = undefined; + if (type.indexOf("image") !== -1) { + ctor = Docs.ImageDocument; + } + if (type.indexOf("video") !== -1) { + ctor = Docs.VideoDocument; + } + if (type.indexOf("audio") !== -1) { + ctor = Docs.AudioDocument; + } + if (type.indexOf("pdf") !== -1) { + ctor = Docs.PdfDocument; + options.nativeWidth = 1200; + } + if (type.indexOf("excel") !== -1) { + ctor = Docs.DBDocument; + options.dropAction = "copy"; + } + if (type.indexOf("html") !== -1) { + if (path.includes(window.location.hostname)) { + let s = path.split('/'); + let id = s[s.length - 1]; + DocServer.GetRefField(id).then(field => { + if (field instanceof Doc) { + let alias = Doc.MakeAlias(field); + alias.x = options.x || 0; + alias.y = options.y || 0; + alias.width = options.width || 300; + alias.height = options.height || options.width || 300; + addDocument && addDocument(alias, false); + } + }); + return undefined; + } + ctor = Docs.WebDocument; + options = { height: options.width, ...options, title: path, nativeWidth: undefined }; + } + return ctor ? ctor(path, options) : undefined; + } + export function CaptionDocument(doc: Doc) { const captionDoc = Doc.MakeAlias(doc); captionDoc.overlayLayout = FixedCaption(); diff --git a/src/client/util/Import & Export/ImageImporter.tsx b/src/client/util/Import & Export/ImageImporter.tsx new file mode 100644 index 000000000..d664f6487 --- /dev/null +++ b/src/client/util/Import & Export/ImageImporter.tsx @@ -0,0 +1,67 @@ +import "fs"; +import React = require("react"); +import { Doc } from "../../../new_fields/Doc"; +import { DocServer } from "../../DocServer"; +import { RouteStore } from "../../../server/RouteStore"; +import { action } from "mobx"; +import { Docs } from "../../documents/Documents"; +import { FieldViewProps } from "../../views/nodes/FieldView"; + +interface ImageImporterProps { + addSchema: (imageDocs: Doc[]) => void; +} + +export default class BulkImporter extends React.Component { + private selector = React.createRef(); + + handleSelection = async (e: React.ChangeEvent) => { + let promises: Promise[] = []; + let docs: Doc[] = []; + + let files = e.target.files; + if (!files) return; + + for (let i = 0; i < files.length; i++) { + let target = files.item(i); + + if (target === null) { + continue; + } + + let type = target.type; + let formData = new FormData(); + formData.append('file', target); + let dropFileName = target ? target.name : "-empty-"; + + let prom = fetch(DocServer.prepend(RouteStore.upload), { + method: 'POST', + body: formData + }).then(async (res: Response) => { + (await res.json()).map(action((file: any) => { + let path = window.location.origin + file; + let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName }); + docPromise.then(doc => doc && docs.push(doc)); + })); + }); + promises.push(prom); + } + + await Promise.all(promises); + + let parent = Docs.SchemaDocument(["title", "data"], docs, { width: 300, height: 300, title: "Bulk Import from Directory" }); + } + + componentDidMount() { + this.selector.current!.setAttribute("directory", "true"); + this.selector.current!.setAttribute("webkitdirectory", "true"); + } + + render() { + return ( +
    + +
    + ); + } + +} \ No newline at end of file diff --git a/src/client/util/Import & Export/ImportBox.tsx b/src/client/util/Import & Export/ImportBox.tsx new file mode 100644 index 000000000..630911710 --- /dev/null +++ b/src/client/util/Import & Export/ImportBox.tsx @@ -0,0 +1,134 @@ +import "fs"; +import React = require("react"); +import { Doc } from "../../../new_fields/Doc"; +import { DocServer } from "../../DocServer"; +import { RouteStore } from "../../../server/RouteStore"; +import { action, observable } from "mobx"; +import { FieldViewProps, FieldView } from "../../views/nodes/FieldView"; +import Measure, { ContentRect } from "react-measure"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArrowUp } from '@fortawesome/free-solid-svg-icons'; +import { Docs, DocumentOptions } from "../../documents/Documents"; + +interface ImageImporterProps { + addSchema: (imageDocs: Doc[]) => void; +} + +export default class ImportBox extends React.Component { + @observable private top = 0; + @observable private left = 0; + private dimensions = 50; + + constructor(props: FieldViewProps) { + super(props); + library.add(faArrowUp); + } + + public static LayoutString() { return FieldView.LayoutString(ImportBox); } + + private selector = React.createRef(); + + handleSelection = async (e: React.ChangeEvent) => { + let promises: Promise[] = []; + let docs: Doc[] = []; + + let files = e.target.files; + if (!files || files.length === 0) return; + + let directory = (files.item(0) as any).webkitRelativePath.split("/", 1); + + for (let i = 0; i < files.length; i++) { + let uploaded_file = files.item(i); + + if (!uploaded_file) { + continue; + } + + let formData = new FormData(); + formData.append('file', uploaded_file); + let dropFileName = uploaded_file ? uploaded_file.name : "-empty-"; + let type = uploaded_file.type; + + let prom = fetch(DocServer.prepend(RouteStore.upload), { + method: 'POST', + body: formData + }).then(async (res: Response) => { + (await res.json()).map(action((file: any) => { + let path = DocServer.prepend(file); + let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName }); + docPromise.then(doc => doc && docs.push(doc)); + })); + }); + promises.push(prom); + } + + await Promise.all(promises); + + let doc = this.props.Document; + let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") }; + let parent = this.props.ContainingCollectionView; + if (parent) { + let importContainer = Docs.StackingDocument(docs, options); + Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + this.props.removeDocument && this.props.removeDocument(doc); + } + } + + componentDidMount() { + this.selector.current!.setAttribute("directory", "true"); + this.selector.current!.setAttribute("webkitdirectory", "true"); + } + + @action + preserveCentering = (rect: ContentRect) => { + let bounds = rect.offset!; + if (bounds.width === 0 || bounds.height === 0) { + return; + } + let offset = this.dimensions / 2; + this.left = bounds.width / 2 - offset; + this.top = bounds.height / 2 - offset; + } + + render() { + let dimensions = 50; + return ( + + {({ measureRef }) => +
    + +
    ; } - titleClicked = (e: React.MouseEvent) => { - if (this._collapsed) return false; - else { - this.props.document.embed = !BoolCast(this.props.document.embed); - return true; - } - } static loadId = ""; editableView = (key: string, style?: string) => ( StrCast(this.props.document[key])} @@ -236,18 +231,19 @@ 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 - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.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: (BoolCast(this.props.document.embed) ? "Collapse" : "Expand") + " inline", event: () => this.props.document.embed = !BoolCast(this.props.document.embed), icon: "expand" }); if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { 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.resolvedDataDoc).length) { - ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).map(view => view.props.focus(this.props.document, true)) }); + ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" }); } - ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); + ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" }); } else { - ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)) }); - ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); + ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)), icon: "caret-square-right" }); + ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" }); } + ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.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.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); e.stopPropagation(); e.preventDefault(); @@ -268,6 +264,8 @@ class TreeView extends React.Component { e.stopPropagation(); } if (de.data instanceof DragManager.DocumentDragData) { + e.stopPropagation(); + if (de.data.draggedDocuments[0] === this.props.document) return true; let addDoc = (doc: Doc) => this.props.addDocument(doc, this.resolvedDataDoc, before); if (inside) { let docList = Cast(this.resolvedDataDoc.data, listSpec(Doc)); @@ -275,7 +273,6 @@ class TreeView extends React.Component { addDoc = (doc: Doc) => { docList && docList.push(doc); return true; }; } } - e.stopPropagation(); let movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments); return (de.data.dropAction || de.data.userDropAction) ? de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.resolvedDataDoc, before) || added, false) @@ -314,18 +311,37 @@ class TreeView extends React.Component { return ele; } - fitToBox = () => { + @computed get docBounds() { + if (StrCast(this.props.document.type).indexOf(DocTypes.COL) === -1) return undefined; let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc); - let bounds = Doc.ComputeContentBounds(layoutDoc); - return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Math.min(this.props.panelHeight() / (bounds.b - bounds.y), this.props.panelWidth() / (bounds.r - bounds.x))]; + return Doc.ComputeContentBounds(layoutDoc); + } + docWidth = () => { + let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); + if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 5)); + return NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 5) : this.props.panelWidth() - 5; } + docHeight = () => { + let bounds = this.docBounds; + return Math.min(this.MAX_EMBED_HEIGHT, (() => { + let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); + if (aspect) return this.docWidth() * aspect; + if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); + return NumCast(this.props.document.height) ? NumCast(this.props.document.height) : 50; + })()); + } + fitToBox = () => { + let bounds = this.docBounds!; + return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Math.min(this.docHeight() / (bounds.b - bounds.y), this.docWidth() / (bounds.r - bounds.x))]; + } + render() { let contentElement: (JSX.Element | null) = null; let docList = Cast(this.resolvedDataDoc[this._chosenKey], listSpec(Doc)); let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey); let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.resolvedDataDoc, this._chosenKey, doc, addBefore, before); let doc = Cast(this.resolvedDataDoc[this._chosenKey], Doc); - let docWidth = () => NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 5) : this.props.panelWidth() - 5; + if (!this._collapsed) { if (!this.props.document.embed) { contentElement =
      @@ -335,14 +351,14 @@ class TreeView extends React.Component {
    ; } else { let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc); - contentElement =
    + contentElement =
    this.cleanupInteractions(false); e.stopPropagation(); } - if (e.key === "c" || e.key === "s" || e.key === "S" || e.key === "e" || e.key === "p") { + if (e.key === "c" || e.key === "s" || e.key === "S" || e.key === "e") { this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); @@ -277,7 +277,7 @@ export class MarqueeView extends React.Component width: bounds.width, height: bounds.height, ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined, - title: e.key === "s" || e.key === "S" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection", + title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", }); this.marqueeInkDelete(inkData); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8b75d0d25..69dc31fec 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -589,8 +589,10 @@ export class DocumentView extends DocComponent(Docu style={{ outlineColor: "maroon", outlineStyle: "dashed", - outlineWidth: BoolCast(this.props.Document.libraryBrush) || BoolCast(this.props.Document.protoBrush) ? + outlineWidth: (BoolCast(this.props.Document.libraryBrush) || BoolCast(this.props.Document.protoBrush)) && !NumCast(this.props.Document.borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", + border: (BoolCast(this.props.Document.libraryBrush) || BoolCast(this.props.Document.protoBrush)) && NumCast(this.props.Document.borderRounding) ? + `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined, borderRadius: "inherit", background: backgroundColor, width: nativeWidth, -- cgit v1.2.3-70-g09d2 From 575ee72cd6ddd65d6c02c6f50a3266f632b1b858 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 4 Jul 2019 00:17:15 -0400 Subject: highlights current workspace in treeview --- src/client/views/collections/CollectionDockingView.tsx | 2 ++ src/client/views/collections/CollectionTreeView.tsx | 3 ++- src/client/views/nodes/DocumentView.tsx | 4 ++-- src/client/views/presentationview/PresentationElement.tsx | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 8b6f5b6a6..c5f8fb728 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -240,6 +240,7 @@ export class CollectionDockingView extends React.Component this.setupGoldenLayout(), 1); + this.props.Document.workspaceBrush = true; } this._ignoreStateChange = ""; }, { fireImmediately: true }); @@ -249,6 +250,7 @@ export class CollectionDockingView extends React.Component void = () => { try { + this.props.Document.workspaceBrush = false; this._goldenLayout.unbind('itemDropped', this.itemDropped); this._goldenLayout.unbind('tabCreated', this.tabCreated); this._goldenLayout.unbind('stackCreated', this.stackCreated); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 622ba37d1..3db786043 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -217,7 +217,8 @@ class TreeView extends React.Component { return <>
    diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 69dc31fec..f751a45fe 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -589,9 +589,9 @@ export class DocumentView extends DocComponent(Docu style={{ outlineColor: "maroon", outlineStyle: "dashed", - outlineWidth: (BoolCast(this.props.Document.libraryBrush) || BoolCast(this.props.Document.protoBrush)) && !NumCast(this.props.Document.borderRounding) ? + outlineWidth: BoolCast(this.props.Document.libraryBrush) && !NumCast(this.props.Document.borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", - border: (BoolCast(this.props.Document.libraryBrush) || BoolCast(this.props.Document.protoBrush)) && NumCast(this.props.Document.borderRounding) ? + border: BoolCast(this.props.Document.libraryBrush) && NumCast(this.props.Document.borderRounding) ? `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined, borderRadius: "inherit", background: backgroundColor, diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index d63c0b066..6896ee452 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -377,7 +377,7 @@ export default class PresentationElement extends React.Component { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}> -- cgit v1.2.3-70-g09d2 From e56a29faab91c5059af41e0b2abd79e46eacce0c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 4 Jul 2019 00:37:06 -0400 Subject: fixed edit font size for tree view --- src/client/views/EditableView.tsx | 3 ++- src/client/views/collections/CollectionTreeView.tsx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 97a2d19dd..f7aa6cc94 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -25,6 +25,7 @@ export interface EditableProps { */ contents: any; fontStyle?: string; + fontSize?: number; height?: number; display?: string; oneLine?: boolean; @@ -80,7 +81,7 @@ export class EditableView extends React.Component { if (this._editing) { return this._editing = false)} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} - style={{ display: this.props.display }} />; + style={{ display: this.props.display, fontSize: this.props.fontSize }} />; } else { return (
    { contents={StrCast(this.props.document[key])} height={36} fontStyle={style} + fontSize={12} GetValue={() => StrCast(this.props.document[key])} SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc)[key] = value) ? true : true} OnFillDown={(value: string) => { -- cgit v1.2.3-70-g09d2 From d479f5e6bcdb7d1a0edb2f8cf366549abe2a910b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 4 Jul 2019 14:08:00 -0400 Subject: added text font color setting. adding interactive border radius. moved main toolbar. --- src/client/documents/Documents.ts | 4 +-- src/client/util/DragManager.ts | 1 + src/client/util/RichTextSchema.tsx | 11 ++++++++ src/client/util/TooltipTextMenu.tsx | 3 +- src/client/views/DocumentDecorations.scss | 22 +++++++++++++++ src/client/views/DocumentDecorations.tsx | 32 ++++++++++++++++++++++ src/client/views/InkingControl.tsx | 3 ++ src/client/views/Main.scss | 2 +- src/client/views/MainOverlayTextBox.tsx | 5 ++++ src/client/views/MainView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 16 ++++------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 15 ++++++---- src/client/views/nodes/DocumentView.tsx | 4 +-- src/client/views/nodes/FormattedTextBox.tsx | 11 +++++++- 14 files changed, 106 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7a976e7d7..2bddf053a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -77,7 +77,7 @@ export interface DocumentOptions { backgroundLayout?: string; curPage?: number; documentText?: string; - borderRounding?: number; + borderRounding?: string; schemaColumns?: List; dockingConfig?: string; dbDoc?: Doc; @@ -93,7 +93,7 @@ export namespace DocUtils { if (target === CurrentUserUtils.UserDocument) return; UndoManager.RunInBatch(() => { - let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 }); + let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: "100%" }); linkDoc.type = DocTypes.LINK; let linkDocProto = Doc.GetProto(linkDoc); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 7dc48fb78..d4b1bc8f4 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -315,6 +315,7 @@ export namespace DragManager { scaleYs.push(scaleY); let dragElement = ele.cloneNode(true) as HTMLElement; dragElement.style.opacity = "0.7"; + dragElement.style.borderRadius = getComputedStyle(ele).borderRadius; dragElement.style.position = "absolute"; dragElement.style.margin = "0"; dragElement.style.top = "0"; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 63c879d67..2a57180d3 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -348,6 +348,17 @@ export const marks: { [index: string]: MarkSpec } = { }] }, + pFontColor: { + attrs: { + color: { default: "yellow" } + }, + parseDOM: [{ style: 'background: #d9dbdd' }], + toDOM: (node) => { + return ['span', { + style: `color: ${node.attrs.color}` + }]; + } + }, /** FONT SIZES */ pFontSize: { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9f8d0b2f6..e3e26d1f4 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -111,7 +111,8 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p32, 32); this.fontSizeToNum.set(schema.marks.p48, 48); this.fontSizeToNum.set(schema.marks.p72, 72); - //this.fontSizeToNum.set(schema.marks.pFontSize,schema.marks.pFontSize.) + this.fontSizeToNum.set(schema.marks.pFontSize, 10); + this.fontSizeToNum.set(schema.marks.pFontSize, 10); this.fontSizes = Array.from(this.fontSizeToNum.keys()); //list types diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index ba9f32d7d..2d430512b 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -26,6 +26,14 @@ $linkGap : 3px; opacity: 0.8; } + .documentDecorations-radius { + pointer-events: auto; + background: black; + opacity: 0.8; + transform: translate(10px, 10px); + grid-row: 4; + } + #documentDecorations-topLeftResizer, #documentDecorations-leftResizer, #documentDecorations-bottomLeftResizer { @@ -44,11 +52,25 @@ $linkGap : 3px; grid-column-start: 5; grid-column-end: 7; } + + #documentDecorations-borderRadius{ + grid-column-start: 5; + grid-column-end: 7; + border-radius: 100%; + .borderRadiusTooltip{ + width:10px; + height:10px; + position:absolute; + } + } #documentDecorations-topLeftResizer, #documentDecorations-bottomRightResizer { cursor: nwse-resize; } + #documentDecorations-bottomRightResizer { + grid-row:4; + } #documentDecorations-topRightResizer, #documentDecorations-bottomLeftResizer { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3df520428..c7990647a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -341,6 +341,37 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> iconDoc.y = where[1] + NumCast(selView.props.Document.y); } + _radiusDown = [0, 0]; + @action + onRadiusDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + if (e.button === 0) { + this._radiusDown = [e.clientX, e.clientY]; + this._isPointerDown = true; + this._resizeUndo = UndoManager.StartBatch("DocDecs set radius"); + document.removeEventListener("pointermove", this.onRadiusMove); + document.removeEventListener("pointerup", this.onRadiusUp); + document.addEventListener("pointermove", this.onRadiusMove); + document.addEventListener("pointerup", this.onRadiusUp); + } + } + + onRadiusMove = (e: PointerEvent): void => { + 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 => Doc.GetProto(dv.props.Document).borderRounding = `${Math.min(100, dist)}%`); + e.stopPropagation(); + e.preventDefault(); + } + + onRadiusUp = (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._isPointerDown = false; + this._resizeUndo && this._resizeUndo.end(); + document.removeEventListener("pointermove", this.onRadiusMove); + document.removeEventListener("pointerup", this.onRadiusUp); + } + @action onPointerDown = (e: React.PointerEvent): void => { e.stopPropagation(); @@ -705,6 +736,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
    e.preventDefault()}>
    e.preventDefault()}>
    e.preventDefault()}>
    +
    e.preventDefault()}>
    {linkButton}
    diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 0461d7299..c7f7bdb66 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -10,6 +10,8 @@ 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 { FormattedTextBox } from "./nodes/FormattedTextBox"; +import { MainOverlayTextBox } from "./MainOverlayTextBox"; library.add(faPen, faHighlighter, faEraser, faBan); @@ -42,6 +44,7 @@ export class InkingControl extends React.Component { switchColor = action((color: ColorResult): void => { this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff"); if (InkingControl.Instance.selectedTool === InkTool.None) { + if (MainOverlayTextBox.Instance.SetColor(color.hex)) return; let selected = SelectionManager.SelectedDocuments(); let oldColors = selected.map(view => { let targetDoc = view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 44c1cb9fc..b85a8040a 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -144,7 +144,7 @@ button:hover { #add-nodes-menu { position: absolute; bottom: 22px; - left: 24px; + left: 250px; > label { background: $dark-color; diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 7d15702a2..d8aaea259 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -36,6 +36,11 @@ export class MainOverlayTextBox extends React.Component this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip); } + public SetColor(color: string) { + return this._textBox && this._textBox.setFontColor(color); + } + + constructor(props: MainOverlayTextBoxProps) { super(props); this._textProxyDiv = React.createRef(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 1542fedef..61ccf4c1d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -354,7 +354,7 @@ export class MainView extends React.Component { [React.createRef(), "tree", "Add Tree", addTreeNode], ]; - return < div id="add-nodes-menu" > + return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 5 }} > diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 31c0fe499..4850c6218 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -44,14 +44,12 @@ export class MarqueeView extends React.Component _commandExecuted = false; @action - cleanupInteractions = (all: boolean = false, rem_keydown: boolean = true) => { + cleanupInteractions = (all: boolean = false) => { if (all) { document.removeEventListener("pointerup", this.onPointerUp, true); document.removeEventListener("pointermove", this.onPointerMove, true); } - if (rem_keydown) { - document.removeEventListener("keydown", this.marqueeCommand, true); - } + document.removeEventListener("keydown", this.marqueeCommand, true); this._visible = false; } @@ -191,12 +189,9 @@ export class MarqueeView extends React.Component SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); - mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true); - } - else { - //console.log("invisible"); - this.cleanupInteractions(true); } + //console.log("invisible"); + this.cleanupInteractions(true); if (e.altKey) { e.preventDefault(); @@ -266,13 +261,12 @@ export class MarqueeView extends React.Component } let ink = Cast(this.props.container.props.Document.ink, InkField); let inkData = ink ? ink.inkData : undefined; - let zoomBasis = NumCast(this.props.container.props.Document.scale, 1); let newCollection = Docs.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panX: 0, panY: 0, - borderRounding: e.key === "e" ? -1 : undefined, + borderRounding: e.key === "e" ? "100%" : undefined, backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", width: bounds.width, height: bounds.height, diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 30cf74f3e..1c00687ed 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -70,11 +70,14 @@ export class CollectionFreeFormDocumentView extends DocComponent { - let br = NumCast(this.props.Document.borderRounding); - return br >= 0 ? br : - NumCast(this.props.Document.nativeWidth) === 0 ? - Math.min(this.props.PanelWidth(), this.props.PanelHeight()) - : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0); + let br = StrCast(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 minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight())); + return minDim; + } + return undefined; } render() { @@ -84,7 +87,7 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu style={{ outlineColor: "maroon", outlineStyle: "dashed", - outlineWidth: BoolCast(this.props.Document.libraryBrush) && !NumCast(this.props.Document.borderRounding) ? + outlineWidth: BoolCast(this.props.Document.libraryBrush) && !StrCast(this.props.Document.borderRounding) ? `${this.props.ScreenToLocalTransform().Scale}px` : "0px", - border: BoolCast(this.props.Document.libraryBrush) && NumCast(this.props.Document.borderRounding) ? + border: BoolCast(this.props.Document.libraryBrush) && StrCast(this.props.Document.borderRounding) ? `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined, borderRadius: "inherit", background: backgroundColor, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 1eeb04755..bf6f4c764 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -97,6 +97,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return ""; } + @undoBatch + public setFontColor(color: string) { + if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false; + 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)); + return true; + } + constructor(props: FieldViewProps) { super(props); if (this.props.outer_div) { @@ -418,7 +427,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } render() { let style = this.props.isOverlay ? "scroll" : "hidden"; - let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : ""; + let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; let interactive = InkingControl.Instance.selectedTool ? "" : "interactive"; return (
    Date: Thu, 4 Jul 2019 14:15:11 -0400 Subject: from last --- src/client/views/collections/CollectionSchemaView.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index d06d1dab6..568949efb 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -461,13 +461,28 @@ export class CollectionSchemaPreview extends React.Component) => { this.props.setPreviewScript(e.currentTarget.value); } + @computed get borderRounding() { + let br = StrCast(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 minDim = percent * (nativeDim ? nativeDim : Math.min(this.PanelWidth(), this.PanelHeight())); + return minDim; + } + return undefined; + } render() { let input = this.props.previewScript === undefined ? (null) :
    ; return (
    {!this.props.Document || !this.props.width ? (null) : ( -
    +
    Date: Thu, 4 Jul 2019 18:20:54 -0400 Subject: added dropping and reordering within Stacking views. needs GUI feedback. --- src/client/util/DragManager.ts | 2 +- .../views/collections/CollectionStackingView.tsx | 58 +++++++++++++++++++++- src/client/views/collections/CollectionSubView.tsx | 9 +++- src/client/views/nodes/ImageBox.tsx | 4 +- 4 files changed, 68 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d4b1bc8f4..ce1f9d890 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -448,7 +448,7 @@ export namespace DragManager { x: e.x, y: e.y, data: dragData, - mods: e.altKey ? "AltKey" : "" + mods: e.altKey ? "AltKey" : e.ctrlKey ? "CtrlKey" : "" } }) ); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index b10907937..6b4eddec9 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -11,6 +11,8 @@ import { CollectionSchemaPreview } from "./CollectionSchemaView"; import "./CollectionStackingView.scss"; import { CollectionSubView } from "./CollectionSubView"; import { resolve } from "bluebird"; +import { undoBatch } from "../../util/UndoManager"; +import { DragManager } from "../../util/DragManager"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { @@ -96,8 +98,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
    ; }); } + docXfs: any[] = [] @computed get children() { + this.docXfs.length = 0; return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { let aspect = d.nativeHeight ? NumCast(d.nativeWidth) / NumCast(d.nativeHeight) : undefined; let dref = React.createRef(); @@ -105,6 +109,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { let width = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth; let height = () => aspect ? width() / aspect : d[HeightSym](); let rowSpan = Math.ceil((height() + this.gridGap) / (this._gridSize + this.gridGap)); + this.docXfs.push({ dxf: dxf, width: width, height: height }); return (
    doc) { }); } } + + @undoBatch + @action + drop = (e: Event, de: DragManager.DropEvent) => { + let targInd = -1; + let where = [de.x, de.y]; + if (de.data instanceof DragManager.DocumentDragData) { + this.docXfs.map((cd, i) => { + let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + let pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height()); + if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) { + targInd = i; + } + }) + } + if (super.drop(e, de)) { + if (targInd !== -1) { + let newDoc = de.data.droppedDocuments[0]; + let docs = this.childDocList; + if (docs) { + let srcInd = docs.indexOf(newDoc); + docs.splice(srcInd, 1); + docs.splice(targInd > srcInd ? targInd - 1 : targInd, 0, newDoc); + } + } + } + return false; + } + @undoBatch + @action + onDrop = (e: React.DragEvent): void => { + let where = [e.clientX, e.clientY]; + let targInd = -1; + this.docXfs.map((cd, i) => { + let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + let pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height()); + if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) { + targInd = i; + } + }) + super.onDrop(e, {}, () => { + if (targInd !== -1) { + let newDoc = this.childDocs[this.childDocs.length - 1]; + let docs = this.childDocList; + if (docs) { + docs.splice(docs.length - 1, 1); + docs.splice(targInd, 0, newDoc); + } + } + }); + } render() { let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.childDocs.filter(d => !d.isMinimized).length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); let templatecols = ""; for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `; return ( -
    e.stopPropagation()} > +
    e.stopPropagation()} >
    (schemaCtor: (doc: Doc) => T) { //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) return DocListCast((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]); } + get childDocList() { + //TODO tfs: This might not be what we want? + //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) + return Cast((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc)); + } @action protected async setCursorPosition(position: [number, number]) { @@ -145,7 +150,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @undoBatch @action - protected onDrop(e: React.DragEvent, options: DocumentOptions): void { + protected onDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) { if (e.ctrlKey) { e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl return; @@ -255,7 +260,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { } if (promises.length) { - Promise.all(promises).finally(() => batch.end()); + Promise.all(promises).finally(() => { completed && completed(); batch.end(); }); } else { batch.end(); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 06bf65f73..dd4e4262d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -73,7 +73,7 @@ export class ImageBox extends DocComponent(ImageD 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 { + } else if (de.mods === "CtrlKey") { if (this.extensionDoc !== this.dataDoc) { let layout = StrCast(drop.backgroundLayout); if (layout.indexOf(ImageBox.name) !== -1) { @@ -205,6 +205,8 @@ export class ImageBox extends DocComponent(ImageD requestImageSize(window.origin + RouteStore.corsProxy + "/" + srcpath) .then((size: any) => { let aspect = size.height / size.width; + let rotation = NumCast(this.dataDoc.rotation) % 180; + if (rotation === 90 || rotation === 270) aspect = 1 / aspect; if (Math.abs(layoutdoc[HeightSym]() / layoutdoc[WidthSym]() - aspect) > 0.01) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; -- cgit v1.2.3-70-g09d2 From 8fc3fe167d81631cb2f2010f197099c0ae6988a1 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Thu, 4 Jul 2019 18:38:57 -0400 Subject: Added drag to search items --- src/client/views/search/SearchItem.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 601f4032d..79a06c81a 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -10,7 +10,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils"; import { DocTypes } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; -import { SetupDrag } from "../../util/DragManager"; +import { SetupDrag, DragManager } from "../../util/DragManager"; import { LinkManager } from "../../util/LinkManager"; import { SearchUtil } from "../../util/SearchUtil"; import { Transform } from "../../util/Transform"; @@ -22,6 +22,7 @@ import { SearchBox } from "./SearchBox"; import "./SearchItem.scss"; import "./SelectorContextMenu.scss"; import { ContextMenu } from "../ContextMenu"; +import { faFile } from '@fortawesome/free-solid-svg-icons'; export interface SearchItemProps { doc: Doc; @@ -30,6 +31,7 @@ export interface SearchItemProps { library.add(faCaretUp); library.add(faObjectGroup); library.add(faStickyNote); +library.add(faFile); library.add(faFilePdf); library.add(faFilm); library.add(faMusic); @@ -227,12 +229,23 @@ export class SearchItem extends React.Component { ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } + onPointerDown = (e: React.PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; + DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, { + handlers: { dragComplete: emptyFunction }, + hideSource: false, + }); + } + render() { return (
    +
    {this.props.doc.title}
    -- cgit v1.2.3-70-g09d2 From f53ec30e26f9f49d6ae82dbdf9781387fdbd212a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 4 Jul 2019 18:46:56 -0400 Subject: added drag of search results --- src/client/views/search/SearchItem.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 601f4032d..f75a1875b 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -230,12 +230,13 @@ export class SearchItem extends React.Component { render() { return (
    -
    {this.props.doc.title}
    -
    +
    this.props.doc, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}>
    {this.DocumentIcon}
    {this.props.doc.type}
    -- cgit v1.2.3-70-g09d2 From 1b02511185e22349e68bab1f6b8ce74b874a3ba5 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Thu, 4 Jul 2019 19:54:27 -0400 Subject: Made some changes to clean up some GoldenLayout stuff --- src/client/goldenLayout.js | 11 ++++ src/client/util/DragManager.ts | 1 + src/client/views/_nodeModuleOverrides.scss | 18 +++---- .../views/collections/CollectionDockingView.tsx | 58 ++++++++++------------ src/client/views/nodes/DocumentView.tsx | 20 ++++---- src/client/views/search/SearchItem.tsx | 2 +- 6 files changed, 57 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 54c9c6068..28c009645 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -2271,6 +2271,17 @@ this._dragListener.on('dragStop', this._createDragListener, this); }, + destroy: function () { + this._dragListener.destroy(); + this._element = null; + this._itemConfig = null; + this._dragListener = null; + const index = this._layoutManager._dragSources.indexOf(this); + if (index > -1) { + this._layoutManager._dragSources.splice(index, 1); + } + }, + /** * Callback for the DragListener's dragStart event * diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index ce1f9d890..2fd83ae56 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -43,6 +43,7 @@ export function SetupDrag( e.stopPropagation(); e.preventDefault(); if (e.shiftKey && CollectionDockingView.Instance) { + e.persist(); CollectionDockingView.Instance.StartOtherDrag(e, [await docFunc()]); } else { document.addEventListener("pointermove", onRowMove); diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss index 3594ac9f4..b8a7db034 100644 --- a/src/client/views/_nodeModuleOverrides.scss +++ b/src/client/views/_nodeModuleOverrides.scss @@ -2,21 +2,21 @@ // goldenlayout stuff div .lm_header { - background: $dark-color; + background: $dark-color; } .lm_tab { - margin-top: 0.6em !important; - padding-top: 0.5em !important; - min-height: 1.35em; - padding-bottom: 0px; - border-radius: 5px; - font-family: $sans-serif !important; + // margin-top: 0.6em !important; + // padding-top: 0.5em !important; + // min-height: 1.35em; + // padding-bottom: 0px; + // border-radius: 5px; + font-family: $sans-serif !important; } .lm_header .lm_controls { - right: 1em !important; + right: 1em !important; } // @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out -// why. Low priority for now but it's bugging me. --Julie +// why. Low priority for now but it's bugging me. --Julie \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index c5f8fb728..f146ec62b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -62,12 +62,30 @@ export class CollectionDockingView extends React.Component - this.AddRightSplit(dragDoc, dragDataDocs ? dragDataDocs[i] : undefined, true).contentItems[0].tab._dragListener. - onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); + public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs: (Doc | undefined)[] = []) { + let config: any; + if (dragDocs.length === 1) { + config = CollectionDockingView.makeDocumentConfig(dragDocs[0], dragDataDocs[0]); + } else { + config = { + type: 'row', + content: dragDocs.map((doc, i) => { + CollectionDockingView.makeDocumentConfig(doc, dragDataDocs[i]); + }) + }; + } + const div = document.createElement("div"); + const dragSource = this._goldenLayout.createDragSource(div, config); + dragSource._dragListener.on("dragStop", () => { + dragSource.destroy(); + }); + dragSource._dragListener.onMouseDown(e); + // dragSource.destroy(); + // this.hack = true; + // this.undohack = UndoManager.StartBatch("goldenDrag"); + // dragDocs.map((dragDoc, i) => + // this.AddRightSplit(dragDoc, dragDataDocs[i], true).contentItems[0].tab._dragListener. + // onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); } @action @@ -299,33 +317,7 @@ export class CollectionDockingView extends React.Component) => (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc))); - } else - if ((className === "lm_title" || className === "lm_tab lm_active") && e.shiftKey) { - e.stopPropagation(); - e.preventDefault(); - let x = e.clientX; - let y = e.clientY; - let docid = (e.target as any).DashDocId; - let datadocid = (e.target as any).DashDataDocId; - let tab = (e.target as any).parentElement as HTMLElement; - let glTab = (e.target as any).Tab; - if (glTab && glTab.contentItem && glTab.contentItem.parent) { - glTab.contentItem.parent.setActiveContentItem(glTab.contentItem); - } - DocServer.GetRefField(docid).then(action(async (f: Opt) => { - if (f instanceof Doc) { - let dataDoc = (datadocid !== docid) ? await DocServer.GetRefField(datadocid) : f; - DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f], [dataDoc instanceof Doc ? dataDoc : f]), x, y, - { - handlers: { - dragComplete: emptyFunction, - }, - hideSource: false, - withoutShiftDrag: true - }); - } - })); - } + } if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") { this._flush = true; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a84cac37f..2610d0e6d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -362,16 +362,16 @@ export class DocumentView extends DocComponent(Docu 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 { - 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("pointerup", this.onPointerUp); - } + // if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) { + // CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]); + // e.stopPropagation(); + // } else { + 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("pointerup", this.onPointerUp); + // } } onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.active) { diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 79a06c81a..606ac4e85 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -231,7 +231,7 @@ export class SearchItem extends React.Component { onPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); - e.preventDefault(); + // e.preventDefault(); const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, { handlers: { dragComplete: emptyFunction }, -- cgit v1.2.3-70-g09d2 From de51f2f89cc5bec616ddd06b7647768dc61f9e74 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Thu, 4 Jul 2019 19:57:31 -0400 Subject: Reverted one of the search drag changes --- src/client/views/search/SearchItem.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index f941152d2..6cedc7cfb 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -248,8 +248,7 @@ export class SearchItem extends React.Component {
    {this.props.doc.title}
    -
    this.props.doc, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}> +
    {this.DocumentIcon}
    {this.props.doc.type}
    -- cgit v1.2.3-70-g09d2 From ad87140f4a52984d754c06fcb55e66a0cc935ce2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 4 Jul 2019 23:18:13 -0400 Subject: fixed shift-dragging of documents --- src/client/util/DragManager.ts | 1 - src/client/views/DocumentDecorations.tsx | 1 - src/client/views/collections/CollectionDockingView.tsx | 3 --- src/client/views/collections/CollectionSchemaView.tsx | 1 - src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 1 - src/client/views/search/SearchItem.tsx | 1 - 7 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 2fd83ae56..f9492f452 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -41,7 +41,6 @@ export function SetupDrag( let onItemDown = async (e: React.PointerEvent) => { if (e.button === 0) { e.stopPropagation(); - e.preventDefault(); if (e.shiftKey && CollectionDockingView.Instance) { e.persist(); CollectionDockingView.Instance.StartOtherDrag(e, [await docFunc()]); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index c7990647a..48b89ff4e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -172,7 +172,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.addEventListener("pointermove", this.onBackgroundMove); document.addEventListener("pointerup", this.onBackgroundUp); e.stopPropagation(); - e.preventDefault(); } @action diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f146ec62b..19d07ecdc 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -321,9 +321,6 @@ export class CollectionDockingView extends React.Component doc) { onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { if (this.props.isSelected()) e.stopPropagation(); - else e.preventDefault(); } } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index dd4e4262d..5da52f4c2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -95,7 +95,7 @@ export class ImageBox extends DocComponent(ImageD onPointerDown = (e: React.PointerEvent): void => { if (e.shiftKey && e.ctrlKey) { e.stopPropagation(); // allows default system drag drop of images with shift+ctrl only - } else e.preventDefault(); + } // if (Date.now() - this._lastTap < 300) { // if (e.buttons === 1) { // this._downX = e.clientX; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 1239b498f..264d3c1f7 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -119,7 +119,6 @@ export class VideoBox extends DocComponent(VideoD }); } onPointerDown = (e: React.PointerEvent) => { - e.preventDefault(); e.stopPropagation(); } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 606ac4e85..51f0ed607 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -231,7 +231,6 @@ export class SearchItem extends React.Component { onPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); - // e.preventDefault(); const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, { handlers: { dragComplete: emptyFunction }, -- cgit v1.2.3-70-g09d2 From 149fdb6c172a84c8ec2fbb170bbdd5625c1ac0fa Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 5 Jul 2019 00:34:00 -0400 Subject: restored dragging hyperlink from menu for formattedText boxes --- src/client/util/TooltipTextMenu.scss | 2 +- src/client/util/TooltipTextMenu.tsx | 32 ++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index b10573b3e..40ac3abb9 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -248,7 +248,7 @@ transform: translateY(-85px); pointer-events: all; height: 30px; - width:500px; + width:550px; .ProseMirror-example-setup-style hr { padding: 2px 10px; border: none; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index e3e26d1f4..cb7ed976a 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -32,7 +32,6 @@ export class TooltipTextMenu { private fontSizeToNum: Map; private fontStylesToName: Map; private listTypeToIcon: Map; - private link: HTMLAnchorElement; private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; @@ -89,6 +88,7 @@ export class TooltipTextMenu { }); }); + this.updateLinkMenu(); //list of font styles this.fontStylesToName = new Map(); @@ -121,11 +121,6 @@ export class TooltipTextMenu { this.listTypeToIcon.set(schema.nodes.ordered_list, "1)"); this.listTypes = Array.from(this.listTypeToIcon.keys()); - this.link = document.createElement("a"); - this.link.target = "_blank"; - this.link.style.color = "white"; - //this.tooltip.appendChild(this.link); - this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); @@ -191,6 +186,7 @@ export class TooltipTextMenu { updateLinkMenu() { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); + this.linkEditor.className = "ProseMirror-icon menuicon"; this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); this.linkText.style.cssFloat = "left"; @@ -231,8 +227,9 @@ export class TooltipTextMenu { }; this.linkDrag = document.createElement("img"); this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "20px"; - this.linkDrag.style.height = "20px"; + this.linkDrag.style.width = "15px"; + this.linkDrag.style.height = "15px"; + this.linkDrag.title = "Drag to create link"; this.linkDrag.style.color = "black"; this.linkDrag.style.background = "black"; this.linkDrag.style.cssFloat = "left"; @@ -250,10 +247,10 @@ export class TooltipTextMenu { hideSource: false }); }; - // this.linkEditor.appendChild(this.linkDrag); + this.linkEditor.appendChild(this.linkDrag); // this.linkEditor.appendChild(this.linkText); // this.linkEditor.appendChild(linkBtn); - //this.tooltip.appendChild(this.linkEditor); + this.tooltip.appendChild(this.linkEditor); } let node = this.view.state.selection.$from.nodeAfter; @@ -444,16 +441,24 @@ export class TooltipTextMenu { enable(state) { return !state.selection.empty; }, run: (state, dispatch, view) => { // to remove link + let curLink = ""; if (this.markActive(state, markType)) { - toggleMark(markType)(state, dispatch); - return true; + + let { from, $from, to, empty } = state.selection; + let node = state.doc.nodeAt(from); + node && node.marks.map(m => { + m.type === markType && (curLink = m.attrs.href); + }) + //toggleMark(markType)(state, dispatch); + //return true; } // to create link openPrompt({ title: "Create a link", fields: { href: new TextField({ - label: "Link target", + value: curLink, + label: "Link Target", required: true }), title: new TextField({ label: "Title" }) @@ -603,7 +608,6 @@ export class TooltipTextMenu { } } this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks)); - this.updateLinkMenu(); } //finds all active marks on selection in given group -- cgit v1.2.3-70-g09d2 From aedd283fb9f9eff4145e27658bc6647982256032 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 5 Jul 2019 00:34:19 -0400 Subject: beginning implementation of key value tags for all imported documents --- src/client/documents/Documents.ts | 12 +- .../util/Import & Export/DirectoryImportBox.scss | 0 .../util/Import & Export/DirectoryImportBox.tsx | 181 +++++++++++++++++++++ src/client/util/Import & Export/ImageImporter.tsx | 67 -------- src/client/util/Import & Export/ImportBox.tsx | 134 --------------- src/client/util/request-image-size.js | 6 +- src/client/views/MainView.tsx | 1 - src/client/views/collections/CollectionSubView.tsx | 4 +- src/client/views/nodes/DocumentContentsView.tsx | 4 +- src/client/views/nodes/ImageBox.tsx | 4 +- 10 files changed, 198 insertions(+), 215 deletions(-) create mode 100644 src/client/util/Import & Export/DirectoryImportBox.scss create mode 100644 src/client/util/Import & Export/DirectoryImportBox.tsx delete mode 100644 src/client/util/Import & Export/ImageImporter.tsx delete mode 100644 src/client/util/Import & Export/ImportBox.tsx (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5d637dd3a..26b5498a2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -36,7 +36,7 @@ import { UndoManager } from "../util/UndoManager"; import { RouteStore } from "../../server/RouteStore"; import { LinkManager } from "../util/LinkManager"; import { DocumentManager } from "../util/DocumentManager"; -import ImportBox from "../util/Import & Export/ImportBox"; +import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox"; var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -180,7 +180,7 @@ export namespace Docs { } function CreateImportPrototype(): Doc { - let importProto = setupPrototypeOptions(importProtoId, "IMPORT_PROTO", ImportBox.LayoutString(), { x: 0, y: 0, width: 600, height: 600, type: DocTypes.IMPORT }); + let importProto = setupPrototypeOptions(importProtoId, "IMPORT_PROTO", DirectoryImportBox.LayoutString(), { x: 0, y: 0, width: 600, height: 600, type: DocTypes.IMPORT }); return importProto; } @@ -347,7 +347,7 @@ export namespace Docs { return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); } - export async function getDocumentFromType(type: string, path: string, options: DocumentOptions, addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean): Promise> { + export async function getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise> { let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise)) | undefined = undefined; if (type.indexOf("image") !== -1) { ctor = Docs.ImageDocument; @@ -370,17 +370,17 @@ export namespace Docs { if (path.includes(window.location.hostname)) { let s = path.split('/'); let id = s[s.length - 1]; - DocServer.GetRefField(id).then(field => { + return DocServer.GetRefField(id).then(field => { if (field instanceof Doc) { let alias = Doc.MakeAlias(field); alias.x = options.x || 0; alias.y = options.y || 0; alias.width = options.width || 300; alias.height = options.height || options.width || 300; - addDocument && addDocument(alias, false); + return alias; } + return undefined; }); - return undefined; } ctor = Docs.WebDocument; options = { height: options.width, ...options, title: path, nativeWidth: undefined }; diff --git a/src/client/util/Import & Export/DirectoryImportBox.scss b/src/client/util/Import & Export/DirectoryImportBox.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx new file mode 100644 index 000000000..2d77f6ae6 --- /dev/null +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -0,0 +1,181 @@ +import "fs"; +import React = require("react"); +import { Doc } from "../../../new_fields/Doc"; +import { DocServer } from "../../DocServer"; +import { RouteStore } from "../../../server/RouteStore"; +import { action, observable, runInAction } from "mobx"; +import { FieldViewProps, FieldView } from "../../views/nodes/FieldView"; +import Measure, { ContentRect } from "react-measure"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArrowUp, faTag, faFileExcel } from '@fortawesome/free-solid-svg-icons'; +import { Docs, DocumentOptions } from "../../documents/Documents"; +import { EditableView } from "../../views/EditableView"; + +export default class DirectoryImportBox extends React.Component { + private selector = React.createRef(); + @observable private top = 0; + @observable private left = 0; + private dimensions = 50; + + @observable private key = "Key"; + @observable private value = "Value"; + + public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); } + + constructor(props: FieldViewProps) { + super(props); + library.add(faArrowUp, faTag); + } + + updateKey = (newKey: string) => { + runInAction(() => this.key = newKey); + console.log("KEY ", this.key); + return true; + } + + updateValue = (newValue: string) => { + runInAction(() => this.value = newValue); + console.log("VALUE ", this.value); + return true; + } + + handleSelection = async (e: React.ChangeEvent) => { + let promises: Promise[] = []; + let docs: Doc[] = []; + + let files = e.target.files; + if (!files || files.length === 0) return; + + let directory = (files.item(0) as any).webkitRelativePath.split("/", 1); + + for (let i = 0; i < files.length; i++) { + let uploaded_file = files.item(i); + + if (!uploaded_file) { + continue; + } + + let formData = new FormData(); + formData.append('file', uploaded_file); + let dropFileName = uploaded_file ? uploaded_file.name : "-empty-"; + let type = uploaded_file.type; + + let prom = fetch(DocServer.prepend(RouteStore.upload), { + method: 'POST', + body: formData + }).then(async (res: Response) => { + (await res.json()).map(action((file: any) => { + let path = DocServer.prepend(file); + console.log(path); + let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName }); + docPromise.then(doc => doc && docs.push(doc)); + })); + }); + promises.push(prom); + } + + await Promise.all(promises); + + let doc = this.props.Document; + let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") }; + let parent = this.props.ContainingCollectionView; + if (parent) { + let importContainer = Docs.StackingDocument(docs, options); + Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + this.props.removeDocument && this.props.removeDocument(doc); + } + } + + componentDidMount() { + this.selector.current!.setAttribute("directory", ""); + this.selector.current!.setAttribute("webkitdirectory", ""); + } + + @action + preserveCentering = (rect: ContentRect) => { + let bounds = rect.offset!; + if (bounds.width === 0 || bounds.height === 0) { + return; + } + let offset = this.dimensions / 2; + this.left = bounds.width / 2 - offset; + this.top = bounds.height / 2 - offset; + } + + render() { + let dimensions = 50; + let keyValueStyle = { paddingLeft: 5, width: "50%" }; + return ( + + {({ measureRef }) => +
    + +