From 8fd0f6f8b3ce585e58f388d856360373480ecdde Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 May 2020 16:53:49 -0400 Subject: fixed pdf text selection --- src/client/views/collections/CollectionTreeView.tsx | 2 +- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 8 ++++---- src/client/views/pdf/PDFViewer.scss | 4 ++++ src/client/views/pdf/PDFViewer.tsx | 4 +++- 4 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 3e99af724..87fe79e19 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -748,7 +748,7 @@ export class CollectionTreeView extends CollectionSubView (this.Document.scrollHeight || this.Document._nativeHeight || 0); panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0); @computed get overlayLayer() { - return
Date: Thu, 28 May 2020 18:43:14 -0400 Subject: added aimated transitions to presentations of frame animations --- .../collectionFreeForm/CollectionFreeFormView.tsx | 14 +++++++++++--- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 5 +++-- src/client/views/nodes/PresBox.tsx | 5 +++++ 3 files changed, 19 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9d10ff158..cb1228fed 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1350,8 +1350,14 @@ export class CollectionFreeFormView extends CollectionSubView - + {this.children} {this._timelineVisible ? : (null)} @@ -1445,6 +1451,7 @@ interface CollectionFreeFormViewPannableContentsProps { viewDefDivClick?: ScriptField; children: () => JSX.Element[]; shifted: boolean; + transition?: string; } @observer @@ -1459,7 +1466,8 @@ class CollectionFreeFormViewPannableContents extends React.Component {this.props.children()}
; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 682aed8f5..697111ac8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -96,12 +96,12 @@ export class CollectionFreeFormDocumentView extends DocComponent docs.forEach(doc => doc.transition = undefined), 1010); + setTimeout(() => docs.forEach(doc => doc.transition = "inherit"), 1010); } public static gotoKeyframe(docs: Doc[]) { docs.forEach(doc => doc.transition = "all 1s"); - setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010); + setTimeout(() => docs.forEach(doc => doc.transition = "inherit"), 1010); } public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) { @@ -119,6 +119,7 @@ export class CollectionFreeFormDocumentView extends DocComponent const lastFrame = Cast(presTargetDoc.lastFrame, "number", null); const curFrame = NumCast(presTargetDoc.currentFrame); if (lastFrame !== undefined && curFrame < lastFrame) { + //const docs = DocListCast(presTargetDoc[Doc.LayoutFieldKey(presTargetDoc)]); + presTargetDoc.transition = "all 1s"; + setTimeout(() => presTargetDoc.transition = undefined, 1010); + // docs.forEach(doc => doc.transition = "all 1s"); + // setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010); presTargetDoc.currentFrame = curFrame + 1; } else if (this.childDocs[this.itemIndex + 1] !== undefined) { -- cgit v1.2.3-70-g09d2 From 895e84fe6999a39eb9553db68b378aee5c1864a3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 29 May 2020 01:29:02 -0400 Subject: moved frame animation into freeform chrome. fixed marquees of docs at non-zero frame. fixed pile view to use its own _pileLayoutEngine key to avoid leaving colleciton in an animated state. added toggle for snap linew views. cached transforms in CollectionFreeformView for performance. --- src/client/views/MainView.tsx | 6 +- .../views/collections/CollectionPileView.tsx | 8 +- .../views/collections/CollectionViewChromes.scss | 60 +++++- .../views/collections/CollectionViewChromes.tsx | 201 +++++++++++++-------- .../collectionFreeForm/CollectionFreeFormView.scss | 26 --- .../collectionFreeForm/CollectionFreeFormView.tsx | 68 +++---- .../collections/collectionFreeForm/MarqueeView.tsx | 12 +- src/client/views/nodes/DocumentView.tsx | 9 +- src/client/views/nodes/PresBox.tsx | 3 - 9 files changed, 223 insertions(+), 170 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 958ba0ce3..a0a6adab7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -534,10 +534,10 @@ export class MainView extends React.Component { } @computed get snapLines() { - return
+ return !Doc.UserDoc().showSnapLines ? (null) :
- {/* {SnappingManager.horizSnapLines().map(l => )} - {SnappingManager.vertSnapLines().map(l => )} */} + {SnappingManager.horizSnapLines().map(l => )} + {SnappingManager.vertSnapLines().map(l => )}
; } diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 020a87b2e..fc48e0327 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -23,7 +23,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { @observable _childClickedScript: Opt; componentDidMount() { if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") { - this.Document._layoutEngine = "pass"; + this.Document._pileLayoutEngine = "pass"; } this._originalChrome = StrCast(this.layoutDoc._chromeStatus); this.layoutDoc._chromeStatus = "disabled"; @@ -34,7 +34,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { this.layoutDoc._chromeStatus = this._originalChrome; } - layoutEngine = () => StrCast(this.Document._layoutEngine); + layoutEngine = () => StrCast(this.Document._pileLayoutEngine); @computed get contents() { return
@@ -53,7 +53,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { Doc.pileup(this.childDocs); this.layoutDoc._panX = 0; this.layoutDoc._panY = -10; - this.props.Document._layoutEngine = 'pass'; + this.props.Document._pileLayoutEngine = 'pass'; } else { const defaultSize = 25; this.layoutDoc._overflow = 'visible'; @@ -67,7 +67,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { } this.layoutDoc._panX = this.layoutDoc._panY = 0; this.layoutDoc._width = this.layoutDoc._height = defaultSize; - this.props.Document._layoutEngine = 'starburst'; + this.props.Document._pileLayoutEngine = 'starburst'; } }); diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index e4581eb46..03bd9a01a 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -8,10 +8,12 @@ z-index: 9001; transition: top .5s; background: lightgrey; + transform-origin: top left; .collectionViewChrome { display: flex; - padding-bottom: 10px; + padding-bottom: 1px; + height:32px; border-bottom: .5px solid rgb(180, 180, 180); overflow: hidden; @@ -21,12 +23,12 @@ .collectionViewBaseChrome-viewPicker { font-size: 75%; //text-transform: uppercase; - letter-spacing: 2px; + //letter-spacing: 2px; background: rgb(238, 238, 238); color: grey; outline-color: black; border: none; - padding: 12px 10px 11px 10px; + //padding: 12px 10px 11px 10px; } .collectionViewBaseChrome-viewPicker:active { @@ -47,6 +49,7 @@ .collectionViewBaseChrome-cmdPicker { margin-left: 3px; margin-right: 0px; + font-size: 75%; background: rgb(238, 238, 238); border: none; color: grey; @@ -56,6 +59,7 @@ background-color: gray; display: flex; flex-direction: row; + height:30px; .commandEntry-drop { color:white; width:25px; @@ -95,8 +99,8 @@ margin: auto; background: gray; color: white; - width: 40px; - height: 40px; + width: 30px; + height: 30px; align-items: center; justify-content: center; } @@ -195,8 +199,7 @@ .collectionTreeViewChrome-pivotField-label { vertical-align: center; padding-left: 10px; - padding-top: 10px; - padding-bottom: 10px; + margin:auto; } .collectionStackingViewChrome-pivotField, @@ -204,14 +207,15 @@ color: white; width:100%; min-width: 100px; - text-align: center; + display: flex; + align-items: center; background: rgb(238, 238, 238); .editable-view-input, input, .editableView-container-editing-oneLine, .editableView-container-editing { - padding: 12px 10px 11px 10px; + margin:auto; border: 0px; color: grey; text-align: center; @@ -235,6 +239,44 @@ } } +.collectionFreeFormViewChrome-cont { + width: 60px; + display: flex; + position: relative; + align-items: center; + .fwdKeyframe, .numKeyframe, .backKeyframe { + cursor: pointer; + position: absolute; + width: 20; + height: 30; + bottom: 0; + background: gray; + display: flex; + align-items: center; + color:white; + } + .backKeyframe { + left:0; + svg { + display:block; + margin:auto; + } + } + .numKeyframe { + left:20; + display: flex; + flex-direction: column; + padding: 5px; + } + .fwdKeyframe { + left:40; + svg { + display:block; + margin:auto; + } + } +} + .collectionSchemaViewChrome-cont { display: flex; font-size: 10.5px; diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 8c4d53d63..29a3e559a 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -15,6 +15,7 @@ import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss"; import { CollectionViewType } from "./CollectionView"; import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; const datepicker = require('js-datepicker'); interface CollectionViewChromeProps { @@ -44,7 +45,7 @@ export class CollectionViewBaseChrome extends React.Component click clicked open view", + params: ["target", "source"], title: "=> child click view", script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])", immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, @@ -83,22 +84,16 @@ export class CollectionViewBaseChrome extends React.Component(); @observable private _currentKey: string = ""; - componentDidMount = () => { - runInAction(() => { - // chrome status is one of disabled, collapsed, or visible. this determines initial state from document - const chromeStatus = this.props.CollectionView.props.Document._chromeStatus; - if (chromeStatus) { - if (chromeStatus === "disabled") { - throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!"); - } - else if (chromeStatus === "collapsed") { - if (this.props.collapse) { - this.props.collapse(true); - } - } - } - }); - } + componentDidMount = action(() => { + // chrome status is one of disabled, collapsed, or visible. this determines initial state from document + switch (this.props.CollectionView.props.Document._chromeStatus) { + case "disabled": + throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!"); + case "collapsed": + this.props.collapse?.(true); + break; + } + }) @undoBatch viewChanged = (e: React.ChangeEvent) => { @@ -197,10 +192,11 @@ export class CollectionViewBaseChrome extends React.Component { + @computed get subChrome() { const collapsed = this.document._chromeStatus !== "enabled"; if (collapsed) return null; switch (this.props.type) { + case CollectionViewType.Freeform: return (); case CollectionViewType.Stacking: return (); case CollectionViewType.Schema: return (); case CollectionViewType.Tree: return (); @@ -236,7 +232,7 @@ export class CollectionViewBaseChrome extends React.Component this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, }; @@ -254,14 +250,61 @@ export class CollectionViewBaseChrome extends React.Component +
+
+ +
+ +
+
; + } + + @computed get viewModes() { + const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; + return
+
+
+ +
+ +
+
; + } + render() { const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; + const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale); return (
@@ -276,58 +319,71 @@ export class CollectionViewBaseChrome extends React.Component -
-
-
- -
- -
-
+ {this.viewModes}
-
-
-
- -
- -
-
+ {this.templateChrome}
- {this.subChrome()} + {this.subChrome}
); } } +@observer +export class CollectionFreeFormViewChrome extends React.Component { + + get Document() { return this.props.CollectionView.props.Document; } + @computed get dataField() { + return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)]; + } + @computed get childDocs() { + return DocListCast(this.dataField); + } + @undoBatch + @action + nextKeyframe = (): void => { + const currentFrame = NumCast(this.Document.currentFrame); + if (currentFrame === undefined) { + this.Document.currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); + } + CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); + this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1); + this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame)); + } + @undoBatch + @action + prevKeyframe = (): void => { + const currentFrame = NumCast(this.Document.currentFrame); + if (currentFrame === undefined) { + this.Document.currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); + } + CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); + this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1); + } + render() { + return this.Document.isAnnotationOverlay ? (null) : +
+
+ +
+
this.Document.editing = !this.Document.editing)} > + {NumCast(this.Document.currentFrame)} +
+
+ +
+
; + } +} + @observer export class CollectionStackingViewChrome extends React.Component { @observable private _currentKey: string = ""; @@ -474,19 +530,20 @@ export class CollectionSchemaViewChrome extends React.Component { - get dataExtension() { - return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "_ext"] as Doc; + get sortAscending() { + return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"]; } - @computed private get descending() { - return this.dataExtension && Cast(this.dataExtension.sortAscending, "boolean", null); + set sortAscending(value) { + this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value; + } + @computed private get ascending() { + return Cast(this.sortAscending, "boolean", null); } @action toggleSort = () => { - if (this.dataExtension) { - if (this.dataExtension.sortAscending) this.dataExtension.sortAscending = undefined; - else if (this.dataExtension.sortAscending === undefined) this.dataExtension.sortAscending = false; - else this.dataExtension.sortAscending = true; - } + if (this.sortAscending) this.sortAscending = undefined; + else if (this.sortAscending === undefined) this.sortAscending = false; + else this.sortAscending = true; } render() { @@ -496,7 +553,7 @@ export class CollectionTreeViewChrome extends React.Component Sort
-
+
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 5478a1c4a..d9011c9d3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -41,32 +41,6 @@ // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions. touch-action: none; - .fwdKeyframe, .numKeyframe, .backKeyframe{ - cursor: pointer; - position: absolute; - width: 20; - height:20; - bottom:0; - background:gray; - } - .backKeyframe { - right:45; - svg { - display:block; - margin:auto; - } - } - .numKeyframe { - right:25; - text-align:center; - } - .fwdKeyframe { - right:5; - svg { - display:block; - margin:auto; - } - } .collectionfreeformview-placeholder { background: gray; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cb1228fed..c753a703d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed, trace } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc"; @@ -118,12 +118,28 @@ export class CollectionFreeFormView extends CollectionSubView !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections - private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 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()); - private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1); - private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); - private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); + @computed get cachedCenteringShiftX(): number { + return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections + } + @computed get cachedCenteringShiftY(): number { + return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections + } + @computed get cachedGetLocalTransform(): Transform { + return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); + } + @computed get cachedGetContainerTransform(): Transform { + return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); + } + @computed get cachedGetTransform(): Transform { + return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); + } + + private centeringShiftX = () => this.cachedCenteringShiftX; + private centeringShiftY = () => this.cachedCenteringShiftY; + private getTransform = () => this.cachedGetTransform.copy(); + private getLocalTransform = () => this.cachedGetLocalTransform.copy(); + private getContainerTransform = () => this.cachedGetContainerTransform.copy(); + private getTransformOverlay = () => this.getContainerTransform().translate(1, 1); private addLiveTextBox = (newBox: Doc) => { FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newBox); @@ -162,30 +178,6 @@ export class CollectionFreeFormView extends CollectionSubView { - const currentFrame = this.Document.currentFrame; - if (currentFrame === undefined) { - this.Document.currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); - } - CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); - this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1); - this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame)); - } - @undoBatch - @action - prevKeyframe = (): void => { - const currentFrame = this.Document.currentFrame; - if (currentFrame === undefined) { - this.Document.currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); - } - CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); - this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1); - } - private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); @@ -1219,6 +1211,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); + optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }); optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); @@ -1397,18 +1390,7 @@ export class CollectionFreeFormView extends CollectionSubView 0 ? this.placeholder : this.marqueeView} - {this.isAnnotationOverlay || !this.props.isSelected() || this.props.Document._viewType === CollectionViewType.Pile ? (null) : - <> -
- -
-
this.Document.editing = !this.Document.editing)} > - {NumCast(this.Document.currentFrame)} -
-
- -
- } +
{ - //this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + const dx = NumCast(d.x); + const dy = NumCast(d.y); + delete d.x; + delete d.y; + delete d.activeFrame; + delete d.displayTimecode; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + d.x = dx - bounds.left - bounds.width / 2; + d.y = dy - bounds.top - bounds.height / 2; return d; })); this.props.removeDocument(selected); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 00d19752f..2de7b3460 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -965,9 +965,6 @@ export class DocumentView extends DocComponent(Docu return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; } childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); - panelWidth = () => this.props.PanelWidth(); - panelHeight = () => this.props.PanelHeight(); - screenToLocalTransform = () => this.props.ScreenToLocalTransform(); @computed get contents() { TraceMobx(); return (<> @@ -988,10 +985,10 @@ export class DocumentView extends DocComponent(Docu addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument} - ScreenToLocalTransform={this.screenToLocalTransform} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} renderDepth={this.props.renderDepth} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} + PanelWidth={this.props.PanelWidth} + PanelHeight={this.props.PanelHeight} ignoreAutoHeight={this.props.ignoreAutoHeight} focus={this.props.focus} parentActive={this.props.parentActive} diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 56a543f46..9f1e99c77 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -62,11 +62,8 @@ export class PresBox extends ViewBoxBaseComponent const lastFrame = Cast(presTargetDoc.lastFrame, "number", null); const curFrame = NumCast(presTargetDoc.currentFrame); if (lastFrame !== undefined && curFrame < lastFrame) { - //const docs = DocListCast(presTargetDoc[Doc.LayoutFieldKey(presTargetDoc)]); presTargetDoc.transition = "all 1s"; setTimeout(() => presTargetDoc.transition = undefined, 1010); - // docs.forEach(doc => doc.transition = "all 1s"); - // setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010); presTargetDoc.currentFrame = curFrame + 1; } else if (this.childDocs[this.itemIndex + 1] !== undefined) { -- cgit v1.2.3-70-g09d2 From e23602188d575c3f2303bfc85114ac82561c7bbe Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 29 May 2020 13:14:59 -0400 Subject: added toggle to switch from fade to default keyframe. added mixBlendMode for collections --- src/client/views/animationtimeline/Keyframe.tsx | 60 +++++++++------------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 + 2 files changed, 26 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index 92b0f05b3..e3274fb35 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -12,11 +12,9 @@ import { Transform } from "../../util/Transform"; import { TimelineMenu } from "./TimelineMenu"; import { Docs } from "../../documents/Documents"; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; import { emptyPath } from "../../../Utils"; - /** * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also */ @@ -100,16 +98,11 @@ export namespace KeyframeFunc { export const convertPixelTime = (pos: number, unit: "mili" | "sec" | "min" | "hr", dir: "pixel" | "time", tickSpacing: number, tickIncrement: number) => { const time = dir === "pixel" ? (pos * tickSpacing) / tickIncrement : (pos / tickSpacing) * tickIncrement; switch (unit) { - case "mili": - return time; - case "sec": - return dir === "pixel" ? time / 1000 : time * 1000; - case "min": - return dir === "pixel" ? time / 60000 : time * 60000; - case "hr": - return dir === "pixel" ? time / 3600000 : time * 3600000; - default: - return time; + case "mili": return time; + case "sec": return dir === "pixel" ? time / 1000 : time * 1000; + case "min": return dir === "pixel" ? time / 60000 : time * 60000; + case "hr": return dir === "pixel" ? time / 3600000 : time * 3600000; + default: return time; } }; } @@ -333,31 +326,28 @@ export class Keyframe extends React.Component { */ @action makeKeyframeMenu = (kf: Doc, e: MouseEvent) => { - TimelineMenu.Instance.addItem("button", "Show Data", () => { - runInAction(() => { + TimelineMenu.Instance.addItem("button", "Toggle Fade Only", () => { + kf.type = kf.type === KeyframeFunc.KeyframeType.fade ? KeyframeFunc.KeyframeType.default : KeyframeFunc.KeyframeType.fade; + }), + TimelineMenu.Instance.addItem("button", "Show Data", action(() => { const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 }); CollectionDockingView.AddRightSplit(kvp, emptyPath); - }); - }), - TimelineMenu.Instance.addItem("button", "Delete", () => { - runInAction(() => { - (this.regiondata.keyframes as List).splice(this.keyframes.indexOf(kf), 1); - this.forceUpdate(); - }); - }), - TimelineMenu.Instance.addItem("input", "Move", (val) => { - runInAction(() => { - let cannotMove: boolean = false; - const kfIndex: number = this.keyframes.indexOf(kf); - if (val < 0 || (val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time))) { - cannotMove = true; - } - if (!cannotMove) { - this.keyframes[kfIndex].time = parseInt(val, 10); - this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn; - } - }); - }); + })), + TimelineMenu.Instance.addItem("button", "Delete", action(() => { + (this.regiondata.keyframes as List).splice(this.keyframes.indexOf(kf), 1); + this.forceUpdate(); + })), + TimelineMenu.Instance.addItem("input", "Move", action((val) => { + let cannotMove: boolean = false; + const kfIndex: number = this.keyframes.indexOf(kf); + if (val < 0 || (val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time))) { + cannotMove = true; + } + if (!cannotMove) { + this.keyframes[kfIndex].time = parseInt(val, 10); + this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn; + } + })); TimelineMenu.Instance.addMenu("Keyframe"); TimelineMenu.Instance.openMenu(e.clientX, e.clientY); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 697111ac8..910aa744d 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -154,6 +154,7 @@ export class CollectionFreeFormDocumentView extends DocComponent -- cgit v1.2.3-70-g09d2 From 5c4a948d506de2497d685f9ccba56ef6a855af1d Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 29 May 2020 21:52:00 -0400 Subject: added a version of client-side ACL's --- src/client/views/animationtimeline/Keyframe.tsx | 8 ++--- src/client/views/collections/CollectionView.tsx | 16 ++++++---- src/client/views/nodes/DocumentContentsView.tsx | 4 +-- src/client/views/nodes/DocumentView.tsx | 6 +++- src/fields/Doc.ts | 40 ++++++++++++++++++++++--- src/fields/util.ts | 10 +++++-- 6 files changed, 66 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index e3274fb35..b562bd957 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -180,10 +180,10 @@ export class Keyframe extends React.Component { const fadeIn = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade); const fadeOut = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, KeyframeFunc.KeyframeType.fade); const finish = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration, KeyframeFunc.KeyframeType.end); - (fadeIn.key as Doc).opacity = 1; - (fadeOut.key as Doc).opacity = 1; - (start.key as Doc).opacity = 0.1; - (finish.key as Doc).opacity = 0.1; + (fadeIn as Doc).opacity = 1; + (fadeOut as Doc).opacity = 1; + (start as Doc).opacity = 0.1; + (finish as Doc).opacity = 0.1; this.forceUpdate(); //not needed, if setTimeout is gone... }, 1000); } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index ee4755355..fecba32c5 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { DateField } from '../../../fields/DateField'; -import { DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, Field, Opt, AclSym, AclAddonly, AclReadonly } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; @@ -126,10 +126,16 @@ export class CollectionView extends Touchable !docList.includes(d)); if (added.length) { - added.map(doc => doc.context = this.props.Document); - added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add)); - targetDataDoc[this.props.fieldKey] = new List([...docList, ...added]); - targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + if (this.dataDoc[AclSym] === AclReadonly) { + return false; + } else if (this.dataDoc[AclSym] === AclAddonly) { + added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc)); + } else { + added.map(doc => doc.context = this.props.Document); + added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add)); + targetDataDoc[this.props.fieldKey] = new List([...docList, ...added]); + targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + } } return true; } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index ef56e6fcd..126e9ac14 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,6 +1,6 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, Field } from "../../../fields/Doc"; +import { Doc, Opt, Field, AclSym, AclPrivate } from "../../../fields/Doc"; import { Cast, StrCast, NumCast } from "../../../fields/Types"; import { OmitKeys, Without, emptyPath } from "../../../Utils"; import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; @@ -185,7 +185,7 @@ export class DocumentContentsView extends React.Component 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns - return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) : + return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || this.layoutDoc[AclSym] === AclPrivate) ? (null) : (Docu const more = cm.findByDescription("More..."); const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : []; + moreItems.push({ description: "Make Add Only", event: () => this.layoutDoc.ACL = this.dataDoc.ACL = "addOnly", icon: "concierge-bell" }); + moreItems.push({ description: "Make Read Only", event: () => this.layoutDoc.ACL = this.dataDoc.ACL = "readOnly", icon: "concierge-bell" }); + moreItems.push({ description: "Make Private", event: () => this.layoutDoc[AclSym] = this.dataDoc[AclSym] = "noAccess", icon: "concierge-bell" }); moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); @@ -1119,6 +1122,7 @@ export class DocumentView extends DocComponent(Docu } render() { + if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null); if (!(this.props.Document instanceof Doc)) return (null); const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document); const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null))); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 1ea686cbb..6891bf652 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -93,13 +93,40 @@ export const WidthSym = Symbol("Width"); export const HeightSym = Symbol("Height"); export const DataSym = Symbol("Data"); export const LayoutSym = Symbol("Layout"); +export const AclSym = Symbol("Acl"); +export const AclPrivate = Symbol("AclNoAccess"); +export const AclReadonly = Symbol("AclReadOnly"); +export const AclAddonly = Symbol("AclAddonly"); export const UpdatingFromServer = Symbol("UpdatingFromServer"); const CachedUpdates = Symbol("Cached updates"); function fetchProto(doc: Doc) { + if (doc.author !== Doc.CurrentUserEmail) { + if (doc.ACL === "noAccess") { + doc[AclSym] = AclPrivate; + return undefined; + } else if (doc.ACL === "readOnly") { + doc[AclSym] = AclReadonly; + } else if (doc.ACL === "addOnly") { + doc[AclSym] = AclAddonly; + } + } + const proto = doc.proto; if (proto instanceof Promise) { + proto.then(proto => { + if (proto.author !== Doc.CurrentUserEmail) { + if (proto.ACL === "noAccess") { + proto[AclSym] = doc[AclSym] = AclPrivate; + return undefined; + } else if (proto.ACL === "readOnly") { + proto[AclSym] = doc[AclSym] = AclReadonly; + } else if (proto.ACL === "addOnly") { + proto[AclSym] = doc[AclSym] = AclAddonly; + } + } + }); return proto; } } @@ -113,10 +140,10 @@ export class Doc extends RefField { set: setter, get: getter, // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter - has: (target, key) => key in target.__fields, + has: (target, key) => target[AclSym] !== AclPrivate && key in target.__fields, ownKeys: target => { const obj = {} as any; - Object.assign(obj, target.___fields); + (target[AclSym] !== AclPrivate) && Object.assign(obj, target.___fields); runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); return Object.keys(obj); }, @@ -170,8 +197,11 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; + public [AclSym]: any = undefined; public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); + public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } + public [ToString]() { return `Doc(${this[AclSym] === AclPrivate ? "-inaccessible-" : this.title})`; } public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } public get [DataSym]() { const self = this[SelfProxy]; @@ -193,8 +223,6 @@ export class Doc extends RefField { return undefined; } - [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } - [ToString]() { return `Doc(${this.title})`; } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; public static CurrentUserEmail: string = ""; @@ -794,6 +822,7 @@ export namespace Doc { } // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) export function IsBrushedDegreeUnmemoized(doc: Doc) { + if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return 0; return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; } export function IsBrushedDegree(doc: Doc) { @@ -802,11 +831,13 @@ export namespace Doc { })(doc); } export function BrushDoc(doc: Doc) { + if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc; brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetProto(doc), true); return doc; } export function UnBrushDoc(doc: Doc) { + if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc; brushManager.BrushedDoc.delete(doc); brushManager.BrushedDoc.delete(Doc.GetProto(doc)); return doc; @@ -836,6 +867,7 @@ export namespace Doc { } const highlightManager = new HighlightBrush(); export function IsHighlighted(doc: Doc) { + if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return false; return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); } export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { diff --git a/src/fields/util.ts b/src/fields/util.ts index 024c0f80e..54e7eca28 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,5 +1,5 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym } from "./Doc"; +import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; @@ -106,6 +106,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe "LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; + if (target[AclSym]) return true; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { console.log(prop + " is deprecated - switch to _" + prop); @@ -124,6 +125,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { let prop = in_prop; + if (in_prop === AclSym) return target[AclSym]; + if (target[AclSym] === AclPrivate) return undefined; if (prop === LayoutSym) { return target.__LAYOUT__; } @@ -148,6 +151,9 @@ export function getter(target: any, in_prop: string | symbol | number, receiver: function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any { receiver = receiver || target[SelfProxy]; + if (target === undefined) { + console.log(""); + } let field = target.__fields[prop]; for (const plugin of getterPlugins) { const res = plugin(receiver, prop, field); @@ -160,7 +166,7 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP } if (field === undefined && !ignoreProto && prop !== "proto") { const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters - if (proto instanceof Doc) { + if (proto instanceof Doc && proto[AclSym] !== AclPrivate) { return getFieldImpl(proto[Self], prop, receiver, ignoreProto); } return undefined; -- cgit v1.2.3-70-g09d2 From 1681c03a0509c2e8989fe268e7997dbe912770ec Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 30 May 2020 15:42:07 -0700 Subject: buxton pdf linking implementation --- src/client/documents/Documents.ts | 25 +++++-- .../views/collections/CollectionTreeView.tsx | 7 +- src/scraping/buxton/final/BuxtonImporter.ts | 74 +++++++++++++++++---- .../buxton/final/assets/pdfs/3DCad_Brochure.pdf | Bin 0 -> 107790 bytes 4 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdf (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7f5b62f22..f7e19eecd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -432,17 +432,28 @@ export namespace Docs { parentProto.data = new List(); } if (device) { - const { __images } = device; + const { title, __images, additionalMedia } = device; delete device.__images; + delete device.additionalMedia; const { ImageDocument, StackingDocument } = Docs.Create; const constructed = __images.map(({ url, nativeWidth, nativeHeight }) => ({ url: Utils.prepend(url), nativeWidth, nativeHeight })); - const deviceImages = constructed.map(({ url, nativeWidth, nativeHeight }, i) => ImageDocument(url, { - title: `image${i}.${extname(url)}`, - _nativeWidth: nativeWidth, - _nativeHeight: nativeHeight - })); + const deviceImages = constructed.map(({ url, nativeWidth, nativeHeight }, i) => { + const imageDoc = ImageDocument(url, { + title: `image${i}.${extname(url)}`, + _nativeWidth: nativeWidth, + _nativeHeight: nativeHeight + }); + const media = additionalMedia[i]; + if (media) { + for (const key of Object.keys(media)) { + imageDoc[`additionalMedia_${key}`] = Utils.prepend(`/files/${key}/buxton/${media[key]}`); + } + } + return imageDoc; + }); // the main document we create - const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true, hero: new ImageField(constructed[0].url) }); + const doc = StackingDocument(deviceImages, { title, _LODdisable: true, hero: new ImageField(constructed[0].url) }); + doc.nameAliases = new List([title.toLowerCase()]); // add the parsed attributes to this main document Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } }); Doc.AddDocToList(parentProto, "data", doc); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 87fe79e19..4d67b1e2c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -717,7 +717,7 @@ export class CollectionTreeView extends CollectionSubView { - const { ImageDocument } = Docs.Create; + const { ImageDocument, PdfDocument } = Docs.Create; const { Document } = this.props; const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif"; const detailView = Cast(Cast(Doc.UserDoc()["template-button-detail"], Doc, null)?.dragFactory, Doc, null); @@ -726,13 +726,14 @@ export class CollectionTreeView extends CollectionSubView { DocListCast(d.data).map((img, i) => { const caption = (d.captions as any)[i]; if (caption) { Doc.GetProto(img).caption = caption; - Doc.GetProto(img).doubleClickView = doubleClickView; + Doc.GetProto(img).doubleClickView = (pdfContent = StrCast(img.additionalMedia_pdfs)) ? PdfDocument(pdfContent) : fallback; } }); Doc.GetProto(d).type = "buxton"; diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts index e55850b29..684c00c0d 100644 --- a/src/scraping/buxton/final/BuxtonImporter.ts +++ b/src/scraping/buxton/final/BuxtonImporter.ts @@ -1,4 +1,4 @@ -import { readdirSync, writeFile, mkdirSync } from "fs"; +import { readdirSync, writeFile, mkdirSync, createReadStream, createWriteStream, existsSync, statSync } from "fs"; import * as path from "path"; import { red, cyan, yellow } from "colors"; import { Utils } from "../../../Utils"; @@ -9,6 +9,7 @@ const createImageSizeStream = require("image-size-stream"); import { parseXml } from "libxmljs"; import { strictEqual } from "assert"; import { Readable, PassThrough } from "stream"; +import { Directory, serverPathToFile, pathToDirectory } from "../../../server/ApiManagers/UploadManager"; /** * This is an arbitrary bundle of data that gets populated @@ -18,8 +19,7 @@ interface DocumentContents { body: string; imageData: ImageData[]; hyperlinks: string[]; - captions: string[]; - embeddedFileNames: string[]; + tableData: TableData[]; longDescription: string; } @@ -40,6 +40,7 @@ export interface DeviceDocument { secondaryKey: string; attribute: string; __images: ImageData[]; + additionalMedia: ({ [type: string]: string } | undefined)[]; hyperlinks: string[]; captions: string[]; // from the table column embeddedFileNames: string[]; // from the table column @@ -255,6 +256,8 @@ const FormatMap = new Map>([ ]); const sourceDir = path.resolve(__dirname, "source"); // where the Word documents are assumed to be stored +const assetDir = path.resolve(__dirname, "assets"); // where any additional media content like pdfs will be stored. Each subdirectory of this +// must follow the enum Directory. naming scheme const outDir = path.resolve(__dirname, "json"); // where the JSON output of these device documents will be written const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton"); // where, in the server, these images will be written const successOut = "buxton.json"; // the JSON list representing properly formatted documents @@ -277,18 +280,45 @@ export default async function executeImport(emitter: ResultCallback, terminator: rimraf.sync(dir); mkdirSync(dir); }); + await transferAssets(); return parseFiles(wordDocuments, emitter, terminator); } catch (e) { const message = [ "Unable to find a source directory.", - "Please ensure that the following directory exists and is populated with Word documents:", - `${sourceDir}` + "Please ensure that the following directory exists:", + `${e.message}` ].join('\n'); console.log(red(message)); return { error: message }; } } +/** + * Builds a mirrored directory structure of all media / asset files + * within the server's public directory. + */ +async function transferAssets() { + for (const assetType of readdirSync(assetDir)) { + const subroot = path.resolve(assetDir, assetType); + if (!statSync(subroot).isDirectory()) { + continue; + } + const outputSubroot = serverPathToFile(assetType as Directory, "buxton"); + if (existsSync(outputSubroot)) { + continue; + } else { + mkdirSync(outputSubroot); + } + for (const fileName of readdirSync(subroot)) { + const readStream = createReadStream(path.resolve(subroot, fileName)); + const writeStream = createWriteStream(path.resolve(outputSubroot, fileName)); + await new Promise(resolve => { + readStream.pipe(writeStream).on("close", resolve); + }); + } + } +} + /** * Parse every Word document in the directory, notifying any callers as needed * at each iteration via the emitter. @@ -356,6 +386,16 @@ const xPaths = { hyperlinks: '//*[name()="Relationship" and contains(@Type, "hyperlink")]' }; +interface TableData { + fileName: string; + caption: string; + additionalMedia?: { [type: string]: string }; +} + +const SuffixDirectoryMap = new Map([ + ["p", Directory.pdfs] +]); + /** * The meat of the script, images and text content are extracted here * @param pathToDocument the path to the document relative to the root of the zip @@ -370,8 +410,7 @@ async function extractFileContents(pathToDocument: string): Promise Utilities.correctSentences(node.text()).transformed!); @@ -382,7 +421,7 @@ async function extractFileContents(pathToDocument: string): Promise node.text().trim()); const { length } = tableRowsFlattened; - const numCols = 3; + const numCols = 4; strictEqual(length > numCols, true, "No captions written."); // first row has the headers, not content strictEqual(length % numCols === 0, true, "Improper caption formatting."); @@ -392,8 +431,14 @@ async function extractFileContents(pathToDocument: string): Promise { * @param contents the data already computed / parsed by extractFileContents */ function analyze(fileName: string, contents: DocumentContents): AnalysisResult { - const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescription } = contents; + const { body, imageData, hyperlinks, tableData, longDescription } = contents; const device: any = { hyperlinks, - captions, - embeddedFileNames, + captions: tableData.map(({ caption }) => caption), + embeddedFileNames: tableData.map(({ fileName }) => fileName), + additionalMedia: tableData.map(({ additionalMedia }) => additionalMedia), longDescription, __images: imageData }; diff --git a/src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdf b/src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdf new file mode 100644 index 000000000..4746d2f41 Binary files /dev/null and b/src/scraping/buxton/final/assets/pdfs/3DCad_Brochure.pdf differ -- cgit v1.2.3-70-g09d2 From 85721c9ed95b4c026d0a1c7891e1fee311e9f50e Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 30 May 2020 17:11:23 -0700 Subject: buxton pdf fix --- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 14 ++++++- src/server/ApiManagers/PDFManager.ts | 43 +++++++++++----------- 3 files changed, 34 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 4d67b1e2c..b2e1c0f73 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -733,7 +733,7 @@ export class CollectionTreeView extends CollectionSubView this._showWaiting = this._showCover = true); this.props.startupLive && this.setupPdfJsViewer(); this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => { diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts index 0136b758e..d2a9e9cce 100644 --- a/src/server/ApiManagers/PDFManager.ts +++ b/src/server/ApiManagers/PDFManager.ts @@ -7,54 +7,54 @@ import { createCanvas } from "canvas"; const imageSize = require("probe-image-size"); import * as express from "express"; import * as path from "path"; -import { Directory, serverPathToFile, clientPathToFile } from "./UploadManager"; +import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from "./UploadManager"; import { red } from "colors"; +import { resolve } from "path"; export default class PDFManager extends ApiManager { protected initialize(register: Registration): void { register({ - method: Method.GET, - subscription: new RouteSubscriber("thumbnail").add("filename"), - secureHandler: ({ req, res }) => getOrCreateThumbnail(req.params.filename, res) + method: Method.POST, + subscription: new RouteSubscriber("thumbnail"), + secureHandler: async ({ req, res }) => { + const { coreFilename, pageNum, subtree } = req.body; + return getOrCreateThumbnail(coreFilename, pageNum, res, subtree); + } }); } } -async function getOrCreateThumbnail(thumbnailName: string, res: express.Response): Promise { - const noExtension = thumbnailName.substring(0, thumbnailName.length - ".png".length); - const pageString = noExtension.split('-')[1]; - const pageNumber = parseInt(pageString); +async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string): Promise { + const resolved = `${coreFilename}-${pageNum}.png`; return new Promise(async resolve => { - const path = serverPathToFile(Directory.pdf_thumbnails, thumbnailName); + const path = serverPathToFile(Directory.pdf_thumbnails, resolved); if (existsSync(path)) { const existingThumbnail = createReadStream(path); const { err, viewport } = await new Promise(resolve => { imageSize(existingThumbnail, (err: any, viewport: any) => resolve({ err, viewport })); }); if (err) { - console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${thumbnailName}:`)); + console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${resolved}:`)); console.log(err); return; } - dispatchThumbnail(res, viewport, thumbnailName); + dispatchThumbnail(res, viewport, resolved); } else { - const offset = thumbnailName.length - pageString.length - 5; - const name = thumbnailName.substring(0, offset) + ".pdf"; - const path = serverPathToFile(Directory.pdfs, name); - await CreateThumbnail(path, pageNumber, res); + await CreateThumbnail(coreFilename, pageNum, res, subtree); } resolve(); }); } -async function CreateThumbnail(file: string, pageNumber: number, res: express.Response) { - const documentProxy = await Pdfjs.getDocument(file).promise; +async function CreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string) { + const sourcePath = resolve(pathToDirectory(Directory.pdfs), `${subtree ?? ""}${coreFilename}.pdf`); + const documentProxy = await Pdfjs.getDocument(sourcePath).promise; const factory = new NodeCanvasFactory(); - const page = await documentProxy.getPage(pageNumber); + const page = await documentProxy.getPage(pageNum); const viewport = page.getViewport(1 as any); const { canvas, context } = factory.create(viewport.width, viewport.height); const renderContext = { @@ -64,14 +64,13 @@ async function CreateThumbnail(file: string, pageNumber: number, res: express.Re }; await page.render(renderContext).promise; const pngStream = canvas.createPNGStream(); - const filenames = path.basename(file).split("."); - const thumbnailName = `${filenames[0]}-${pageNumber}.png`; - const pngFile = serverPathToFile(Directory.pdf_thumbnails, thumbnailName); + const resolved = `${coreFilename}-${pageNum}.png`; + const pngFile = serverPathToFile(Directory.pdf_thumbnails, resolved); const out = createWriteStream(pngFile); pngStream.pipe(out); return new Promise((resolve, reject) => { out.on("finish", () => { - dispatchThumbnail(res, viewport, thumbnailName); + dispatchThumbnail(res, viewport, resolved); resolve(); }); out.on("error", error => { -- cgit v1.2.3-70-g09d2 From dc42fe7c83b86a53839994f2eae1ef545fbd5a9d Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 30 May 2020 21:17:55 -0400 Subject: fixed private ACL. added event so that crhome extension can tell Dash which document to navigate to. --- package-lock.json | 88 +++++++++++++++++++++++++++++++++ package.json | 2 + src/client/util/DocumentManager.ts | 14 +++--- src/client/views/MainView.tsx | 10 ++++ src/client/views/nodes/DocumentView.tsx | 8 +-- 5 files changed, 111 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 21e4c4e27..842553659 100644 --- a/package-lock.json +++ b/package-lock.json @@ -412,6 +412,15 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, + "@types/chrome": { + "version": "0.0.114", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.114.tgz", + "integrity": "sha512-i7qRr74IrxHtbnrZSKUuP5Uvd5EOKwlwJq/yp7+yTPihOXnPhNQO4Z5bqb1XTnrjdbUKEJicaVVbhcgtRijmLA==", + "requires": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, "@types/color": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz", @@ -547,6 +556,19 @@ "express-validator": "*" } }, + "@types/filesystem": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.29.tgz", + "integrity": "sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==", + "requires": { + "@types/filewriter": "*" + } + }, + "@types/filewriter": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.28.tgz", + "integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=" + }, "@types/formidable": { "version": "1.0.31", "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-1.0.31.tgz", @@ -584,6 +606,11 @@ "integrity": "sha512-L8O9HAVFZj0TuiS8h5ORthiMsrrhjxTC8XUusp5k47oXCst4VTm+qWKvrAvmYMybZVokbp4Udco1mNwJrTNZPQ==", "dev": true }, + "@types/har-format": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.4.tgz", + "integrity": "sha512-iUxzm1meBm3stxUMzRqgOVHjj4Kgpgu5w9fm4X7kPRfSgVRzythsucEN7/jtOo8SQzm+HfcxWWzJS0mJDH/3DQ==" + }, "@types/jquery": { "version": "3.3.36", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.36.tgz", @@ -3253,6 +3280,15 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, + "chrome": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chrome/-/chrome-0.1.0.tgz", + "integrity": "sha1-9h2beS/v6MGUxwVt3BAscmqGQyk=", + "requires": { + "exeq": "^2.2.0", + "plist": "^1.1.0" + } + }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -5290,6 +5326,15 @@ "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" }, + "exeq": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/exeq/-/exeq-2.4.0.tgz", + "integrity": "sha1-Td8qaEZIxCeteZNJzzO9dTWPiEo=", + "requires": { + "bluebird": "^3.0.3", + "native-or-bluebird": "^1.2.0" + } + }, "exif": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/exif/-/exif-0.6.0.tgz", @@ -8695,6 +8740,11 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "native-or-bluebird": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz", + "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck=" + }, "needle": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", @@ -13260,6 +13310,24 @@ "semver-compare": "^1.0.0" } }, + "plist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", + "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=", + "requires": { + "base64-js": "0.0.8", + "util-deprecate": "1.0.2", + "xmlbuilder": "4.0.0", + "xmldom": "0.1.x" + }, + "dependencies": { + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=" + } + } + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -17816,12 +17884,32 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xmlbuilder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz", + "integrity": "sha1-mLj2UcowqmJANvEn0RzGbce5B6M=", + "requires": { + "lodash": "^3.5.0" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } + } + }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==" + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", diff --git a/package.json b/package.json index ca82d523d..d1d7935af 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "@hig/flyout": "^1.2.0", "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.13.0", + "@types/chrome": "0.0.114", "adm-zip": "^0.4.13", "archiver": "^3.1.1", "array-batcher": "^1.2.3", @@ -128,6 +129,7 @@ "bootstrap": "^4.4.1", "canvas": "^2.5.0", "child_process": "^1.0.2", + "chrome": "^0.1.0", "class-transformer": "^0.2.0", "color": "^3.1.2", "colors": "^1.4.0", diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ab087335e..67f2f244c 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -126,13 +126,13 @@ export class DocumentManager { finished?.(); } public jumpToDocument = async ( - targetDoc: Doc, - willZoom: boolean, - createViewFunc = DocumentManager.addRightSplit, - docContext?: Doc, - linkId?: string, - closeContextIfNotFound: boolean = false, - originatingDoc: Opt = undefined, + targetDoc: Doc, // document to display + willZoom: boolean, // whether to zoom doc to take up most of screen + createViewFunc = DocumentManager.addRightSplit, // how to create a view of the doc if it doesn't exist + docContext?: Doc, // context to load that should contain the target + linkId?: string, // link that's being followed + closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there + originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc finished?: () => void ): Promise => { const getFirstDocView = DocumentManager.Instance.getFirstDocumentView; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a0a6adab7..a1d1b0ece 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -51,6 +51,8 @@ import { PreviewCursor } from './PreviewCursor'; import { ScriptField } from '../../fields/ScriptField'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; import { SnappingManager } from '../util/SnappingManager'; +import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { DocumentManager } from '../util/DocumentManager'; @observer export class MainView extends React.Component { @@ -83,6 +85,14 @@ export class MainView extends React.Component { window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("paste", KeyManager.Instance.paste as any); + document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on + const id = FormattedTextBox.GetDocFromUrl(e.detail); + DocServer.GetRefField(id).then(doc => { + if (doc instanceof Doc) { + DocumentManager.Instance.jumpToDocument(doc, false, undefined); + } + }); + }); } componentWillUnMount() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7a4ecfa9c..e245e045c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -742,16 +742,16 @@ export class DocumentView extends DocComponent(Docu const more = cm.findByDescription("More..."); const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : []; - moreItems.push({ description: "Make Add Only", event: () => this.layoutDoc.ACL = this.dataDoc.ACL = "addOnly", icon: "concierge-bell" }); - moreItems.push({ description: "Make Read Only", event: () => this.layoutDoc.ACL = this.dataDoc.ACL = "readOnly", icon: "concierge-bell" }); - moreItems.push({ description: "Make Private", event: () => this.layoutDoc[AclSym] = this.dataDoc[AclSym] = "noAccess", icon: "concierge-bell" }); + moreItems.push({ description: "Make Add Only", event: () => this.dataDoc.ACL = this.layoutDoc.ACL = "addOnly", icon: "concierge-bell" }); + moreItems.push({ description: "Make Read Only", event: () => this.dataDoc.ACL = this.layoutDoc.ACL = "readOnly", icon: "concierge-bell" }); + moreItems.push({ description: "Make Private", event: () => this.dataDoc.ACL = this.layoutDoc.ACL = "noAccess", icon: "concierge-bell" }); moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); if (!ClientUtils.RELEASE) { // let copies: ContextMenuProps[] = []; - moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" }); + moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); // cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" }); } if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) { -- cgit v1.2.3-70-g09d2