From 553fb2a11aa638c35192a1a2f7da99729867e542 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 13 Feb 2020 22:30:32 -0500 Subject: starting a broad cleanup of code. trying to make standard doc field names be more regular --- src/server/authentication/models/current_user_utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/server') diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 45875d455..32c375bd7 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -139,7 +139,7 @@ export class CurrentUserUtils { static setupThumbDoc(userDoc: Doc) { if (!userDoc.thumbDoc) { userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { - _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, isExpanded: true, backgroundColor: "white" + _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white" }); } return userDoc.thumbDoc; @@ -181,12 +181,12 @@ export class CurrentUserUtils { }); doc.documents = Docs.Create.TreeDocument([], { - title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" + title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" }); // setup Recently Closed library item doc.recentlyClosed = Docs.Create.TreeDocument([], { - title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" + title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" }); return Docs.Create.ButtonDocument({ @@ -255,7 +255,7 @@ export class CurrentUserUtils { { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List(["dropAction"]), title: "presentation slide", icon: "sticky-note" }); doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc], { title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", - backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, + backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) }); -- cgit v1.2.3-70-g09d2 From 3cce5982497564a0f5d69d0248ed07d76ec7bbe8 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 14 Feb 2020 15:36:53 -0500 Subject: more image formats --- src/server/DashUploadUtils.ts | 6 +++++- src/server/SharedMediaTypes.ts | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src/server') diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 0f1758c26..39e5538af 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -270,7 +270,7 @@ export namespace DashUploadUtils { }); }; - const { pngs, jpgs } = AcceptibleMedia; + const { pngs, jpgs, webps, tiffs } = AcceptibleMedia; const pngOptions = { compressionLevel: 9, adaptiveFiltering: true, @@ -314,6 +314,10 @@ export namespace DashUploadUtils { initial = initial.png(pngOptions); } else if (jpgs.includes(ext)) { initial = initial.jpeg(); + } else if (webps.includes(ext)) { + initial = initial.webp(); + } else if (tiffs.includes(ext)) { + initial = initial.tiff(); } else { initial = undefined; } diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 8d0f441f0..274b4f01e 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -2,7 +2,9 @@ export namespace AcceptibleMedia { export const gifs = [".gif"]; export const pngs = [".png"]; export const jpgs = [".jpg", ".jpeg"]; - export const imageFormats = [...pngs, ...jpgs, ...gifs]; + export const webps = [".webp"]; + export const tiffs = [".tiff"]; + export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; export const videoFormats = [".mov", ".mp4"]; export const applicationFormats = [".pdf"]; } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 8034deb2249b23e418427c4713c84f1ad76223c2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 15 Feb 2020 14:43:42 -0500 Subject: fixed dark scheme a bit more. fixed entering metadata on goldenlayout tabs --- src/client/util/DocumentManager.ts | 2 +- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/DocumentDecorations.scss | 4 ++ src/client/views/DocumentDecorations.tsx | 62 +++++++++++++--------- src/client/views/MainView.scss | 9 +++- src/client/views/MainView.tsx | 8 +-- src/client/views/MetadataEntryMenu.scss | 6 +++ src/client/views/MetadataEntryMenu.tsx | 25 ++++----- .../CollectionStackingViewFieldColumn.tsx | 2 +- .../views/collections/ParentDocumentSelector.tsx | 5 +- src/client/views/nodes/ButtonBox.tsx | 2 +- .../authentication/models/current_user_utils.ts | 14 ++--- 12 files changed, 84 insertions(+), 57 deletions(-) (limited to 'src/server') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 60bb25272..d66bb3f09 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -208,7 +208,7 @@ export class DocumentManager { @action zoomIntoScale = (docDelegate: Doc, scale: number) => { const docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate)); - docView && docView.props.zoomToScale(scale); + docView?.props.zoomToScale(scale); } getScaleOfDocView = (docDelegate: Doc) => { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 2201fe710..ec1f879c2 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -242,7 +242,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | return !view0 ? (null) :
this.props.views.filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}> -
+
e.stopPropagation()} > {}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 455e53a79..c847dd4d0 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -259,6 +259,10 @@ $linkGap : 3px; } } +.documentDecorations-darkScheme { + background: dimgray; +} + #template-list { position: absolute; top: 25px; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 65c02591c..12427cdac 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -479,17 +479,29 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return ; } render() { + const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined; const bounds = this.Bounds; const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; if (SelectionManager.GetIsDragging() || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } const minimizeIcon = ( -
+
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} - {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} + {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
); + const titleArea = this._edtingTitle ? + <> + this.titleBlur(true)} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> +
DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument)}> + +
+ : +
{`${this.selectionTitle}`}
; + bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; bounds.y = Math.max(0, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; const borderRadiusDraggerWidth = 15; @@ -519,38 +531,36 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> opacity: this._opacity }}> {minimizeIcon} - - {this._edtingTitle ? <> - this.titleBlur(true)} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> -
{ - const promoteDoc = SelectionManager.SelectedDocuments()[0]; - DocUtils.Publish(promoteDoc.props.Document, this._accumulatedTitle, promoteDoc.props.addDocument, promoteDoc.props.removeDocument); - }}> - -
- : -
{`${this.selectionTitle}`}
} -
+ {titleArea} +
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
-
+
); } } \ No newline at end of file diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index d39c217ec..b8fd7be0b 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -21,7 +21,7 @@ z-index: 1; } -#mainView-container { +.mainView-container, .mainView-container-dark { width: 100%; height: 100%; position: absolute; @@ -31,6 +31,13 @@ touch-action: none; } +.mainView-container-dark { + .lm_splitter { + background: dimgray; + opacity: 0.5; + } +} + .mainView-mainContent { width: 100%; height: 100%; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ba49a2b53..5d739474e 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -276,7 +276,7 @@ export class MainView extends React.Component { childBackgroundColor = (doc: Doc) => { if (this.darkScheme) { - return doc.type === DocumentType.TEXT ? "#112423" : "black"; + return doc.type === DocumentType.TEXT ? "#2d2d2d" : "black"; } return doc.type === DocumentType.TEXT ? "#f1efeb" : doc.type === DocumentType.COL && doc._viewType === CollectionViewType.Tree ? "lightgray" : "white"; @@ -448,10 +448,10 @@ export class MainView extends React.Component { @computed get mainContent() { const sidebar = this.userDoc && this.userDoc.sidebarContainer; return !this.userDoc || !(sidebar instanceof Doc) ? (null) : ( -
+
+ style={{ backgroundColor: `${StrCast(sidebar.backgroundColor, this.darkScheme ? "dimGray" : "black")}` }} > + return (
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss index 5f4a52c0c..5776cf070 100644 --- a/src/client/views/MetadataEntryMenu.scss +++ b/src/client/views/MetadataEntryMenu.scss @@ -8,6 +8,12 @@ } } +.metadataEntry-autoSuggester { + width: 100%; + height: 100%; + padding-right: 10px; +} + #metadataEntry-outer { overflow: auto !important; } diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 23b21ae0c..8bc80ed06 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -195,10 +195,10 @@ export class MetadataEntryMenu extends React.Component{ _ref = React.createRef(); render() { - return ( -
-
- Key: + return (
e.stopPropagation()}> +
+ Key: +
this.autosuggestRef.current!.input?.focus()} > { onSuggestionsFetchRequested={emptyFunction} onSuggestionsClearRequested={emptyFunction} ref={this.autosuggestRef} /> - Value: - this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} /> - {this.considerChildOptions} -
-
-
    - {this._allSuggestions.slice().sort().map(s =>
  • { this._currentKey = s; this.previewValue(); })} >{s}
  • )} -
+ Value: + this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} /> + {this.considerChildOptions} +
+
+
    + {this._allSuggestions.slice().sort().map(s =>
  • { this._currentKey = s; this.previewValue(); })} >{s}
  • )} +
