diff options
author | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2025-03-04 04:32:50 -0500 |
---|---|---|
committer | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2025-03-04 04:32:50 -0500 |
commit | 95abdada5a275fc258fa72781f7f3c40c0b306ea (patch) | |
tree | 6d729cebe0937ae81108005de9895b5398d1f475 /src/client/documents/DocUtils.ts | |
parent | 0a8f3739cf5c30852f18751a4c05d81e0dabe928 (diff) | |
parent | 215ad40efa2e343e290d18bffbc55884829f1a0d (diff) |
Merge branch 'master' of https://github.com/brown-dash/Dash-Web into Merge
Diffstat (limited to 'src/client/documents/DocUtils.ts')
-rw-r--r-- | src/client/documents/DocUtils.ts | 162 |
1 files changed, 129 insertions, 33 deletions
diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 1130a9ae8..7f22e9376 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -3,14 +3,14 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { saveAs } from 'file-saver'; import * as JSZip from 'jszip'; import { action, runInAction } from 'mobx'; -import { ClientUtils } from '../../ClientUtils'; +import { ClientUtils, DashColor } from '../../ClientUtils'; import * as JSZipUtils from '../../JSZipUtils'; import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, Field, FieldResult, FieldType, LinkedTo, Opt, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; -import { InkDataFieldName, InkField } from '../../fields/InkField'; +import { InkData, InkDataFieldName, InkField } from '../../fields/InkField'; import { List, ListFieldName } from '../../fields/List'; import { ProxyField } from '../../fields/Proxy'; import { RichTextField } from '../../fields/RichTextField'; @@ -33,19 +33,22 @@ import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import { DocumentType } from './DocumentTypes'; import { Docs, DocumentOptions } from './Documents'; import { DocumentView } from '../views/nodes/DocumentView'; -import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; - -// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports -const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore - -const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); +import { INode, parse } from 'svgson'; +import { SVGToBezier, SVGType } from '../util/bezierFit'; +import { SmartDrawHandler } from '../views/smartdraw/SmartDrawHandler'; +import { PointData } from '../../pen-gestures/GestureTypes'; export namespace DocUtils { + function HasFunctionFilter(val: string) { + if (val.includes(ClientUtils.isTransparentFunctionHack)) return (d: Doc, color: string) => !d.disableMixBlend && color !== '' && DashColor(color).alpha() !== 1; + // add other function filters here... + return undefined; + } function matchFieldValue(doc: Doc, key: string, valueIn: unknown): boolean { let value = valueIn; - const hasFunctionFilter = ClientUtils.HasFunctionFilter(value as string); + const hasFunctionFilter = HasFunctionFilter(value as string); if (hasFunctionFilter) { - return hasFunctionFilter(StrCast(doc[key])); + return hasFunctionFilter(doc, StrCast(doc[key])); } if (key === LinkedTo) { // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) @@ -72,9 +75,9 @@ export namespace DocUtils { } const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings if (vals.length) { - return vals.some(v => typeof v === 'string' && v.includes(value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return vals.some(v => typeof v === 'string' && v === (value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } - return Field.toString(fieldVal as FieldType).includes(value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return Field.toString(fieldVal as FieldType) === (value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } /** * @param docs @@ -108,7 +111,6 @@ export namespace DocUtils { return false; } const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]); - // eslint-disable-next-line no-restricted-syntax for (const facetKey of facetKeys) { const facet = filterFacets[facetKey]; @@ -169,7 +171,7 @@ export namespace DocUtils { return rangeFilteredDocs; } - export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) { + export function MakeLink(source: Doc, target: Doc, linkSettings: { layout_isSvg?: boolean; link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) { if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; if (target.doc === Doc.UserDoc()) return undefined; @@ -221,6 +223,7 @@ export namespace DocUtils { link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, link_relationship: linkSettings.link_relationship, link_description: linkSettings.link_description, + layout_isSvg: linkSettings.layout_isSvg, x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as unknown as number, // x can accept functions even though type says it can't y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as unknown as number, // y can accept functions even though type says it can't link_autoMoveAnchors: true, @@ -293,7 +296,6 @@ export namespace DocUtils { return doc; } export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { - // eslint-disable-next-line no-return-assign return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); } @@ -359,6 +361,16 @@ export namespace DocUtils { return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined; } + /** + * Adds items to the doc creator (':') context menu for creating each document type + * @param docTextAdder + * @param docAdder + * @param x + * @param y + * @param simpleMenu + * @param pivotField + * @param pivotValue + */ export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string | number | boolean): void { const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) .filter(btnDoc => !btnDoc.hidden) @@ -372,6 +384,7 @@ export namespace DocUtils { newDoc.author = ClientUtils.CurrentUserEmail(); newDoc.x = x; newDoc.y = y; + newDoc[DocData].backgroundColor = Doc.UserDoc().textBackgroundColor; Doc.SetSelectOnLoad(newDoc); if (pivotField) { newDoc[pivotField] = pivotValue; @@ -383,7 +396,10 @@ export namespace DocUtils { })) as ContextMenuProps[]; documentList.push({ description: ':Smart Drawing', - event: e => (DocumentView.Selected().lastElement().ComponentView as CollectionFreeFormView)?.showSmartDraw(e?.x || 0, e?.y || 0), + event: e => + DocumentView.Selected() + .lastElement() + .ComponentView?.showSmartDraw?.(e?.x || 0, e?.y || 0), icon: 'file', }); ContextMenu.Instance.addItem({ @@ -629,7 +645,7 @@ export namespace DocUtils { export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) { const proto = protoIn; if (Upload.isImageInformation(result)) { - const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); + const maxNativeDim = Math.max(result.nativeHeight, result.nativeWidth); const exifRotation = StrCast(result.exifData?.data?.Orientation).toLowerCase(); proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; @@ -676,22 +692,51 @@ export namespace DocUtils { export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) { const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout); - const tbox = Docs.Create.TextDocument('', { - annotationOn, - backgroundColor, - x, - y, - title, - ...(defaultTextTemplate - ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance - : { - _width: width || 200, - _height: 35, - _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), - _layout_fitWidth: true, - _layout_autoHeight: true, - }), - }); + const tbox = + StrCast(Doc.UserDoc().fontFamily) === 'Math' + ? Docs.Create.EquationDocument('', { + // + annotationOn, + backgroundColor: backgroundColor ?? StrCast(Doc.UserDoc().textBackgroundColor), + borderColor: Doc.UserDoc().borderColor as string, + borderWidth: Doc.UserDoc().borderWidth as number, + x, + y, + title, + text_fontColor: StrCast(Doc.UserDoc().fontColor), + _width: 50, + _height: 50, + _yMargin: 10, + _xMargin: 10, + nativeWidth: 40, + nativeHeight: 40, + }) + : Docs.Create.TextDocument('', { + annotationOn, + backgroundColor, + x, + y, + title, + ...(defaultTextTemplate + ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance + : { + _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, + _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, + _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), + _layout_fitWidth: true, + _layout_autoHeight: true, + backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), + borderColor: Doc.UserDoc().borderColor as string, + borderWidth: Doc.UserDoc().borderWidth as number, + text_fitBox: BoolCast(Doc.UserDoc().fitBox), + text_align: StrCast(Doc.UserDoc().textAlign), + text_fontColor: StrCast(Doc.UserDoc().fontColor), + text_fontFamily: StrCast(Doc.UserDoc().fontFamily), + text_fontWeight: StrCast(Doc.UserDoc().fontWeight), + text_fontStyle: StrCast(Doc.UserDoc().fontStyle), + text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), + }), + }); if (defaultTextTemplate) { tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); @@ -739,6 +784,57 @@ export namespace DocUtils { return generatedDocuments; } + export async function openSVGfile(file: File, options: DocumentOptions) { + const reader = new FileReader(); + const scale = 1; + const startPoint = { X: (options.x as number) ?? 0, Y: (options.y as number) ?? 0 }; + const buffer = await new Promise<string>((res, rej) => { + reader.onload = event => { + const fileContent = event.target?.result; + // Process the file content here + console.log(fileContent); + typeof fileContent === 'string' ? res(fileContent) : rej(); + }; + + reader.readAsText(file); + }); + const svg = buffer.match(/<svg[^>]*>([\s\S]*?)<\/svg>/g); + if (svg) { + const svgObject = await parse(svg[0]); + const strokeData: [InkData, string, string][] = []; + const tl = { X: Number.MAX_SAFE_INTEGER, Y: Number.MAX_SAFE_INTEGER }; + let last: PointData = { X: 0, Y: 0 }; + const processStroke = (child: INode) => { + child.attributes.d + .split(/[\n]?M/) + .slice(1) + .map((d, ind) => { + const convertedBezier: InkData = SVGToBezier(child.name as SVGType, { ...child, d: '\nM' + d } as unknown as Record<string, string>, last); + last = convertedBezier.lastElement(); + convertedBezier.forEach(point => { + if (point.X < tl.X) tl.X = point.X; + if (point.Y < tl.Y) tl.Y = point.Y; + }); + strokeData.push([convertedBezier, child.attributes.stroke || 'black', ind === 0 ? child.attributes.fill : child.attributes.fill === 'none' ? child.attributes.fill : DashColor(child.attributes.fill).negate().toString()]); + }); + }; + const processNode = (parent: INode) => { + if (parent.children.length) parent.children.forEach(processNode); + else if (parent.type !== 'text') processStroke(parent); + }; + processNode(svgObject); + + const mapStroke = (pd: PointData): PointData => ({ X: startPoint.X + (pd.X - tl.X) * scale, Y: startPoint.Y + (pd.Y - tl.Y) * scale }); + + return SmartDrawHandler.CreateDrawingDoc( + strokeData.map(sdata => [sdata[0].map(mapStroke), sdata[1], sdata[2]] as [PointData[], string, string]), + { autoColor: true }, + '', + undefined + ); + } + } + export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) { const generatedDocuments: Doc[] = []; // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid. |