diff options
Diffstat (limited to 'src')
25 files changed, 135 insertions, 116 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c0f0e0552..58aec6173 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -56,6 +56,7 @@ const requestImageSize = require('../util/request-image-size'); const path = require('path'); export interface DocumentOptions { + _autoHeight?: boolean; _panX?: number; _panY?: number; _width?: number; @@ -103,7 +104,6 @@ export interface DocumentOptions { sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views schemaColumns?: List<SchemaHeaderField>; dockingConfig?: string; - autoHeight?: boolean; annotationOn?: Doc; removeDropProperties?: List<string>; // 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; @@ -333,7 +333,7 @@ export namespace Docs { */ export namespace Create { - const delegateKeys = ["x", "y", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "_dropAction", "_annotationOn", "_chromeStatus", "_forceActive", "_fitWidth", "_LODdisable"]; + const delegateKeys = ["x", "y", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "_dropAction", "_annotationOn", "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable"]; /** * This function receives the relevant document prototype and uses @@ -703,7 +703,7 @@ export namespace Docs { created = Docs.Create.StackingDocument(DocListCast(field), resolved); layout = CollectionView.LayoutString; } else { - created = Docs.Create.TextDocument({ ...{ _width: 200, _height: 25, autoHeight: true }, ...resolved }); + created = Docs.Create.TextDocument({ ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved }); layout = FormattedTextBox.LayoutString; } created.layout = layout?.(fieldKey); diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index a99e3bc4f..fa7f63965 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -332,8 +332,7 @@ export namespace DictationManager { ["new outline", { action: (target: DocumentView) => { - const newBox = Docs.Create.TextDocument({ _width: 400, _height: 200, title: "My Outline" }); - newBox.autoHeight = true; + const newBox = Docs.Create.TextDocument({ _width: 400, _height: 200, title: "My Outline", _autoHeight: true }); const proto = newBox.proto!; const prompt = "Press alt + r to start dictating here..."; const head = 3; diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts index 6a9486f83..2f4db0e17 100644 --- a/src/client/util/Import & Export/ImageUtils.ts +++ b/src/client/util/Import & Export/ImageUtils.ts @@ -14,9 +14,17 @@ export namespace ImageUtils { return false; } const source = field.url.href; - const response = await Networking.PostToServer("/inspectImage", { source }); - const { error, data } = response.exifData; + const { + contentSize, + nativeWidth, + nativeHeight, + exifData: { error, data } + } = await Networking.PostToServer("/inspectImage", { source }); document.exif = error || Docs.Get.DocumentHierarchyFromJson(data); + const proto = Doc.GetProto(document); + proto.nativeWidth = nativeWidth; + proto.nativeHeight = nativeHeight; + proto.contentSize = contentSize; return data !== undefined; }; diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index fb22e0d18..02b7502d8 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -196,7 +196,7 @@ export const inpRules = { new InputRule( new RegExp(/%#$/), (state, match, start, end) => { - const target = Docs.Create.TextDocument({ _width: 75, _height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, autoHeight: true, fontSize: 9, title: "inline comment" }); + const target = Docs.Create.TextDocument({ _width: 75, _height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, _autoHeight: true, fontSize: 9, title: "inline comment" }); const node = (state.doc.resolve(start) as any).nodeAfter; const newNode = schema.nodes.dashComment.create({ docid: target[Id] }); const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: target[Id], float: "right" }); diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js index 27605d167..beb030635 100644 --- a/src/client/util/request-image-size.js +++ b/src/client/util/request-image-size.js @@ -38,7 +38,7 @@ module.exports = function requestImageSize(options) { return reject(new HttpError(res.statusCode, res.statusMessage)); } - let buffer = new Buffer([]); + let buffer = new Buffer.from([]); let size; let imageSizeError; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 095b9b17c..8b0f34bdf 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -493,7 +493,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else { dW && (layoutDoc._width = actualdW); dH && (layoutDoc._height = actualdH); - dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false); + dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false); } } })); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 831d9ca0b..7993e951e 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -42,7 +42,7 @@ export class PreviewCursor extends React.Component<{}> { if (re.test(e.clipboardData.getData("text/plain"))) { const url = e.clipboardData.getData("text/plain"); return PreviewCursor._addDocument(Docs.Create.WebDocument(url, { - title: url, width: 500, height: 300, + title: url, _width: 500, _height: 300, // nativeWidth: 300, nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] })); @@ -50,9 +50,9 @@ export class PreviewCursor extends React.Component<{}> { // creates text document return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument({ - width: 500, + _width: 500, limitHeight: 400, - autoHeight: true, + _autoHeight: true, x: newPoint[0], y: newPoint[1], title: "-pasted text-" diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 372db5fee..55fba2d11 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -104,7 +104,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { componentDidMount() { super.componentDidMount(); this._heightDisposer = reaction(() => { - if (this.props.Document.autoHeight) { + if (this.props.Document._autoHeight) { const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); if (this.isStackingView) { const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => { diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 249d3de68..0960b0e96 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -152,7 +152,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } this._createAliasSelected = false; const key = StrCast(this.props.parent.props.Document.sectionFilter); - const newDoc = Docs.Create.TextDocument({ _height: 18, _width: 200, documentText: "@@@" + value, title: value, autoHeight: true }); + const newDoc = Docs.Create.TextDocument({ _height: 18, _width: 200, documentText: "@@@" + value, title: value, _autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7b42c6166..e2bcbda9c 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -310,12 +310,19 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { formData.append('file', file); const dropFileName = file ? file.name : "-empty-"; promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => { - results.map(action(({ clientAccessPath }: any) => { - const full = { ...options, _width: 300, title: dropFileName }; + results.map(action((result: any) => { + const { clientAccessPath, nativeWidth, nativeHeight, contentSize } = result; + const full = { ...options, width: 300, title: dropFileName }; const pathname = Utils.prepend(clientAccessPath); Docs.Get.DocumentFromType(type, pathname, full).then(doc => { - doc && (Doc.GetProto(doc).fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "")); - doc && this.props.addDocument(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); + } }); })); })); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 92317b317..6c6e6b449 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -638,7 +638,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { const detailedLayout = Docs.Create.StackingDocument([ ImageDocument(fallbackImg, { title: "activeHero" }), MulticolumnDocument([], { title: "data", _height: 100, onChildClick: ScriptField.MakeFunction(`containingCollection.resolvedDataDoc.activeHero = copyField(this.data)`, { containingCollection: Doc.name }) }), - TextDocument({ title: "short_description", autoHeight: true }), + TextDocument({ title: "short_description", _autoHeight: true }), ...["year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key, _height: 30 })) ], { _chromeStatus: "disabled", title: "detailed layout stack" }); detailedLayout.isTemplateDoc = makeTemplate(detailedLayout); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 715094bf5..338d74ca7 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -214,7 +214,7 @@ export class CollectionView extends Touchable<FieldViewProps> { subItems.push({ description: "Stacking (AutoHeight)", event: () => { this.props.Document._viewType = CollectionViewType.Stacking; - this.props.Document.autoHeight = true; + this.props.Document._autoHeight = true; }, icon: "ellipsis-v" }); subItems.push({ description: "Staff", event: () => this.props.Document._viewType = CollectionViewType.Staff, icon: "music" }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 8bf56338b..09ee3255d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -82,7 +82,7 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth; if (hgt > pivotAxisWidth) { hgt = pivotAxisWidth; - wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; + wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } docMap.set(doc, { x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.length < numCols ? (numCols - val.length) * pivotAxisWidth / 2 : 0), diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cd99c02fe..7aa9d4922 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -921,7 +921,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { description: "Add Note ...", subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({ description: (i + 1) + ": " + StrCast(note.title), - event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ _width: 200, _height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })), + event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ _width: 200, _height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], _autoHeight: true, layout: note, title: StrCast(note.title) })), icon: "eye" })) as ContextMenuProps[], icon: "eye" diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0e02d1ef2..741712b6e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -101,10 +101,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque }); } else if (!e.ctrlKey) { this.props.addLiveTextDocument( - Docs.Create.TextDocument({ _width: 200, _height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" })); + Docs.Create.TextDocument({ _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" })); } else if (e.keyCode > 48 && e.keyCode <= 57) { const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); - const text = Docs.Create.TextDocument({ _width: 200, _height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }); + const text = Docs.Create.TextDocument({ _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }); text.layout = notes[(e.keyCode - 49) % notes.length]; this.props.addLiveTextDocument(text); } @@ -374,14 +374,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque return d; }); newCollection._chromeStatus = "disabled"; - const summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, _width: 300, _height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + const summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, _width: 300, _height: 100, _autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]); newCollection.x = bounds.left + bounds.width; Doc.GetProto(newCollection).summaryDoc = summary; Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. const container = Docs.Create.FreeformDocument([summary, newCollection], { - x: bounds.left, y: bounds.top, _width: 300, _height: 200, autoHeight: true, + x: bounds.left, y: bounds.top, _width: 300, _height: 200, _autoHeight: true, _viewType: CollectionViewType.Stacking, _chromeStatus: "disabled", title: "-summary-" }); Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 42cf7cca3..0b01e6471 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -75,12 +75,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") { // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), // then we render the layout document as a template and use this document as the data context for the template layout. - return this.props.Document; + const proto = Doc.GetProto(this.props.Document); + return proto instanceof Promise ? undefined : proto; } - return this.props.DataDoc; + return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc; } get layoutDoc() { - return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document); + return Doc.Layout(this.props.Document); } CreateBindings(): JsxBindings { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 120bc4fd1..c8b1f9794 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -427,7 +427,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } else { dW && (layoutDoc._width = actualdW); dH && (layoutDoc._height = actualdH); - dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false); + dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false); } } e.stopPropagation(); @@ -548,7 +548,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu fieldTemplate.backgroundColor = doc.backgroundColor; fieldTemplate.heading = 1; - fieldTemplate.autoHeight = true; + fieldTemplate._autoHeight = true; const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); @@ -621,7 +621,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action freezeNativeDimensions = (): void => { - this.layoutDoc.autoHeight = this.layoutDoc.autoHeight = false; + this.layoutDoc._autoHeight = false; this.layoutDoc.ignoreAspect = !this.layoutDoc.ignoreAspect; if (!this.layoutDoc.ignoreAspect && !this.layoutDoc._nativeWidth) { this.layoutDoc._nativeWidth = this.props.PanelWidth(); @@ -740,7 +740,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }); } layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); + layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ef92ddf2e..46000a03d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -530,7 +530,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & ); this._heightReactionDisposer = reaction( - () => [this.layoutDoc[WidthSym](), this.layoutDoc.autoHeight], + () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], () => this.tryUpdateHeight() ); @@ -1051,12 +1051,12 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @action tryUpdateHeight(limitHeight?: number) { let scrollHeight = this._ref.current?.scrollHeight; - if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight && + if (!this.layoutDoc.animateToPos && this.layoutDoc._autoHeight && scrollHeight && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation if (limitHeight && scrollHeight > limitHeight) { scrollHeight = limitHeight; this.layoutDoc.limitHeight = undefined; - this.layoutDoc.autoHeight = false; + this.layoutDoc._autoHeight = false; } const nh = this.Document.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0); const dh = NumCast(this.layoutDoc._height, 0); @@ -1084,7 +1084,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & return ( <div className={`formattedTextBox-cont`} ref={this._ref} style={{ - height: this.layoutDoc.autoHeight ? "max-content" : undefined, + height: this.layoutDoc._autoHeight ? "max-content" : undefined, background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined, opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, color: this.props.hideOnLeave ? "white" : "inherit", diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index b6874961e..03753e9d3 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -27,6 +27,7 @@ import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; import { TraceMobx } from '../../../new_fields/util'; import { SelectionManager } from '../../util/SelectionManager'; +import { cache } from 'sharp'; const requestImageSize = require('../../util/request-image-size'); const path = require('path'); const { Howl } = require('howler'); @@ -212,33 +213,34 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum } _curSuffix = "_m"; - _resized = ""; resize = (imgPath: string) => { - // if (!this.dataDoc[this.props.fieldKey + "-nativeHeight"]) - // { - requestImageSize(imgPath) - .then((size: any) => { + const cachedNativeSize = { + width: NumCast(this.Document[this.props.fieldKey + "-nativeWidth"]), + height: NumCast(this.Document[this.props.fieldKey + "-nativeHeight"]) + }; + const cachedImgPath = this.dataDoc[this.props.fieldKey + "-imgPath"]; + if (!cachedNativeSize.width || !cachedNativeSize.height || imgPath !== cachedImgPath) { + (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) && requestImageSize(imgPath).then((inquiredSize: any) => { const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) % 180; - const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; - const aspect = realsize.height / realsize.width; - if (this.Document._width && (Math.abs(1 - NumCast(this.Document._height) / NumCast(this.Document._width) / (realsize.height / realsize.width)) > 0.1)) { - setTimeout(action(() => { - if (this.paths[NumCast(this.props.Document.curPage)] === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) { - this._resized = imgPath; - this.Document._height = this.Document[WidthSym]() * aspect; - this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = realsize.height; - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = realsize.width; - } - }), 0); - } else this._resized = imgPath; + const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize; + const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width; + const docAspect = this.Document[HeightSym]() / this.Document[WidthSym](); + setTimeout(action(() => { + if (this.Document[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) { + this.Document._height = this.Document[WidthSym]() * rotatedAspect; + this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width; + this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height; + } + this.dataDoc[this.props.fieldKey + "-imgPath"] = imgPath; + }), 0); }) - .catch((err: any) => console.log(err)); - // } else { - // setTimeout(() => { - // this.Document._nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"]); - // this.Document._nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]); - // }, 0); - // } + .catch((err: any) => console.log(err)); + } else if (this.Document._nativeHeight !== cachedNativeSize.width || this.Document._nativeWidth !== cachedNativeSize.height) { + setTimeout(() => { + this.Document._nativeWidth = cachedNativeSize.width; + this.Document._nativeHeight = cachedNativeSize.height; + }, 0); + } } @action @@ -309,7 +311,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1; const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; - !this.Document.ignoreAspect && this._resized !== srcpath && this.resize(srcpath); + !this.Document.ignoreAspect && this.resize(srcpath); return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <div className="imageBox-fader" > diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 7d093b6ea..2cedda7a6 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -101,7 +101,7 @@ export function makeEditable() { } let layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox", - "LODdisable", "dropAction", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin"]; + "LODdisable", "dropAction", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && @@ -110,7 +110,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an console.log(prop + " is deprecated - switch to _" + prop); prop = "_" + prop; } - const resolvedLayout = getFieldImpl(target, getFieldImpl(target, "layoutKey", receiver), receiver); // + "-" + (prop as string).substring(1); + const resolvedLayout = getFieldImpl(target, getFieldImpl(target, "layoutKey", receiver), receiver); if (resolvedLayout instanceof Doc) { resolvedLayout[prop] = value; return true; @@ -127,11 +127,10 @@ export function getter(target: any, in_prop: string | symbol | number, receiver: console.log(prop + " is deprecated - switch to _" + prop); prop = "_" + prop; } - const resolvedLayout = getFieldImpl(target, getFieldImpl(target, "layoutKey", receiver), receiver); // + "-" + (prop as string).substring(1); + const resolvedLayout = getFieldImpl(target, getFieldImpl(target, "layoutKey", receiver), receiver); if (resolvedLayout instanceof Doc) { return resolvedLayout[prop]; } - // prop = getFieldImpl(target, "layoutKey", receiver) + "-" + (prop as string).substring(1); } if (prop === "then") {//If we're being awaited return undefined; diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index f16bbfd14..16b248ae7 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -161,7 +161,7 @@ def write_text_doc(content): "__type": "date" }, "isPrototype": True, - "autoHeight": True, + "_autoHeight": True, "page": -1, "nativeHeight": 200, "height": 200, diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 74f45ae62..583eaa59b 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -169,8 +169,7 @@ export default class UploadManager extends ApiManager { secureHandler: async ({ req, res }) => { const { source } = req.body; if (typeof source === "string") { - const { serverAccessPaths } = await DashUploadUtils.UploadImage(source); - return res.send(await DashUploadUtils.InspectImage(serverAccessPaths[SizeSuffix.Original])); + return res.send(await DashUploadUtils.InspectImage(source)); } res.send({}); } diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index d9d985ca5..b826b4882 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -1,4 +1,4 @@ -import * as fs from 'fs'; +import { unlinkSync, createWriteStream, readFileSync, rename } from 'fs'; import { Utils } from '../Utils'; import * as path from 'path'; import * as sharp from 'sharp'; @@ -12,8 +12,9 @@ import { basename } from "path"; import { createIfNotExists } from './ActionUtilities'; import { ParsedPDF } from "../server/PdfTypes"; const parse = require('pdf-parse'); -import { Directory, serverPathToFile, clientPathToFile } from './ApiManagers/UploadManager'; +import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './ApiManagers/UploadManager'; import { red } from 'colors'; +const requestImageSize = require("../client/util/request-image-size"); export enum SizeSuffix { Small = "_s", @@ -27,6 +28,10 @@ export function InjectSize(filename: string, size: SizeSuffix) { return filename.substring(0, filename.length - extension.length) + size + extension; } +function isLocal() { + return /Dash-Web[\\\/]src[\\\/]server[\\\/]public[\\\/](.*)/; +} + export namespace DashUploadUtils { export interface Size { @@ -92,12 +97,12 @@ export namespace DashUploadUtils { } async function UploadPdf(absolutePath: string) { - const dataBuffer = fs.readFileSync(absolutePath); + const dataBuffer = readFileSync(absolutePath); const result: ParsedPDF = await parse(dataBuffer); const parsedName = basename(absolutePath); await new Promise<void>((resolve, reject) => { const textFilename = `${parsedName.substring(0, parsedName.length - 4)}.txt`; - const writeStream = fs.createWriteStream(serverPathToFile(Directory.text, textFilename)); + const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename)); writeStream.write(result.text, error => error ? reject(error) : resolve()); }); return MoveParsedFile(absolutePath, Directory.pdfs); @@ -134,12 +139,13 @@ export namespace DashUploadUtils { }; export interface InspectionResults { - isLocal: boolean; - stream: any; - normalizedUrl: string; + source: string; + requestable: string; exifData: EnrichedExifData; - contentSize?: number; - contentType?: string; + contentSize: number; + contentType: string; + nativeWidth: number; + nativeHeight: number; } export interface EnrichedExifData { @@ -152,6 +158,12 @@ export namespace DashUploadUtils { return Promise.all(pending); } + export interface RequestedImageSize { + width: number; + height: number; + type: string; + } + /** * Based on the url's classification as local or remote, gleans * as much information as possible about the specified image @@ -159,24 +171,28 @@ export namespace DashUploadUtils { * @param source is the path or url to the image in question */ export const InspectImage = async (source: string): Promise<InspectionResults> => { - const { isLocal, stream, normalized: normalizedUrl } = classify(source); - const exifData = await parseExifData(source); + let resolvedUrl: string; + const matches = isLocal().exec(source); + if (matches === null) { + resolvedUrl = source; + } else { + resolvedUrl = `http://localhost:1050/${matches[1].split("\\").join("/")}`; + } + const exifData = await parseExifData(resolvedUrl); const results = { exifData, - isLocal, - stream, - normalizedUrl + requestable: resolvedUrl }; - // stop here if local, since request.head() can't handle local paths, only urls on the web - if (isLocal) { - return results; - } const { headers } = (await new Promise<any>((resolve, reject) => { - request.head(source, (error, res) => error ? reject(error) : resolve(res)); - })); + request.head(resolvedUrl, (error, res) => error ? reject(error) : resolve(res)); + }).catch(error => console.error(error))); + const { width: nativeWidth, height: nativeHeight }: RequestedImageSize = await requestImageSize(resolvedUrl); return { + source, contentSize: parseInt(headers[size]), contentType: headers[type], + nativeWidth, + nativeHeight, ...results }; }; @@ -185,22 +201,20 @@ export namespace DashUploadUtils { return new Promise<{ clientAccessPath: Opt<string> }>(resolve => { const filename = basename(absolutePath); const destinationPath = serverPathToFile(destination, filename); - fs.rename(absolutePath, destinationPath, error => { + rename(absolutePath, destinationPath, error => { resolve({ clientAccessPath: error ? undefined : clientPathToFile(destination, filename) }); }); }); } export const UploadInspectedImage = async (metadata: InspectionResults, filename?: string, format?: string, prefix = ""): Promise<ImageUploadInformation> => { - const { isLocal, stream, normalizedUrl, contentSize, contentType, exifData } = metadata; - const resolved = filename || generate(prefix, normalizedUrl); - const extension = format || sanitizeExtension(normalizedUrl || resolved); + const { requestable, source, ...remaining } = metadata; + const resolved = filename || generate(prefix, requestable); + const extension = format || sanitizeExtension(requestable || resolved); const information: ImageUploadInformation = { clientAccessPath: clientPathToFile(Directory.images, resolved), serverAccessPaths: {}, - exifData, - contentSize, - contentType, + ...remaining }; const { pngs, jpgs } = AcceptibleMedia; return new Promise<ImageUploadInformation>(async (resolve, reject) => { @@ -220,32 +234,22 @@ export namespace DashUploadUtils { await new Promise<void>(resolve => { const filename = InjectSize(resolved, suffix); information.serverAccessPaths[suffix] = serverPathToFile(Directory.images, filename); - stream(normalizedUrl).pipe(resizer).pipe(fs.createWriteStream(serverPathToFile(Directory.images, filename))) + request(requestable).pipe(resizer).pipe(createWriteStream(serverPathToFile(Directory.images, filename))) .on('close', resolve) .on('error', reject); }); } - if (isLocal) { - await new Promise<boolean>(resolve => { - fs.unlink(normalizedUrl, error => resolve(error === null)); - }); + if (isLocal().test(source)) { + unlinkSync(source); } resolve(information); }); }; - const classify = (url: string) => { - const isLocal = /Dash-Web(\\|\/)src(\\|\/)server(\\|\/)public(\\|\/)files/g.test(url); - return { - isLocal, - stream: isLocal ? fs.createReadStream : request, - normalized: isLocal ? path.normalize(url) : url - }; - }; - const parseExifData = async (source: string): Promise<EnrichedExifData> => { + const image = await request.get(source, { encoding: null }); return new Promise<EnrichedExifData>(resolve => { - new ExifImage(source, (error, data) => { + new ExifImage({ image }, (error, data) => { let reason: Opt<string> = undefined; if (error) { reason = (error as any).code; diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index 6bc75ca21..63e957cd1 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -180,7 +180,7 @@ export const STATUS = { }; export function _error(res: Response, message: string, error?: any) { - console.error(message); + console.error(message, error); res.statusMessage = message; res.status(STATUS.EXECUTION_ERROR).send(error); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 361cf9216..818a30e9f 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -130,7 +130,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, isExpanded: true, backgroundColor: "white" }); } return userDoc.thumbDoc; @@ -138,7 +138,7 @@ export class CurrentUserUtils { static setupMobileDoc(userDoc: Doc) { return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { - columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", autoHeight: true, _yMargin: 5 + columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5 }); } @@ -146,7 +146,7 @@ export class CurrentUserUtils { static setupToolsPanel(sidebarContainer: Doc, doc: Doc) { // setup a masonry view of all he creators const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(doc), { - _width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", + _width: 500, _autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), _yMargin: 5 }); // setup a color picker |