+
); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 3fc05c6b7..2ff477c57 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -360,7 +360,7 @@ export class CollectionStackingViewFieldColumn extends React.Component diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 6e406638b..41b9e821c 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -78,13 +78,12 @@ export class SelectorContextMenu extends React.Component { export class ParentDocSelector extends React.Component { render() { const flyout = ( -
+
); return
e.stopPropagation()} className="parentDocumentSelector-linkFlyout"> - + diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index ee48b47b7..de0b509eb 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -81,7 +81,7 @@ export class ButtonBox extends DocComponent(Butt
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 32c375bd7..d09837d96 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -164,7 +164,7 @@ export class CurrentUserUtils { }); return Docs.Create.ButtonDocument({ - _width: 35, _height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Tools", fontSize: 10, targetContainer: sidebarContainer, + _width: 35, _height: 25, title: "Tools", fontSize: 10, targetContainer: sidebarContainer, letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: Docs.Create.StackingDocument([dragCreators, color], { _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack" @@ -177,20 +177,20 @@ export class CurrentUserUtils { static setupLibraryPanel(sidebarContainer: Doc, doc: Doc) { // setup workspaces library item doc.workspaces = Docs.Create.TreeDocument([], { - title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, backgroundColor: "#eeeeee" + title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, }); doc.documents = Docs.Create.TreeDocument([], { - title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" + title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, }); // setup Recently Closed library item doc.recentlyClosed = Docs.Create.TreeDocument([], { - title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" + title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, }); return Docs.Create.ButtonDocument({ - _width: 50, _height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Library", fontSize: 10, + _width: 50, _height: 25, title: "Library", fontSize: 10, letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], { title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true, boxShadow: "0 0", @@ -203,7 +203,7 @@ export class CurrentUserUtils { // setup the Search button which will display the search panel. static setupSearchPanel(sidebarContainer: Doc) { return Docs.Create.ButtonDocument({ - _width: 50, _height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Search", fontSize: 10, + _width: 50, _height: 25, title: "Search", fontSize: 10, letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: Docs.Create.QueryDocument({ title: "search stack", ignoreClick: true @@ -239,7 +239,7 @@ export class CurrentUserUtils { Docs.Create.MulticolumnDocument([], { title: "images", _height: 200 }), Docs.Create.TextDocument("", { title: "contents", _height: 100 }) ], - { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, backgroundColor: "lightGray", _autoHeight: true }); + { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, _autoHeight: true }); slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("setNativeView(this)") }); -- cgit v1.2.3-70-g09d2 From 040f86c99465071301daf43481ec7c54fb593234 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 15 Feb 2020 17:19:48 -0500 Subject: commented google photos manager --- .../apis/google_docs/GooglePhotosClientUtils.ts | 4 +- src/new_fields/RichTextUtils.ts | 2 +- src/server/ApiManagers/GooglePhotosManager.ts | 132 +++++++++++++++------ 3 files changed, 102 insertions(+), 36 deletions(-) (limited to 'src/server') diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 7e5d5fe1b..f8723f02d 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -306,7 +306,7 @@ export namespace GooglePhotos { }; export const WriteMediaItemsToServer = async (body: { mediaItems: any[] }): Promise => { - const uploads = await Networking.PostToServer("/googlePhotosMediaDownload", body); + const uploads = await Networking.PostToServer("/googlePhotosMediaGet", body); return uploads; }; @@ -344,7 +344,7 @@ export namespace GooglePhotos { media.push({ url, description }); } if (media.length) { - const results = await Networking.PostToServer("/googlePhotosMediaUpload", { media, album }); + const results = await Networking.PostToServer("/googlePhotosMediaPost", { media, album }); return results; } }; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 7c1fc39d8..1d90c984d 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -128,7 +128,7 @@ export namespace RichTextUtils { return { baseUrl: embeddedObject.imageProperties!.contentUri! }; }); - const uploads = await Networking.PostToServer("/googlePhotosMediaDownload", { mediaItems }); + const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems }); if (uploads.length !== mediaItems.length) { throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" }); diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 3236d1ee2..04b724f4b 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -7,28 +7,33 @@ import { GooglePhotosUploadUtils } from "../apis/google/GooglePhotosUploadUtils" import { Opt } from "../../new_fields/Doc"; import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; import { Database } from "../database"; +import { red } from "colors"; +const prefix = "google_photos_"; +const remoteUploadError = "None of the preliminary uploads to Google's servers was successful."; const authenticationError = "Unable to authenticate Google credentials before uploading to Google Photos!"; const mediaError = "Unable to convert all uploaded bytes to media items!"; -const UploadError = (count: number) => `Unable to upload ${count} images to Dash's server`; +const localUploadError = (count: number) => `Unable to upload ${count} images to Dash's server`; const requestError = "Unable to execute download: the body's media items were malformed."; const downloadError = "Encountered an error while executing downloads."; + interface GooglePhotosUploadFailure { batch: number; index: number; url: string; reason: string; } + interface MediaItem { baseUrl: string; } + interface NewMediaItem { description: string; simpleMediaItem: { uploadToken: string; }; } -const prefix = "google_photos_"; /** * This manager handles the creation of routes for google photos functionality. @@ -37,27 +42,47 @@ export default class GooglePhotosManager extends ApiManager { protected initialize(register: Registration): void { + /** + * This route receives a list of urls that point to images stored + * on Dash's file system, and, in a two step process, uploads them to Google's servers and + * returns the information Google generates about the associated uploaded remote images. + */ register({ method: Method.POST, - subscription: "/googlePhotosMediaUpload", + subscription: "/googlePhotosMediaPost", secureHandler: async ({ user, req, res }) => { const { media } = req.body; + + // first we need to ensure that we know the google account to which these photos will be uploaded const token = await GoogleApiServerUtils.retrieveAccessToken(user.id); if (!token) { return _error(res, authenticationError); } + + // next, having one large list or even synchronously looping over things trips a threshold + // set on Google's servers, and would instantly return an error. So, we ease things out and send the photos to upload in + // batches of 25, where the next batch is sent 100 millieconds after we receive a response from Google's servers. const failed: GooglePhotosUploadFailure[] = []; const batched = BatchedArray.from(media, { batchSize: 25 }); + const interval = { magnitude: 100, unit: TimeUnit.Milliseconds }; const newMediaItems = await batched.batchedMapPatientInterval( - { magnitude: 100, unit: TimeUnit.Milliseconds }, + interval, async (batch: any, collector: any, { completedBatches }: any) => { for (let index = 0; index < batch.length; index++) { const { url, description } = batch[index]; + // a local function used to record failure of an upload const fail = (reason: string) => failed.push({ reason, batch: completedBatches + 1, index, url }); - const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(token, InjectSize(url, SizeSuffix.Original)).catch(fail); + // see image resizing - we store the size-agnostic url in our logic, but write out size-suffixed images to the file system + // so here, given a size agnostic url, we're just making that conversion so that the file system knows which bytes to actually upload + const imageToUpload = InjectSize(url, SizeSuffix.Original); + // STEP 1/2: send the raw bytes of the image from our server to Google's servers. We'll get back an upload token + // which acts as a pointer to those bytes that we can use to locate them later on + const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(token, imageToUpload).catch(fail); if (!uploadToken) { fail(`${path.extname(url)} is not an accepted extension`); } else { + // gather the upload token return from Google (a pointer they give us to the raw, currently useless bytes + // we've uploaded to their servers) and put in the JSON format that the API accepts for image creation (used soon, below) collector.push({ description, simpleMediaItem: { uploadToken } @@ -66,11 +91,24 @@ export default class GooglePhotosManager extends ApiManager { } } ); - const failedCount = failed.length; - if (failedCount) { - console.error(`Unable to upload ${failedCount} image${failedCount === 1 ? "" : "s"} to Google's servers`); + + // inform the developer / server console of any failed upload attempts + // does not abort the operation, since some subset of the uploads may have been successful + const { length } = failed; + if (length) { + console.error(`Unable to upload ${length} image${length === 1 ? "" : "s"} to Google's servers`); console.log(failed.map(({ reason, batch, index, url }) => `@${batch}.${index}: ${url} failed:\n${reason}`).join('\n\n')); } + + // if none of the preliminary uploads was successful, no need to try and create images + // report the failure to the client and return + if (!newMediaItems.length) { + console.error(red(`${remoteUploadError} Thus, aborting image creation. Please try again.`)); + _error(res, remoteUploadError); + return; + } + + // STEP 2/2: create the media items and return the API's response to the client, along with any failures return GooglePhotosUploadUtils.CreateMediaItems(token, newMediaItems, req.body.album).then( results => _success(res, { results, failed }), error => _error(res, mediaError, error) @@ -78,40 +116,68 @@ export default class GooglePhotosManager extends ApiManager { } }); + /** + * This route receives a list of urls that point to images + * stored on Google's servers and (following a *rough* heuristic) + * uploads each image to Dash's server if it hasn't already been uploaded. + * Unfortunately, since Google has so many of these images on its servers, + * these user content urls expire every 6 hours. So we can't store the url of a locally uploaded + * Google image and compare the candidate url to it to figure out if we already have it, + * since the same bytes on their server might now be associated with a new, random url. + * So, we do the next best thing and try to use an intrinsic attribute of those bytes as + * an identifier: the precise content size. This works in small cases, but has the obvious flaw of failing to upload + * an image locally if we already have uploaded another Google user content image with the exact same content size. + */ register({ method: Method.POST, - subscription: "/googlePhotosMediaDownload", + subscription: "/googlePhotosMediaGet", secureHandler: async ({ req, res }) => { const { mediaItems } = req.body as { mediaItems: MediaItem[] }; + if (!mediaItems) { + // non-starter, since the input was in an invalid format + _invalid(res, requestError); + return; + } let failed = 0; - if (mediaItems) { - const completed: Opt[] = []; - for (const { baseUrl } of mediaItems) { - const results = await DashUploadUtils.InspectImage(baseUrl); - if (results instanceof Error) { - failed++; - continue; - } - const { contentSize, ...attributes } = results; - const found: Opt = await Database.Auxiliary.QueryUploadHistory(contentSize); - if (!found) { - const upload = await DashUploadUtils.UploadInspectedImage({ contentSize, ...attributes }, undefined, prefix, false).catch(error => _error(res, downloadError, error)); - if (upload) { - completed.push(upload); - await Database.Auxiliary.LogUpload(upload); - } else { - failed++; - } + const completed: Opt[] = []; + for (const { baseUrl } of mediaItems) { + // start by getting the content size of the remote image + const results = await DashUploadUtils.InspectImage(baseUrl); + if (results instanceof Error) { + // if something went wrong here, we can't hope to upload it, so just move on to the next + failed++; + continue; + } + const { contentSize, ...attributes } = results; + // check to see if we have uploaded a Google user content image *specifically via this route* already + // that has this exact content size + const found: Opt = await Database.Auxiliary.QueryUploadHistory(contentSize); + if (!found) { + // if we haven't, then upload it locally to Dash's server + const upload = await DashUploadUtils.UploadInspectedImage({ contentSize, ...attributes }, undefined, prefix, false).catch(error => _error(res, downloadError, error)); + if (upload) { + completed.push(upload); + // inform the heuristic that we've encountered an image with this content size, + // to be later checked against in future uploads + await Database.Auxiliary.LogUpload(upload); } else { - completed.push(found); + // make note of a failure to upload locallys + failed++; } + } else { + // if we have, the variable 'found' is handily the upload information of the + // existing image, so we add it to the list as if we had just uploaded it now without actually + // making a duplicate write + completed.push(found); } - if (failed) { - return _error(res, UploadError(failed)); - } - return _success(res, completed); } - _invalid(res, requestError); + // if there are any failures, report a general failure to the client + if (failed) { + return _error(res, localUploadError(failed)); + } + // otherwise, return the image upload information list corresponding to the newly (or previously) + // uploaded images + _success(res, completed); } }); -- cgit v1.2.3-70-g09d2 From 286b1a69b5b300b414fe299efa3ae018d55c90be Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 15 Feb 2020 20:21:24 -0500 Subject: title bar fixes and more dark scheme stuff --- src/client/goldenLayout.js | 2 +- src/client/views/MainView.scss | 6 +++-- src/client/views/MainView.tsx | 5 ++-- .../views/collections/CollectionDockingView.scss | 20 +++++++++++++--- .../views/collections/CollectionDockingView.tsx | 27 +++++++++++----------- src/client/views/collections/CollectionView.tsx | 3 ++- .../authentication/models/current_user_utils.ts | 2 +- 7 files changed, 41 insertions(+), 24 deletions(-) (limited to 'src/server') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 29b750720..b510385ff 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -2868,7 +2868,7 @@ * @type {String} */ lm.controls.Tab._template = '
  • ' + - '
    ' + + '
    ' + '
  • '; lm.utils.copy(lm.controls.Tab.prototype, { diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index b8fd7be0b..df594be4d 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -32,9 +32,11 @@ } .mainView-container-dark { - .lm_splitter { + .lm_goldenlayout { background: dimgray; - opacity: 0.5; + } + .marquee { + border-color: white; } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5d739474e..de76106d1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -276,10 +276,11 @@ export class MainView extends React.Component { childBackgroundColor = (doc: Doc) => { if (this.darkScheme) { - return doc.type === DocumentType.TEXT ? "#2d2d2d" : "black"; + return doc.type === DocumentType.TEXT || doc.type === DocumentType.BUTTON ? "#2d2d2d" : + (doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) || doc.type === DocumentType.BUTTON ? "#2d2d2d" : "black"; } return doc.type === DocumentType.TEXT ? "#f1efeb" : - doc.type === DocumentType.COL && doc._viewType === CollectionViewType.Tree ? "lightgray" : "white"; + (doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) || doc.type === DocumentType.BUTTON ? "lightgray" : "white"; } sidebarBackgroundColor = (doc: Doc) => { return this.childBackgroundColor(doc); diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 819332d86..2fafcecb2 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,8 +1,22 @@ @import "../../views/globalCssVariables.scss"; -.lm_active .messageCounter { - color: white; - background: #999999; +.lm_title { + margin-top: 3px; + background: black; + border-radius: 5px; + border: solid 1px dimgray; + border-width: 2px 2px 0px; + height: 20px; + transform: translate(0px, -3px); +} +.lm_title_wrap { + overflow: hidden; + height: 19px; + margin-top: -3px; + display:inline-block; +} +.lm_active .lm_title { + border: solid 1px lightgray; } .lm_header .lm_tab .lm_close_tab { position: absolute; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 67a7577eb..6abfb59c3 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -379,16 +379,6 @@ export class CollectionDockingView extends React.Component) => - (sourceDoc instanceof Doc) && DragManager.StartLinkTargetsDrag(tab, x, y, sourceDoc))); - } if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") { this._flush = true; } @@ -430,6 +420,7 @@ export class CollectionDockingView extends React.Component { + tab.titleElement[0].Tab = tab; if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { if (tab.contentItem.config.fixed) { tab.contentItem.parent.config.fixed = true; @@ -438,6 +429,14 @@ export class CollectionDockingView extends React.Component`; + tab.titleElement[0].onclick = (e: any) => tab.titleElement[0].focus(); + tab.titleElement[0].onchange = (e: any) => { + tab.titleElement[0].size = e.currentTarget.value.length + 1; + Doc.SetInPlace(doc, "title", e.currentTarget.value, true); + } + tab.titleElement[0].size = StrCast(doc.title).length + 1; + tab.titleElement[0].value = doc.title; const gearSpan = document.createElement("span"); gearSpan.className = "collectionDockingView-gear"; gearSpan.style.position = "relative"; @@ -464,15 +463,15 @@ export class CollectionDockingView extends React.Component, gearSpan); tab.reactComponents = [gearSpan]; tab.element.append(gearSpan); - tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => { - tab.titleElement[0].textContent = doc.title, { fireImmediately: true }; - tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegreeUnmemoized(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegreeUnmemoized(doc)]} 1px`; + tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => { + tab.titleElement[0].textContent = title, { fireImmediately: true }; + tab.titleElement[0].style.padding = degree ? 0 : 2; + tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`; }); //TODO why can't this just be doc instead of the id? tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; } } - tab.titleElement[0].Tab = tab; tab.closeElement.off('click') //unbind the current click handler .click(async function () { tab.reactionDisposer && tab.reactionDisposer(); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index be971eda6..48294f9c2 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -293,7 +293,8 @@ export class CollectionView extends Touchable { return (
    {this.showIsTagged()} diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index d09837d96..888e27f28 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -227,7 +227,7 @@ export class CurrentUserUtils { // Finally, setup the list of buttons to display in the sidebar doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.ToolsBtn as Doc], { _width: 500, _height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true, - backgroundColor: "rgb(100, 100, 100)", _chromeStatus: "disabled", title: "library stack", + _chromeStatus: "disabled", title: "library stack", _yMargin: 10, }); } -- cgit v1.2.3-70-g09d2 From a71f1cbfe47bbe5bcefa7deb63fa812683a37178 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 15 Feb 2020 22:44:33 -0500 Subject: more color scheme fixes. --- src/client/documents/DocumentTypes.ts | 1 - src/client/views/DocumentDecorations.tsx | 24 +++++++------- src/client/views/MainView.scss | 16 ++++++++++ src/client/views/MainView.tsx | 37 ++++++++++++++-------- src/client/views/nodes/DocumentView.tsx | 4 ++- src/client/views/search/FilterBox.tsx | 2 +- src/client/views/search/SearchBox.tsx | 2 +- .../authentication/models/current_user_utils.ts | 3 +- 8 files changed, 57 insertions(+), 32 deletions(-) (limited to 'src/server') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 1220e9923..0e6b59b33 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -14,7 +14,6 @@ export enum DocumentType { LINKDOC = "linkdoc", BUTTON = "button", SLIDER = "slider", - TEMPLATE = "template", EXTENSION = "extension", YOUTUBE = "youtube", WEBCAM = "webcam", diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 12427cdac..3388ea46c 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -491,16 +491,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
    ); - const titleArea = this._edtingTitle ? - <> - this.titleBlur(true)} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> -
    DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument)}> - -
    - : -
    {`${this.selectionTitle}`}
    ; + const titleArea = this._edtingTitle ? + <> + this.titleBlur(true)} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> +
    DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument)}> + +
    + : +
    {`${this.selectionTitle}`}
    ; bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; bounds.y = Math.max(0, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; @@ -539,7 +539,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
    e.preventDefault()}>
    e.preventDefault()}>
    + style={{ background: darkScheme }} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}>
    e.preventDefault()}>
    e.preventDefault()}>
    e.preventDefault()}>
    + style={{ background: darkScheme }} onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>
    diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index df594be4d..e91f7e94c 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -29,6 +29,9 @@ left: 0; z-index: 1; touch-action: none; + .searchBox-container { + background: lightgray; + } } .mainView-container-dark { @@ -38,6 +41,19 @@ .marquee { border-color: white; } + #search-input { + background: lightgray; + } + .searchBox-container { + background: rgb(45,45,45); + } + .contextMenu-cont, .contextMenu-item { + background: dimGray; + color: lightgray; + } + .contextMenu-item:hover { + background: gray; + } } .mainView-mainContent { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index de76106d1..7948da3c5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -274,16 +274,25 @@ export class MainView extends React.Component { getPHeight = () => this._panelHeight; getContentsHeight = () => this._panelHeight - this._buttonBarHeight; - childBackgroundColor = (doc: Doc) => { + defaultBackgroundColors = (doc: Doc) => { if (this.darkScheme) { - return doc.type === DocumentType.TEXT || doc.type === DocumentType.BUTTON ? "#2d2d2d" : - (doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) || doc.type === DocumentType.BUTTON ? "#2d2d2d" : "black"; + switch (doc.type) { + case DocumentType.TEXT || DocumentType.BUTTON: return "#2d2d2d"; + case DocumentType.COL: { + if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; + } + default: return "black"; + } + } else { + switch (doc.type) { + case DocumentType.TEXT: return "#f1efeb"; + case DocumentType.BUTTON: return "lightgray"; + case DocumentType.COL: { + if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray"; + } + default: return "white"; + } } - return doc.type === DocumentType.TEXT ? "#f1efeb" : - (doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) || doc.type === DocumentType.BUTTON ? "lightgray" : "white"; - } - sidebarBackgroundColor = (doc: Doc) => { - return this.childBackgroundColor(doc); } @computed get mainDocView() { return -
    +
    ; } - + //`${StrCast(sidebar.backgroundColor, this.darkScheme ? "dimGray" : "black")}` }} > @computed get mainContent() { const sidebar = this.userDoc && this.userDoc.sidebarContainer; return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
    + style={{ backgroundColor: this.defaultBackgroundColors(sidebar) }}> (Docu const borderRounding = this.layoutDoc.borderRounding; const localScale = fullDegree; - const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; + const highlightColors = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? + ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] : + ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 684f50766..d4c9e67fb 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -33,7 +33,7 @@ export enum Keys { export class FilterBox extends React.Component { static Instance: FilterBox; - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB, DocumentType.TEMPLATE]; + public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB]; //if true, any keywords can be used. if false, all keywords are required. //this also serves as an indicator if the word status filter is applied diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index be13dae03..9bd42b516 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -234,7 +234,7 @@ export class SearchBox extends React.Component { y += 300; } } - return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` }); + return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, title: `Search Docs: "${this._searchString}"` }); } @action.bound diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 888e27f28..930b021a4 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -227,8 +227,7 @@ export class CurrentUserUtils { // Finally, setup the list of buttons to display in the sidebar doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.ToolsBtn as Doc], { _width: 500, _height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true, - _chromeStatus: "disabled", title: "library stack", - _yMargin: 10, + _chromeStatus: "disabled", title: "library stack", backgroundColor: "dimGray", }); } -- cgit v1.2.3-70-g09d2 From f33ad290f1de3a01f2c4536f03f040e09771f82e Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 16 Feb 2020 00:50:23 -0500 Subject: improved file upload api --- src/client/Network.ts | 14 ++- .../util/Import & Export/DirectoryImportBox.tsx | 17 ++-- src/client/views/collections/CollectionSubView.tsx | 56 +++++------ src/new_fields/Doc.ts | 6 +- src/scraping/buxton/final/BuxtonImporter.ts | 7 +- src/server/ApiManagers/GooglePhotosManager.ts | 5 +- src/server/ApiManagers/UploadManager.ts | 8 +- src/server/DashUploadUtils.ts | 107 +++++++-------------- src/server/SharedMediaTypes.ts | 39 ++++++++ src/server/database.ts | 6 +- 10 files changed, 137 insertions(+), 128 deletions(-) (limited to 'src/server') diff --git a/src/client/Network.ts b/src/client/Network.ts index ccf60f199..6982ecf19 100644 --- a/src/client/Network.ts +++ b/src/client/Network.ts @@ -1,5 +1,6 @@ import { Utils } from "../Utils"; import requestPromise = require('request-promise'); +import { Upload } from "../server/SharedMediaTypes"; export namespace Networking { @@ -17,12 +18,21 @@ export namespace Networking { return requestPromise.post(options); } - export async function PostFormDataToServer(relativeRoute: string, formData: FormData) { + export async function UploadFilesToServer(files: File | File[]): Promise[]> { + const formData = new FormData(); + if (Array.isArray(files)) { + if (!files.length) { + return []; + } + files.forEach(file => formData.append(Utils.GenerateGuid(), file)); + } else { + formData.append(Utils.GenerateGuid(), files); + } const parameters = { method: 'POST', body: formData }; - const response = await fetch(relativeRoute, parameters); + const response = await fetch("/uploadFormData", parameters); return response.json(); } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index d04f56e57..3d8bcbab7 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -22,7 +22,7 @@ import "./DirectoryImportBox.scss"; import { Networking } from "../../Network"; import { BatchedArray } from "array-batcher"; import * as path from 'path'; -import { AcceptibleMedia } from "../../../server/SharedMediaTypes"; +import { AcceptibleMedia, Upload } from "../../../server/SharedMediaTypes"; const unsupported = ["text/html", "text/plain"]; @@ -107,20 +107,21 @@ export default class DirectoryImportBox extends React.Component runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); const batched = BatchedArray.from(validated, { batchSize: 15 }); - const uploads = await batched.batchedMapAsync(async (batch, collector) => { - const formData = new FormData(); - + const uploads = await batched.batchedMapAsync>(async (batch, collector) => { batch.forEach(file => { sizes.push(file.size); modifiedDates.push(file.lastModified); - formData.append(Utils.GenerateGuid(), file); }); - - collector.push(...(await Networking.PostFormDataToServer("/uploadFormData", formData))); + collector.push(...(await Networking.UploadFilesToServer(batch))); runInAction(() => this.completed += batch.length); }); - await Promise.all(uploads.map(async ({ name, type, accessPaths, exifData }) => { + await Promise.all(uploads.map(async response => { + const { source: { type }, result } = response; + if (result instanceof Error) { + return; + } + const { accessPaths, exifData } = result; const path = Utils.prepend(accessPaths.agnostic.client); const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name }); const { data, error } = exifData; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 0963e1ea6..e0b2d524b 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import * as rp from 'request-promise'; import CursorField from "../../../new_fields/CursorField"; import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; @@ -25,6 +25,7 @@ import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { Networking } from "../../Network"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../../util/InteractionUtils"; +import { Upload } from "../../../server/SharedMediaTypes"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc) => boolean; @@ -288,6 +289,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { } const { items } = e.dataTransfer; const { length } = items; + const files: File[] = []; if (length) { const batch = UndoManager.StartBatch("collection view drop"); const promises: Promise[] = []; @@ -307,41 +309,31 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { }); promises.push(prom); } - const type = item.type; if (item.kind === "file") { const file = item.getAsFile(); - const formData = new FormData(); - - if (!file || !file.type) { - continue; - } - - formData.append('file', file); - const dropFileName = file ? file.name : "-empty-"; - promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => { - results.map(action((result: any) => { - const { accessPaths, nativeWidth, nativeHeight, contentSize } = result; - if (Object.keys(accessPaths).length) { - const full = { ...options, _width: 300, title: dropFileName }; - const pathname = Utils.prepend(accessPaths.agnostic.client); - Docs.Get.DocumentFromType(type, pathname, full).then(doc => { - if (doc) { - const proto = Doc.GetProto(doc); - proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); - nativeWidth && (proto["data-nativeWidth"] = nativeWidth); - nativeHeight && (proto["data-nativeHeight"] = nativeHeight); - contentSize && (proto.contentSize = contentSize); - this.props?.addDocument(doc); - } - }); - } else { - alert("Upload failed..."); - } - })); - })); + file && file.type && files.push(file); } } - + (await Networking.UploadFilesToServer(files)).forEach(({ source: { name, type }, result }) => { + if (result instanceof Error) { + alert(`Upload failed: ${result.message}`); + return; + } + const full = { ...options, _width: 300, title: name }; + const pathname = Utils.prepend(result.accessPaths.agnostic.client); + Docs.Get.DocumentFromType(type, pathname, full).then(doc => { + if (doc) { + const proto = Doc.GetProto(doc); + proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); + if (Upload.isImageInformation(result)) { + proto["data-nativeWidth"] = result.nativeWidth; + proto["data-nativeHeight"] = result.nativeHeight; + proto.contentSize = result.contentSize; + } + this.props?.addDocument(doc); + } + }); + }); if (promises.length) { Promise.all(promises).finally(() => { completed && completed(); batch.end(); }); } else { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 55c0660c0..a722f552e 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -112,10 +112,10 @@ export class Doc extends RefField { // 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, ownKeys: target => { - let obj = {} as any; + const obj = {} as any; Object.assign(obj, target.___fields); runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); - return Object.keys(obj) + return Object.keys(obj); }, getOwnPropertyDescriptor: (target, prop) => { if (prop.toString() === "__LAYOUT__") { @@ -864,7 +864,7 @@ Scripting.addGlobal(function redo() { return UndoManager.Redo(); }); Scripting.addGlobal(function curPresentationItem() { const curPres = Doc.UserDoc().curPresentation as Doc; return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; -}) +}); Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().SelectedDocs = new List([doc]); }); Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { const docs = DocListCast(Doc.UserDoc().SelectedDocs).filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCUMENT && d.type !== DocumentType.KVP && (!excludeCollections || !Cast(d.data, listSpec(Doc), null))); diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts index 47d6bbe83..8041343fd 100644 --- a/src/scraping/buxton/final/BuxtonImporter.ts +++ b/src/scraping/buxton/final/BuxtonImporter.ts @@ -8,6 +8,7 @@ const StreamZip = require('node-stream-zip'); const createImageSizeStream = require("image-size-stream"); import { parseXml } from "libxmljs"; import { strictEqual } from "assert"; +import { Readable, PassThrough } from "stream"; interface DocumentContents { body: string; @@ -293,15 +294,15 @@ async function writeImages(zip: any): Promise { const imageUrls: ImageData[] = []; for (const mediaPath of imageEntries) { - const streamImage = () => new Promise((resolve, reject) => { + const streamImage = () => new Promise((resolve, reject) => { zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream)); }); const { width, height, type } = await new Promise(async resolve => { - const sizeStream = createImageSizeStream().on('size', (dimensions: Dimensions) => { + const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: Dimensions) => { readStream.destroy(); resolve(dimensions); - }); + }).on("error", () => readStream.destroy()); const readStream = await streamImage(); readStream.pipe(sizeStream); }); diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 04b724f4b..25c54ee2e 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -8,6 +8,7 @@ import { Opt } from "../../new_fields/Doc"; import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; import { Database } from "../database"; import { red } from "colors"; +import { Upload } from "../SharedMediaTypes"; const prefix = "google_photos_"; const remoteUploadError = "None of the preliminary uploads to Google's servers was successful."; @@ -139,7 +140,7 @@ export default class GooglePhotosManager extends ApiManager { return; } let failed = 0; - const completed: Opt[] = []; + const completed: Opt[] = []; for (const { baseUrl } of mediaItems) { // start by getting the content size of the remote image const results = await DashUploadUtils.InspectImage(baseUrl); @@ -151,7 +152,7 @@ export default class GooglePhotosManager extends ApiManager { const { contentSize, ...attributes } = results; // check to see if we have uploaded a Google user content image *specifically via this route* already // that has this exact content size - const found: Opt = await Database.Auxiliary.QueryUploadHistory(contentSize); + const found: Opt = await Database.Auxiliary.QueryUploadHistory(contentSize); if (!found) { // if we haven't, then upload it locally to Dash's server const upload = await DashUploadUtils.UploadInspectedImage({ contentSize, ...attributes }, undefined, prefix, false).catch(error => _error(res, downloadError, error)); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 4d09528f4..8f2a5ea3e 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -4,12 +4,12 @@ import * as formidable from 'formidable'; import v4 = require('uuid/v4'); const AdmZip = require('adm-zip'); import { extname, basename, dirname } from 'path'; -import { createReadStream, createWriteStream, unlink, readFileSync } from "fs"; +import { createReadStream, createWriteStream, unlink } from "fs"; import { publicDirectory, filesDirectory } from ".."; import { Database } from "../database"; -import { DashUploadUtils, SizeSuffix } from "../DashUploadUtils"; +import { DashUploadUtils } from "../DashUploadUtils"; import * as sharp from 'sharp'; -import { AcceptibleMedia } from "../SharedMediaTypes"; +import { AcceptibleMedia, Upload } from "../SharedMediaTypes"; import { normalize } from "path"; const imageDataUri = require('image-data-uri'); @@ -47,7 +47,7 @@ export default class UploadManager extends ApiManager { form.keepExtensions = true; return new Promise(resolve => { form.parse(req, async (_err, _fields, files) => { - const results: any[] = []; + const results: Upload.FileResponse[] = []; for (const key in files) { const result = await DashUploadUtils.upload(files[key]); result && results.push(result); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 913ddc1c3..b66651ef2 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -3,9 +3,9 @@ import { Utils } from '../Utils'; import * as path from 'path'; import * as sharp from 'sharp'; import request = require('request-promise'); -import { ExifData, ExifImage } from 'exif'; +import { ExifImage } from 'exif'; import { Opt } from '../new_fields/Doc'; -import { AcceptibleMedia } from './SharedMediaTypes'; +import { AcceptibleMedia, Upload } from './SharedMediaTypes'; import { filesDirectory } from '.'; import { File } from 'formidable'; import { basename } from "path"; @@ -14,7 +14,7 @@ import { ParsedPDF } from "../server/PdfTypes"; const parse = require('pdf-parse'); import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './ApiManagers/UploadManager'; import { red } from 'colors'; -import { Writable } from 'stream'; +import { Stream } from 'stream'; const requestImageSize = require("../client/util/request-image-size"); export enum SizeSuffix { @@ -40,13 +40,6 @@ export namespace DashUploadUtils { suffix: SizeSuffix; } - export interface ImageFileResponse { - name: string; - path: string; - type: string; - exif: Opt; - } - export const Sizes: { [size: string]: Size } = { SMALL: { width: 100, suffix: SizeSuffix.Small }, MEDIUM: { width: 400, suffix: SizeSuffix.Medium }, @@ -60,20 +53,9 @@ export namespace DashUploadUtils { const size = "content-length"; const type = "content-type"; - export interface ImageUploadInformation { - accessPaths: AccessPathInfo; - exifData: EnrichedExifData; - contentSize?: number; - contentType?: string; - } - - export interface AccessPathInfo { - [suffix: string]: { client: string, server: string }; - } - const { imageFormats, videoFormats, applicationFormats } = AcceptibleMedia; - export async function upload(file: File): Promise { + export async function upload(file: File): Promise { const { type, path, name } = file; const types = type.split("/"); @@ -83,33 +65,33 @@ export namespace DashUploadUtils { switch (category) { case "image": if (imageFormats.includes(format)) { - const results = await UploadImage(path, basename(path)); - return { ...results, name, type }; + const result = await UploadImage(path, basename(path)); + return { source: file, result }; } case "video": if (videoFormats.includes(format)) { - return MoveParsedFile(path, Directory.videos); + return MoveParsedFile(file, Directory.videos); } case "application": if (applicationFormats.includes(format)) { - return UploadPdf(path); + return UploadPdf(file); } } console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`)); - return { accessPaths: {} }; + return { source: file, result: new Error(`Could not upload unsupported file (${name}) with upload type (${type}).`) }; } - async function UploadPdf(absolutePath: string) { - const dataBuffer = readFileSync(absolutePath); + async function UploadPdf(file: File) { + const { path, name } = file; + const dataBuffer = readFileSync(path); const result: ParsedPDF = await parse(dataBuffer); - const parsedName = basename(absolutePath); await new Promise((resolve, reject) => { - const textFilename = `${parsedName.substring(0, parsedName.length - 4)}.txt`; + const textFilename = `${name.substring(0, name.length - 4)}.txt`; const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename)); writeStream.write(result.text, error => error ? reject(error) : resolve()); }); - return MoveParsedFile(absolutePath, Directory.pdfs); + return MoveParsedFile(file, Directory.pdfs); } /** @@ -123,13 +105,13 @@ export namespace DashUploadUtils { * @param {string} prefix is a string prepended to the generated image name in the * event that @param filename is not specified * - * @returns {ImageUploadInformation} This method returns + * @returns {ImageUploadInformation | Error} This method returns * 1) the paths to the uploaded images (plural due to resizing) - * 2) the file name of each of the resized images + * 2) the exif data embedded in the image, or the error explaining why exif couldn't be parsed * 3) the size of the image, in bytes (4432130) * 4) the content type of the image, i.e. image/(jpeg | png | ...) */ - export const UploadImage = async (source: string, filename?: string, prefix: string = ""): Promise => { + export const UploadImage = async (source: string, filename?: string, prefix: string = ""): Promise => { const metadata = await InspectImage(source); if (metadata instanceof Error) { return metadata; @@ -137,22 +119,6 @@ export namespace DashUploadUtils { return UploadInspectedImage(metadata, filename || metadata.filename, prefix); }; - export interface InspectionResults { - source: string; - requestable: string; - exifData: EnrichedExifData; - contentSize: number; - contentType: string; - nativeWidth: number; - nativeHeight: number; - filename?: string; - } - - export interface EnrichedExifData { - data: ExifData; - error?: string; - } - export async function buildFileDirectories() { const pending = Object.keys(Directory).map(sub => createIfNotExists(`${filesDirectory}/${sub}`)); return Promise.all(pending); @@ -175,7 +141,7 @@ export namespace DashUploadUtils { * * @param source is the path or url to the image in question */ - export const InspectImage = async (source: string): Promise => { + export const InspectImage = async (source: string): Promise => { let rawMatches: RegExpExecArray | null; let filename: string | undefined; if ((rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source)) !== null) { @@ -216,14 +182,17 @@ export namespace DashUploadUtils { }; }; - export async function MoveParsedFile(absolutePath: string, destination: Directory): Promise> { + export async function MoveParsedFile(file: File, destination: Directory): Promise { + const { name, path: sourcePath } = file; return new Promise(resolve => { - const filename = basename(absolutePath); - const destinationPath = serverPathToFile(destination, filename); - rename(absolutePath, destinationPath, error => { - resolve(error ? undefined : { - accessPaths: { - agnostic: getAccessPaths(destination, filename) + const destinationPath = serverPathToFile(destination, name); + rename(sourcePath, destinationPath, error => { + resolve({ + source: file, + result: error ? error : { + accessPaths: { + agnostic: getAccessPaths(destination, name) + } } }); }); @@ -237,16 +206,16 @@ export namespace DashUploadUtils { }; } - export const UploadInspectedImage = async (metadata: InspectionResults, filename?: string, prefix = "", cleanUp = true): Promise => { + export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = "", cleanUp = true): Promise => { const { requestable, source, ...remaining } = metadata; const extension = `.${remaining.contentType.split("/")[1].toLowerCase()}`; const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}${extension}`; const { images } = Directory; - const information: ImageUploadInformation = { + const information: Upload.ImageInformation = { accessPaths: { agnostic: getAccessPaths(images, resolved) }, - ...remaining + ...metadata }; const outputPath = pathToDirectory(Directory.images); const writtenFiles = await outputResizedImages(() => request(requestable), outputPath, resolved, extension); @@ -259,9 +228,9 @@ export namespace DashUploadUtils { return information; }; - const parseExifData = async (source: string): Promise => { + const parseExifData = async (source: string): Promise => { const image = await request.get(source, { encoding: null }); - return new Promise(resolve => { + return new Promise(resolve => { new ExifImage({ image }, (error, data) => { let reason: Opt = undefined; if (error) { @@ -279,18 +248,14 @@ export namespace DashUploadUtils { force: true }; - export interface ReadStreamLike { - pipe: (dest: Writable) => Writable; - } - - export async function outputResizedImages(readStreamSource: () => ReadStreamLike | Promise, outputPath: string, fileName: string, ext: string) { + export async function outputResizedImages(readStreamSource: () => Stream | Promise, outputPath: string, fileName: string, ext: string) { const writtenFiles: { [suffix: string]: string } = {}; for (const { resizer, suffix } of resizers(ext)) { const resolved = writtenFiles[suffix] = InjectSize(fileName, suffix); await new Promise(async (resolve, reject) => { const writeStream = createWriteStream(path.resolve(outputPath, resolved)); - let readStream: ReadStreamLike; const source = readStreamSource(); + let readStream: Stream; if (source instanceof Promise) { readStream = await source; } else { @@ -320,7 +285,7 @@ export namespace DashUploadUtils { initial = initial.webp(); } else if (tiffs.includes(ext)) { initial = initial.tiff(); - } else { + } else if (ext === ".gif") { initial = undefined; } return { diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 274b4f01e..185e787cc 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -1,3 +1,6 @@ +import { ExifData } from 'exif'; +import { File } from 'formidable'; + export namespace AcceptibleMedia { export const gifs = [".gif"]; export const pngs = [".png"]; @@ -7,4 +10,40 @@ export namespace AcceptibleMedia { export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; export const videoFormats = [".mov", ".mp4"]; export const applicationFormats = [".pdf"]; +} + +export namespace Upload { + + export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation { + return "nativeWidth" in uploadResponse; + } + + export interface FileInformation { + accessPaths: AccessPathInfo; + } + + export type FileResponse = { source: File, result: T | Error }; + + export type ImageInformation = FileInformation & InspectionResults; + + export interface AccessPathInfo { + [suffix: string]: { client: string, server: string }; + } + + export interface InspectionResults { + source: string; + requestable: string; + exifData: EnrichedExifData; + contentSize: number; + contentType: string; + nativeWidth: number; + nativeHeight: number; + filename?: string; + } + + export interface EnrichedExifData { + data: ExifData; + error?: string; + } + } \ No newline at end of file diff --git a/src/server/database.ts b/src/server/database.ts index 83ce865c6..055f04c49 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -2,12 +2,12 @@ import * as mongodb from 'mongodb'; import { Transferable } from './Message'; import { Opt } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; -import { DashUploadUtils } from './DashUploadUtils'; import { Credentials } from 'google-auth-library'; import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; import { IDatabase } from './IDatabase'; import { MemoryDatabase } from './MemoryDatabase'; import * as mongoose from 'mongoose'; +import { Upload } from './SharedMediaTypes'; export namespace Database { @@ -297,7 +297,7 @@ export namespace Database { }; export const QueryUploadHistory = async (contentSize: number) => { - return SanitizedSingletonQuery({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory); + return SanitizedSingletonQuery({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory); }; export namespace GoogleAuthenticationToken { @@ -326,7 +326,7 @@ export namespace Database { } - export const LogUpload = async (information: DashUploadUtils.ImageUploadInformation) => { + export const LogUpload = async (information: Upload.ImageInformation) => { const bundle = { _id: Utils.GenerateDeterministicGuid(String(information.contentSize!)), ...information -- cgit v1.2.3-70-g09d2 From e5897bc9b2d7266c11c8d8f31b39e4666fe3a6a3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 16 Feb 2020 01:17:10 -0500 Subject: cleanup --- src/server/DashUploadUtils.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'src/server') diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index b66651ef2..d4bcd22ae 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -248,13 +248,12 @@ export namespace DashUploadUtils { force: true }; - export async function outputResizedImages(readStreamSource: () => Stream | Promise, outputPath: string, fileName: string, ext: string) { + export async function outputResizedImages(streamProvider: () => Stream | Promise, outputPath: string, fileName: string, ext: string) { const writtenFiles: { [suffix: string]: string } = {}; for (const { resizer, suffix } of resizers(ext)) { - const resolved = writtenFiles[suffix] = InjectSize(fileName, suffix); + const resolvedOutputPath = path.resolve(outputPath, writtenFiles[suffix] = InjectSize(fileName, suffix)); await new Promise(async (resolve, reject) => { - const writeStream = createWriteStream(path.resolve(outputPath, resolved)); - const source = readStreamSource(); + const source = streamProvider(); let readStream: Stream; if (source instanceof Promise) { readStream = await source; @@ -264,9 +263,7 @@ export namespace DashUploadUtils { if (resizer) { readStream = readStream.pipe(resizer.withMetadata()); } - const out = readStream.pipe(writeStream); - out.on("close", resolve); - out.on("error", reject); + readStream.pipe(createWriteStream(resolvedOutputPath)).on("close", resolve).on("error", reject); }); } return writtenFiles; @@ -275,8 +272,8 @@ export namespace DashUploadUtils { function resizers(ext: string): DashUploadUtils.ImageResizer[] { return [ { suffix: SizeSuffix.Original }, - ...Object.values(DashUploadUtils.Sizes).map(size => { - let initial: sharp.Sharp | undefined = sharp().resize(size.width, undefined, { withoutEnlargement: true }); + ...Object.values(DashUploadUtils.Sizes).map(({ suffix, width }) => { + let initial: sharp.Sharp | undefined = sharp().resize(width, undefined, { withoutEnlargement: true }); if (pngs.includes(ext)) { initial = initial.png(pngOptions); } else if (jpgs.includes(ext)) { @@ -290,7 +287,7 @@ export namespace DashUploadUtils { } return { resizer: initial, - suffix: size.suffix + suffix }; }) ]; -- cgit v1.2.3-70-g09d2 From b42e1e1e2da941955d7751b6003f18fecd5f2f8d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 16 Feb 2020 12:22:10 -0500 Subject: collection sub view and google authentication manager cleanup, deleteAssets route added --- src/client/apis/GoogleAuthenticationManager.tsx | 46 ++-- .../views/collections/CollectionCarouselView.tsx | 28 +-- .../views/collections/CollectionLinearView.tsx | 2 +- .../collections/CollectionMasonryViewFieldRow.tsx | 2 +- .../views/collections/CollectionSchemaView.tsx | 7 +- .../views/collections/CollectionStackingView.tsx | 12 +- .../CollectionStackingViewFieldColumn.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 264 +++++++++++---------- .../views/collections/CollectionTreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +- .../CollectionMulticolumnView.tsx | 6 +- .../CollectionMultirowView.tsx | 6 +- src/server/ApiManagers/DeleteManager.ts | 31 +-- 13 files changed, 213 insertions(+), 209 deletions(-) (limited to 'src/server') diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx index ce1277667..417dc3c3b 100644 --- a/src/client/apis/GoogleAuthenticationManager.tsx +++ b/src/client/apis/GoogleAuthenticationManager.tsx @@ -12,8 +12,8 @@ const prompt = "Paste authorization code here..."; @observer export default class GoogleAuthenticationManager extends React.Component<{}> { public static Instance: GoogleAuthenticationManager; - @observable private openState = false; private authenticationLink: Opt = undefined; + @observable private openState = false; @observable private authenticationCode: Opt = undefined; @observable private clickedState = false; @observable private success: Opt = undefined; @@ -39,24 +39,18 @@ export default class GoogleAuthenticationManager extends React.Component<{}> { const disposer = reaction( () => this.authenticationCode, async authenticationCode => { - if (!authenticationCode) { - return; + if (authenticationCode) { + disposer(); + const { access_token, avatar, name } = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode }); + runInAction(() => { + this.avatar = avatar; + this.username = name; + this.hasBeenClicked = false; + this.success = false; + }); + this.beginFadeout(); + resolve(access_token); } - const { access_token, avatar, name } = await Networking.PostToServer( - "/writeGoogleAccessToken", - { authenticationCode } - ); - runInAction(() => { - this.avatar = avatar; - this.username = name; - }); - this.beginFadeout(); - disposer(); - resolve(access_token); - action(() => { - this.hasBeenClicked = false; - this.success = false; - }); } ); }); @@ -86,26 +80,20 @@ export default class GoogleAuthenticationManager extends React.Component<{}> { GoogleAuthenticationManager.Instance = this; } - private handleClick = () => { - window.open(this.authenticationLink); - setTimeout(() => this.hasBeenClicked = true, 500); - } - - private handlePaste = action((e: React.ChangeEvent) => { - this.authenticationCode = e.currentTarget.value; - }); - private get renderPrompt() { return (
    {this.displayLauncher ? : (null)} {this.clickedState ? this.authenticationCode = e.currentTarget.value)} placeholder={prompt} /> : (null)} {this.avatar ? { //used for stacking and masonry view this._dropDisposer?.(); if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); } } @@ -41,21 +41,21 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) } panelHeight = () => this.props.PanelHeight() - 50; - @computed get content() { + @computed get content() { const index = NumCast(this.layoutDoc._itemIndex); return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) : - <> -
    - -
    -
    - -
    - + <> +
    + +
    +
    + +
    + ; } @computed get buttons() { return <> diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 7eb316cf0..9bbc9f1b6 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -67,7 +67,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); } } diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index e25a2f5eb..f3d512a97 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -80,7 +80,7 @@ export class CollectionMasonryViewFieldRow extends React.Component d[key] = castedValue); - this.props.parent.drop(e, de); + this.props.parent.onInternalDrop(e, de); e.stopPropagation(); } }); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index fa8be5177..c422c38f1 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -14,7 +14,6 @@ import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { ComputedField } from "../../../new_fields/ScriptField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { Docs, DocumentOptions } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; import { Gateway } from "../../northstar/manager/Gateway"; import { CompileScript, Transformer, ts } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; @@ -175,7 +174,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { moveDocument={this.props.moveDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} active={this.props.active} - onDrop={this.onDrop} + onDrop={this.onExternalDrop} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} isSelected={this.props.isSelected} @@ -199,7 +198,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { render() { return
    -
    this.props.active(true) && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}> +
    this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}> {this.schemaTable}
    {this.dividerDragger} @@ -692,7 +691,7 @@ export class SchemaTable extends React.Component { onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // 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: "Make DB", event: this.makeDB, icon: "table" }); - ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" }) + ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" }); } } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 055035b3e..a9cefae6a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -189,7 +189,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } getDocHeight(d?: Doc) { if (!d) return 0; - let layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); const nw = NumCast(layoutDoc._nativeWidth); const nh = NumCast(layoutDoc._nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); @@ -234,7 +234,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @undoBatch @action - drop = (e: Event, de: DragManager.DropEvent) => { + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { const where = [de.x, de.y]; let targInd = -1; let plusOne = 0; @@ -248,7 +248,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0; } }); - if (super.drop(e, de)) { + if (super.onInternalDrop(e, de)) { const newDoc = de.complete.docDragData.droppedDocuments[0]; const docs = this.childDocList; if (docs) { @@ -264,7 +264,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @undoBatch @action - onDrop = async (e: React.DragEvent): Promise => { + onExternalDrop = async (e: React.DragEvent): Promise => { const where = [e.clientX, e.clientY]; let targInd = -1; this._docXfs.map((cd, i) => { @@ -274,7 +274,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { targInd = i; } }); - super.onDrop(e, {}, () => { + super.onExternalDrop(e, {}, () => { if (targInd !== -1) { const newDoc = this.childDocs[this.childDocs.length - 1]; const docs = this.childDocList; @@ -405,7 +405,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { transformOrigin: "top left", }} onScroll={action((e: React.UIEvent) => this._scroll = e.currentTarget.scrollTop)} - onDrop={this.onDrop.bind(this)} + onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this.props.active() && e.stopPropagation()} > {this.renderedSections} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 2ff477c57..87c35679f 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -71,7 +71,7 @@ export class CollectionStackingViewFieldColumn extends React.Component d[key] = undefined); } - this.props.parent.drop(e, de); + this.props.parent.onInternalDrop(e, de); e.stopPropagation(); } }); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e80e1c802..042385dcd 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -57,7 +57,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this.gestureDisposer?.(); this.multiTouchDisposer?.(); if (ele) { - this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this)); } @@ -156,7 +156,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @undoBatch @action - protected drop(e: Event, de: DragManager.DropEvent): boolean { + protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; (this.props.Document.dropConverter instanceof ScriptField) && this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this @@ -195,158 +195,172 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @undoBatch @action - protected async onDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) { + protected async onExternalDrop(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; } - const html = e.dataTransfer.getData("text/html"); - const text = e.dataTransfer.getData("text/plain"); + + const { dataTransfer } = e; + const html = dataTransfer.getData("text/html"); + const text = dataTransfer.getData("text/plain"); if (text && text.startsWith(" { - if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView - (f instanceof Doc) && this.props.addDocument(f); - } - }); - } else { - this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href })); - } - } else if (text) { - this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 })); - } + const { addDocument } = this.props; + if (!addDocument) { + alert("this.props.addDocument does not exist. Aborting drop operation."); return; } - if (html && !html.startsWith(" 1 && tags[1].startsWith("img") ? tags[1] : ""; - if (img) { - const split = img.split("src=\"")[1].split("\"")[0]; - let source = split; - if (split.startsWith("data:image") && split.includes("base64")) { - const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] }); - source = Utils.prepend(accessPaths.agnostic.client); + + if (html) { + if (FormattedTextBox.IsFragment(html)) { + const href = FormattedTextBox.GetHref(html); + if (href) { + const docid = FormattedTextBox.GetDocFromUrl(href); + if (docid) { // prosemirror text containing link to dash document + DocServer.GetRefField(docid).then(f => { + if (f instanceof Doc) { + if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + (f instanceof Doc) && addDocument(f); + } + }); + } else { + addDocument(Docs.Create.WebDocument(href, { ...options, title: href })); + } + } else if (text) { + addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 })); } - const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 }); - ImageUtils.ExtractExif(doc); - this.props.addDocument(doc); return; - } else { - const path = window.location.origin + "/doc/"; - if (text.startsWith(path)) { - const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => { - if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView - (f instanceof Doc) && this.props.addDocument(f); - } - }); + } + if (!html.startsWith(" 1 && tags[1].startsWith("img") ? tags[1] : ""; + if (img) { + const split = img.split("src=\"")[1].split("\"")[0]; + let source = split; + if (split.startsWith("data:image") && split.includes("base64")) { + const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] }); + source = Utils.prepend(accessPaths.agnostic.client); + } + const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 }); + ImageUtils.ExtractExif(doc); + addDocument(doc); + return; } else { - const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 }); - Doc.GetProto(htmlDoc)["data-text"] = text; - this.props.addDocument(htmlDoc); + const path = window.location.origin + "/doc/"; + if (text.startsWith(path)) { + const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0]; + DocServer.GetRefField(docid).then(f => { + if (f instanceof Doc) { + if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + (f instanceof Doc) && this.props.addDocument(f); + } + }); + } else { + const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 }); + Doc.GetProto(htmlDoc)["data-text"] = text; + this.props.addDocument(htmlDoc); + } + return; } - return; } } - if (text && text.indexOf("www.youtube.com/watch") !== -1) { - const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/"); - this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5 })); - return; - } - let matches: RegExpExecArray | null; - if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) { - const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." }); - const proto = newBox.proto!; - const documentId = matches[2]; - proto[GoogleRef] = documentId; - proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs..."; - proto.backgroundColor = "#eeeeff"; - this.props.addDocument(newBox); - // const parent = Docs.Create.StackingDocument([newBox], { title: `Google Doc Import (${documentId})` }); - // CollectionDockingView.Instance.AddRightSplit(parent, undefined); - // proto.height = parent[HeightSym](); - return; - } - if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) { - const albums = await GooglePhotos.Transactions.ListAlbums(); - const albumId = matches[3]; - const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId); - console.log(mediaItems); - return; + + if (text) { + if (text.includes("www.youtube.com/watch")) { + const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/"); + addDocument(Docs.Create.VideoDocument(url, { + ...options, + title: url, + _width: 400, + _height: 315, + _nativeWidth: 600, + _nativeHeight: 472.5 + })); + return; + } + let matches: RegExpExecArray | null; + if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) { + const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." }); + const proto = newBox.proto!; + const documentId = matches[2]; + proto[GoogleRef] = documentId; + proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs..."; + proto.backgroundColor = "#eeeeff"; + addDocument(newBox); + return; + } + if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) { + const albumId = matches[3]; + const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId); + console.log(mediaItems); + return; + } } + const { items } = e.dataTransfer; const { length } = items; const files: File[] = []; - if (length) { - const batch = UndoManager.StartBatch("collection view drop"); - const promises: Promise[] = []; - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < length; i++) { - const item = e.dataTransfer.items[i]; - if (item.kind === "string" && item.type.indexOf("uri") !== -1) { - let str: string; - const prom = new Promise(resolve => item.getAsString(resolve)) - .then(action((s: string) => rp.head(Utils.CorsProxy(str = s)))) - .then(result => { - const type = result["content-type"]; - if (type) { - Docs.Get.DocumentFromType(type, str, options) - .then(doc => doc && this.props.addDocument(doc)); - } - }); - promises.push(prom); - } - if (item.kind === "file") { - const file = item.getAsFile(); - file && file.type && files.push(file); + const generatedDocuments: Doc[] = []; + if (!length) { + alert("No uploadable content found."); + return; + } + + const batch = UndoManager.StartBatch("collection view drop"); + for (let i = 0; i < length; i++) { + const item = e.dataTransfer.items[i]; + if (item.kind === "string" && item.type.includes("uri")) { + const stringContents = await new Promise(resolve => item.getAsString(resolve)); + const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"]; + if (type) { + const doc = await Docs.Get.DocumentFromType(type, stringContents, options); + doc && generatedDocuments.push(doc); } } - promises.push(Networking.UploadFilesToServer(files).then(responses => responses.forEach(({ source: { name, type }, result }) => { - if (result instanceof Error) { - alert(`Upload failed: ${result.message}`); - return; - } - const full = { ...options, _width: 300, title: name }; - const pathname = Utils.prepend(result.accessPaths.agnostic.client); - Docs.Get.DocumentFromType(type, pathname, full).then(doc => { - if (doc) { - const proto = Doc.GetProto(doc); - proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); - if (Upload.isImageInformation(result)) { - proto["data-nativeWidth"] = result.nativeWidth; - proto["data-nativeHeight"] = result.nativeHeight; - proto.contentSize = result.contentSize; - } - this.props?.addDocument(doc); - } - }); - }))); - if (promises.length) { - Promise.all(promises).finally(() => { completed && completed(); batch.end(); }); - } else { - if (text && !text.includes("https://")) { - this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 })); - } - batch.end(); + if (item.kind === "file") { + const file = item.getAsFile(); + file && file.type && files.push(file); + } + } + for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) { + if (result instanceof Error) { + alert(`Upload failed: ${result.message}`); + return; } + const full = { ...options, _width: 300, title: name }; + const pathname = Utils.prepend(result.accessPaths.agnostic.client); + const doc = await Docs.Get.DocumentFromType(type, pathname, full); + if (!doc) { + continue; + } + const proto = Doc.GetProto(doc); + proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); + if (Upload.isImageInformation(result)) { + proto["data-nativeWidth"] = result.nativeWidth; + proto["data-nativeHeight"] = result.nativeHeight; + proto.contentSize = result.contentSize; + } + generatedDocuments.push(doc); + } + if (generatedDocuments.length) { + generatedDocuments.forEach(addDocument); + completed && completed(); } else { - alert("No uploadable content found."); + if (text && !text.includes("https://")) { + addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 })); + } } + batch.end(); } } + return CollectionSubView; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 8720ce002..13ab7c1a4 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -210,7 +210,7 @@ class TreeView extends React.Component { } else { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" }); ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" }); - } + } ContextMenu.Instance.addItem({ description: "Toggle Theme Colors", event: () => this.props.document.darkScheme = !this.props.document.darkScheme, icon: "minus" }); ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { _width: 300, _height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" }); ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" }); @@ -594,7 +594,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer && this.treedropDisposer(); if (this._mainEle = ele) { - this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); + this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); } } @@ -702,7 +702,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } outerXf = () => Utils.GetScreenTransform(this._mainEle!); - onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {}); + onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {}); @computed get renderClearButton() { return
    diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 969d6b3c8..bdc5e03e3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -117,20 +117,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - onDrop = (e: React.DragEvent): Promise => { + onExternalDrop = (e: React.DragEvent): Promise => { const pt = this.getTransform().transformPoint(e.pageX, e.pageY); - return super.onDrop(e, { x: pt[0], y: pt[1] }); + return super.onExternalDrop(e, { x: pt[0], y: pt[1] }); } @undoBatch @action - drop = (e: Event, de: DragManager.DropEvent) => { + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (this.props.Document.isBackground) return false; const xf = this.getTransform(); const xfo = this.getTransformOverlay(); const [xp, yp] = xf.transformPoint(de.x, de.y); const [xpo, ypo] = xfo.transformPoint(de.x, de.y); - if (super.drop(e, de)) { + if (super.onInternalDrop(e, de)) { if (de.complete.docDragData) { if (de.complete.docDragData.droppedDocuments.length) { const firstDoc = de.complete.docDragData.droppedDocuments[0]; @@ -1079,7 +1079,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return
    { - if (super.drop(e, de)) { + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + if (super.onInternalDrop(e, de)) { de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { d.dimUnit = "*"; d.dimMagnitude = 1; @@ -214,7 +214,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu getTransform={dxf} onClick={this.onChildClickHandler} renderDepth={this.props.renderDepth + 1} - /> + />; } /** * @returns the resolved list of rendered child documents, displayed diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 630a178cf..5e59f8237 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -190,8 +190,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) @undoBatch @action - drop = (e: Event, de: DragManager.DropEvent) => { - if (super.drop(e, de)) { + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + if (super.onInternalDrop(e, de)) { de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { d.dimUnit = "*"; d.dimMagnitude = 1; @@ -215,7 +215,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) getTransform={dxf} onClick={this.onChildClickHandler} renderDepth={this.props.renderDepth + 1} - /> + />; } /** * @returns the resolved list of rendered child documents, displayed diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index be452c0ff..9e70af2eb 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -2,6 +2,11 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _permission_denied, PublicHandler } from "../RouteManager"; import { WebSocket } from "../Websocket/Websocket"; import { Database } from "../database"; +import rimraf = require("rimraf"); +import { pathToDirectory, Directory } from "./UploadManager"; +import { filesDirectory } from ".."; +import { DashUploadUtils } from "../DashUploadUtils"; +import { mkdirSync } from "fs"; export default class DeleteManager extends ApiManager { @@ -31,21 +36,19 @@ export default class DeleteManager extends ApiManager { } }); - const hi: PublicHandler = async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); + register({ + method: Method.GET, + subscription: "/deleteAssets", + secureHandler: async ({ res, isRelease }) => { + if (isRelease) { + return _permission_denied(res, deletionPermissionError); + } + rimraf.sync(filesDirectory); + mkdirSync(filesDirectory); + await DashUploadUtils.buildFileDirectories(); + res.redirect("/delete"); } - await Database.Instance.deleteAll('users'); - res.redirect("/home"); - }; - - // register({ - // method: Method.GET, - // subscription: "/deleteUsers", - // onValidation: hi, - // onUnauthenticated: hi - // }); - + }); register({ method: Method.GET, -- cgit v1.2.3-70-g09d2 From ae4b072bb8f360c60cd61bcd1e49a075010e20b4 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 16 Feb 2020 16:00:29 -0500 Subject: clean up --- src/scraping/buxton/final/BuxtonImporter.ts | 6 ++---- src/server/DashUploadUtils.ts | 14 ++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) (limited to 'src/server') diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts index 8041343fd..ae5d7237d 100644 --- a/src/scraping/buxton/final/BuxtonImporter.ts +++ b/src/scraping/buxton/final/BuxtonImporter.ts @@ -310,10 +310,8 @@ async function writeImages(zip: any): Promise { continue; } - const ext = `.${type}`.toLowerCase(); - const generatedFileName = `upload_${Utils.GenerateGuid()}${ext}`; - - await DashUploadUtils.outputResizedImages(streamImage, imageDir, generatedFileName, ext); + const generatedFileName = `upload_${Utils.GenerateGuid()}.${type.toLowerCase()}`; + await DashUploadUtils.outputResizedImages(streamImage, generatedFileName, imageDir); imageUrls.push({ url: `/files/images/buxton/${generatedFileName}`, diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index d4bcd22ae..8f31f990f 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -208,8 +208,7 @@ export namespace DashUploadUtils { export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = "", cleanUp = true): Promise => { const { requestable, source, ...remaining } = metadata; - const extension = `.${remaining.contentType.split("/")[1].toLowerCase()}`; - const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}${extension}`; + const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split("/")[1].toLowerCase()}`; const { images } = Directory; const information: Upload.ImageInformation = { accessPaths: { @@ -217,8 +216,7 @@ export namespace DashUploadUtils { }, ...metadata }; - const outputPath = pathToDirectory(Directory.images); - const writtenFiles = await outputResizedImages(() => request(requestable), outputPath, resolved, extension); + const writtenFiles = await outputResizedImages(() => request(requestable), resolved, pathToDirectory(Directory.images)); for (const suffix of Object.keys(writtenFiles)) { information.accessPaths[suffix] = getAccessPaths(images, writtenFiles[suffix]); } @@ -248,10 +246,10 @@ export namespace DashUploadUtils { force: true }; - export async function outputResizedImages(streamProvider: () => Stream | Promise, outputPath: string, fileName: string, ext: string) { + export async function outputResizedImages(streamProvider: () => Stream | Promise, outputFileName: string, outputDirectory: string) { const writtenFiles: { [suffix: string]: string } = {}; - for (const { resizer, suffix } of resizers(ext)) { - const resolvedOutputPath = path.resolve(outputPath, writtenFiles[suffix] = InjectSize(fileName, suffix)); + for (const { resizer, suffix } of resizers(path.extname(outputFileName))) { + const outputPath = path.resolve(outputDirectory, writtenFiles[suffix] = InjectSize(outputFileName, suffix)); await new Promise(async (resolve, reject) => { const source = streamProvider(); let readStream: Stream; @@ -263,7 +261,7 @@ export namespace DashUploadUtils { if (resizer) { readStream = readStream.pipe(resizer.withMetadata()); } - readStream.pipe(createWriteStream(resolvedOutputPath)).on("close", resolve).on("error", reject); + readStream.pipe(createWriteStream(outputPath)).on("close", resolve).on("error", reject); }); } return writtenFiles; -- cgit v1.2.3-70-g09d2 From 2575b947e592da2313d8dc93a4c03e5278a32986 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 16 Feb 2020 16:09:36 -0500 Subject: cleanup --- src/scraping/buxton/final/BuxtonImporter.ts | 6 +++--- src/server/apis/google/GoogleApiServerUtils.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/server') diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts index ae5d7237d..afbd487c2 100644 --- a/src/scraping/buxton/final/BuxtonImporter.ts +++ b/src/scraping/buxton/final/BuxtonImporter.ts @@ -294,7 +294,7 @@ async function writeImages(zip: any): Promise { const imageUrls: ImageData[] = []; for (const mediaPath of imageEntries) { - const streamImage = () => new Promise((resolve, reject) => { + const getImageStream = () => new Promise((resolve, reject) => { zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream)); }); @@ -303,7 +303,7 @@ async function writeImages(zip: any): Promise { readStream.destroy(); resolve(dimensions); }).on("error", () => readStream.destroy()); - const readStream = await streamImage(); + const readStream = await getImageStream(); readStream.pipe(sizeStream); }); if (Math.abs(width - height) < 10) { @@ -311,7 +311,7 @@ async function writeImages(zip: any): Promise { } const generatedFileName = `upload_${Utils.GenerateGuid()}.${type.toLowerCase()}`; - await DashUploadUtils.outputResizedImages(streamImage, generatedFileName, imageDir); + await DashUploadUtils.outputResizedImages(getImageStream, generatedFileName, imageDir); imageUrls.push({ url: `/files/images/buxton/${generatedFileName}`, diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 329107a71..0f75833ee 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -318,13 +318,14 @@ export namespace GoogleApiServerUtils { */ async function retrieveCredentials(userId: string): Promise<{ credentials: Opt, refreshed: boolean }> { let credentials: Opt = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId); - const refreshed = false; + let refreshed = false; if (!credentials) { return { credentials: undefined, refreshed }; } // check for token expiry if (credentials.expiry_date! <= new Date().getTime()) { credentials = await refreshAccessToken(credentials, userId); + refreshed = true; } return { credentials, refreshed }; } -- cgit v1.2.3-70-g09d2 From f7fae0325a0adb64797bff2bcaffc2890ab198d7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 17 Feb 2020 14:54:30 -0500 Subject: fixed pdf upload naming issues --- src/server/DashUploadUtils.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/server') diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 8f31f990f..ea4c26ca2 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -83,10 +83,11 @@ export namespace DashUploadUtils { } async function UploadPdf(file: File) { - const { path, name } = file; - const dataBuffer = readFileSync(path); + const { path: sourcePath } = file; + const dataBuffer = readFileSync(sourcePath); const result: ParsedPDF = await parse(dataBuffer); await new Promise((resolve, reject) => { + const name = path.basename(sourcePath); const textFilename = `${name.substring(0, name.length - 4)}.txt`; const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename)); writeStream.write(result.text, error => error ? reject(error) : resolve()); @@ -183,7 +184,8 @@ export namespace DashUploadUtils { }; export async function MoveParsedFile(file: File, destination: Directory): Promise { - const { name, path: sourcePath } = file; + const { path: sourcePath } = file; + const name = path.basename(sourcePath); return new Promise(resolve => { const destinationPath = serverPathToFile(destination, name); rename(sourcePath, destinationPath, error => { -- cgit v1.2.3-70-g09d2 From 266892f40d5abc9f8ce4cbb2d6fb70564ecd8da3 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 17 Feb 2020 19:45:30 -0500 Subject: fixed initialization of note types and alias titles. --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/server/authentication/models/current_user_utils.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src/server') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index a29823734..b1e4ca9db 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -74,7 +74,7 @@ export class MarqueeView extends React.Component ({ description: ":" + (i + 1) + " " + StrCast(note.title), - event: (args: { x: number, y: number }) => this.props.addLiveTextDocument(Docs.Create.TextDocument("", { _width: 200, _height: 100, x, y, _autoHeight: true, layout: note, title: StrCast(note.title) })), + event: (args: { x: number, y: number }) => this.props.addLiveTextDocument(Docs.Create.TextDocument("", { _width: 200, _height: 100, x, y, _autoHeight: true, layout: note, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) })), icon: "eye" })) as ContextMenuProps[], icon: "eye" diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 930b021a4..66d1129f2 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -44,13 +44,15 @@ export class CurrentUserUtils { Docs.Create.TextDocument("", { title: "Todo", backgroundColor: "orange", isTemplateDoc: true }) ]; } + static setupDefaultDocTemplates(doc: Doc, buttons?: string[]) { + doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(CurrentUserUtils.setupNoteTypes(doc), { title: "Note Types", _height: 75 })); + } // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static setupCreatorButtons(doc: Doc, buttons?: string[]) { - const notes = CurrentUserUtils.setupNoteTypes(doc); + const notes = DocListCast(Cast(doc.noteTypes, Doc, null)?.data); const emptyPresentation = Docs.Create.PresDocument(new List(), { title: "Presentation", _viewType: CollectionViewType.Stacking, _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); const emptyCollection = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" }); - doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", _height: 75 }); doc.activePen = doc; const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ { title: "collection", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: emptyCollection }, @@ -280,6 +282,7 @@ export class CurrentUserUtils { static updateUserDocument(doc: Doc) { doc.title = Doc.CurrentUserEmail; new InkingControl(); + (doc.noteTypes === undefined) && CurrentUserUtils.setupDefaultDocTemplates(doc); (doc.optionalRightCollection === undefined) && CurrentUserUtils.setupMobileUploads(doc); (doc.overlays === undefined) && CurrentUserUtils.setupOverlays(doc); (doc.expandingButtons === undefined) && CurrentUserUtils.setupExpandingButtons(doc); -- cgit v1.2.3-70-g09d2 From 0f2872775a9a3bab73c92afc329e1279645f3a71 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 17 Feb 2020 22:50:10 -0500 Subject: made creation of template docs from context menu simpler --- src/client/documents/Documents.ts | 31 +++++++++++++++- .../CollectionStackingViewFieldColumn.tsx | 8 +++-- .../collections/collectionFreeForm/MarqueeView.tsx | 42 ++++------------------ src/client/views/nodes/DocumentView.tsx | 9 +++-- src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/new_fields/Doc.ts | 16 ++++----- .../authentication/models/current_user_utils.ts | 11 ++---- 7 files changed, 61 insertions(+), 58 deletions(-) (limited to 'src/server') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 072e8c612..669aa97b5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -21,7 +21,7 @@ import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../ import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; -import { Cast, NumCast } from "../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; import { dropActionType } from "../util/DragManager"; @@ -54,6 +54,8 @@ import { InkingControl } from "../views/InkingControl"; import { RichTextField } from "../../new_fields/RichTextField"; import { extname } from "path"; import { MessageStore } from "../../server/Message"; +import { ContextMenuProps } from "../views/ContextMenuItem"; +import { ContextMenu } from "../views/ContextMenu"; const requestImageSize = require('../util/request-image-size'); const path = require('path'); @@ -898,6 +900,33 @@ export namespace DocUtils { return linkDocProto; } + export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void { + ContextMenu.Instance.addItem({ + description: "Add Note ...", + subitems: DocListCast((Doc.UserDoc().noteTypes as Doc).data).map((note, i) => ({ + description: ":" + StrCast(note.title), + event: (args: { x: number, y: number }) => docTextAdder(Docs.Create.TextDocument("", { _width: 200, x, y, _autoHeight: true, layout: note, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) })), + icon: "eye" + })) as ContextMenuProps[], + icon: "eye" + }); + ContextMenu.Instance.addItem({ + description: "Add Template Doc ...", + subitems: DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc).map((dragDoc, i) => ({ + description: ":" + StrCast(dragDoc.title), + event: (args: { x: number, y: number }) => { + const newDoc = Doc.ApplyTemplate(dragDoc); + if (newDoc) { + newDoc.x = x; + newDoc.y = y; + docAdder(newDoc); + } + }, + icon: "eye" + })) as ContextMenuProps[], + icon: "eye" + }); + } } Scripting.addGlobal("Docs", Docs); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 2e3467d90..8c23ecd49 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -8,10 +8,10 @@ import { Doc, DocListCast } from "../../../new_fields/Doc"; import { RichTextField } from "../../../new_fields/RichTextField"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { NumCast, StrCast } from "../../../new_fields/Types"; +import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { ImageField } from "../../../new_fields/URLField"; import { TraceMobx } from "../../../new_fields/util"; -import { Docs } from "../../documents/Documents"; +import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; @@ -268,8 +268,10 @@ export class CollectionStackingViewFieldColumn extends React.Component dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey => docItems.push({ description: ":" + fieldKey, event: () => { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 44109edc5..1db963fa4 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -3,24 +3,20 @@ import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../../new_fields/Doc"; import { InkField } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; -import { listSpec } from "../../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../../../new_fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { Cast, NumCast } from "../../../../new_fields/Types"; import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; import { Utils } from "../../../../Utils"; -import { Docs } from "../../../documents/Documents"; +import { Docs, DocUtils } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; +import { ContextMenu } from "../../ContextMenu"; import { PreviewCursor } from "../../PreviewCursor"; -import { CollectionViewType } from "../CollectionView"; +import { SubCollectionViewProps } from "../CollectionSubView"; +import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); -import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; -import { SubCollectionViewProps } from "../CollectionSubView"; -import { ContextMenu } from "../../ContextMenu"; -import { ContextMenuProps } from "../../ContextMenuItem"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -69,32 +65,8 @@ export class MarqueeView extends React.Component ({ - description: ":" + StrCast(note.title), - event: (args: { x: number, y: number }) => this.props.addLiveTextDocument(Docs.Create.TextDocument("", { _width: 200, x, y, _autoHeight: true, layout: note, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) })), - icon: "eye" - })) as ContextMenuProps[], - icon: "eye" - }); - ContextMenu.Instance.addItem({ - description: "Add Template Doc ...", - subitems: DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc).map((dragDoc, i) => ({ - description: ":" + StrCast(dragDoc.title), - event: (args: { x: number, y: number }) => { - const newDoc = Doc.ApplyTemplate(dragDoc); - if (newDoc) { - newDoc.x = x; - newDoc.y = y; - this.props.addDocument(newDoc); - } - }, - icon: "eye" - })) as ContextMenuProps[], - icon: "eye" - }); + DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y); + ContextMenu.Instance.displayMenu(this._downX, this._downY); } else if (e.key === "q" && e.ctrlKey) { e.preventDefault(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 704da3c74..e1d5fcc8a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -628,6 +628,8 @@ export class DocumentView extends DocComponent(Docu } e.preventDefault(); + const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); + const cm = ContextMenu.Instance; const subitems: ContextMenuProps[] = []; subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this, this.props.LibraryPath), icon: "desktop" }); @@ -636,6 +638,7 @@ export class DocumentView extends DocComponent(Docu subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" }); subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" }); + templateDoc && subitems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, undefined, "onRight"), icon: "eye" }); subitems.push({ description: "Open Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); @@ -756,7 +759,9 @@ export class DocumentView extends DocComponent(Docu select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; chromeHeight = () => { - return 1; + const showTitle = StrCast(this.layoutDoc._showTitle); + const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined; + return showTextTitle ? 25 : 1; } @computed get finalLayoutKey() { @@ -857,7 +862,7 @@ export class DocumentView extends DocComponent(Docu this.contents :
    -
    +
    {this.contents}
    {titleView} diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 317f95f81..c1e630e67 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -369,7 +369,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = true; Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); }, icon: "eye" }); + !this.props.Document.expandedTemplate && funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = true; Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); }, icon: "eye" }); funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document._showSidebar = !this.props.Document._showSidebar; }, icon: "expand-arrows-alt" }); funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 48890ac8d..43bbea623 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -1,22 +1,22 @@ -import { observable, ObservableMap, runInAction, action, computed } from "mobx"; +import { action, computed, observable, ObservableMap, runInAction } from "mobx"; +import { computedFn } from "mobx-utils"; import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; import { DocumentType } from "../client/documents/DocumentTypes"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; -import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; +import { UndoManager } from "../client/util/UndoManager"; +import { intersectRect } from "../Utils"; +import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; import { List } from "./List"; import { ObjectField } from "./ObjectField"; import { PrefetchProxy, ProxyField } from "./Proxy"; import { FieldId, RefField } from "./RefField"; +import { RichTextField } from "./RichTextField"; import { listSpec } from "./Schema"; -import { ComputedField, ScriptField } from "./ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; +import { ComputedField } from "./ScriptField"; +import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; -import { intersectRect } from "../Utils"; -import { UndoManager, undoBatch } from "../client/util/UndoManager"; -import { computedFn } from "mobx-utils"; -import { RichTextField } from "./RichTextField"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 66d1129f2..e9c12b70c 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -34,30 +34,25 @@ export class CurrentUserUtils { @observable public static GuestWorkspace: Doc | undefined; @observable public static GuestMobile: Doc | undefined; - // a default set of note types .. not being used yet... - static setupNoteTypes(doc: Doc) { - return [ + static setupDefaultDocTemplates(doc: Doc, buttons?: string[]) { + const noteTemplates = [ Docs.Create.TextDocument("", { title: "Note", backgroundColor: "yellow", isTemplateDoc: true }), Docs.Create.TextDocument("", { title: "Idea", backgroundColor: "pink", isTemplateDoc: true }), Docs.Create.TextDocument("", { title: "Topic", backgroundColor: "lightBlue", isTemplateDoc: true }), Docs.Create.TextDocument("", { title: "Person", backgroundColor: "lightGreen", isTemplateDoc: true }), Docs.Create.TextDocument("", { title: "Todo", backgroundColor: "orange", isTemplateDoc: true }) ]; - } - static setupDefaultDocTemplates(doc: Doc, buttons?: string[]) { - doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(CurrentUserUtils.setupNoteTypes(doc), { title: "Note Types", _height: 75 })); + doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates, { title: "Note Types", _height: 75 })); } // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static setupCreatorButtons(doc: Doc, buttons?: string[]) { - const notes = DocListCast(Cast(doc.noteTypes, Doc, null)?.data); const emptyPresentation = Docs.Create.PresDocument(new List(), { title: "Presentation", _viewType: CollectionViewType.Stacking, _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); const emptyCollection = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" }); doc.activePen = doc; const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ { title: "collection", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: emptyCollection }, { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' }, - { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] }, { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' }, { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' }, { title: "buxton", icon: "cloud-upload-alt", ignoreClick: true, drag: "Docs.Create.Buxton()" }, -- cgit v1.2.3-70-g09d2 From e625ff09a94160e28f0afda56f4ae7e48b58c0b3 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 19 Feb 2020 19:02:10 -0500 Subject: enabled multicolumn document to show contents of original document if its a template and no fields are specified ... probably will reverse this later --- .../collectionMulticolumn/CollectionMulticolumnView.tsx | 11 ++++++++--- src/server/authentication/models/current_user_utils.ts | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src/server') diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 785f90212..82175c0b5 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import { Doc } from '../../../../new_fields/Doc'; import { documentSchema } from '../../../../new_fields/documentSchemas'; import { makeInterface } from '../../../../new_fields/Schema'; -import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../new_fields/Types'; +import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../new_fields/Types'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -13,6 +13,7 @@ import { CollectionSubView } from '../CollectionSubView'; import "./collectionMulticolumnView.scss"; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; +import { List } from '../../../../new_fields/List'; type MulticolumnDocument = makeInterface<[typeof documentSchema]>; const MulticolumnDocument = makeInterface(documentSchema); @@ -222,13 +223,17 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu */ @computed private get contents(): JSX.Element[] | null { - const { childLayoutPairs } = this; + // bcz: feels like a hack ... trying to show something useful when there's no list document in the data field of a templated object + const expanded = Cast(this.props.Document.expandedTemplate, Doc, null); + let { childLayoutPairs } = this.dataDoc[this.props.fieldKey] instanceof List || !expanded ? this : { childLayoutPairs: [] } as { childLayoutPairs: { layout: Doc, data: Doc }[] }; + const replaced = !childLayoutPairs.length && !Cast(expanded?.layout, Doc, null) && expanded; + childLayoutPairs = childLayoutPairs.length || !replaced ? childLayoutPairs : [{ layout: replaced, data: replaced }]; const { Document, PanelHeight } = this.props; const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)); - const width = () => this.lookupPixels(layout); + const width = () => expanded ? this.props.PanelWidth() : this.lookupPixels(layout); const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push(
    Date: Fri, 21 Feb 2020 09:32:23 -0500 Subject: fixed link naming and exposed link database (at least for testing) --- src/client/documents/Documents.ts | 16 ++++++++++------ src/client/util/LinkManager.ts | 18 +++++++++--------- src/client/views/DocumentButtonBar.tsx | 5 +++-- src/client/views/GestureOverlay.tsx | 2 +- src/client/views/collections/CollectionTreeView.tsx | 4 ++-- src/client/views/linking/LinkEditor.tsx | 10 +++++----- src/client/views/nodes/DocuLinkBox.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 5 +++-- src/server/authentication/models/current_user_utils.ts | 2 +- 9 files changed, 35 insertions(+), 28 deletions(-) (limited to 'src/server') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9c83a6a9e..4bad24be9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -119,6 +119,7 @@ export interface DocumentOptions { annotationOn?: Doc; removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document dbDoc?: Doc; + linkRelationship?: string; // type of relatinoship a link represents ischecked?: ScriptField; // returns whether a font icon box is checked activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) onClick?: ScriptField; @@ -225,11 +226,12 @@ export namespace Docs { }], [DocumentType.LINK, { layout: { view: LinkBox, dataField: data }, - options: { _height: 75 } + options: { _height: 150 } }], [DocumentType.LINKDOC, { data: new List(), layout: { view: EmptyBox, dataField: data }, + options: { childDropAction: "alias", title: "LINK DB" } }], [DocumentType.YOUTUBE, { layout: { view: YoutubeBox, dataField: data } @@ -407,7 +409,8 @@ export namespace Docs { Scripting.addGlobal(Buxton); const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn", - "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "isButton", "isBackground", "removeDropProperties"]; + "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", + "isButton", "isBackground", "removeDropProperties", "treeViewOpen"]; /** * This function receives the relevant document prototype and uses @@ -524,7 +527,7 @@ export namespace Docs { } export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) { - const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { isBackground: true, isButton: true, removeDropProperties: new List(["isBackground", "isButton"]), ...options }); + const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { isButton: true, treeViewHideTitle: true, treeViewOpen: false, removeDropProperties: new List(["isBackground", "isButton"]), ...options }); const linkDocProto = Doc.GetProto(doc); linkDocProto.anchor1 = source.doc; linkDocProto.anchor2 = target.doc; @@ -536,7 +539,7 @@ export namespace Docs { if (linkDocProto.layout_key1 === undefined) { Cast(linkDocProto.proto, Doc, null)!.layout_key1 = DocuLinkBox.LayoutString("anchor1"); Cast(linkDocProto.proto, Doc, null)!.layout_key2 = DocuLinkBox.LayoutString("anchor2"); - Cast(linkDocProto.proto, Doc, null)!.linkBoxExcludedKeys = new List(["treeViewExpandedView", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "proto", "aliasNumber", "title", "isPrototype", "lastOpened", "creationDate", "author"]); + Cast(linkDocProto.proto, Doc, null)!.linkBoxExcludedKeys = new List(["treeViewExpandedView", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "proto", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]); Cast(linkDocProto.proto, Doc, null)!.layoutKey = undefined; } @@ -896,12 +899,13 @@ export namespace DocUtils { }); } - export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) { + export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", linkRelationship: string = "", id?: string) { const sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; if (target.doc === CurrentUserUtils.UserDocument) return undefined; - const linkDoc = Docs.Create.LinkDocument(source, target, { title }, id); + const linkDoc = Docs.Create.LinkDocument(source, target, { title, linkRelationship }, id); + Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('this.anchor1.title +" " + (this.linkRelationship||"to") +" " + this.anchor2.title'); Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)"); Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)"); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 1c328c04e..4457f41e2 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -40,7 +40,7 @@ export class LinkManager { public getAllLinks(): Doc[] { const ldoc = LinkManager.Instance.LinkManagerDoc; if (ldoc) { - const docs = DocListCast(ldoc.allLinks); + const docs = DocListCast(ldoc.data); return docs; } return []; @@ -50,7 +50,7 @@ export class LinkManager { const linkList = LinkManager.Instance.getAllLinks(); linkList.push(linkDoc); if (LinkManager.Instance.LinkManagerDoc) { - LinkManager.Instance.LinkManagerDoc.allLinks = new List(linkList); + LinkManager.Instance.LinkManagerDoc.data = new List(linkList); return true; } return false; @@ -62,7 +62,7 @@ export class LinkManager { if (index > -1) { linkList.splice(index, 1); if (LinkManager.Instance.LinkManagerDoc) { - LinkManager.Instance.LinkManagerDoc.allLinks = new List(linkList); + LinkManager.Instance.LinkManagerDoc.data = new List(linkList); return true; } } @@ -136,12 +136,12 @@ export class LinkManager { } } public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) { - linkDoc.title = groupDoc.title; + linkDoc.linkRelationship = groupDoc.linkRelationship; } // removes group doc of given group type only from given anchor on given link public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) { - linkDoc.title = "-ungrouped-"; + linkDoc.linkRelationship = "-ungrouped-"; } // returns map of group type to anchor's links in that group type @@ -149,9 +149,9 @@ export class LinkManager { const related = this.getAllRelatedLinks(anchor); const anchorGroups = new Map>(); related.forEach(link => { - if (link.title && link.title !== "-ungrouped-") { - const group = anchorGroups.get(StrCast(link.title)); - anchorGroups.set(StrCast(link.title), group ? [...group, link] : [link]); + if (!link.linkRelationship || link?.linkRelationship !== "-ungrouped-") { + const group = anchorGroups.get(StrCast(link.linkRelationship)); + anchorGroups.set(StrCast(link.linkRelationship), group ? [...group, link] : [link]); } else { // if link is in no groups then put it in default group @@ -184,7 +184,7 @@ export class LinkManager { const md: Doc[] = []; const allLinks = LinkManager.Instance.getAllLinks(); allLinks.forEach(linkDoc => { - if (StrCast(linkDoc.title).toUpperCase() === groupType.toUpperCase()) { md.push(linkDoc); } + if (StrCast(linkDoc.linkRelationship).toUpperCase() === groupType.toUpperCase()) { md.push(linkDoc); } }); return md; } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index b1cc8f26a..a3d313224 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -25,6 +25,7 @@ import { DragManager } from '../util/DragManager'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; +import { ComputedField } from '../../new_fields/ScriptField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -125,8 +126,8 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; const text = RichTextMenu.Instance.MakeLinkToSelection(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", anchor2Id); - if (linkDoc.anchor2 instanceof Doc) { - proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link + if (linkDoc.anchor2 instanceof Doc && !proto.title) { + proto.title = Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('this.anchor1.title +" " + (this.linkRelationship||"to") +" " + this.anchor2.title'); } } linkDrag?.end(); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 580c53a37..a8cf8c197 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -333,7 +333,7 @@ export default class GestureOverlay extends Touchable { this._d1 = doc; } else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) { - DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }); + DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link"); actionPerformed = true; } }; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 15ad49e26..ee64b7d09 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -128,7 +128,7 @@ class TreeView extends React.Component { } @undoBatch delete = () => this.props.deleteDoc(this.props.document); - @undoBatch openRight = () => this.props.addDocTab(this.props.document, "onRight", this.props.libraryPath); + @undoBatch openRight = () => this.props.addDocTab(this.props.containingCollection.childDropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath); @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete(); @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => { return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); @@ -229,7 +229,7 @@ class TreeView extends React.Component { if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceDocument; const destDoc = this.props.document; - DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }); + DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree drop link"); e.stopPropagation(); } if (de.complete.docDragData) { diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 6cdd11430..913a04d66 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -206,13 +206,13 @@ export class LinkGroupEditor extends React.Component { constructor(props: LinkGroupEditorProps) { super(props); - const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.title)); + const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.linkRelationship)); groupMdKeys.forEach(key => this._metadataIds.set(key, Utils.GenerateGuid())); } @action setGroupType = (groupType: string): void => { - this.props.groupDoc.title = groupType; + Doc.GetProto(this.props.groupDoc).linkRelationship = groupType; } removeGroupFromLink = (groupType: string): void => { @@ -241,7 +241,7 @@ export class LinkGroupEditor extends React.Component { renderMetadata = (): JSX.Element[] => { const metadata: Array = []; const groupDoc = this.props.groupDoc; - const groupType = StrCast(groupDoc.title); + const groupType = StrCast(groupDoc.linkRelationship); const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType); groupMdKeys.forEach((key) => { @@ -254,7 +254,7 @@ export class LinkGroupEditor extends React.Component { } render() { - const groupType = StrCast(this.props.groupDoc.title); + const groupType = StrCast(this.props.groupDoc.linkRelationship); // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") { let buttons = ; let addButton = ; @@ -293,7 +293,7 @@ export class LinkEditor extends React.Component { const destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); const groups = [this.props.linkDoc].map(groupDoc => { - return ; + return ; }); return !destination ? (null) : ( diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 1983ae529..c4d99456d 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -67,6 +67,7 @@ export class DocuLinkBox extends DocComponent(Doc const alias = Doc.MakeAlias(this.props.Document); alias.isButton = undefined; alias.isBackground = undefined; + alias.layoutKey = "layout"; this.props.addDocTab(alias, StrCast(this.props.Document.linkOpenLocation, "inTab")); } else { DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5a358b6d8..84e9b6abb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -534,7 +534,8 @@ export class DocumentView extends DocComponent(Docu // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); de.complete.linkDragData.linkSourceDocument !== this.props.Document && - (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "-ungrouped-")); // TODODO this is where in text links get passed + (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, + { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `link from ${de.complete.linkDragData.linkSourceDocument.title} to ${this.props.Document.title}`)); // TODODO this is where in text links get passed } } @@ -868,7 +869,7 @@ export class DocumentView extends DocComponent(Docu ; } @computed get ignorePointerEvents() { - return (this.Document.isBackground && !this.isSelected()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None); + return (this.Document.isBackground && !this.isSelected()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None); } @observable _animate = 0; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 125780c8e..3bab00bed 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -189,7 +189,7 @@ export class CurrentUserUtils { return Docs.Create.ButtonDocument({ _width: 50, _height: 25, title: "Library", fontSize: 10, letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", - sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], { + sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, Docs.Prototypes.MainLinkDocument(), doc.recentlyClosed as Doc], { title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true, boxShadow: "0 0", }), targetContainer: sidebarContainer, -- cgit v1.2.3-70-g09d2 From ba31e8581df2341db4bbe4077005664da28888e9 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 21 Feb 2020 15:47:18 -0500 Subject: added a descriptionView template --- src/server/authentication/models/current_user_utils.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/server') diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 3bab00bed..32439757c 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -18,6 +18,7 @@ import { CollectionViewType } from "../../../client/views/collections/Collection import { makeTemplate } from "../../../client/util/DropConverter"; import { RichTextField } from "../../../new_fields/RichTextField"; import { PrefetchProxy } from "../../../new_fields/Proxy"; +import { FormattedTextBox } from "../../../client/views/nodes/FormattedTextBox"; export class CurrentUserUtils { private static curr_id: string; @@ -237,6 +238,9 @@ export class CurrentUserUtils { ], { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, _autoHeight: false }); slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); + const descriptionTemplate = Docs.Create.TextDocument("", { title: "descriptionView", _height: 100, _showTitle: "title" }); + Doc.GetProto(descriptionTemplate).layout = FormattedTextBox.LayoutString("description"); + descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate); const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("setNativeView(this)") }); Doc.GetProto(iconDoc).data = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); @@ -249,10 +253,11 @@ export class CurrentUserUtils { { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List(["dropAction"]), title: "redo button", icon: "redo-alt" }); doc.slidesBtn = Docs.Create.FontIconDocument( { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List(["dropAction"]), title: "presentation slide", icon: "sticky-note" }); - doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc], { + doc.descriptionBtn = Docs.Create.FontIconDocument( + { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: descriptionTemplate, removeDropProperties: new List(["dropAction"]), title: "description view", icon: "sticky-note" }); + doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc, doc.descriptionBtn as Doc], { title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) }); } -- cgit v1.2.3-70-g09d2 From 1c73798b14bc91e59ab95225b341008271d0159a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 22 Feb 2020 01:04:55 -0500 Subject: removed LinkFollow box. --- src/client/documents/DocumentTypes.ts | 1 - src/client/documents/Documents.ts | 8 - src/client/views/OverlayView.tsx | 2 +- .../views/collections/CollectionLinearView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- src/client/views/linking/LinkFollowBox.scss | 93 ---- src/client/views/linking/LinkFollowBox.tsx | 571 --------------------- src/client/views/linking/LinkMenuItem.tsx | 1 - src/client/views/nodes/DocumentContentsView.tsx | 3 +- .../authentication/models/current_user_utils.ts | 2 - 12 files changed, 7 insertions(+), 685 deletions(-) delete mode 100644 src/client/views/linking/LinkFollowBox.scss delete mode 100644 src/client/views/linking/LinkFollowBox.tsx (limited to 'src/server') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 0e6b59b33..06b15d78c 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -19,7 +19,6 @@ export enum DocumentType { WEBCAM = "webcam", FONTICON = "fonticonbox", PRES = "presentation", - LINKFOLLOW = "linkfollow", PRESELEMENT = "preselement", QUERY = "search", COLOR = "color", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index aed14e4f6..ff64489bb 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -40,7 +40,6 @@ import { PresBox } from "../views/nodes/PresBox"; import { ComputedField, ScriptField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; -import { LinkFollowBox } from "../views/linking/LinkFollowBox"; import { PresElementBox } from "../views/presentationview/PresElementBox"; import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { QueryBox } from "../views/nodes/QueryBox"; @@ -250,9 +249,6 @@ export namespace Docs { layout: { view: FontIconBox, dataField: data }, options: { _width: 40, _height: 40, borderRounding: "100%" }, }], - [DocumentType.LINKFOLLOW, { - layout: { view: LinkFollowBox, dataField: data } - }], [DocumentType.WEBCAM, { layout: { view: DashWebRTCVideo, dataField: data } }], @@ -658,10 +654,6 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) }); } - export function LinkFollowBoxDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) }); - } - export function PresElementBoxDocument(options?: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 7a99bf0ae..220efd4a8 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -169,7 +169,7 @@ export class OverlayView extends React.Component { document.addEventListener("pointermove", onPointerMove); document.addEventListener("pointerup", onPointerUp); }; - return
    + return
    NumCast(this.props.Document._height); // 2 * the padding getTransform = (ele: React.RefObject) => () => { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 0d5d3e449..a1cc21319 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -38,7 +38,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @observable _scroll = 0; // used to force the document decoration to update when scrolling @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); } @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); } - @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); } + @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); } @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } @computed get yMargin() { return Math.max(this.props.Document._showTitle && !this.props.Document._showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); } @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ed1d674c6..7eeeb6ff1 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -421,7 +421,6 @@ class TreeView extends React.Component { return <>
    DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); } - public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } + public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } public getActiveDocuments = () => { return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); @@ -433,7 +433,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let x = this.Document._panX || 0; let y = this.Document._panY || 0; - const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); + const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) { PDFMenu.Instance.fadeOut(true); @@ -869,7 +869,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { })); this._cachedPool.clear(); Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k))); - const elements:ViewDefResult[] = computedElementData.slice(); + const elements: ViewDefResult[] = computedElementData.slice(); this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair => elements.push({ ele: { - - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkFollowBox, fieldKey); } - public static Instance: LinkFollowBox | undefined; - @observable static linkDoc: Doc | undefined = undefined; - @observable static destinationDoc: Doc | undefined = undefined; - @observable static sourceDoc: Doc | undefined = undefined; - @observable selectedMode: string = ""; - @observable selectedContext: Doc | undefined = undefined; - @observable selectedContextAliases: Doc[] | undefined = undefined; - @observable selectedOption: string = ""; - @observable selectedContextString: string = ""; - @observable sourceView: DocumentView | undefined = undefined; - @observable canPan: boolean = false; - @observable shouldUseOnlyParentContext = false; - _contextDisposer?: IReactionDisposer; - - @observable private _docs: { col: Doc, target: Doc }[] = []; - @observable private _otherDocs: { col: Doc, target: Doc }[] = []; - - constructor(props: FieldViewProps) { - super(props); - LinkFollowBox.Instance = this; - this.resetVars(); - this.props.Document.isBackground = true; - } - - componentDidMount = () => { - this.resetVars(); - - this._contextDisposer = reaction( - () => this.selectedContextString, - async () => { - const ref = await DocServer.GetRefField(this.selectedContextString); - runInAction(() => { - if (ref instanceof Doc) { - this.selectedContext = ref; - } - }); - if (this.selectedContext instanceof Doc) { - const aliases = await SearchUtil.GetViewsOfDocument(this.selectedContext); - runInAction(() => { this.selectedContextAliases = aliases; }); - } - } - ); - } - - componentWillUnmount = () => { - this._contextDisposer && this._contextDisposer(); - } - - async resetPan() { - if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionDoc) { - runInAction(() => this.canPan = false); - if (this.sourceView.props.ContainingCollectionDoc._viewType === CollectionViewType.Freeform) { - const docs = Cast(this.sourceView.props.ContainingCollectionDoc.data, listSpec(Doc), []); - const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc)); - - aliases.forEach(alias => { - if (docs.filter(doc => doc === alias).length > 0) { - runInAction(() => { this.canPan = true; }); - } - }); - } - } - } - - @action - resetVars = () => { - this.selectedContext = undefined; - this.selectedContextString = ""; - this.selectedMode = ""; - this.selectedOption = ""; - LinkFollowBox.linkDoc = undefined; - LinkFollowBox.sourceDoc = undefined; - LinkFollowBox.destinationDoc = undefined; - this.sourceView = undefined; - this.canPan = false; - this.shouldUseOnlyParentContext = false; - } - - async fetchDocuments() { - if (LinkFollowBox.destinationDoc) { - const dest: Doc = LinkFollowBox.destinationDoc; - const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(dest)); - const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${dest[Id]}"` }); - const map: Map = new Map; - const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); - allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); - docs.forEach(doc => map.delete(doc)); - runInAction(async () => { - this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest })); - this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); - const tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc; - runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest })); - }); - } - } - - @action - setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => { - this.resetVars(); - - LinkFollowBox.linkDoc = linkDoc; - LinkFollowBox.sourceDoc = source; - LinkFollowBox.destinationDoc = dest; - this.fetchDocuments(); - - SelectionManager.SelectedDocuments().forEach(dv => { - if (dv.props.Document === LinkFollowBox.sourceDoc) { - this.sourceView = dv; - } - }); - - this.resetPan(); - } - - highlightDoc = () => LinkFollowBox.destinationDoc && Doc.linkFollowHighlight(LinkFollowBox.destinationDoc); - - @undoBatch - openFullScreen = () => { - if (LinkFollowBox.destinationDoc) { - const view = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc); - view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); - } - } - - @undoBatch - openColFullScreen = (options: { context: Doc }) => { - if (LinkFollowBox.destinationDoc) { - if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2; - options.context._panX = newPanX; - options.context._panY = newPanY; - } - const view = DocumentManager.Instance.getDocumentView(options.context); - view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); - this.highlightDoc(); - } - } - - // should container be a doc or documentview or what? This one needs work and is more long term - @undoBatch - openInContainer = (options: { container: Doc }) => { - - } - - static _addDocTab: (undefined | ((doc: Doc, where: string) => boolean)); - - static setAddDocTab = (addFunc: (doc: Doc, where: string) => boolean) => { - LinkFollowBox._addDocTab = addFunc; - } - - @undoBatch - openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => { - if (LinkFollowBox.destinationDoc) { - options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; - if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2; - options.context._panX = newPanX; - options.context._panY = newPanY; - } - (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, "onRight"); - - if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom }); - - this.highlightDoc(); - SelectionManager.DeselectAll(); - } - } - - @undoBatch - openLinkRight = () => { - if (LinkFollowBox.destinationDoc) { - const alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); - (LinkFollowBox._addDocTab || this.props.addDocTab)(alias, "onRight"); - this.highlightDoc(); - SelectionManager.DeselectAll(); - } - - } - - @undoBatch - jumpToLink = async (options: { shouldZoom: boolean }) => { - if (LinkFollowBox.sourceDoc && LinkFollowBox.linkDoc) { - const focus = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, "inTab"); SelectionManager.DeselectAll(); }; - //let focus = (doc: Doc, maxLocation: string) => this.props.focus(docthis.props.focus(LinkFollowBox.destinationDoc, true, 1, () => this.props.addDocTab(doc, maxLocation)); - - DocumentManager.Instance.FollowLink(LinkFollowBox.linkDoc, LinkFollowBox.sourceDoc, focus, options && options.shouldZoom, false, undefined); - } - } - - @undoBatch - openLinkTab = () => { - if (LinkFollowBox.destinationDoc) { - const fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc); - // this.prosp.addDocTab is empty -- use the link source's addDocTab - (LinkFollowBox._addDocTab || this.props.addDocTab)(fullScreenAlias, "inTab"); - - this.highlightDoc(); - SelectionManager.DeselectAll(); - } - } - - @undoBatch - openLinkColTab = (options: { context: Doc, shouldZoom: boolean }) => { - if (LinkFollowBox.destinationDoc) { - options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; - if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2; - options.context._panX = newPanX; - options.context._panY = newPanY; - } - (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, "inTab"); - if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom }); - - this.highlightDoc(); - SelectionManager.DeselectAll(); - } - } - - @undoBatch - openLinkInPlace = (options: { shouldZoom: boolean }) => { - - if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) { - if (this.sourceView && this.sourceView.props.addDocument) { - const destViews = DocumentManager.Instance.getDocumentViews(LinkFollowBox.destinationDoc); - if (!destViews.find(dv => dv.props.ContainingCollectionView === this.sourceView!.props.ContainingCollectionView)) { - const alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); - const y = NumCast(LinkFollowBox.sourceDoc.y); - const x = NumCast(LinkFollowBox.sourceDoc.x); - - const width = NumCast(LinkFollowBox.sourceDoc._width); - const height = NumCast(LinkFollowBox.sourceDoc._height); - - alias.x = x + width + 30; - alias.y = y; - alias._width = width; - alias._height = height; - - this.sourceView.props.addDocument(alias); - } - } - - this.jumpToLink({ shouldZoom: options.shouldZoom }); - - this.highlightDoc(); - SelectionManager.DeselectAll(); - } - } - - //set this to be the default link behavior, can be any of the above - public defaultLinkBehavior: (options?: any) => void = this.jumpToLink; - - @action - currentLinkBehavior = () => { - // this.resetPan(); - if (LinkFollowBox.destinationDoc) { - if (this.selectedContextString === "") { - this.selectedContextString = "self"; - this.selectedContext = LinkFollowBox.destinationDoc; - } - if (this.selectedOption === "") this.selectedOption = FollowOptions.NOZOOM; - const shouldZoom: boolean = this.selectedOption === FollowOptions.NOZOOM ? false : true; - const notOpenInContext: boolean = this.selectedContextString === "self" || this.selectedContextString === LinkFollowBox.destinationDoc[Id]; - - if (this.selectedMode === FollowModes.INPLACE) { - if (shouldZoom !== undefined) this.openLinkInPlace({ shouldZoom: shouldZoom }); - } - else if (this.selectedMode === FollowModes.OPENFULL) { - if (notOpenInContext) this.openFullScreen(); - else this.selectedContext && this.openColFullScreen({ context: this.selectedContext }); - } - else if (this.selectedMode === FollowModes.OPENRIGHT) { - if (notOpenInContext) this.openLinkRight(); - else this.selectedContext && this.openLinkColRight({ context: this.selectedContext, shouldZoom: shouldZoom }); - } - else if (this.selectedMode === FollowModes.OPENTAB) { - if (notOpenInContext) this.openLinkTab(); - else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom }); - } - else if (this.selectedMode === FollowModes.PAN) { - this.jumpToLink({ shouldZoom: shouldZoom }); - } - else return; - } - } - - @action - handleModeChange = (e: React.ChangeEvent) => { - const target = e.target as HTMLInputElement; - this.selectedMode = target.value; - this.selectedContext = undefined; - this.selectedContextString = ""; - - this.shouldUseOnlyParentContext = (this.selectedMode === FollowModes.INPLACE || this.selectedMode === FollowModes.PAN); - - if (this.shouldUseOnlyParentContext) { - if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { - this.selectedContext = this.sourceView.props.ContainingCollectionDoc; - this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionDoc.title)); - } - } - } - - @action - handleOptionChange = (e: React.ChangeEvent) => { - const target = e.target as HTMLInputElement; - this.selectedOption = target.value; - } - - @action - handleContextChange = (e: React.ChangeEvent) => { - const target = e.target as HTMLInputElement; - this.selectedContextString = target.value; - // selectedContext is updated in reaction - this.selectedOption = ""; - } - - @computed - get canOpenInPlace() { - if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { - const colDoc = this.sourceView.props.ContainingCollectionDoc; - if (colDoc._viewType === CollectionViewType.Freeform) return true; - } - return false; - } - - @computed - get availableModes() { - return ( -
    -
    -
    -
    -
    -
    -
    - ); - } - - @computed - get parentName() { - if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { - return this.sourceView.props.ContainingCollectionDoc.title; - } - } - - @computed - get parentID(): string { - if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { - return StrCast(this.sourceView.props.ContainingCollectionDoc[Id]); - } - return "col"; - } - - @computed - get availableContexts() { - return ( - this.shouldUseOnlyParentContext ? - - : -
    -
    - {[...this._docs, ...this._otherDocs].map(doc => { - if (doc && doc.target && doc.col.title !== "Recently Closed") { - return

    ; - } - })} -
    - ); - } - - @computed - get shouldShowZoom(): boolean { - if (this.selectedMode === FollowModes.OPENFULL) return false; - if (this.shouldUseOnlyParentContext) return true; - if (LinkFollowBox.destinationDoc ? this.selectedContextString === LinkFollowBox.destinationDoc[Id] : "self") return false; - - let contextMatch: boolean = false; - if (this.selectedContextAliases) { - this.selectedContextAliases.forEach(alias => { - if (alias._viewType === CollectionViewType.Freeform) contextMatch = true; - }); - } - if (contextMatch) return true; - - return false; - } - - @computed - get availableOptions() { - if (LinkFollowBox.destinationDoc) { - return ( - this.shouldShowZoom ? -
    -
    -
    -
    - : -
    No Available Options
    - ); - } - return null; - } - - render() { - return ( -
    -
    -
    - {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"} -
    this.props.Document.isMinimized = true} className="closeDocument">
    -
    -
    {LinkFollowBox.linkDoc ? - LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title) - : "" : ""}
    -
    -
    -
    -
    Mode
    -
    - {LinkFollowBox.linkDoc ? this.availableModes : "Please select a link to view modes"} -
    -
    -
    -
    Context
    -
    - {this.selectedMode !== "" ? this.availableContexts : "Please select a mode to view contexts"} -
    -
    -
    -
    Options
    -
    - {this.selectedContextString !== "" ? this.availableOptions : "Please select a context to view options"} -
    -
    -
    -
    - -
    - -
    -
    - ); - } -} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 376afa64b..5fd6e4630 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ContextMenu } from '../ContextMenu'; -import { LinkFollowBox } from './LinkFollowBox'; import './LinkMenuItem.scss'; import React = require("react"); import { DocumentManager } from '../../util/DocumentManager'; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 73a53f5cc..bfda13eb3 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -9,7 +9,6 @@ import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView } from "../collections/CollectionView"; -import { LinkFollowBox } from "../linking/LinkFollowBox"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; import { ButtonBox } from "./ButtonBox"; @@ -108,7 +107,7 @@ export class DocumentContentsView extends React.Component