From a974aa4e6573c8becf93f78610406747fec14c1c Mon Sep 17 00:00:00 2001
From: bobzel
Date: Tue, 19 Mar 2024 17:08:46 -0400
Subject: cleaned up user templates to not get changed on reload. made setting
a template add it to the template tools list and as a tools button. fixed
linking to parts of a template. fixed disappearing templates caused by
stacking view set a field with an empty key. updated field assignment
syntax in trees, dash field views, and key value box to all use :,:=,=,=:=
syntax. added text elide button. added @(title) syntax for hyperlinking.
made using a template both inherit from the template to get default values
and use the template to render. fixed submenu placement of context menu.
updated RTF markdown doc.
---
src/fields/Doc.ts | 42 +++++++++++++++++++++---------------------
src/fields/util.ts | 4 ++++
2 files changed, 25 insertions(+), 21 deletions(-)
(limited to 'src/fields')
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index daae32e9f..4b40d11b9 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -445,6 +445,12 @@ export namespace Doc {
export function GetT(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): FieldResult {
return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult;
}
+ export function isTemplateDoc(doc: Doc) {
+ return GetT(doc, 'isTemplateDoc', 'boolean', true);
+ }
+ export function isTemplateForField(doc: Doc) {
+ return GetT(doc, 'isTemplateForField', 'string', true);
+ }
export function IsDataProto(doc: Doc) {
return GetT(doc, 'isDataDoc', 'boolean', true);
}
@@ -642,7 +648,7 @@ export namespace Doc {
cloneLinks: boolean,
cloneTemplates: boolean
): Promise {
- if (Doc.IsBaseProto(doc) || ((Doc.Get(doc, 'isTemplateDoc', true) || Doc.Get(doc, 'isTemplateForField', true)) && !cloneTemplates)) {
+ if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
return doc;
}
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
@@ -735,7 +741,7 @@ export namespace Doc {
const docAtKey = DocCast(clone[key]);
if (docAtKey && !Doc.IsSystem(docAtKey)) {
if (!Array.from(cloneMap.values()).includes(docAtKey)) {
- clone[key] = !cloneTemplates && (Doc.Get(docAtKey, 'isTemplateDoc', true) || Doc.Get(docAtKey, 'isTemplateForField', true)) ? docAtKey : cloneMap.get(docAtKey[Id]);
+ clone[key] = !cloneTemplates && (Doc.isTemplateDoc(docAtKey) || Doc.isTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]);
} else {
repairClone(docAtKey, cloneMap, cloneTemplates, visited);
}
@@ -857,7 +863,7 @@ export namespace Doc {
// of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) {
// nothing to do if the layout isn't a template or we don't have a target that's different than the template
- if (!targetDoc || templateLayoutDoc === targetDoc || (!templateLayoutDoc.isTemplateForField && !templateLayoutDoc.isTemplateDoc)) {
+ if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) {
return templateLayoutDoc;
}
@@ -874,7 +880,7 @@ export namespace Doc {
expandedTemplateLayout = undefined;
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
} else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) {
- if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument ?? Doc.GetProto(targetDoc))) {
+ if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
@@ -910,8 +916,9 @@ export namespace Doc {
console.log('Warning: GetLayoutDataDocPair childDoc not defined');
return { layout: childDoc, data: childDoc };
}
- const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc;
- return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
+ const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
+ const templateRoot = DocCast(containerDoc?.rootDocument);
+ return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data: resolvedDataDoc };
}
export function FindReferences(infield: Doc | List, references: Set, system: boolean | undefined) {
@@ -1035,20 +1042,13 @@ export namespace Doc {
// (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
// This is appropriate if you're trying to create a document that behaves like all
// regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
- export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string): Doc {
- const delegateProto = new Doc();
- delegateProto[Initializing] = true;
- delegateProto.proto = doc;
- delegateProto.author = Doc.CurrentUserEmail;
- delegateProto.isDataDoc = true;
- title && (delegateProto.title = title);
- const delegate = new Doc(id, true);
- delegate[Initializing] = true;
- delegate.proto = delegateProto;
- delegate.author = Doc.CurrentUserEmail;
- delegate[Initializing] = false;
- delegateProto[Initializing] = false;
- return delegate;
+ export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string) {
+ const ndoc = Doc.ApplyTemplate(doc);
+ if (ndoc) {
+ Doc.GetProto(ndoc).isDataDoc = true;
+ ndoc && (Doc.GetProto(ndoc).proto = doc);
+ }
+ return ndoc;
}
let _applyCount: number = 0;
@@ -1671,7 +1671,7 @@ ScriptingGlobals.add(function getEmbedding(doc: any) {
return Doc.MakeEmbedding(doc);
});
ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) {
- return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto);
+ return doc.isTemplateDoc ? Doc.MakeDelegateWithProto(doc) : Doc.MakeCopy(doc, copyProto);
});
ScriptingGlobals.add(function copyField(field: any) {
return Field.Copy(field);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index b73520999..c2ec3f13a 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -286,6 +286,10 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
// target should be either a Doc or ListImpl. receiver should be a Proxy Or List.
//
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
+ if (!in_prop) {
+ console.log('WARNING: trying to set an empty property. This should be fixed. ');
+ return false;
+ }
let prop = in_prop;
const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true;
--
cgit v1.2.3-70-g09d2
From b949608ff69fb66c30bbed439b1c37f8fffd2333 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Sat, 23 Mar 2024 21:13:38 -0400
Subject: fixed linking to parts of a template. fixed highlting part of a
template when following a link to it.
---
.../collectionFreeForm/CollectionFreeFormView.tsx | 12 +++++++++---
src/fields/Doc.ts | 3 ++-
2 files changed, 11 insertions(+), 4 deletions(-)
(limited to 'src/fields')
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 01d379b96..1fd453e96 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -314,7 +314,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).includes(anchor)) return;
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -335,6 +335,7 @@ export class CollectionFreeFormView extends CollectionSubView> =>
new Promise>(res => {
if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
+ if (doc === this.Document) return res(this.DocumentView?.());
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -1262,7 +1263,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout)?.includes(doc)) {
if (doc.hidden) doc.hidden = false;
return true;
}
@@ -1405,7 +1406,12 @@ export class CollectionFreeFormView extends CollectionSubView {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
- const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document });
+ const anchor = Docs.Create.ConfigDocument({
+ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection),
+ layout_unrendered: true,
+ presentation_transition: 500,
+ annotationOn: this.Document,
+ });
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document);
if (addAsAnnotation) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4b40d11b9..246828709 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1279,7 +1279,8 @@ export namespace Doc {
highlightedDocs.add(doc);
doc[Highlight] = true;
doc[Animation] = presentation_effect;
- if (dataAndDisplayDocs) {
+ if (dataAndDisplayDocs && !doc.resolvedDataDoc) {
+ // if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field
highlightedDocs.add(doc[DocData]);
doc[DocData][Highlight] = true;
}
--
cgit v1.2.3-70-g09d2
From ab76273acd2126d92455ff12c4ba80d8bb9473c5 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Sun, 24 Mar 2024 19:22:41 -0400
Subject: fixed crash on richtextfield to string when there's no data. fixed
schema cells not to edit unless selected.
---
src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 2 +-
src/fields/RichTextField.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
(limited to 'src/fields')
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index ce73ff8a4..67dc508d5 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -141,7 +141,7 @@ export class SchemaTableCell extends ObservableReactComponent
Date: Mon, 25 Mar 2024 08:23:51 -0400
Subject: fixed rich text to string formatting
---
src/fields/RichTextField.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src/fields')
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 855b63d4b..f5801de73 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -31,7 +31,7 @@ export class RichTextField extends ObjectField {
return '`' + this.Text + '`';
}
[ToScriptString]() {
- return `new RichTextField("${this.Data?.replace(/"/g, '\\"')}", "${this.Text}")`;
+ return `new RichTextField(\`${this.Data?.replace(/"/g, '\\"')}\`, \`${this.Text}\`)`;
}
[ToString]() {
return this.Text;
--
cgit v1.2.3-70-g09d2
From b420caf2c7ecd386cae2cc550904522474b541aa Mon Sep 17 00:00:00 2001
From: bobzel
Date: Tue, 26 Mar 2024 22:34:10 -0400
Subject: added empty image tool and click on empty image to select from
filesystem. fixed following links in lightbox and showing links to
stackedTimelines. fixed embedding docs into text. fixed not resizing text
boxes that also show up in pivot view. prevent context menu from going off
top of screen. fixed freeform clustering colors and click to type. fixed
links to stackedTimeline marks, and titles for marks. made title editing
from doc deco and header use same syntax as keyValue. fixed marquee
selection on webBoxes. turn off transitions in freeformdocview after
timeout. enabled iconifying templates to propagate to "offspring". fixes
images in templates. don't show headr on schema views.
---
src/client/documents/Documents.ts | 52 ++++++++--------
src/client/util/DocumentManager.ts | 4 +-
src/client/views/ContextMenu.tsx | 6 +-
src/client/views/DocumentDecorations.tsx | 71 ++++++++--------------
src/client/views/FieldsDropdown.tsx | 1 -
src/client/views/LightboxView.tsx | 2 +
src/client/views/MarqueeAnnotator.tsx | 19 ++++--
src/client/views/ObservableReactComponent.tsx | 3 +-
src/client/views/PropertiesView.tsx | 2 +-
src/client/views/StyleProvider.tsx | 6 +-
.../collections/CollectionStackedTimeline.tsx | 13 ++--
.../CollectionFreeFormLayoutEngines.tsx | 2 +-
.../collectionFreeForm/CollectionFreeFormView.tsx | 11 ++--
.../collections/collectionFreeForm/MarqueeView.tsx | 4 +-
.../collectionSchema/SchemaTableCell.tsx | 2 +-
src/client/views/global/globalScripts.ts | 4 +-
.../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++--
src/client/views/nodes/DocumentContentsView.tsx | 7 ++-
src/client/views/nodes/DocumentView.tsx | 35 +++++++----
src/client/views/nodes/FieldView.tsx | 1 +
src/client/views/nodes/ImageBox.tsx | 55 +++++++++++++----
src/client/views/nodes/KeyValueBox.tsx | 2 +-
src/client/views/nodes/LabelBox.tsx | 2 +-
src/client/views/nodes/LinkBox.tsx | 47 ++++++++++----
src/client/views/nodes/VideoBox.tsx | 5 +-
src/client/views/nodes/WebBox.tsx | 65 ++++++++++++++------
.../views/nodes/formattedText/FormattedTextBox.tsx | 17 ++++--
src/client/views/nodes/trails/PresBox.tsx | 2 +-
src/fields/Doc.ts | 29 +++++----
src/fields/PresField.ts | 6 --
src/fields/RichTextField.ts | 7 ---
31 files changed, 311 insertions(+), 195 deletions(-)
delete mode 100644 src/fields/PresField.ts
(limited to 'src/fields')
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 17cb6fef8..b63c5e429 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1017,8 +1017,8 @@ export namespace Docs {
}
export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
- const imgField = url instanceof ImageField ? url : new ImageField(url);
- return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
+ const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
+ return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
}
export function PresDocument(options: DocumentOptions = {}) {
@@ -1950,6 +1950,31 @@ export namespace DocUtils {
return dd;
}
+ export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
+ if (Upload.isImageInformation(result)) {
+ const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+ const exifRotation = StrCast((result.exifData?.data as any)?.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;
+ proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ if (NumCast(proto['data-nativeOrientation']) >= 5) {
+ proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ }
+ proto.data_exif = JSON.stringify(result.exifData?.data);
+ proto.data_contentSize = result.contentSize;
+ // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
+ const latitude = result.exifData?.data?.GPSLatitude;
+ const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
+ const longitude = result.exifData?.data?.GPSLongitude;
+ const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
+ if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
+ proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
+ proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
+ }
+ }
+ }
+
async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
if (result instanceof Error) {
alert(`Upload failed: ${result.message}`);
@@ -1961,28 +1986,7 @@ export namespace DocUtils {
if (doc) {
const proto = Doc.GetProto(doc);
proto.text = result.rawText;
- if (Upload.isImageInformation(result)) {
- const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
- const exifRotation = StrCast((result.exifData?.data as any)?.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;
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- if (NumCast(proto['data-nativeOrientation']) >= 5) {
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- }
- proto.data_exif = JSON.stringify(result.exifData?.data);
- proto.data_contentSize = result.contentSize;
- // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
- const latitude = result.exifData?.data?.GPSLatitude;
- const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
- const longitude = result.exifData?.data?.GPSLongitude;
- const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
- if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
- proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
- proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
- }
- }
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
if (Upload.isVideoInformation(result)) {
proto.data_duration = result.duration;
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a38a330da..40d28c690 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -59,7 +59,7 @@ export class DocumentManager {
private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
public AddViewRenderedCb = (doc: Opt, func: (dv: DocumentView) => any) => {
if (doc) {
- const dv = this.getDocumentView(doc);
+ const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc);
this._viewRenderedCbs.push({ doc, func });
if (dv) {
this.callAddViewFuncs(dv);
@@ -262,7 +262,7 @@ export class DocumentManager {
return res(this.getDocumentView(docContextPath[0])!);
}
options.didMove = true;
- docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
}));
if (options.openLocation === OpenWhere.lightbox) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8f4e43978..ca877b93e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -218,11 +218,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
this._width = DivWidth(r);
this._height = DivHeight(r);
}
+ this._searchRef.current?.focus();
})}
style={{
display: this._display ? '' : 'none',
left: this.pageX,
- ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
+ ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
background: SettingsManager.userBackgroundColor,
color: SettingsManager.userColor,
}}>
@@ -265,7 +266,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
const item = this.flatItems[this._selectedIndex];
if (item) {
item.event({ x: this.pageX, y: this.pageY });
- } else if (this._searchString.startsWith(this._defaultPrefix)) {
+ } else {
+ //if (this._searchString.startsWith(this._defaultPrefix)) {
this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
}
this.closeMenu();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2fb9f0fc1..4d9b93896 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -34,6 +34,7 @@ import { Colors } from './global/globalEnums';
import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
import { ImageBox } from './nodes/ImageBox';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from './nodes/KeyValueBox';
interface DocumentDecorationsProps {
PanelWidth: number;
@@ -57,7 +58,7 @@ export class DocumentDecorations extends ObservableReactComponent {
- if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
+ if (this._accumulatedTitle.startsWith('$')) {
this._titleControlString = this._accumulatedTitle;
- } else if (this._titleControlString.startsWith('#')) {
+ } else if (this._titleControlString.startsWith('$')) {
if (this._accumulatedTitle.startsWith('-->#')) {
SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
}
@@ -131,26 +132,7 @@ export class DocumentDecorations extends ObservableReactComponent')) {
- const title = titleField.toString().replace(/\.?/, '');
- const curKey = Doc.LayoutFieldKey(d.Document);
- if (curKey !== title) {
- if (title) {
- if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
- d.Document.layout_fieldKey = `layout_${title}`;
- d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title);
- d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0;
- }
- } else {
- d.Document.layout_fieldKey = undefined;
- }
- }
- } else {
- Doc.SetInPlace(d.Document, titleFieldKey, titleField, true);
- }
+ KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
}),
'edit title'
);
@@ -181,7 +163,8 @@ export class DocumentDecorations extends ObservableReactComponent this.onBackgroundMove(true, e),
emptyFunction,
action(e => {
- !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+ const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
+ !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
this._editingTitle = true;
this._keyinput.current && setTimeout(this._keyinput.current.focus);
})
@@ -622,11 +605,8 @@ export class DocumentDecorations extends ObservableReactComponent {
- this._editingTitle = false;
- !hideTitle && this.titleBlur();
- })}
- onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
- onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
- onPointerDown={e => e.stopPropagation()}
- />
+ <>
+ {r - x < 150 ? null : {this._titleControlString + ':'}}
+ {
+ this._editingTitle = false;
+ !hideTitle && this.titleBlur();
+ })}
+ onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+ onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+ onPointerDown={e => e.stopPropagation()}
+ />
+ >
) : (
{hideTitle ? null : (
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 5638d34c6..6a5c2cb4c 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -61,7 +61,6 @@ export class FieldsDropdown extends ObservableReactComponent
filteredOptions.push(pair[0]));
const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));
- console.log(options);
return (
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5a326ecb0..e729e2fa2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -7,7 +7,7 @@ import { OmitKeys, Without, emptyPath } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { InkingStroke } from '../InkingStroke';
import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -128,7 +128,9 @@ export class DocumentContentsView extends ObservableReactComponentawaiting layout
';
if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
- const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+ const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]);
+ const layoutDoc = tempLayout ?? this.layoutDoc;
+ const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(layoutDoc.layout_fieldKey, 'layout')], 'string');
if (layout === undefined) return this._props.Document.data ? "" : KeyValueBox.LayoutString();
if (typeof layout === 'string') return layout;
return 'Loading layout
';
@@ -154,6 +156,7 @@ export class DocumentContentsView extends ObservableReactComponent string | undefined;
NativeWidth?: () => number;
NativeHeight?: () => number;
- contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
+ contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
dragConfig?: (data: DragManager.DocumentDragData) => void;
dragStarting?: () => void;
dragEnding?: () => void;
@@ -560,7 +559,13 @@ export class DocumentViewInternal extends DocComponent
cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
);
- this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
+ this._props
+ .contextMenuItems?.()
+ .forEach(
+ item =>
+ item.label &&
+ cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
+ );
if (!this.Document.isFolder) {
const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
@@ -597,7 +602,7 @@ export class DocumentViewInternal extends DocComponent this.toggleFollowLink(false, false), icon: 'link' });
!Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
- cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (LinkManager.Links(this.Document).length) {
onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
@@ -810,8 +815,7 @@ export class DocumentViewInternal extends DocComponent Field.toString(targetDoc[field.trim()] as Field))
+ .map(field => Field.toJavascriptString(this.Document[field] as Field))
.join(' \\ ') || '-unset-'
}
display="block"
oneLine={true}
fontSize={(this.titleHeight / 15) * 10}
- GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.Document, showTitle.split(';')[0]))}
+ GetValue={() =>
+ showTitle
+ .split(';')
+ .map(field => Field.toKeyValueString(this.Document, field))
+ .join('\\')
+ }
SetValue={undoBatch((input: string) => {
- if (input?.startsWith('#')) {
+ if (input?.startsWith('$')) {
if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = input?.substring(1);
+ this.layoutDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
} else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
+ Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'title';
}
- } else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
+ } else if (showTitle && !showTitle.includes(';') && !showTitle.includes('Date') && showTitle !== 'author') {
KeyValueBox.SetField(targetDoc, showTitle, input);
}
return true;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5b47dd91d..771856788 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -92,6 +92,7 @@ export interface FieldViewSharedProps {
waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
pointerEvents?: () => Opt;
+ suppressSetHeight?: boolean;
}
/**
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 251235b93..86e8ed60a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -9,7 +9,7 @@ import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -29,6 +29,12 @@ import { OpenWhere } from './DocumentView';
import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
+import { Colors } from 'browndash-components';
+import { listSpec } from '../../../fields/Schema';
+import { List } from '../../../fields/List';
+import { url } from 'inspector';
+import { OverlayView } from '../OverlayView';
+import { Networking } from '../../Network';
@observer
export class ImageBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
@@ -134,7 +140,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
- return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+ return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
}, true);
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -259,7 +265,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
const lower = url.href.toLowerCase();
if (url.protocol === 'data') return url.href;
if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
const ext = extname(url.href);
return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -297,7 +303,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
ref={this._overlayIconRef}
onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
style={{
- display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
width: 'min(10%, 25px)',
height: 'min(10%, 25px)',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -311,13 +317,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this.dataDoc[this.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
- const altpaths = alts
- .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
- .filter(url => url)
- .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+ const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List; // retrieve alternate documents that may be rendered as alternate images
+ const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+ const altpaths =
+ alts
+ ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
+ .filter(url => url)
+ .map(url => this.choosePath(url)) ?? []; // acc ess the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
- return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
+ return paths.length ? paths : [defaultUrl.href];
}
@observable _error = '';
@@ -326,7 +334,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
@computed get content() {
TraceMobx();
- const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
+ const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) ?? Colors.WHITE);
const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -370,6 +378,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
}
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
marqueeDown = (e: React.PointerEvent) => {
+ if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
@@ -468,4 +477,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
);
}
+
+ public chooseImage = () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.multiple = true;
+ input.accept = 'image/*';
+ input.onchange = async _e => {
+ const file = input.files?.[0];
+ if (file) {
+ const disposer = OverlayView.ShowSpinner();
+ const [{ result }] = await Networking.UploadFilesToServer({ file });
+ if (result instanceof Error) {
+ alert('Error uploading files - possibly due to unsupported file types');
+ } else {
+ this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+ }
+ disposer();
+ } else {
+ console.log('No file selected');
+ }
+ };
+ input.click();
+ };
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 5394c7dbb..78e4435ce 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -113,7 +113,7 @@ export class KeyValueBox extends ObservableReactComponent {
if (key) target[key] = script.originalScript;
return false;
}
- field === undefined && (field = res.result);
+ field === undefined && (field = res.result instanceof Array ? new List(res.result) : res.result);
}
}
if (!key) return false;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index be20b5934..74e78c671 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent() {
}
@computed get Title() {
- return Field.toString(this.dataDoc[this.fieldKey] as Field);
+ return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 6e4d0e92a..0809e2ad6 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,12 +2,13 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
import { observer } from 'mobx-react';
import * as React from 'react';
import Xarrow from 'react-xarrows';
+import { FieldResult } from '../../../fields/Doc';
import { DocCss, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
@@ -22,8 +23,8 @@ export class LinkBox extends ViewBoxBaseComponent() {
public static LayoutString(fieldKey: string = 'link') {
return FieldView.LayoutString(LinkBox, fieldKey);
}
- disposer: IReactionDisposer | undefined;
- @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
+ _disposer: IReactionDisposer | undefined;
+ @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
@observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
constructor(props: FieldViewProps) {
@@ -39,11 +40,11 @@ export class LinkBox extends ViewBoxBaseComponent() {
return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
};
componentWillUnmount() {
- this.disposer?.();
+ this._disposer?.();
}
componentDidMount() {
this._props.setContentViewBox?.(this);
- this.disposer = reaction(
+ this._disposer = reaction(
() => ({ drag: SnappingManager.IsDragging }),
({ drag }) => {
!LightboxView.Contains(this.DocumentView?.()) &&
@@ -64,12 +65,13 @@ export class LinkBox extends ViewBoxBaseComponent() {
}
})
);
- },
- { fireImmediately: true }
+ }
);
}
render() {
+ TraceMobx();
+
if (this._hide) return null;
const a = this.anchor1;
const b = this.anchor2;
@@ -92,18 +94,34 @@ export class LinkBox extends ViewBoxBaseComponent() {
const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
+ var foundParent = false;
+ const getAnchor = (field: FieldResult): Element[] => {
+ const doc = DocCast(field);
+ const ele = document.getElementById(doc[Id]);
+ if (ele?.getBoundingClientRect().width) return [ele];
+ const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+ const annoOn = DocCast(doc.annotationOn);
+ if (eles.length || !annoOn) return eles;
+ const pareles = getAnchor(annoOn);
+ foundParent = pareles.length ? true : false;
+ return pareles;
+ };
// if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext ),
// then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
- const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+ const targetAhyperlinks = getAnchor(this.dataDoc.link_anchor_1);
+ const targetBhyperlinks = getAnchor(this.dataDoc.link_anchor_2);
- const aid = targetAhyperlink?.id || a.Document[Id];
- const bid = targetBhyperlink?.id || b.Document[Id];
- if (!document.getElementById(aid) || !document.getElementById(bid)) {
+ const container = this.DocumentView?.().containerViewPath?.().lastElement().ContentDiv;
+ const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
+ const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
+ if (!aid || !bid) {
setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
return null;
}
+ if (foundParent) {
+ setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 1)));
+ }
if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
@@ -192,6 +210,11 @@ export class LinkBox extends ViewBoxBaseComponent() {
>
);
}
+
+ setTimeout(
+ action(() => (this._forceAnimate = this._forceAnimate + 1)),
+ 2
+ );
return (
() impl
Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
- link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
+ // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
@@ -373,7 +373,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl
getView = (doc: Doc, options: FocusViewOptions) => {
if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
if (this.heightPercent === 100) {
- this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+ // do we want to always open up the timeline when followin a link? kind of clunky visually
+ //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
options.didMove = true;
}
return this._stackedTimeline.getView(doc, options);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c9340edc0..f4d5eef05 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { htmlToText } from 'html-to-text';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as WebRequest from 'web-request';
@@ -340,8 +340,18 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
_textAnnotationCreator: (() => ObservableMap) | undefined;
savedAnnotationsCreator: () => ObservableMap = () => this._textAnnotationCreator?.() || this._savedAnnotations;
+ @action
+ iframeMove = (e: PointerEvent) => {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ this._marqueeref.current?.onMove(theclick);
+ };
@action
iframeUp = (e: PointerEvent) => {
+ this._iframe?.contentDocument?.removeEventListener('pointermove', this.iframeMove);
+ this.marqueeing = undefined;
this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
this._textAnnotationCreator = undefined;
this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
@@ -358,6 +368,29 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
}
+ } else {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ if (!this._marqueeref.current?.isEmpty) this._marqueeref.current?.onEnd(theclick[0], theclick[1]);
+ else {
+ if (!(e.target as any)?.tagName?.includes('INPUT')) this.finishMarquee(theclick[0], theclick[1]);
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this.marqueeing = undefined;
+ }
+
+ ContextMenu.Instance.closeMenu();
+ ContextMenu.Instance.setIgnoreEvents(false);
+ if (e?.button === 2 || e?.altKey) {
+ e?.preventDefault();
+ e?.stopPropagation();
+ setTimeout(() => {
+ // if menu comes up right away, the down event can still be active causing a menu item to be selected
+ this.specificContextMenu(undefined as any);
+ this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
+ });
+ }
}
};
@action
@@ -400,6 +433,12 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
};
@action
iframeDown = (e: PointerEvent) => {
+ this._textAnnotationCreator = undefined;
+ const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
+ if (sel?.empty)
+ sel.empty(); // Chrome
+ else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+
this._props.select(false);
const theclick = this.props
.ScreenToLocalTransform()
@@ -409,6 +448,8 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
this.marqueeing = theclick;
+ this._marqueeref.current?.onInitiateSelection(this.marqueeing);
+ this._iframe?.contentDocument?.addEventListener('pointermove', this.iframeMove);
e.preventDefault();
}
};
@@ -739,28 +780,10 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
this.marqueeing = undefined;
}
};
- @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+ @action finishMarquee = (x?: number, y?: number) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.marqueeing = undefined;
- this._textAnnotationCreator = undefined;
- const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
- if (sel?.empty)
- sel.empty(); // Chrome
- else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
- if (x !== undefined && y !== undefined) {
- ContextMenu.Instance.closeMenu();
- ContextMenu.Instance.setIgnoreEvents(false);
- if (e?.button === 2 || e?.altKey) {
- e?.preventDefault();
- e?.stopPropagation();
- setTimeout(() => {
- // if menu comes up right away, the down event can still be active causing a menu item to be selected
- this.specificContextMenu(undefined as any);
- this.DocumentView?.().onContextMenu(undefined, x, y);
- });
- }
- }
};
@observable lighttext = false;
@@ -992,6 +1015,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
}
childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
@computed get webpage() {
+ TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
@@ -1071,6 +1095,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
: 'none';
annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
render() {
+ TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 793595694..eb7293054 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -450,16 +450,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
const newAutoLinks = new Set();
- const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.Links(this.Document).filter(
+ link =>
+ ((!Doc.isTemplateForField(this.Document) &&
+ (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+ (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+ (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
+ link.link_relationship === LinkManager.AutoKeywords
+ ); // prettier-ignore
if (this._editorView?.state.doc.textContent) {
- const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
+
const t = this._editorView.state.selection.to;
var tr = this._editorView.state.tr as any;
const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
- tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
@@ -624,7 +631,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent p + toHgt(child), margins);
const scrollHeight = this.ProseRef && proseHeight;
- if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
+ if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e34144fae..ae6da8fb0 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -703,7 +703,7 @@ export class PresBox extends ViewBoxBaseComponent() {
if (pinProps.pinData.temporal) {
pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
- pinDoc.config_clipEnd = NumCast(targetDoc.clipEnd, duration);
+ pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
}
}
if (pinProps?.pinViewport) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 246828709..1bd49cf3f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -45,18 +45,23 @@ export namespace Field {
export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- return !Field.IsField(field)
- ? key.startsWith('_')
- ? '='
- : ''
- : (onDelegate ? '=' : '') +
- (field instanceof ComputedField && showComputedValue
- ? field._lastComputedResult
- : field instanceof ComputedField
- ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
- : field instanceof ScriptField
- ? `$=${field.script.originalScript}`
- : Field.toScriptString(field));
+ const valFunc = (field: Field): string => {
+ const res =
+ field instanceof ComputedField && showComputedValue
+ ? field._lastComputedResult
+ : field instanceof ComputedField
+ ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
+ : field instanceof ScriptField
+ ? `$=${field.script.originalScript}`
+ : Field.toScriptString(field);
+ const resStr = (res + '').replace(/^`(.*)`$/, '$1');
+ return typeof field === 'string' && (+resStr).toString() !== resStr && !Array.from('+-*/.').some(k => Array.from(resStr).includes(k))
+ ? resStr
+ : (res + '') // adjust the key value string to be easier to enter: represent any initial list as an array with []
+ .trim()
+ .replace(/^new List\((.*)\)$/, '$1');
+ };
+ return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
}
export function toScriptString(field: Field) {
switch (typeof field) {
diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts
deleted file mode 100644
index f236a04fd..000000000
--- a/src/fields/PresField.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-//insert code here
-import { ObjectField } from "./ObjectField";
-
-export abstract class PresField extends ObjectField {
-
-}
\ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index f5801de73..d0a149506 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -36,11 +36,4 @@ export class RichTextField extends ObjectField {
[ToString]() {
return this.Text;
}
-
- public static DashField(fieldKey: string) {
- 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":[{"type":"dashField","attrs":{"fieldKey":"${fieldKey}","docId":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`,
- ''
- );
- }
}
--
cgit v1.2.3-70-g09d2
From 52bd492747cf169df7bcdecdbdbb353d45b19734 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Thu, 28 Mar 2024 00:05:05 -0400
Subject: fixed tabbing between items in nested dashFieldViews
---
src/client/views/EditableView.tsx | 1 +
.../views/collections/collectionSchema/CollectionSchemaView.scss | 9 +++++++++
.../views/collections/collectionSchema/SchemaTableCell.tsx | 8 ++++----
src/fields/Doc.ts | 3 +++
src/fields/RichTextField.ts | 7 +++++++
5 files changed, 24 insertions(+), 4 deletions(-)
(limited to 'src/fields')
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 44c8e4e1f..85e893e19 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -121,6 +121,7 @@ export class EditableView extends ObservableReactComponent {
@action
onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here
switch (e.key) {
case 'Tab':
e.stopPropagation();
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index ac0bd2378..9768877ff 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -226,6 +226,15 @@
align-items: center;
}
+.schemaRTFCell {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ min-width: 10px;
+ min-height: 10px;
+}
+
.schema-row {
cursor: grab;
justify-content: flex-end;
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bda8afa41..ab05a62d3 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -17,6 +17,7 @@ import { FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
import { dropActionType } from '../../../util/DragManager';
import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
import { EditableView } from '../../EditableView';
@@ -29,7 +30,6 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
-import { SnappingManager } from '../../../util/SnappingManager';
export interface SchemaTableCellProps {
Document: Doc;
@@ -128,7 +128,7 @@ export class SchemaTableCell extends ObservableReactComponent
this._props.autoFocus && r?.setIsFocused(true)}
+ ref={r => this.selected && this._props.autoFocus && r?.setIsFocused(true)}
oneLine={this._props.oneLine}
allowCRs={this._props.allowCRs}
contents={undefined}
@@ -141,7 +141,7 @@ export class SchemaTableCell extends ObservableReactComponent
+
{this.selected ? this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1bd49cf3f..bdf600475 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1713,3 +1713,6 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran
ScriptingGlobals.add(function toJavascriptString(str: string) {
return Field.toJavascriptString(str as Field);
});
+ScriptingGlobals.add(function RtfField() {
+ return RichTextField.RTFfield();
+});
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index d0a149506..3f13f7e6d 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -36,4 +36,11 @@ export class RichTextField extends ObjectField {
[ToString]() {
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":[]}`,
+ ''
+ );
+ }
}
--
cgit v1.2.3-70-g09d2
From 1b592f74a7df8f6dd7b2881032725f26aedff403 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Thu, 28 Mar 2024 11:38:56 -0400
Subject: fixed keyvaluebox to show props document, never the doc in the
fieldKey slot. changed computedFIelds to do mobx caching. changed text
boxes to do updating from templates based on a fieldKey_autoUpdate flag
combined with modification timestamps. enabled comparison box to work with
text fields in addition to docs.
---
src/client/util/Scripting.ts | 9 +--
src/client/views/InkControlPtHandles.tsx | 85 ++++++++++++----------
src/client/views/PropertiesButtons.tsx | 4 +-
src/client/views/nodes/ComparisonBox.tsx | 6 +-
src/client/views/nodes/KeyValueBox.tsx | 2 +-
.../views/nodes/formattedText/FormattedTextBox.tsx | 31 +++++---
src/fields/Doc.ts | 2 +-
src/fields/ScriptField.ts | 22 ++++--
8 files changed, 90 insertions(+), 71 deletions(-)
(limited to 'src/fields')
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index f5e162d16..31222aa50 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -88,15 +88,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
}
const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
- if (batch) {
- batch.end();
- }
-
+ batch?.end();
return { success: true, result };
} catch (error) {
- if (batch) {
- batch.end();
- }
+ batch?.end();
onError?.(script + ' ' + error);
return { success: false, error, result: errorVal };
}
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 31b13d2c8..01d52135a 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
import { ControlPoint, InkData, PointData } from '../../fields/InkField';
@@ -13,6 +13,7 @@ import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { SnappingManager } from '../util/SnappingManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
export interface InkControlProps {
inkDoc: Doc;
@@ -24,10 +25,15 @@ export interface InkControlProps {
}
@observer
-export class InkControlPtHandles extends React.Component {
+export class InkControlPtHandles extends ObservableReactComponent {
@observable private _overControl = -1;
get docView() {
- return this.props.inkView.DocumentView?.();
+ return this._props.inkView.DocumentView?.();
+ }
+
+ constructor(props: InkControlProps) {
+ super(props);
+ makeObservable(this);
}
componentDidMount() {
@@ -42,51 +48,51 @@ export class InkControlPtHandles extends React.Component {
*/
@action
onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
- const ptFromScreen = this.props.inkView.ptFromScreen;
+ const ptFromScreen = this._props.inkView.ptFromScreen;
if (ptFromScreen) {
const order = controlIndex % 4;
- const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
- const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
- const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'));
+ const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this._props.inkCtrlPoints.length) % this._props.inkCtrlPoints.length;
+ const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this._props.inkCtrlPoints.length;
+ const brokenIndices = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'));
const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1;
- const origInk = this.props.inkCtrlPoints.slice();
+ const origInk = this._props.inkCtrlPoints.slice();
setupMoveUpEvents(
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
this.docView && InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
return false;
}),
action(() => {
- if (this.props.inkView.controlUndo && this.docView) {
+ if (this._props.inkView.controlUndo && this.docView) {
InkStrokeProperties.Instance.snapControl(this.docView, controlIndex);
}
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}),
action((e: PointerEvent, doubleTap: boolean | undefined) => {
- const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
+ const equivIndex = controlIndex === 0 ? this._props.inkCtrlPoints.length - 1 : controlIndex === this._props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
if (doubleTap || e.button === 2) {
if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) {
if (brokenIndices) brokenIndices.push(controlIndex);
- else this.props.inkDoc.brokenInkIndices = new List([controlIndex]);
+ else this._props.inkDoc.brokenInkIndices = new List([controlIndex]);
} else {
if (brokenIndices?.includes(equivIndex)) {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
}
if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
}
}
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
}
this.changeCurrPoint(controlIndex);
}),
@@ -126,14 +132,14 @@ export class InkControlPtHandles extends React.Component {
render() {
// Accessing the current ink's data and extracting all control points.
- const scrData = this.props.screenCtrlPoints;
+ const scrData = this._props.screenCtrlPoints;
const sreenCtrlPoints: ControlPoint[] = [];
for (let i = 0; i <= scrData.length - 4; i += 4) {
sreenCtrlPoints.push({ ...scrData[i], I: i });
sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 });
}
- const inkData = this.props.inkCtrlPoints;
+ const inkData = this._props.inkCtrlPoints;
const inkCtrlPts: ControlPoint[] = [];
for (let i = 0; i <= inkData.length - 4; i += 4) {
inkCtrlPts.push({ ...inkData[i], I: i });
@@ -141,23 +147,23 @@ export class InkControlPtHandles extends React.Component {
}
const closed = InkingStroke.IsClosed(inkData);
- const nearestScreenPt = this.props.nearestScreenPt();
+ const nearestScreenPt = this._props.nearestScreenPt();
const TagType = (broken?: boolean) => (broken ? 'rect' : 'circle');
const hdl = (control: { X: number; Y: number; I: number }, scale: number, color: string) => {
- const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
+ const broken = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements;
return (
this.onControlDown(e, control.I)}
@@ -170,7 +176,7 @@ export class InkControlPtHandles extends React.Component {
};
return (
);
@@ -185,10 +191,15 @@ export interface InkEndProps {
endPt: () => PointData;
}
@observer
-export class InkEndPtHandles extends React.Component {
+export class InkEndPtHandles extends ObservableReactComponent {
@observable _overStart: boolean = false;
@observable _overEnd: boolean = false;
+ constructor(props: InkEndProps) {
+ super(props);
+ makeObservable(this);
+ }
+
_throttle = 0; // need to throttle dragging since the position may change when the control points change. this allows the stroke to settle so that we don't get increasingly bad jitter
@action
dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
@@ -198,7 +209,7 @@ export class InkEndPtHandles extends React.Component {
e,
action(e => {
if (this._throttle++ % 2 !== 0) return false;
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
// compute stretch factor by finding scaling along axis between start and end points
const p1 = pt1();
const p2 = pt2();
@@ -216,8 +227,8 @@ export class InkEndPtHandles extends React.Component {
}),
action(() => {
SnappingManager.SetIsDragging(false);
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['stroke', 'x', 'y', 'width', 'height']);
}),
returnFalse
@@ -230,7 +241,7 @@ export class InkEndPtHandles extends React.Component {
key={key}
cx={pt.X}
cy={pt.Y}
- r={this.props.screenSpaceLineWidth * 2}
+ r={this._props.screenSpaceLineWidth * 2}
fill={this._overStart ? '#aaaaaa' : '#99999977'}
stroke={'#00007777'}
strokeWidth={0}
@@ -242,8 +253,8 @@ export class InkEndPtHandles extends React.Component {
);
return (
);
}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 02f288a68..517a80d63 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -87,7 +87,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
containerDoc._isLightbox = !containerDoc._isLightbox;
//containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
- //dv.Docuemnt.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
});
}
@@ -219,7 +219,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
// containerDoc._isLightbox = !containerDoc._isLightbox;
// //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
// const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
- // //dv.Document.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ // //dv.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
// containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
// });
// }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 59d99b371..9ffdc350d 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -69,8 +69,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
action((e, doubleTap) => {
if (doubleTap) {
this._isAnyChildContentActive = true;
- if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
- if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
}
}),
false,
@@ -131,8 +131,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
return false;
};
- whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));
-
closeDown = (e: React.PointerEvent, which: string) => {
setupMoveUpEvents(
this,
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 78e4435ce..31a2367fc 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -55,7 +55,7 @@ export class KeyValueBox extends ObservableReactComponent {
@observable _splitPercentage = 50;
get fieldDocToLayout() {
- return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
+ return DocCast(this._props.Document);
}
@action
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d25101844..f06e5fad0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -21,7 +21,7 @@ import { List } from '../../../../fields/List';
import { PrefetchProxy } from '../../../../fields/Proxy';
import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
@@ -349,6 +349,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent json?.replace(/"selection":.*/, '');
@@ -369,22 +370,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
this._props.renderDepth &&
@@ -1244,9 +1244,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc;
- const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc;
- return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey]) ?? StrCast(whichDoc[this.fieldKey])) };
+ const protoData = DocCast(this.dataDoc.proto)?.[this.fieldKey];
+ const dataData = this.dataDoc[this.fieldKey];
+ const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey];
+ const dataTime = dataData ? DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+ const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+ const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+ const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
+ const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
+ return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
},
incomingValue => {
if (this._editorView && this._applyingChange !== this.fieldKey) {
@@ -1256,11 +1262,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
}
}
- }
+ },
+ { fireImmediately: true }
);
this._disposers.search = reaction(
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index bdf600475..200896e25 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -48,7 +48,7 @@ export namespace Field {
const valFunc = (field: Field): string => {
const res =
field instanceof ComputedField && showComputedValue
- ? field._lastComputedResult
+ ? field.value(doc)
: field instanceof ComputedField
? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
: field instanceof ScriptField
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 9021c8896..8b51088b2 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -198,14 +198,22 @@ export class ComputedField extends ScriptField {
}
_lastComputedResult: FieldResult;
- value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
- _valueOutsideReaction = (doc: Doc) => {
- this._lastComputedResult =
- this._cachedResult ?? (this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _setCacheResult_: this.setCacheResult, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
- return this._lastComputedResult;
- };
+ value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ??
+ computedFn((doc: Doc) =>
+ this.script.compiled &&
+ this.script.run( {
+ this: doc,
+ //value: '',
+ _setCacheResult_: this.setCacheResult,
+ _last_: this._lastComputedResult,
+ _readOnly_: true,
+ },
+ console.log
+ ).result
+ )(doc)
+ ); // prettier-ignore
- [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this._valueOutsideReaction(doc) }; } // prettier-ignore
+ [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this.value(doc) }; } // prettier-ignore
[Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
--
cgit v1.2.3-70-g09d2