diff options
| author | bobzel <zzzman@gmail.com> | 2025-03-10 16:13:04 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2025-03-10 16:13:04 -0400 |
| commit | b7989dded8bb001876de6cbca59bf77935f0daf7 (patch) | |
| tree | 0dba0665674db7bb84770833df0a4100d0520701 /src/fields | |
| parent | 4979415d4604d280e81a162bf9a9d39c731d3738 (diff) | |
| parent | 5bf944035c0ba94ad15245416f51ca0329a51bde (diff) | |
Merge branch 'master' into alyssa-starter
Diffstat (limited to 'src/fields')
| -rw-r--r-- | src/fields/Doc.ts | 41 | ||||
| -rw-r--r-- | src/fields/InkField.ts | 33 | ||||
| -rw-r--r-- | src/fields/RichTextField.ts | 68 | ||||
| -rw-r--r-- | src/fields/RichTextUtils.ts | 2 | ||||
| -rw-r--r-- | src/fields/ScriptField.ts | 9 | ||||
| -rw-r--r-- | src/fields/Types.ts | 12 |
6 files changed, 108 insertions, 57 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6ec195910..fc89dcbe7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -7,14 +7,14 @@ import { CollectionViewType, DocumentType } from '../client/documents/DocumentTy import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; import { undoable, UndoManager } from '../client/util/UndoManager'; -import { ClientUtils, incrementTitleCopy } from '../ClientUtils'; +import { ClientUtils, imageUrlToBase64, incrementTitleCopy } from '../ClientUtils'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks, DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight, Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width } from './DocSymbols'; // prettier-ignore import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; -import { InkTool } from './InkField'; +import { InkEraserTool, InkInkTool, InkTool } from './InkField'; import { List } from './List'; import { ObjectField, serverOpType } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; @@ -22,8 +22,9 @@ import { FieldId, RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { listSpec } from './Schema'; import { ComputedField, ScriptField } from './ScriptField'; -import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor, toList } from './Types'; +import { BoolCast, Cast, DocCast, FieldValue, ImageCastWithSuffix, NumCast, RTFCast, StrCast, ToConstructor, toList } from './Types'; import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util'; +import { gptImageLabel } from '../client/apis/gpt/GPT'; export let ObjGetRefField: (id: string, force?: boolean) => Promise<Doc | undefined>; export let ObjGetRefFields: (ids: string[]) => Promise<Map<string, Doc | undefined>>; @@ -236,7 +237,7 @@ export class Doc extends RefField { public static get MyPublishedDocs() { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs).concat(DocListCast(DocCast(Doc.UserDoc().myPublishedDocs)?.data)); } // prettier-ignore public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore - public static get MyAnnos() { return DocCast(Doc.UserDoc().myAnnos); } // prettier-ignore + public static get MyStickers() { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore public static get MyLightboxDrawings() { return DocCast(Doc.UserDoc().myLightboxDrawings); } // prettier-ignore public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore @@ -253,6 +254,10 @@ export class Doc extends RefField { public static set ActivePage(val) { Doc.UserDoc().activePage = val; } // prettier-ignore public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } // prettier-ignore public static set ActiveTool(tool:InkTool){ Doc.UserDoc().activeTool = tool; } // prettier-ignore + public static get ActiveInk(): InkInkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkInkTool; } // prettier-ignore + public static set ActiveInk(tool:InkInkTool){ Doc.UserDoc().activeInkTool = tool; } // prettier-ignore + public static get ActiveEraser(): InkEraserTool { return StrCast(Doc.UserDoc().activeEraserTool, InkTool.None) as InkEraserTool; } // prettier-ignore + public static set ActiveEraser(tool:InkEraserTool){ Doc.UserDoc().activeEraserTool = tool; } // prettier-ignore public static get ActivePresentation() { return DocCast(Doc.ActiveDashboard?.activePresentation) as Opt<Doc>; } // prettier-ignore public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore @@ -463,10 +468,6 @@ export class Doc extends RefField { } } export namespace Doc { - export let SelectOnLoad: Doc | undefined; - export function SetSelectOnLoad(doc: Doc | undefined) { - SelectOnLoad = doc; - } export let DocDragDataName: string = ''; export function SetDocDragDataName(name: string) { DocDragDataName = name; @@ -1463,6 +1464,25 @@ export namespace Doc { }); } + /** + * text description of a Doc. RTF documents will have just their text and pdf documents will have the first 50 words. + * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. + * @param doc + * @returns + */ + export function getDescription(doc: Doc) { + const curDescription = StrCast(doc[DocData][Doc.LayoutFieldKey(doc) + '_description']); + const docText = (async (tdoc:Doc) => { + switch (tdoc.type) { + case DocumentType.PDF: return curDescription || StrCast(tdoc.text).split(/\s+/).slice(0, 50).join(' '); // first 50 words of pdf text + case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(Doc.LayoutField(tdoc), '_o') ?? '') + .then(hrefBase64 => gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.')); + case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutFieldKey(tdoc)]).Text; + default: return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title); + }}); // prettier-ignore + return docText(doc).then(text => (doc[DocData][Doc.LayoutFieldKey(doc) + '_description'] = text)); + } + // prettier-ignore export function toIcon(doc?: Doc, isOpen?: Opt<boolean>) { if (isOpen) return doc?.isFolder ? 'chevron-down' : 'folder-open'; @@ -1497,7 +1517,6 @@ export namespace Doc { case DocumentType.MAP: return 'map-marker-alt'; case DocumentType.DATAVIZ: return 'chart-bar'; case DocumentType.EQUATION: return 'calculator'; - case DocumentType.SIMULATION: return 'rocket'; case DocumentType.CONFIG: return 'folder-closed'; default: } @@ -1777,7 +1796,3 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran ScriptingGlobals.add(function toJavascriptString(str: string) { return Field.toJavascriptString(str as FieldType); }); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function RtfField() { - return RichTextField.RTFfield(); -}); diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 17b99b033..d1dda106a 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -8,19 +8,32 @@ import { ObjectField } from './ObjectField'; // Helps keep track of the current ink tool in use. export enum InkTool { - None = 'none', - Pen = 'pen', - Highlighter = 'highlighter', - StrokeEraser = 'strokeeraser', - SegmentEraser = 'segmenteraser', - RadiusEraser = 'radiuseraser', - Eraser = 'eraser', // not a real tool, but a class of tools - Stamp = 'stamp', - Write = 'write', - PresentationPin = 'presentationpin', + None = 'None', + Ink = 'Ink', + Eraser = 'Eraser', // not a real tool, but a class of tools SmartDraw = 'smartdraw', } +export enum InkInkTool { + Pen = 'Pen', + Highlight = 'Highlight', + Write = 'Write', +} + +export enum InkEraserTool { + Stroke = 'Stroke', + Segment = 'Segment', + Radius = 'Radius', +} + +export enum InkProperty { + Mask = 'inkMask', + Labels = 'labels', + StrokeWidth = 'strokeWidth', + StrokeColor = 'strokeColor', + EraserWidth = ' eraserWidth', +} + export type Segment = Array<Bezier>; // Defines an ink as an array of points. diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index dc636031a..bcef1fefc 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -25,7 +25,7 @@ export class RichTextField extends ObjectField { } Empty() { - return !(this.Text || this.Data.toString().includes('dashField') || this.Data.toString().includes('align')); + return !(this.Text || this.Data.toString().includes('dashField') || this.Data.toString().includes('dashDoc') || this.Data.toString().includes('align')); } [Copy]() { @@ -42,33 +42,51 @@ export class RichTextField extends ObjectField { return this.Text; } - public static RTFfield() { - return 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":[]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`, - '' - ); - } + // AARAV ADD= + static ToProsemirrorDoc = (content: Record<string, unknown>[], selection: Record<string, unknown>) => ({ + doc: { + type: 'doc', + content, + }, + selection, + }); + + private static ToProsemirrorTextContent = (text: string, styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string }) => [ + { + type: 'text', + marks: [ + ...(styles?.bold ? [{ type: 'strong' }] : []), + ...(styles?.italic ? [{ type: 'em' }] : []), + ...(styles?.fontSize ? [{ type: 'pFontSize', attrs: { fontSize: `${styles.fontSize}px` } }] : []), + ...(styles?.color ? [{ type: 'pFontColor', attrs: { fontColor: styles.color } }] : []), + ], + text, + }, + ]; - public static textToRtf(text: string, imgDocId?: string) { - return new RichTextField( - JSON.stringify({ - // this is a RichText json that has the question text placed above a related image - doc: { - type: 'doc', + private static ToProsemirrorDashDocContent = (docId: string) => [ + { + type: 'dashDoc', + attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId }, + }, + ]; + + private static ToProsemirror = (plaintext: string, imgDocId?: string, styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string }, selectBack?: number) => + RichTextField.ToProsemirrorDoc( + plaintext + .split('\n') + .filter(text => (imgDocId ? text : true)) // if there's an image doc, we don't want it repeat for each paragraph -- assume there's only one paragraph with text in it + .map(text => ({ + type: 'paragraph', content: [ - { - type: 'paragraph', - attrs: { align: 'center', color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - content: [ - ...(text ? [{ type: 'text', text }] : []), // - ...(imgDocId ? [{ type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: imgDocId } }] : []), - ], - }, + ...(text.length ? RichTextField.ToProsemirrorTextContent(text, styles) : []), // An empty paragraph gets treated as a line break + ...(imgDocId ? RichTextField.ToProsemirrorDashDocContent(imgDocId) : []), ], - }, - selection: { type: 'text', anchor: 2, head: 2 }, - }), - text + })), + { type: 'text', anchor: 2 + plaintext.length - (selectBack ?? 0), head: 2 + plaintext.length } ); + + public static textToRtf(text: string, imgDocId?: string, styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string }, selectBack?: number) { + return new RichTextField(JSON.stringify(RichTextField.ToProsemirror(text, imgDocId, styles, selectBack)), text); } } diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index 8c073c87b..42dd0d432 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable no-use-before-define */ import { AssertionError } from 'assert'; -import * as Color from 'color'; +import Color from 'color'; import { docs_v1 as docsV1 } from 'googleapis'; import { Fragment, Mark, Node, Schema } from 'prosemirror-model'; import { sinkListItem } from 'prosemirror-schema-list'; diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 0252961d3..b294ee8c6 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -263,14 +263,9 @@ export class ComputedField extends ScriptField { doc[`${fieldKey}_indexed`] = flist; } const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey})`, {}, true, {}); - const setField = ScriptField.CompileScript( - `{setIndexVal (this['${fieldKey}_indexed'], this.${interpolatorKey}, value); console.log(this["${fieldKey}_indexed"][this.${interpolatorKey}],this.data,this["${fieldKey}_indexed"]))}`, - { value: 'any' }, - false, - {} - ); + const setField = ScriptField.CompileScript(`{setIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, value);}`, { value: 'any' }, false, {}); doc[fieldKey] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; - return doc[fieldKey]; + return Field.Copy(doc[fieldKey]); } } diff --git a/src/fields/Types.ts b/src/fields/Types.ts index ef79f72e4..474882959 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -5,7 +5,7 @@ import { ProxyField } from './Proxy'; import { RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { ScriptField } from './ScriptField'; -import { CsvField, ImageField, PdfField, WebField } from './URLField'; +import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; // eslint-disable-next-line no-use-before-define export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T; @@ -122,12 +122,22 @@ export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) export function WebCast(field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } +export function VideoCast(field: FieldResult, defaultVal: VideoField | null = null) { + return Cast(field, VideoField, defaultVal); +} +export function AudioCast(field: FieldResult, defaultVal: AudioField | null = null) { + return Cast(field, AudioField, defaultVal); +} export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null) { return Cast(field, PdfField, defaultVal); } export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) { return Cast(field, ImageField, defaultVal); } +export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultVal: ImageField | null = null) { + const href = ImageCast(field, defaultVal)?.url.href; + return href ? `${href.split('.')[0]}${suffix}.${href.split('.')[1]}` : null; +} export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>; // eslint-disable-next-line no-redeclare |
