diff options
author | Jenny Yu <jennyyu212@outlook.com> | 2022-06-17 22:17:27 -0700 |
---|---|---|
committer | Jenny Yu <jennyyu212@outlook.com> | 2022-06-17 22:17:27 -0700 |
commit | 244afabced075fd0567aca20a306afe088581b91 (patch) | |
tree | b3221edc0ae5f88149901c28f5a5d1850ad84f3d | |
parent | de60c684c063f4a1cac733071405b3be549db86b (diff) | |
parent | d1f828f979b6d0326d8f3fa9dd3598f721578dc4 (diff) |
Merge branch 'mainview-jenny' of https://github.com/brown-dash/Dash-Web into mainview-jenny
-rw-r--r-- | src/client/documents/Documents.ts | 5 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 73 | ||||
-rw-r--r-- | src/client/views/GestureOverlay.tsx | 2 | ||||
-rw-r--r-- | src/client/views/InkTranscription.tsx | 1 | ||||
-rw-r--r-- | src/client/views/MarqueeAnnotator.tsx | 10 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 18 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 | ||||
-rw-r--r-- | src/fields/Types.ts | 4 |
9 files changed, 71 insertions, 48 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d2937b83f..fdc8690cd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,6 +98,7 @@ export class DocumentOptions { allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); + userColor?: string; // color associated with a Dash user (seen in header fields of shared documents) color?: string; // foreground color data doc backgroundColor?: STRt = new StrInfo("background color for data doc"); _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true); @@ -190,6 +191,10 @@ export class DocumentOptions { childLayoutString?: string; // template string for collection to use to render its children childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children + childContextMenuFilters?: List<ScriptField>; + childContextMenuScripts?: List<ScriptField>; + childContextMenuLabels?: List<string>; + childContextMenuIcons?: List<string>; hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideDecorationTitle?: boolean; hideOpenButton?: boolean; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3085e7e72..f1343e472 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -7,7 +7,7 @@ import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, DateCast, DocCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; @@ -355,7 +355,7 @@ export class CurrentUserUtils { } static menuBtnDescriptions(doc: Doc) { - const badgeScript = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") + const badgeValue = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, @@ -363,7 +363,7 @@ export class CurrentUserUtils { { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue: badgeScript}, + { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue}, { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, ]; @@ -371,7 +371,8 @@ export class CurrentUserUtils { static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.menuStack === undefined) { - await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing + await this.setupLinkDocs(doc, linkDatabaseId); + await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => Docs.Create.FontIconDocument({ icon, @@ -902,10 +903,7 @@ export class CurrentUserUtils { } // Sharing sidebar is where shared documents are contained - static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.myPublishedDocs === undefined) { - doc.myPublishedDocs = new List<Doc>(); - } + static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); if (!linkDocs) { @@ -917,29 +915,41 @@ export class CurrentUserUtils { } doc.myLinkDatabase = new PrefetchProxy(linkDocs); } - // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document - if (doc.mySharedDocs === undefined) { - let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); - const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) - if (!sharedDocs) { - sharedDocs = Docs.Create.TreeDocument([], { - title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, - _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - _chromeHidden: true, boxShadow: "0 0", treeViewChildDoubleClick: dblClkScript, - dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" - }, sharingDocumentId + "outer", sharingDocumentId); - (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; - } - if (sharedDocs instanceof Doc) { - Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; - const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); - const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); - sharedDocs.childContextMenuFilters = new List<ScriptField>([dashboardFilter!,]); - sharedDocs.childContextMenuScripts = new List<ScriptField>([addToDashboards!,]); - sharedDocs.childContextMenuLabels = new List<string>(["Add to Dashboards",]); - sharedDocs.childContextMenuIcons = new List<string>(["user-plus",]); - } - doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc); + } + // A user's sharing document is where all documents that are shared to that user are placed. + // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field + // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents + static async setupSharedDocs(doc: Doc, sharingDocumentId: string) { + const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); + const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); + const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) + + const sharedDocOpts:DocumentOptions = { + title: "My Shared Docs", + userColor: "rgb(202, 202, 202)", + childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]), + childContextMenuScripts: new List<ScriptField>([addToDashboards!,]), + childContextMenuLabels: new List<string>(["Add to Dashboards",]), + childContextMenuIcons: new List<string>(["user-plus",]), + treeViewChildDoubleClick: dblClkScript, + }; + const sharedRequiredDocOpts:DocumentOptions = { + "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, + childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, + // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar + _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, + explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" + }; + + const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer")); + if (!(sharedDocs instanceof Doc)) { + doc.mySharedDocs = new PrefetchProxy( + Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); + } else { + Object.entries(sharedRequiredDocOpts).forEach(pair => { + const targetDoc = pair[0].startsWith("_") ? sharedDocs as Doc : Doc.GetProto(sharedDocs as Doc); + targetDoc[pair[0]] = pair[1]; + }); } } @@ -1047,6 +1057,7 @@ export class CurrentUserUtils { doc.savedFilters = new List<Doc>(); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; + doc.myPublishedDocs = doc.myPublishedDocs ?? new List<Doc>(); doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 317f5f5d7..6afe64868 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -847,7 +847,7 @@ export class GestureOverlay extends Touchable { @computed get elements() { const selView = SelectionManager.Views().lastElement(); - const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc.viewScale, 1) / (selView?.props.ScreenToLocalTransform().Scale || 1); + const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1) / (selView?.props.ScreenToLocalTransform().Scale || 1); const rect = this._overlayRef.current?.getBoundingClientRect(); const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true); B.left = B.left - width / 2; diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 291352aa3..8ab54918c 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -10,6 +10,7 @@ import { DocumentManager } from "../util/DocumentManager"; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { InkingStroke } from './InkingStroke'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import "./InkTranscription.scss"; /** diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index e15624e23..20b99788c 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -132,14 +132,14 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { if (savedAnnoMap.size === 0) return undefined; const savedAnnos = Array.from(savedAnnoMap.values())[0]; if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) { - const scale = this.props.scaling?.() || 1; + const scale = (this.props.scaling?.() || 1); const anno = savedAnnos[0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title }); - marqueeAnno.x = (parseInt(anno.style.left || "0") - containerOffset[0]) / scale; - marqueeAnno.y = (parseInt(anno.style.top || "0") - containerOffset[1]) / scale + NumCast(this.props.scrollTop); - marqueeAnno._height = parseInt(anno.style.height || "0") / scale; - marqueeAnno._width = parseInt(anno.style.width || "0") / scale; + marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || "0") - containerOffset[0]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1); + marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || "0") - containerOffset[1]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1) + NumCast(this.props.scrollTop); + marqueeAnno._height = parseInt(anno.style.height || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1); + marqueeAnno._width = parseInt(anno.style.width || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1); anno.remove(); savedAnnoMap.clear(); return marqueeAnno; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 99f74b8a2..52e99f26b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -133,8 +133,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return cb ? {x:cb[0], y:cb[1], r:cb[2], b: cb[3]} : this.props.contentBounds?.() ?? aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } - @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document); } - @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document); } + @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } + @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } @computed get cachedCenteringShiftX(): number { const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections @@ -167,9 +167,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } freeformData = (force?: boolean) => !this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined; reverseNativeScaling = () => this.fitContentsToBox ? true : false; - panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX); - panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY); - zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)); + // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document. + // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image + panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1)); + panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1)); + zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; getTransform = () => this.cachedGetTransform.copy(); getLocalTransform = () => this.cachedGetLocalTransform.copy(); @@ -1414,9 +1416,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) })); - if (this.props.isAnnotationOverlay) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar - if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, NumCast(this.props.Document[this.scaleFieldKey], 1)); - else this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey])); + if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar + if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling()); + else this.props.Document[this.scaleFieldKey] = Math.max(1,this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey])); } this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index ab4ed6b33..8c27b3508 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -357,7 +357,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />; } marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f29b879b3..16a523b40 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -382,11 +382,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - // creates links between terms in a document and documents which have a matching Id + // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 7e2aa5681..bf40a0d7b 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -76,6 +76,10 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal return defaultVal === null ? undefined : defaultVal; } +export function DocCast(field: FieldResult, defaultVal?: Doc) { + return Cast(field, Doc, null) ?? defaultVal; +} + export function NumCast(field: FieldResult, defaultVal: number | null = 0) { return Cast(field, "number", defaultVal); } |