aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-02-27 15:08:45 -0500
committerbobzel <zzzman@gmail.com>2023-02-27 15:08:45 -0500
commitaf2a2c83868c87812e9ae54c8e3cced81374619a (patch)
treedaa3f79b93c22f9c0f90b00c2e6f843de6eedc36 /src
parent536e1ed3f847b0e7343c1cf9eb7fc0c97818e171 (diff)
restructured getAnchor()/scrollFocus to be more consistent. added setterscript for computedFields. restructed getFieldsImpl to avoid making multiple requests for the same document due to timing issues by 'locking' a document cache with a promise before sending the server request. added rotation and fill color as animatable fields. fixed image cropping for
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts143
-rw-r--r--src/client/documents/Documents.ts23
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/util/LinkManager.ts2
-rw-r--r--src/client/util/SharingManager.tsx30
-rw-r--r--src/client/views/DocumentButtonBar.tsx10
-rw-r--r--src/client/views/InkTangentHandles.tsx9
-rw-r--r--src/client/views/InkingStroke.tsx32
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/MarqueeAnnotator.tsx35
-rw-r--r--src/client/views/StyleProvider.tsx3
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx10
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx37
-rw-r--r--src/client/views/collections/TabDocView.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx49
-rw-r--r--src/client/views/nodes/AudioBox.tsx12
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx23
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx20
-rw-r--r--src/client/views/nodes/DocumentView.tsx15
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx6
-rw-r--r--src/client/views/nodes/ImageBox.tsx34
-rw-r--r--src/client/views/nodes/LabelBox.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.tsx27
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx2
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx5
-rw-r--r--src/client/views/nodes/VideoBox.tsx23
-rw-r--r--src/client/views/nodes/WebBox.tsx40
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx28
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx4
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx211
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx7
-rw-r--r--src/client/views/pdf/PDFViewer.tsx9
-rw-r--r--src/fields/Doc.ts28
-rw-r--r--src/fields/ScriptField.ts6
-rw-r--r--src/server/DashUploadUtils.ts2
-rw-r--r--src/server/websocket.ts2
37 files changed, 517 insertions, 387 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index cab90138f..8f79ebb03 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -234,7 +234,7 @@ export namespace DocServer {
* the server if the document has not been cached.
* @param id the id of the requested document
*/
- const _GetRefFieldImpl = async (id: string, force: boolean = false): Promise<Opt<RefField>> => {
+ const _GetRefFieldImpl = (id: string, force: boolean = false): Promise<Opt<RefField>> => {
// an initial pass through the cache to determine whether the document needs to be fetched,
// is already in the process of being fetched or already exists in the
// cache
@@ -309,8 +309,7 @@ export namespace DocServer {
}
export async function getYoutubeChannels() {
- const apiKey = await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
- return apiKey;
+ return await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
}
export function getYoutubeVideos(videoTitle: string, callBack: (videos: any[]) => void) {
@@ -329,10 +328,11 @@ export namespace DocServer {
*/
const _GetRefFieldsImpl = async (ids: string[]): Promise<{ [id: string]: Opt<RefField> }> => {
const requestedIds: string[] = [];
- const waitingIds: string[] = [];
- const promises: Promise<Opt<RefField>>[] = [];
- const map: { [id: string]: Opt<RefField> } = {};
+ const promises: Promise<any>[] = [];
+ let defaultRes: any = undefined;
+ const defaultPromise = new Promise<any>(res => (defaultRes = res));
+ let defaultPromises: { p: Promise<any>; id: string }[] = [];
// 1) an initial pass through the cache to determine
// i) which documents need to be fetched
// ii) which are already in the process of being fetched
@@ -340,6 +340,13 @@ export namespace DocServer {
for (const id of ids) {
const cached = _cache[id];
if (cached === undefined) {
+ defaultPromises.push({
+ id,
+ p: (_cache[id] = new Promise<any>(async res => {
+ await defaultPromise;
+ res(_cache[id]);
+ })),
+ });
// NOT CACHED => we'll have to send a request to the server
requestedIds.push(id);
} else if (cached instanceof Promise) {
@@ -347,10 +354,10 @@ export namespace DocServer {
// and requested one of the documents I'm looking for. Shouldn't fetch again, just
// wait until this promise is resolved (see 7)
promises.push(cached);
- waitingIds.push(id);
+ // waitingIds.push(id);
} else {
// CACHED => great, let's just add it to the field map
- map[id] = cached;
+ // map[id] = cached;
}
}
@@ -358,74 +365,65 @@ export namespace DocServer {
// 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
// fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of
// the fields have been returned from the server
- console.log('Requesting ' + requestedIds.length + ' fields');
+ console.log('Requesting ' + requestedIds.length);
FieldLoader.active && runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length));
- const getSerializedFields: Promise<any> = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
+ const serializedFields = await Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
// 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
// Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
-
- let retrieved = 0;
- const fields: { [id: string]: RefField } = {};
- await getSerializedFields.then(async fieldvals => {
- console.log('deserializing ' + fieldvals.length + ' fields');
- const proms: Promise<void>[] = [];
- await runInAction(async () => {
- for (const field of fieldvals) {
- const cached = _cache[field.id];
- if (!cached) {
- retrieved++;
- if (FieldLoader.active && retrieved % 150 === 0) {
- runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = retrieved));
- await new Promise(res => setTimeout(res));
+ let processed = 0;
+ console.log('deserializing ' + serializedFields.length + ' fields');
+ for (const field of serializedFields) {
+ processed++;
+ if (FieldLoader.active && processed % 150 === 0) {
+ runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = processed));
+ await new Promise(res => setTimeout(res)); // force loading to yield to splash screen rendering to update progress
+ }
+ const cached = _cache[field.id];
+ if (!cached || (cached instanceof Promise && defaultPromises.some(dp => dp.p === cached))) {
+ // deserialize
+ // adds to a list of promises that will be awaited asynchronously
+ promises.push(
+ (_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => {
+ //overwrite or delete any promises (that we inserted as flags
+ // to indicate that the field was in the process of being fetched). Now everything
+ // should be an actual value within or entirely absent from the cache.
+ if (deserialized !== undefined) {
+ _cache[field.id] = deserialized;
+ } else {
+ delete _cache[field.id];
}
- // deserialize
- const prom = SerializationHelper.Deserialize(field).then(async deserialized => {
- fields[field.id] = deserialized;
-
- //overwrite or delete any promises (that we inserted as flags
- // to indicate that the field was in the process of being fetched). Now everything
- // should be an actual value within or entirely absent from the cache.
- if (deserialized !== undefined) {
- _cache[field.id] = deserialized;
- } else {
- delete _cache[field.id];
- }
- return deserialized;
- });
- // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
- // we set the value at the field's id to a promise that will resolve to the field.
- // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
- // The mapping in the .then call ensures that when other callers await these promises, they'll
- // get the resolved field
- _cache[field.id] = prom;
-
- // adds to a list of promises that will be awaited asynchronously
- proms.push(prom);
- } else if (cached instanceof Promise) {
- console.log('.');
- proms.push(cached as any);
- cached.then((f: any) => (fields[field.id] = f));
- } else if (field) {
- console.log('-');
- proms.push(cached as any);
- fields[field.id] = DocServer.GetCachedRefField(field.id) || field;
- }
- }
- });
- return Promise.all(proms);
- });
+ const promInd = defaultPromises.findIndex(dp => dp.id === field.id);
+ promInd !== -1 && defaultPromises.splice(promInd, 1);
+ return deserialized;
+ }))
+ );
+ // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+ // we set the value at the field's id to a promise that will resolve to the field.
+ // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+ // The mapping in the .then call ensures that when other callers await these promises, they'll
+ // get the resolved field
+ } else if (cached instanceof Promise) {
+ console.log('.');
+ //promises.push(cached);
+ } else if (field) {
+ // console.log('-');
+ }
+ }
+ }
- // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose
- // prototype documents, if any, have also been fetched and cached.
- console.log('Deserialized ' + Object.keys(fields).length + ' fields');
+ await Promise.all(promises);
+ defaultPromises.forEach(df => delete _cache[df.id]);
+ defaultRes();
- // 6) with this confidence, we can now go through and update the cache at the ids of the fields that
- // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
- // id to the soon-to-be-returned field mapping.
- requestedIds.forEach(id => (map[id] = fields[id]));
- }
+ // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose
+ // prototype documents, if any, have also been fetched and cached.
+ console.log('Deserialized ' + (requestedIds.length - defaultPromises.length) + ' fields');
+ // 6) with this confidence, we can now go through and update the cache at the ids of the fields that
+ // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
+ // id to the soon-to-be-returned field mapping.
+ //ids.forEach(id => (map[id] = _cache[id] as any));
// 7) those promises we encountered in the else if of 1), which represent
// other callers having already submitted a request to the server for (a) document(s)
@@ -434,16 +432,19 @@ export namespace DocServer {
//
// fortunately, those other callers will also hit their own version of 6) and clean up
// the shared cache when these promises resolve, so all we have to do is...
- const otherCallersFetching = await Promise.all(promises);
+ //const otherCallersFetching = await Promise.all(promises);
// ...extract the RefFields returned from the resolution of those promises and add them to our
// own map.
- waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index]));
+ // waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index]));
// now, we return our completed mapping from all of the ids that were passed into the method
// to their actual RefField | undefined values. This return value either becomes the input
// argument to the caller's promise (i.e. GetRefFields(["_id1_", "_id2_", "_id3_"]).then(map => //do something with map...))
// or it is the direct return result if the promise is awaited (i.e. let fields = await GetRefFields(["_id1_", "_id2_", "_id3_"])).
- return map;
+ return ids.reduce((map, id) => {
+ map[id] = _cache[id] as any;
+ return map;
+ }, {} as { [id: string]: Opt<RefField> });
};
let _GetRefFields: (ids: string[]) => Promise<{ [id: string]: Opt<RefField> }> = errorFunc;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index debb11066..d2ab67849 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -263,6 +263,7 @@ export class DocumentOptions {
viewTransitionTime?: number; // transition duration for view parameters
presTransition?: number; //the time taken for the transition TO a document
presDuration?: number; //the duration of the slide in presentation view
+ presZoomText?: boolean; // whether text anchors should shown in a larger box when following links to make them stand out
borderRounding?: string;
boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
data?: any;
@@ -1043,6 +1044,9 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.MARKER), url, options, id);
}
+ export function CollectionAnchorDocument(options: DocumentOptions = {}, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id);
+ }
export function TextanchorDocument(options: DocumentOptions = {}, id?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id);
}
@@ -1051,6 +1055,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id);
}
+ export function InkAnchorDocument(options: DocumentOptions, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id);
+ }
+
export function HTMLAnchorDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id);
}
@@ -1407,12 +1415,15 @@ export namespace DocUtils {
}
});
funcs &&
- Object.keys(funcs).map(key => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
- doc[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }) : undefined;
- }
- });
+ Object.keys(funcs)
+ .filter(key => key.endsWith('-setter'))
+ .map(key => {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
+ const setFunc = Cast(funcs[key + '-setter'], 'string', null);
+ doc[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined;
+ }
+ });
return doc;
}
export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e4d48d4de..d9273c2c9 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -199,7 +199,7 @@ export class DocumentManager {
static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
if (!doc) return [];
const srcContext = Cast(doc.context, Doc, null) ?? Cast(Cast(doc.annotationOn, Doc, null)?.context, Doc, null);
- var containerDocContext = srcContext ? [srcContext] : [];
+ var containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
containerDocContext[0]?.context &&
@@ -286,7 +286,7 @@ export class DocumentManager {
return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, originalTarget, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30);
}
if (!docView && targetDoc.type !== DocumentType.MARKER) {
- annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+ annoContainerView.focus(targetDoc, options); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
}
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 7da16ca78..46d44fea4 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -48,7 +48,7 @@ export class LinkManager {
const a2 = DocCast(linkdata[1]);
a1 &&
a2 &&
- Promise.all([a1.proto, a2.proto]).then(
+ Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then(
action(protos => {
(protos[0] as Doc)?.[DirectLinksSym].add(link);
(protos[1] as Doc)?.[DirectLinksSym].add(link);
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 00ae85d12..36095700c 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -130,29 +130,21 @@ export class SharingManager extends React.Component<{}> {
if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') {
this.populating = true;
const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
- const raw = JSON.parse(userList) as User[];
- const sharingDocs: ValidatedUser[] = [];
- const evaluating = raw.map(async user => {
- const isCandidate = user.email !== Doc.CurrentUserEmail;
- if (isCandidate) {
- const sharingDoc = await DocServer.GetRefField(user.sharingDocumentId);
- const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId);
+ const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
+ const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
+ raw.map(
+ action((newUser: User) => {
+ const sharingDoc = docs[newUser.sharingDocumentId];
+ const linkDatabase = docs[newUser.linkDatabaseId];
if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
- sharingDocs.push({ user, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
- }
- }
- });
- return Promise.all(evaluating).then(() => {
- runInAction(async () => {
- for (const sharer of sharingDocs) {
- if (!this.users.find(user => user.user.email === sharer.user.email)) {
- this.users.push(sharer);
+ if (!this.users.find(user => user.user.email === newUser.email)) {
+ this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
// LinkManager.addLinkDB(sharer.linkDatabase);
}
}
- });
- this.populating = false;
- });
+ })
+ );
+ this.populating = false;
}
};
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index f61d147cf..9298c881c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -315,7 +315,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
.views()
.filter(v => v)
.map(dv => dv!.rootDoc);
- TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null), currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null) });
+ TabDocView.PinDoc(docs, {
+ pinAudioPlay: true,
+ pinDocLayout,
+ pinData: { dataview: true },
+ activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null),
+ currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null),
+ });
e.stopPropagation();
}}
/>
@@ -496,7 +502,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const rootView = this.props.views()[0];
const rootDoc = rootView?.rootDoc;
if (rootDoc) {
- const anchor = rootView.ComponentView?.getAnchor?.(true) ?? rootDoc;
+ const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc;
const trail = DocCast(anchor.presTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
if (trail !== anchor.presTrail) {
DocUtils.MakeLink({ doc: anchor }, { doc: trail }, 'link trail');
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index c4a2f603e..71e0ff63c 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -38,18 +38,19 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent, down: number[], delta: number[]) => {
+ action((e: PointerEvent, down: number[], delta: number[]) => {
if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent');
if (e.altKey) this.onBreakTangent(controlIndex);
const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
return false;
- },
- () => {
+ }),
+ action(() => {
this.props.inkView.controlUndo?.end();
+ this.props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
- },
+ }),
emptyFunction
);
};
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 76c1aa80a..17cf6a678 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -25,7 +25,7 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
import { InkData, InkField } from '../../fields/InkField';
-import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, RTFCast, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
import { DashColor, OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
import { CognitiveServices } from '../cognitive_services/CognitiveServices';
@@ -41,11 +41,13 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
import './InkStroke.scss';
import { InkStrokeProperties } from './InkStrokeProperties';
import { InkTangentHandles } from './InkTangentHandles';
-import { DocComponentView, DocFocusOptions } from './nodes/DocumentView';
+import { DocComponentView, DocFocusOptions, DocumentView } from './nodes/DocumentView';
import { FieldView, FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { StyleProp } from './StyleProvider';
import Color = require('color');
+import { Docs } from '../documents/Documents';
+import { PinProps, PresBox } from './nodes/trails';
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -78,12 +80,28 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
// fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1);
- getAnchor = (addAsAnnotation: boolean) => {
- return this._subContentView?.getAnchor?.(addAsAnnotation) || this.rootDoc;
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+ if (this._subContentView) {
+ return this._subContentView.getAnchor?.(addAsAnnotation) || this.rootDoc;
+ }
+
+ if (!addAsAnnotation && !pinProps) return this.rootDoc;
+
+ const anchor = Docs.Create.InkAnchorDocument({ title: 'Ink anchor:' + this.rootDoc.title, presDuration: 1100, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc });
+ if (anchor) {
+ anchor.backgroundColor = 'transparent';
+ // /* addAsAnnotation &&*/ this.addDocument(anchor);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, inkable: true } }, this.rootDoc);
+ return anchor;
+ }
+ return this.rootDoc;
};
- scrollFocus = (textAnchor: Doc, options: DocFocusOptions) => {
- return this._subContentView?.scrollFocus?.(textAnchor, options);
+ scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => {
+ if (this._subContentView) return this._subContentView?.scrollFocus?.(docView, anchor, options);
+
+ const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
+ return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
};
/**
* @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
@@ -349,7 +367,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1);
const closed = InkingStroke.IsClosed(inkData);
const isInkMask = BoolCast(this.layoutDoc.isInkMask);
- const fillColor = isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : StrCast(this.layoutDoc.fillColor, 'transparent');
+ const fillColor = isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FillColor) ?? 'transparent';
const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
// bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 625dc2748..64f116a09 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -145,7 +145,6 @@ export class MainView extends React.Component {
if (ele && prog) {
// remove from DOM
setTimeout(() => {
- clearTimeout();
prog.style.transition = '1s';
prog.style.width = '100%';
}, 0);
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 3bdf65d01..30867a774 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -8,7 +8,7 @@ import { GetEffectiveAcl } from '../../fields/util';
import { unimplementedFunction, Utils } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import { DragManager } from '../util/DragManager';
-import { undoBatch } from '../util/UndoManager';
+import { undoBatch, UndoManager } from '../util/UndoManager';
import './MarqueeAnnotator.scss';
import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -48,7 +48,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
constructor(props: any) {
super(props);
- AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true);
+ AnchorMenu.Instance.OnCrop = (e: PointerEvent) => {
+ if (this.props.anchorMenuCrop) {
+ UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
+ }
+ };
AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true));
AnchorMenu.Instance.OnAudio = unimplementedFunction;
AnchorMenu.Instance.Highlight = this.highlight;
@@ -87,11 +91,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
e.preventDefault();
e.stopPropagation();
- const sourceAnchorCreator = () => {
- const annoDoc = this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true); // hyperlink color
- annoDoc && this.props.addDocument(annoDoc);
- return annoDoc;
- };
+ const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
+
const targetCreator = (annotationOn: Doc | undefined) => {
const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
FormattedTextBox.SelectOnLoad = target[Id];
@@ -116,11 +117,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
e.preventDefault();
e.stopPropagation();
var cropRegion: Doc | undefined;
- const sourceAnchorCreator = () => {
- cropRegion = this.highlight('', true); // hyperlink color
- cropRegion && this.props.addDocument(cropRegion);
- return cropRegion;
- };
+ const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color
const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
@@ -159,7 +156,15 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
return marqueeAnno;
}
- const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, text: this.props.selectionText(), backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title });
+ const textRegionAnno = Docs.Create.HTMLAnchorDocument([], {
+ annotationOn: this.props.rootDoc,
+ text: this.props.selectionText(),
+ backgroundColor: 'transparent',
+ presDuration: 2100,
+ presTransition: 500,
+ presZoomText: true,
+ title: 'Selection on ' + this.props.rootDoc.title,
+ });
let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
@@ -262,10 +267,6 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
AnchorMenu.Instance.jumpTo(cliX, cliY);
- if (AnchorMenu.Instance.Highlighting) {
- // when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
- this.highlight('rgba(245, 230, 95, 0.75)', false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color)
- }
this.props.finishMarquee(undefined, undefined, e);
runInAction(() => (this._width = this._height = 0));
} else {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5f87f0697..153c30052 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -31,6 +31,7 @@ export enum StyleProp {
BorderRounding = 'borderRounding', // border radius of the document view
Color = 'color', // foreground color of Document view items
BackgroundColor = 'backgroundColor', // background color of a document view
+ FillColor = 'fillColor', // fill color of an ink stroke or shape
WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
HideLinkButton = 'hideLinkButton', // hides the blue-dot link button. used when a document acts like a button
LinkSource = 'linkSource', // source document of a link -- used by LinkAnchorBox
@@ -158,6 +159,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
)) ||
''
);
+ case StyleProp.FillColor:
+ return Cast(doc?._fillColor, 'string', Cast(doc?.fillColor, 'string', 'transparent'));
case StyleProp.Color:
if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 28f08b6ce..eecab9d86 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -190,7 +190,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
CollectionStackedTimeline.SelectingRegion = this;
} else {
this._markerEnd = this.currentTime;
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
this._markerEnd = undefined;
CollectionStackedTimeline.SelectingRegion = undefined;
}
@@ -257,7 +257,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerEnd = tmp;
}
if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
- const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true);
+ const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
}
(!isClick || !wasSelecting) && (this._markerEnd = undefined);
@@ -273,7 +273,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
undefined,
() => {
if (shiftKey) {
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime, undefined, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true);
} else {
!wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
@@ -388,8 +388,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// creates marker on timeline
@undoBatch
@action
- static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
+ static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
if (anchorStartTime === undefined) return rootDoc;
+ const startTag = '_timecodeToShow';
+ const endTag = '_timecodeToHide';
const anchor =
docAnchor ??
Docs.Create.LabelDocument({
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index e6f29ec37..437b22040 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -21,7 +21,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
import { CollectionSubView } from './CollectionSubView';
import './CollectionTimeView.scss';
import React = require('react');
-import { DocFocusOptions } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
import { PresBox } from '../nodes/trails';
@observer
@@ -52,8 +52,7 @@ export class CollectionTimeView extends CollectionSubView() {
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.rootDoc,
});
- anchor.presPinPivotField = this.pivotField; // should be captured in pinDocView below
- PresBox.pinDocView(anchor, { pinData: { viewType: true, filters: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinData: { viewType: true, pivot: true, filters: true } }, this.rootDoc);
if (addAsAnnotation) {
// when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
@@ -67,32 +66,12 @@ export class CollectionTimeView extends CollectionSubView() {
};
@action
- scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
- if (options.preview) {
- // if in preview, then override document's fields with view spec
- this._focusFilters = StrListCast(anchor.presPinDocFilters);
- this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
- this._focusPivotField = StrCast(anchor.presPinPivotField);
- return undefined;
- }
- const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
-
- // should be part of restoreTargetDocView
- this.layoutDoc._prevFilterIndex = 1;
- this.layoutDoc._pivotField = anchor.presPinPivotField;
-
- return PresBox.restoreTargetDocView(
- this.props.DocumentView?.(), //
- { pinDocLayout: BoolCast(anchor.presPinLayout) },
- anchor,
- focusSpeed,
- {
- viewType: anchor.presPinViewType ? true : false,
- filters: anchor.presPinDocFilters || anchor.presPinDocRangeFilters ? true : false,
- }
- )
- ? focusSpeed
- : undefined;
+ scrollPreview = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => {
+ // if in preview, then override document's fields with view spec
+ this._focusFilters = StrListCast(anchor.presPinDocFilters);
+ this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
+ this._focusPivotField = StrCast(anchor.presPinPivotField);
+ return undefined;
};
layoutEngine = () => this._layoutEngine;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f93810deb..b75f315ca 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -247,13 +247,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
alert('Cannot pin presentation document to itself');
return;
}
- const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false);
- const pinDoc = anchorDoc && anchorDoc !== doc ? Doc.MakeDelegate(anchorDoc) : Doc.MakeDelegate(doc);
+ const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
+ const pinDoc = Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc);
pinDoc.presentationTargetDoc = anchorDoc ?? doc;
pinDoc.title = doc.title + ' - Slide';
pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
- pinDoc.presDuration = 2000;
+ pinDoc.presDuration = pinDoc.presDuration ?? 1000;
pinDoc.groupWithUp = false;
pinDoc.context = curPres;
// these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time
@@ -274,7 +274,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.presStartTime = NumCast(doc.clipStart);
pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
}
- PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc);
+ //PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, anchorDoc && anchorDoc !== doc && !anchorDoc.unrendered ? anchorDoc : doc);
pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
//save position
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 7dd9cdb8b..fa0695fb2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -14,6 +14,7 @@ export interface ViewDefBounds {
x: number;
y: number;
z?: number;
+ rotation?: number;
text?: string;
zIndex?: number;
width?: number;
@@ -31,6 +32,7 @@ export interface PoolData {
x: number;
y: number;
z?: number;
+ rotation?: number;
zIndex?: number;
width?: number;
height?: number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7d176f426..0477c6a16 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -8,7 +8,6 @@ import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { ObjectField } from '../../../../fields/ObjectField';
import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
@@ -41,7 +40,7 @@ import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDo
import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../../nodes/DocumentView';
import { FieldViewProps } from '../../nodes/FieldView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { PresBox } from '../../nodes/trails/PresBox';
+import { PinProps, PresBox } from '../../nodes/trails/PresBox';
import { VideoBox } from '../../nodes/VideoBox';
import { CreateImage } from '../../nodes/WebBoxRenderer';
import { StyleProp } from '../../StyleProvider';
@@ -523,6 +522,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
break;
+ case StyleProp.FillColor:
+ if (doc && this.Document._currentFrame !== undefined) {
+ return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
+ }
}
return styleProp;
};
@@ -1216,7 +1219,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
// SelectionManager.DeselectAll();
// }
- if (this.props.getScrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) {
+ if (this.props.getScrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined) {
this.props.focus(doc, options);
} else {
const xfToCollection = options?.docTransform ?? Transform.Identity();
@@ -1386,7 +1389,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
dontScaleFilter={this.props.dontScaleFilter}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.pointerEvents}
- rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
+ //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
//fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
/>
);
@@ -1425,17 +1428,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
const { z, zIndex } = childDoc;
const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
- const { x, y, opacity } = layoutFrameNumber === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
+ const { x, y, _width, _height, opacity, _rotation } =
+ layoutFrameNumber === undefined
+ ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
+ : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
return {
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
+ rotation: Cast(_rotation, 'number'),
color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
- width: Cast(childDocLayout._width, 'number'),
- height: Cast(childDocLayout._height, 'number'),
+ width: _width,
+ height: _height,
transition: StrCast(childDocLayout.dataTransition),
pair: params.pair,
replica: '',
@@ -1556,6 +1563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
newPos.x !== lastPos.x ||
newPos.y !== lastPos.y ||
newPos.z !== lastPos.z ||
+ newPos.rotation !== lastPos.rotation ||
newPos.zIndex !== lastPos.zIndex ||
newPos.transition !== lastPos.transition
) {
@@ -1592,37 +1600,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@action
- scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
+ scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => {
const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
if (options.preview) {
this._focusFilters = StrListCast(anchor.presPinDocFilters);
this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
return undefined;
}
- return PresBox.restoreTargetDocView(
- this.props.DocumentView?.(), //
- { pinDocLayout: BoolCast(anchor.presPinLayout) },
- anchor,
- focusSpeed,
- {
- dataview: anchor.presData ? true : false,
- pannable: anchor.presPinData ? true : false,
- viewType: anchor.presPinViewType ? true : false,
- filters: anchor.presPinDocFilters || anchor.presPinDocRangeFilters ? true : false,
- }
- )
- ? focusSpeed
- : undefined;
+ return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
}; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
- getAnchor = (addAsAnnotation: boolean) => {
- if (this.props.Document.annotationOn) {
- return this.rootDoc;
- }
-
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
- const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinData: { pannable: true, viewType: true, filters: true } }, this.rootDoc);
+ const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, viewType: true, filters: true } }, this.rootDoc);
if (addAsAnnotation) {
if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1d59d3356..81dc3aafd 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -18,6 +18,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import './AudioBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
+import { PinProps, PresBox } from './trails';
/**
* AudioBox
@@ -136,20 +137,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return { la1, la2, linkTime };
}
- getAnchor = (addAsAnnotation: boolean) => {
- return (
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+ const anchor =
CollectionStackedTimeline.createAnchor(
this.rootDoc,
this.dataDoc,
this.annotationKey,
- '_timecodeToShow' /* audioStart */,
- '_timecodeToHide' /* audioEnd */,
this._ele?.currentTime || Cast(this.props.Document._currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
undefined,
undefined,
addAsAnnotation
- ) || this.rootDoc
- );
+ ) || this.rootDoc;
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc);
+ return anchor;
};
// updates timecode and shows it in timeline, follows links at time
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ba510e1dc..9a32556f8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,7 +1,6 @@
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
-import { InkField } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { ComputedField } from '../../../fields/ScriptField';
@@ -20,11 +19,10 @@ import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import React = require('react');
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
+ dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
renderCutoffProvider: (doc: Doc) => boolean;
zIndex?: number;
- rotation: number;
dataTransition?: string;
replica: string;
CollectionFreeFormView: CollectionFreeFormView;
@@ -46,7 +44,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
{ key: 'panX' },
{ key: 'panY' },
]; // fields that are configured to be animatable using animation frames
- public static animStringFields = ['backgroundColor', 'color']; // fields that are configured to be animatable using animation frames
+ public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
@observable _animPos: number[] | undefined = undefined;
@observable _contentView: DocumentView | undefined | null;
@@ -56,7 +54,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
get transform() {
- return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.rotation, this.props.rotation)}deg)`;
+ return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rot, this.Rot)}deg)`;
}
get X() {
return this.dataProvider?.x ?? NumCast(this.Document.x);
@@ -67,6 +65,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
get ZInd() {
return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex);
}
+ get Rot() {
+ return this.dataProvider?.rotation ?? NumCast(this.Document._rotation);
+ }
get Opacity() {
return this.dataProvider?.opacity;
}
@@ -84,13 +85,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
- if (doc === this.layoutDoc)
+ if (doc === this.layoutDoc) {
// prettier-ignore
switch (property) {
case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
case StyleProp.BackgroundColor: return this.BackgroundColor;
case StyleProp.Color: return this.Color;
}
+ }
return this.props.styleProvider?.(doc, props, property);
};
@@ -108,6 +110,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}, {} as { [val: string]: Opt<string> });
}
+ public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
+ const timecode = Math.round(time);
+ Object.keys(vals).forEach(val => {
+ const findexed = Cast(d[`${val}-indexed`], listSpec('string'), []).slice();
+ findexed[timecode] = vals[val] as any as string;
+ d[`${val}-indexed`] = new List<string>(findexed);
+ });
+ }
+
public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
const timecode = Math.round(time);
Object.keys(vals).forEach(val => {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 2432d4c37..5783d0e57 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -16,6 +16,7 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { TaskCompletionBox } from './TaskCompletedBox';
import React = require('react');
import _ = require('lodash');
+import { PinProps } from './trails';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
@@ -175,7 +176,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
};
public static finishLinkClick = undoBatch(
- action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView) => {
+ action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) => {
if (startLink === endLink) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
@@ -183,7 +184,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.AnnotationUri = undefined;
//!this.props.StartLink
} else if (startLink !== endLink) {
- endLink = endLinkView?.docView?._componentView?.getAnchor?.(true) || endLink;
+ endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined, undefined, undefined, true);
@@ -283,7 +284,18 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
className={'documentLinksButton-endLink'}
ref={this._linkButton}
onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
- onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}>
+ onClick={e =>
+ DocumentLinksButton.StartLink &&
+ DocumentLinksButton.finishLinkClick(
+ e.clientX,
+ e.clientY,
+ DocumentLinksButton.StartLink,
+ this.props.View.props.Document,
+ true,
+ this.props.View,
+ (e.shiftKey ? { pinDocLayout: true, pinDocContent: true, pinData: { poslayoutview: true, dataannos: true, dataview: true } } : {}) as PinProps
+ )
+ }>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
) : null}
@@ -292,7 +304,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}
render() {
- const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
+ const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link (shift key captures viewing state)';
const buttonTitle = 'Tap to view links; double tap to open link collection';
const title = this.props.ShowCount ? buttonTitle : menuTitle;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9d966cdb2..ff6fe3712 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -54,7 +54,7 @@ import { LinkDocPreview } from './LinkDocPreview';
import { RadialMenu } from './RadialMenu';
import { ScriptingBox } from './ScriptingBox';
import { PresEffect, PresEffectDirection } from './trails';
-import { PinProps } from './trails/PresBox';
+import { PinProps, PresBox } from './trails/PresBox';
import React = require('react');
import { GestureOverlay } from '../GestureOverlay';
const { Howl } = require('howler');
@@ -121,8 +121,9 @@ export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void;
export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
export interface DocComponentView {
updateIcon?: () => void; // updates the icon representation of the document
- getAnchor?: (addAsAnnotation: boolean) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
- scrollFocus?: (doc: Doc, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
+ getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
+ scrollPreview?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
+ scrollFocus?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void;
reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
@@ -585,7 +586,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
LightboxView.SetCookie(StrCast(anchor['cookies-set']));
// Restore viewing specification of target by reading them out of the anchor and applying to the target doc.
- const focusSpeed = this._componentView?.scrollFocus?.(anchor, { ...options, preview: LinkDocPreview.LinkInfo ? true : false });
+ const presItem = DocCast(options.originatingDoc); // if originating doc was a presItem, then anchor will be its proto. use presItem instead
+ const targetMatch = Doc.AreProtosEqual(anchor, this.rootDoc) || (DocCast(anchor)?.unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.rootDoc)) ? true : false;
+ const scrollFocus =
+ (LinkDocPreview.LinkInfo ? this._componentView?.scrollPreview : undefined) ??
+ this._componentView?.scrollFocus ??
+ ((docView: DocumentView, anchor: Doc, options: DocFocusOptions) => (focusSpeed => (PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined))(options.instant ? 0 : options.zoomTime ?? 500));
+ const focusSpeed = targetMatch && scrollFocus?.(this.props.DocumentView(), presItem?.proto === anchor ? presItem : anchor, options);
// FOCUS: navigate through the display hierarchy making sure the target is in view
const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing;
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 5c0005dae..8fa01c97a 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -13,7 +13,7 @@ import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { DocFocusOptions } from './DocumentView';
+import { DocFocusOptions, DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
const EquationSchema = createSchema({});
@@ -42,14 +42,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
);
}
getAnchor = (addAsAnnotation: boolean) => {
- const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc });
+ const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true });
anchor.xRange = new List<number>(Array.from(this._plot.options.xAxis.domain));
anchor.yRange = new List<number>(Array.from(this._plot.options.yAxis.domain));
if (addAsAnnotation) this.addDocument(anchor);
return anchor;
};
@action
- scrollFocus = (doc: Doc, smooth: DocFocusOptions) => {
+ scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => {
this.dataDoc.xRange = new List<number>(Array.from(Cast(doc.xRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10]))));
this.dataDoc.yRange = new List<number>(Array.from(Cast(doc.yRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9]))));
return 0;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0ba576e55..363cd1d94 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,7 +8,7 @@ import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { createSchema } from '../../../fields/Schema';
import { ComputedField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -26,11 +26,11 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, OpenWhere } from './DocumentView';
+import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
import { FaceRectangles } from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
-import { PresBox } from './trails';
+import { PinProps, PresBox } from './trails';
import React = require('react');
import Color = require('color');
import { LinkDocPreview } from './LinkDocPreview';
@@ -71,34 +71,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
};
- @action
- scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
- const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
- return PresBox.restoreTargetDocView(
- this.props.DocumentView?.(), //
- { pinDocLayout: BoolCast(anchor.presPinLayout) },
- anchor,
- focusSpeed,
- !anchor.presPinData
- ? {}
- : {
- pannable: true,
- dataannos: anchor.presAnnotations !== undefined,
- dataview: true,
- }
- )
- ? focusSpeed
- : undefined;
- }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
-
- getAnchor = (addAsAnnotation: boolean) => {
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const anchor =
this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
Docs.Create.ImageanchorDocument({ title: 'ImgAnchor:' + this.rootDoc.title, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc });
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
/* addAsAnnotation &&*/ this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true } }, this.rootDoc);
return anchor;
}
return this.rootDoc;
@@ -177,9 +157,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const nh = nscale / NumCast(this.dataDoc[this.fieldKey + '-nativeHeight']);
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']);
this.dataDoc[this.fieldKey + '-nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '-nativeHeight']) * nh;
- this.dataDoc[this.fieldKey + '-nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']) * nw;
+ this.dataDoc[this.fieldKey + '-nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']) * nh;
this.rootDoc._panX = nh * NumCast(this.rootDoc._panX);
- this.rootDoc._panY = nw * NumCast(this.rootDoc._panY);
+ this.rootDoc._panY = nh * NumCast(this.rootDoc._panY);
this.dataDoc._panXMax = this.dataDoc._panXMax ? nh * NumCast(this.dataDoc._panXMax) : undefined;
this.dataDoc._panXMin = this.dataDoc._panXMin ? nh * NumCast(this.dataDoc._panXMin) : undefined;
this.dataDoc._panYMax = this.dataDoc._panYMax ? nw * NumCast(this.dataDoc._panYMax) : undefined;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index a2143f629..33fab6873 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -36,8 +36,6 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
this._timeout && clearTimeout(this._timeout);
}
- getAnchor = (addAsAnnotation: boolean) => this.rootDoc;
-
getTitle() {
return this.rootDoc['title-custom'] ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === 'string' ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 88d134bba..8d7ab2f0d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -5,7 +5,7 @@ import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
-import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -22,13 +22,13 @@ import { LightboxView } from '../LightboxView';
import { CreateImage } from '../nodes/WebBoxRenderer';
import { PDFViewer } from '../pdf/PDFViewer';
import { SidebarAnnos } from '../SidebarAnnos';
+import { DocFocusOptions, DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { ImageBox } from './ImageBox';
import './PDFBox.scss';
+import { PinProps, PresBox } from './trails';
import { VideoBox } from './VideoBox';
import React = require('react');
-import { PresBox } from './trails';
-import { DocFocusOptions } from './DocumentView';
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -200,18 +200,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._pdfViewer?.brushView(view);
- scrollFocus = (doc: Doc, options: DocFocusOptions) => {
+ scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => {
let didToggle = false;
- if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
+ if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) {
this.toggleSidebar(options.preview);
didToggle = true;
}
- if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
- this._initialScrollTarget = doc;
- PresBox.restoreTargetDocView(this.props.DocumentView?.(), {}, doc, options.zoomTime ?? 500, { pannable: doc.presPinData ? true : false });
- return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), options) ?? (didToggle ? 1 : undefined);
+ if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1;
+ this._initialScrollTarget = anchor;
+ PresBox.restoreTargetDocView(docView, anchor, options.zoomTime ?? 500);
+ return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.presPinViewScroll, NumCast(anchor.y)), options) ?? (didToggle ? 1 : undefined);
};
- getAnchor = (addAsAnnotation: boolean) => {
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
let ele: Opt<HTMLDivElement> = undefined;
if (this._pdfViewer?.selectionContent()) {
ele = document.createElement('div');
@@ -223,11 +223,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
unrendered: true,
annotationOn: this.rootDoc,
});
- PresBox.pinDocView(anchor, { pinData: { scrollable: true, pannable: true } }, this.rootDoc);
return anchor;
};
const annoAnchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations(), true);
const anchor = annoAnchor ?? docAnchor();
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc);
anchor.text = ele?.textContent ?? '';
anchor.textHtml = ele?.innerHTML;
if (addAsAnnotation || annoAnchor) {
@@ -287,8 +287,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setPdfViewer = (pdfViewer: PDFViewer) => {
this._pdfViewer = pdfViewer;
- if (this._initialScrollTarget) {
- this.scrollFocus(this._initialScrollTarget, { instant: true });
+ const docView = this.props.DocumentView?.();
+ if (this._initialScrollTarget && docView) {
+ this.scrollFocus(docView, this._initialScrollTarget, { instant: true });
this._initialScrollTarget = undefined;
}
};
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index f94996c66..61e4894f0 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -130,7 +130,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
}
getAnchor = (addAsAnnotation: boolean) => {
const startTime = Cast(this.layoutDoc._currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
- return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow', '_timecodeToHide', startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc;
+ return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc;
};
videoLoad = () => {
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 281967a21..fa2021642 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -20,6 +20,7 @@ import { EditableView } from '../EditableView';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { OverlayView } from '../OverlayView';
import { DocumentIconContainer } from './DocumentIcon';
+import { DocFocusOptions, DocumentView } from './DocumentView';
import './ScriptingBox.scss';
const _global = (window /* browser */ || global) /* node */ as any;
@@ -481,10 +482,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
return value;
}
- scrollFocus = () => {
- return undefined;
- };
-
getSuggestedParams(pos: number) {
const firstScript = this.rawText.slice(0, pos);
const indexP = firstScript.lastIndexOf('.');
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1dfa55c64..c26562e7c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -7,6 +7,7 @@ import * as rp from 'request-promise';
import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
+import { ObjectField } from '../../../fields/ObjectField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
import { emptyFunction, formatTime, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -27,11 +28,11 @@ import { DocumentDecorations } from '../DocumentDecorations';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
+import { OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
+import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
-import { ObjectField } from '../../../fields/ObjectField';
-import { DocFocusOptions, OpenWhere } from './DocumentView';
const path = require('path');
/**
@@ -390,13 +391,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSummary)?.startDragging(downX, downY, 'move', true));
};
- getAnchor = (addAsAnnotation: boolean) => {
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const timecode = Cast(this.layoutDoc._currentTimecode, 'number', null);
const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
- return (
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow' /* videoStart */, '_timecodeToHide' /* videoEnd */, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) ||
- this.rootDoc
- );
+ if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
+ const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.rootDoc;
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc);
+ return anchor;
};
// sets video info on load
@@ -953,14 +954,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
);
};
- scrollFocus = (doc: Doc, options: DocFocusOptions) => {
- if (doc !== this.rootDoc) {
- const showTime = Cast(doc._timecodeToShow, 'number', null);
- showTime !== undefined && setTimeout(() => this.Seek(showTime), 100);
- return 0.1;
- }
- };
-
// renders CollectionStackedTimeline
@computed get renderTimeline() {
return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 4c9bead9d..caab18e0d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as WebRequest from 'web-request';
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
@@ -11,8 +11,9 @@ import { listSpec } from '../../../fields/Schema';
import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, getWordAtPoint, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
+import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -21,7 +22,6 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
-import { DocumentDecorations } from '../DocumentDecorations';
import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -29,13 +29,13 @@ import { AnchorMenu } from '../pdf/AnchorMenu';
import { Annotation } from '../pdf/Annotation';
import { SidebarAnnos } from '../SidebarAnnos';
import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView, DocumentViewInternal, DocumentViewProps } from './DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { LinkDocPreview } from './LinkDocPreview';
import { VideoBox } from './VideoBox';
import './WebBox.scss';
import React = require('react');
-import { DragManager } from '../../util/DragManager';
+import { PinProps, PresBox } from './trails';
const { CreateImage } = require('./WebBoxRenderer');
const _global = (window /* browser */ || global) /* node */ as any;
const htmlToText = require('html-to-text');
@@ -189,6 +189,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const reqdFuncs: { [key: string]: string } = {};
// bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created)
reqdFuncs[this.fieldKey + '-annotations'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`;
+ reqdFuncs[this.fieldKey + '-annotations-setter'] = `this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"] = value`;
reqdFuncs[this.fieldKey + '-sidebar'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`;
DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs);
});
@@ -293,27 +294,35 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
- scrollFocus = (doc: Doc, options: DocFocusOptions) => {
- if (this._url && StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), options.preview);
- if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
+ focus = (doc: Doc, options: DocFocusOptions) => {
+ !doc.unrendered && this.props.DocumentView?.() && this.scrollFocus(this.props.DocumentView?.(), doc, {});
+ this.props.focus(doc, options);
+ };
+ scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => {
+ if (this._url && StrCast(anchor.webUrl) !== this._url) this.submitURL(StrCast(anchor.webUrl), options.preview);
+ if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) {
this.toggleSidebar(options.preview);
}
- if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
- if (doc !== this.rootDoc && this._outerRef.current) {
+ if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1;
+ if (anchor !== this.rootDoc && this._outerRef.current) {
const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this._scrollHeight));
+ const scrollTo = !anchor[HeightSym]()
+ ? NumCast(anchor.y)
+ : Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight));
+ const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
if (scrollTo !== undefined && this._initialScroll === undefined) {
- const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
this.goTo(scrollTo, focusSpeed, options.easeFunc);
+ PresBox.restoreTargetDocView(docView, anchor, focusSpeed);
return focusSpeed;
} else if (!this._webPageHasBeenRendered || !this._scrollHeight || this._initialScroll !== undefined) {
this._initialScroll = scrollTo;
+ return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
}
}
return undefined;
};
- getAnchor = (addAsAnnotation: boolean) => {
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
let ele: Opt<HTMLDivElement> = undefined;
try {
const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents();
@@ -330,9 +339,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
unrendered: true,
annotationOn: this.rootDoc,
});
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc);
anchor.text = ele?.textContent ?? '';
anchor.textHtml = ele?.innerHTML;
- addAsAnnotation && this.addDocumentWrapper(anchor);
+ //addAsAnnotation &&
+ this.addDocumentWrapper(anchor);
return anchor;
};
@@ -901,6 +912,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
PanelHeight={this.panelHeight}
ScreenToLocalTransform={this.scrollXf}
NativeDimScaling={returnOne}
+ focus={this.focus}
dropAction={'alias'}
docFilters={docFilters}
select={emptyFunction}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 93761633c..0468d1c47 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -27,6 +27,7 @@ import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetAc
import { InkTranscription } from '../../InkTranscription';
import { StyleProp } from '../../StyleProvider';
import { FieldView, FieldViewProps } from '.././FieldView';
+import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView';
import { OpenWhere } from '../DocumentView';
import { RichTextMenu } from '../formattedText/RichTextMenu';
import { WebBox } from '../WebBox';
@@ -551,11 +552,30 @@ ScriptingGlobals.add(function setView(view: string) {
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement()?.props.Document ?? LinkManager.currentLink;
- if (checkResult) {
- return selected?._backgroundColor ?? 'transparent';
+ const selectedViews = SelectionManager.Views();
+ if (selectedViews.length) {
+ if (checkResult) {
+ const selView = selectedViews.lastElement();
+ const layoutFrameNumber = Cast(selView.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+ return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber).backgroundColor ?? 'transparent';
+ }
+ selectedViews.forEach(dv => {
+ const layoutFrameNumber = Cast(dv.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+ if (contentFrameNumber !== undefined) {
+ CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color });
+ } else {
+ dv.rootDoc._backgroundColor = color;
+ }
+ });
+ } else {
+ const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : [];
+ if (checkResult) {
+ return selected.lastElement()?._backgroundColor ?? 'transparent';
+ }
+ selected.forEach(doc => (doc._backgroundColor = color));
}
- if (selected) selected._backgroundColor = color;
});
// toggle: Set overlay status of selected document
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 788de7af9..269438a7c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
-import { DocFocusOptions, DocumentViewInternal, OpenWhere } from '../DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
import { DashDocCommentView } from './DashDocCommentView';
@@ -932,7 +932,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return anchorDoc ?? this.rootDoc;
}
- scrollFocus = (textAnchor: Doc, options: DocFocusOptions) => {
+ scrollFocus = (docView: DocumentView, textAnchor: Doc, options: DocFocusOptions) => {
let didToggle = false;
if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {
this.toggleSidebar(options.preview);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 13cbd87eb..45d386436 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -4,9 +4,9 @@ import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState, SketchPicker } from 'react-color';
-import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
+import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
import { Copy, Id } from '../../../../fields/FieldSymbols';
-import { InkTool } from '../../../../fields/InkField';
+import { InkField, InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { ObjectField } from '../../../../fields/ObjectField';
import { listSpec } from '../../../../fields/Schema';
@@ -34,8 +34,23 @@ import { FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { SerializationHelper } from '../../../util/SerializationHelper';
const { Howl } = require('howler');
+export interface pinDataTypes {
+ scrollable?: boolean;
+ pannable?: boolean;
+ viewType?: boolean;
+ inkable?: boolean;
+ filters?: boolean;
+ pivot?: boolean;
+ temporal?: boolean;
+ clippable?: boolean;
+ dataview?: boolean;
+ textview?: boolean;
+ poslayoutview?: boolean;
+ dataannos?: boolean;
+}
export interface PinProps {
audioRange?: boolean;
activeFrame?: number;
@@ -45,18 +60,7 @@ export interface PinProps {
pinDocLayout?: boolean; // pin layout info (width/height/x/y)
pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text)
pinAudioPlay?: boolean; // pin audio annotation
- pinData?: {
- scrollable?: boolean | undefined;
- pannable?: boolean | undefined;
- viewType?: boolean | undefined;
- filters?: boolean | undefined;
- temporal?: boolean | undefined;
- clippable?: boolean | undefined;
- dataview?: boolean | undefined;
- textview?: boolean | undefined;
- poslayoutview?: boolean | undefined;
- dataannos?: boolean | undefined;
- };
+ pinData?: pinDataTypes;
}
@observer
@@ -318,19 +322,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.onHideDocument(); //Handles hide after/before
}
});
- static pinDataTypes(target?: Doc): {
- scrollable?: boolean;
- pannable?: boolean;
- viewType?: boolean;
- filters?: boolean;
- temporal?: boolean;
- clippable?: boolean;
- dataview?: boolean;
- textview?: boolean;
- poslayoutview?: boolean;
- dataannos?: boolean;
- } {
+ static pinDataTypes(target?: Doc): pinDataTypes {
const targetType = target?.type as any;
+ const inkable = [DocumentType.INK].includes(targetType);
const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._viewType === CollectionViewType.Stacking;
const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform);
const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType);
@@ -340,18 +334,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
const viewType = targetType === DocumentType.COL;
const filters = true;
+ const pivot = true;
const dataannos = false;
- return { scrollable, pannable, viewType, filters, temporal, clippable, dataview, textview, poslayoutview, dataannos };
+ return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, textview, poslayoutview, dataannos };
}
@action
playAnnotation = (anno: AudioField) => {};
@action
- static restoreTargetDocView(bestTargetView: Opt<DocumentView>, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTargetView?.rootDoc)) {
- if (!bestTargetView) return;
- const bestTarget = bestTargetView.rootDoc;
+ static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.presPinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
+ const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
+ if (!bestTarget) return;
let changed = false;
- if (pinProps?.pinDocLayout) {
+ if (pinDocLayout) {
if (
bestTarget.x !== NumCast(activeItem.presX, NumCast(bestTarget.x)) ||
bestTarget.y !== NumCast(activeItem.presY, NumCast(bestTarget.y)) ||
@@ -369,33 +364,51 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
changed = true;
}
}
- if (pinDataTypes.clippable) {
+ if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.presPinClipWidth !== undefined)) {
if (bestTarget._clipWidth !== activeItem.presPinClipWidth) {
bestTarget._clipWidth = activeItem.presPinClipWidth;
changed = true;
}
}
- if (pinDataTypes.temporal) {
+ if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.presStartTime !== undefined)) {
if (bestTarget._currentTimecode !== activeItem.presStartTime) {
bestTarget._currentTimecode = activeItem.presStartTime;
changed = true;
}
}
- if (pinDataTypes.viewType && activeItem.presPinViewType !== undefined) {
+ if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.presFillColor !== undefined || activeItem.color !== undefined))) {
+ if (bestTarget.fillColor !== activeItem.presFillColor) {
+ Doc.GetProto(bestTarget).fillColor = activeItem.presFillColor;
+ changed = true;
+ }
+ if (bestTarget.color !== activeItem.color) {
+ Doc.GetProto(bestTarget).color = activeItem.color;
+ changed = true;
+ }
+ }
+ if ((pinDataTypes?.viewType && activeItem.presPinViewType !== undefined) || (!pinDataTypes && activeItem.presPinViewType !== undefined)) {
if (bestTarget._viewType !== activeItem.presPinViewType) {
bestTarget._viewType = activeItem.presPinViewType;
changed = true;
}
}
- if (pinDataTypes.filters && activeItem.presPinDocFilters !== undefined) {
+ if ((pinDataTypes?.filters && activeItem.presPinDocFilters !== undefined) || (!pinDataTypes && activeItem.presPinDocFilters !== undefined)) {
if (bestTarget.docFilters !== activeItem.presPinDocFilters) {
bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presPinDocFilters as ObjectField) || new List<string>([]);
changed = true;
}
}
- if (pinDataTypes.scrollable) {
+ if ((pinDataTypes?.pivot && activeItem.presPinPvitoField !== undefined) || (!pinDataTypes && activeItem.presPinPivotField !== undefined)) {
+ if (bestTarget.pivotField !== activeItem.presPinPivotField) {
+ bestTarget.pivotField = activeItem.presPinPivotField;
+ bestTarget._prevFilterIndex = 1; // need to revisit this...see CollectionTimeView
+ changed = true;
+ }
+ }
+
+ if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presPinViewScroll !== undefined)) {
if (bestTarget._scrollTop !== activeItem.presPinViewScroll) {
bestTarget._scrollTop = activeItem.presPinViewScroll;
changed = true;
@@ -406,39 +419,59 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
}
- if (pinDataTypes.dataannos) {
+ if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.presAnnotations !== undefined)) {
const fkey = Doc.LayoutFieldKey(bestTarget);
- Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List<Doc>([...DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered), ...DocListCast(activeItem.presAnnotations)]);
+ const oldItems = DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered);
+ const newItems = DocListCast(activeItem.presAnnotations).map(doc => {
+ doc.hidden = false;
+ return doc;
+ });
+ const hiddenItems = DocListCast(bestTarget[fkey + '-annotations'])
+ .filter(doc => !doc.unrendered && !newItems.includes(doc))
+ .map(doc => {
+ doc.hidden = true;
+ return doc;
+ });
+ const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]);
+ Doc.GetProto(bestTarget)[fkey + '-annotations'] = newList;
}
- if (pinDataTypes.dataview && activeItem.presData !== undefined) {
+ if ((pinDataTypes?.dataview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) {
bestTarget._dataTransition = `all ${transTime}ms`;
const fkey = Doc.LayoutFieldKey(bestTarget);
Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt;
setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
}
- if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
- if (pinDataTypes.poslayoutview) {
+ if ((pinDataTypes?.textview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) {
+ Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
+ }
+ if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.presPinLayoutData !== undefined)) {
changed = true;
+ const layoutField = Doc.LayoutFieldKey(bestTarget);
+ const transitioned = new Set<Doc>();
StrListCast(activeItem.presPinLayoutData)
- .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number })
- .forEach(data => {
- const doc = DocServer.GetCachedRefField(data.id) as Doc;
- doc._dataTransition = `all ${transTime}ms`;
- doc.x = data.x;
- doc.y = data.y;
- doc._width = data.w;
- doc._height = data.h;
+ .map(str => JSON.parse(str) as { id: string; x: number; y: number; back: string; fill: string; w: number; h: number; data: string; text: string })
+ .forEach(async data => {
+ const doc = DocCast(DocServer.GetCachedRefField(data.id));
+ if (doc) {
+ transitioned.add(doc);
+ const field = !data.data ? undefined : await SerializationHelper.Deserialize(data.data);
+ const tfield = !data.text ? undefined : await SerializationHelper.Deserialize(data.text);
+ doc._dataTransition = `all ${transTime}ms`;
+ doc.x = data.x;
+ doc.y = data.y;
+ data.back && (doc._backgroundColor = data.back);
+ data.fill && (doc._fillColor = data.fill);
+ doc._width = data.w;
+ doc._height = data.h;
+ data.data && (Doc.GetProto(doc).data = field);
+ data.text && (Doc.GetProto(doc).text = tfield);
+ Doc.AddDocToList(Doc.GetProto(bestTarget), layoutField, doc);
+ }
});
- setTimeout(
- () =>
- StrListCast(activeItem.presPinLayoutData)
- .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number })
- .forEach(action(data => ((DocServer.GetCachedRefField(data.id) as Doc)._dataTransition = undefined))),
- transTime + 10
- );
+ setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
}
- if (pinDataTypes.pannable) {
+ if (pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPinViewX !== undefined || activeItem.presPinViewScale !== undefined))) {
const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
if (contentBounds) {
const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
@@ -460,7 +493,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
if (changed) {
- return bestTargetView.setViewTransition('all', transTime);
+ return bestTargetView?.setViewTransition('all', transTime);
}
}
@@ -499,12 +532,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations']).filter(doc => !doc.unrendered));
}
if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text;
+ if (pinProps.pinData.inkable) {
+ pinDoc.presFillColor = targetDoc.fillColor;
+ pinDoc.presColor = targetDoc.color;
+ }
if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop;
if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth;
if (pinProps.pinData.poslayoutview)
- pinDoc.presPinLayoutData = new List<string>(DocListCast(targetDoc[fkey] as ObjectField).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) })));
+ pinDoc.presPinLayoutData = new List<string>(
+ DocListCast(targetDoc[fkey] as ObjectField).map(d =>
+ JSON.stringify({
+ id: d[Id],
+ x: NumCast(d.x),
+ y: NumCast(d.y),
+ w: NumCast(d._width),
+ h: NumCast(d._height),
+ fill: StrCast(d._fillColor),
+ back: StrCast(d._backgroundColor),
+ data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''),
+ text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''),
+ })
+ )
+ );
if (pinProps.pinData.viewType) pinDoc.presPinViewType = targetDoc._viewType;
if (pinProps.pinData.filters) pinDoc.presPinDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField);
+ if (pinProps.pinData.pivot) pinDoc.presPinPivotField = targetDoc._pivotField;
if (pinProps.pinData.pannable) {
pinDoc.presPinViewX = NumCast(targetDoc._panX);
pinDoc.presPinViewY = NumCast(targetDoc._panY);
@@ -586,7 +638,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined;
if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) {
// targetDoc may or may not be displayed. so get the first available document (or alias) view that matches targetDoc and use it
- PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500));
+ // PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500), undefined, targetDoc);
}
};
const finishAndRestoreLayout = () => {
@@ -660,16 +712,43 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
_exitTrail: Opt<() => void>;
PlayTrail = (docs: Doc[]) => {
- const savedStates = docs.map(doc => (doc._viewType !== CollectionViewType.Freeform ? undefined : { c: doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) }));
+ const savedStates = docs.map(doc => {
+ switch (doc.type) {
+ case DocumentType.COL:
+ if (doc._viewType === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) };
+ break;
+ case DocumentType.INK:
+ if (doc.data instanceof InkField) {
+ return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) };
+ }
+ }
+ return undefined;
+ });
this.startPresentation(0);
this._exitTrail = () => {
savedStates
.filter(savedState => savedState)
.map(savedState => {
- const { x, y, s, c } = savedState!;
- c._panX = x;
- c._panY = y;
- c._viewScale = s;
+ switch (savedState?.type) {
+ case CollectionViewType.Freeform:
+ {
+ const { x, y, s, doc } = savedState!;
+ doc._panX = x;
+ doc._panY = y;
+ doc._viewScale = s;
+ }
+ break;
+ case DocumentType.INK:
+ {
+ const { data, fillColor, color, x, y, doc } = savedState!;
+ doc.x = x;
+ doc.y = y;
+ doc.data = data;
+ doc.fillColor = fillColor;
+ doc.color = color;
+ }
+ break;
+ }
});
LightboxView.SetLightboxDoc(undefined);
Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc);
@@ -1869,7 +1948,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
- scrollFocus = () => {
+ scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => {
// this.gotoDocument(0);
// this.startOrPause(false);
return undefined;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 4a7f5d038..fc8f1da49 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -41,7 +41,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable private _showLinkPopup: boolean = false;
- @observable public Highlighting: boolean = false;
@observable public Status: 'marquee' | 'annotation' | '' = '';
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -121,9 +120,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@action
highlightClicked = (e: React.MouseEvent) => {
- if (!this.Highlight(this.highlightColor, false, undefined, true) && this.Pinned) {
- this.Highlighting = !this.Highlighting;
- }
+ this.Highlight(this.highlightColor, false, undefined, true);
AnchorMenu.Instance.fadeOut(true);
};
@@ -138,7 +135,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
const button = (
<button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
<div className="anchor-color-preview">
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: this.Highlighting ? '' : 'rotate(-45deg)' }} />
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />
<div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
</div>
</button>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 2db44788f..9877690f8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -30,7 +30,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
-pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.14.305/build/pdf.worker.js';
+pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js';
interface IViewerProps extends FieldViewProps {
Document: Doc;
@@ -179,7 +179,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
let focusSpeed: Opt<number>;
if (doc !== this.props.rootDoc && mainCont) {
const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
- const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, this._scrollHeight);
+ const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
else if (!options.instant && !options.preview) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
@@ -518,6 +518,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
return this.props.styleProvider?.(doc, props, property);
};
+ focus = (doc: Doc, options: DocFocusOptions) => {
+ !doc.unrendered && this.props.DocumentView?.() && this.scrollToAnnotation(doc);
+ this.props.focus(doc, options);
+ };
renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => (
<div
className="pdfViewerDash-overlay"
@@ -540,6 +544,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
PanelWidth={this.panelWidth}
ScreenToLocalTransform={this.overlayTransform}
dropAction={'alias'}
+ focus={this.focus}
docFilters={docFilters}
select={emptyFunction}
bringToFront={emptyFunction}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index df49c32f0..ed5eaa756 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -722,13 +722,22 @@ export namespace Doc {
return bestAlias ?? Doc.MakeAlias(doc);
}
- export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+ export async function makeClone(
+ doc: Doc,
+ cloneMap: Map<string, Doc>,
+ linkMap: Map<Doc, Doc>,
+ rtfs: { copy: Doc; key: string; field: RichTextField }[],
+ exclusions: string[],
+ topLevelExclusions: string[],
+ dontCreate: boolean,
+ asBranch: boolean
+ ): Promise<Doc> {
if (Doc.IsBaseProto(doc)) return doc;
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions;
- const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])];
+ const filter = [...fieldExclusions, ...topLevelExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])];
await Promise.all(
Object.keys(doc).map(async key => {
if (filter.includes(key)) return;
@@ -739,10 +748,10 @@ export namespace Doc {
const list = await Cast(doc[key], listSpec(Doc));
const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)));
!dontCreate && assignKey(new List<Doc>(clones));
} else if (doc[key] instanceof Doc) {
- assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
+ assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
} else {
!dontCreate && assignKey(ObjectField.MakeCopy(field));
if (field instanceof RichTextField) {
@@ -754,17 +763,18 @@ export namespace Doc {
};
if (key === 'proto') {
if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch));
}
} else if (key === 'anchor1' || key === 'anchor2') {
if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], true, asBranch));
}
} else {
if (field instanceof RefField) {
assignKey(field);
} else if (cfield instanceof ComputedField) {
- !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
+ !dontCreate && assignKey(cfield[Copy]());
+ // ComputedField.MakeFunction(cfield.script.originalScript));
} else if (field instanceof ObjectField) {
await copyObjectField(field);
} else if (field instanceof Promise) {
@@ -776,7 +786,7 @@ export namespace Doc {
})
);
for (const link of Array.from(doc[DirectLinksSym])) {
- const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
+ const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch);
linkMap.set(link, linkClone);
}
if (!dontCreate) {
@@ -793,7 +803,7 @@ export namespace Doc {
export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<Doc, Doc>();
const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf', 'context'], dontCreate, asBranch);
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], ['context'], dontCreate, asBranch);
Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index b23732b45..5f02bd73d 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -185,9 +185,11 @@ export class ComputedField extends ScriptField {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
- return compiled.compiled ? new ComputedField(compiled) : undefined;
+ const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined;
+ const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined;
+ return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined;
}
public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) {
if (!doc[`${fieldKey}-indexed`]) {
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 33809824f..f461cf3fa 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -501,7 +501,7 @@ export namespace DashUploadUtils {
const parseExifData = async (source: string) => {
const image = await request.get(source, { encoding: null });
- const { data, error } = await new Promise(resolve => {
+ const { data, error } = await new Promise<{ data: any; error: any }>(resolve => {
new ExifImage({ image }, (error, data) => {
let reason: Opt<string> = undefined;
if (error) {
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 9b91a35a6..68b003496 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -199,7 +199,7 @@ export namespace WebSocket {
return Database.Instance.getDocument(id, callback);
}
function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
- process.stdout.write(`.`);
+ process.stdout.write(`+`);
GetRefFieldLocal([id, callback]);
}